Merged in isolation_tool (pull request #11)

Isolation tool
This commit is contained in:
Marius Stanciu
2020-05-30 23:01:22 +00:00
239 changed files with 157127 additions and 150699 deletions

View File

@@ -1,5 +1,5 @@
from PyQt5 import QtGui, QtCore, QtWidgets
from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
from AppGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \
FCTree, RadioSet, FCFileSaveDialog
from camlib import to_dict
@@ -8,8 +8,10 @@ import json
from copy import deepcopy
from datetime import datetime
import math
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -655,7 +657,7 @@ class ToolsDB(QtWidgets.QWidget):
l_save=str(self.app.get_last_save_folder()),
n=_("Tools_Database"),
date=date),
filter=filter__)
ext_filter=filter__)
filename = str(filename)
@@ -1030,6 +1032,7 @@ class ToolsDB2(QtWidgets.QWidget):
self.advanced_box.setTitle(_("Advanced Geo Parameters"))
self.advanced_box.setFixedWidth(250)
# NCC TOOL BOX
self.ncc_box = QtWidgets.QGroupBox()
self.ncc_box.setStyleSheet("""
QGroupBox
@@ -1042,6 +1045,7 @@ class ToolsDB2(QtWidgets.QWidget):
self.ncc_box.setTitle(_("NCC Parameters"))
self.ncc_box.setFixedWidth(250)
# PAINT TOOL BOX
self.paint_box = QtWidgets.QGroupBox()
self.paint_box.setStyleSheet("""
QGroupBox
@@ -1054,10 +1058,24 @@ class ToolsDB2(QtWidgets.QWidget):
self.paint_box.setTitle(_("Paint Parameters"))
self.paint_box.setFixedWidth(250)
# ISOLATION TOOL BOX
self.iso_box = QtWidgets.QGroupBox()
self.iso_box.setStyleSheet("""
QGroupBox
{
font-size: 16px;
font-weight: bold;
}
""")
self.iso_vlay = QtWidgets.QVBoxLayout()
self.iso_box.setTitle(_("Isolation Parameters"))
self.iso_box.setFixedWidth(250)
self.basic_box.setLayout(self.basic_vlay)
self.advanced_box.setLayout(self.advanced_vlay)
self.ncc_box.setLayout(self.ncc_vlay)
self.paint_box.setLayout(self.paint_vlay)
self.iso_box.setLayout(self.iso_vlay)
geo_vlay = QtWidgets.QVBoxLayout()
geo_vlay.addWidget(self.basic_box)
@@ -1067,6 +1085,7 @@ class ToolsDB2(QtWidgets.QWidget):
tools_vlay = QtWidgets.QVBoxLayout()
tools_vlay.addWidget(self.ncc_box)
tools_vlay.addWidget(self.paint_box)
tools_vlay.addWidget(self.iso_box)
tools_vlay.addStretch()
param_hlay.addLayout(geo_vlay)
@@ -1478,7 +1497,7 @@ class ToolsDB2(QtWidgets.QWidget):
self.ncc_method_combo = FCComboBox()
self.ncc_method_combo.addItems(
[_("Standard"), _("Seed"), _("Lines")]
[_("Standard"), _("Seed"), _("Lines"), _("Combo")]
)
self.ncc_method_combo.setObjectName("gdb_n_method")
@@ -1621,6 +1640,101 @@ class ToolsDB2(QtWidgets.QWidget):
self.grid3.addWidget(self.pathconnect_cb, 10, 0)
self.grid3.addWidget(self.paintcontour_cb, 10, 1)
# ###########################################################################
# ############### Paint UI form #############################################
# ###########################################################################
self.grid4 = QtWidgets.QGridLayout()
self.iso_vlay.addLayout(self.grid4)
self.grid4.setColumnStretch(0, 0)
self.grid4.setColumnStretch(1, 1)
self.iso_vlay.addStretch()
# Passes
passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
passlabel.setToolTip(
_("Width of the isolation gap in\n"
"number (integer) of tool widths.")
)
self.passes_entry = FCSpinner()
self.passes_entry.set_range(1, 999)
self.passes_entry.setObjectName("gdb_i_passes")
self.grid4.addWidget(passlabel, 0, 0)
self.grid4.addWidget(self.passes_entry, 0, 1)
# Overlap Entry
overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
overlabel.setToolTip(
_("How much (percentage) of the tool width to overlap each tool pass.")
)
self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
self.iso_overlap_entry.set_precision(self.decimals)
self.iso_overlap_entry.setWrapping(True)
self.iso_overlap_entry.set_range(0.0000, 99.9999)
self.iso_overlap_entry.setSingleStep(0.1)
self.iso_overlap_entry.setObjectName("gdb_i_overlap")
self.grid4.addWidget(overlabel, 2, 0)
self.grid4.addWidget(self.iso_overlap_entry, 2, 1)
# Milling Type Radio Button
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
self.milling_type_label.setToolTip(
_("Milling type when the selected tool is of type: 'iso_op':\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conventional'), 'value': 'cv'}])
self.milling_type_radio.setToolTip(
_("Milling type when the selected tool is of type: 'iso_op':\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
self.milling_type_radio.setObjectName("gdb_i_milling_type")
self.grid4.addWidget(self.milling_type_label, 4, 0)
self.grid4.addWidget(self.milling_type_radio, 4, 1)
# Follow
self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
self.follow_label.setToolTip(
_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace.")
)
self.follow_cb = FCCheckBox()
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace."))
self.follow_cb.setObjectName("gdb_i_follow")
self.grid4.addWidget(self.follow_label, 6, 0)
self.grid4.addWidget(self.follow_cb, 6, 1)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
_("Choose how the isolation will be executed:\n"
"- 'Full' -> complete isolation of polygons\n"
"- 'Ext' -> will isolate only on the outside\n"
"- 'Int' -> will isolate only on the inside\n"
"'Exterior' isolation is almost always possible\n"
"(with the right tool) but 'Interior'\n"
"isolation can be done only when there is an opening\n"
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
)
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Ext'), 'value': 'ext'},
{'label': _('Int'), 'value': 'int'}])
self.iso_type_radio.setObjectName("gdb_i_iso_type")
self.grid4.addWidget(self.iso_type_label, 8, 0)
self.grid4.addWidget(self.iso_type_radio, 8, 1)
# ####################################################################
# ####################################################################
# GUI for the lower part of the window
@@ -1743,6 +1857,13 @@ class ToolsDB2(QtWidgets.QWidget):
"tools_paintmethod": self.paintmethod_combo,
"tools_pathconnect": self.pathconnect_cb,
"tools_paintcontour": self.paintcontour_cb,
# Isolation
"tools_iso_passes": self.passes_entry,
"tools_iso_overlap": self.iso_overlap_entry,
"tools_iso_milling_type": self.milling_type_radio,
"tools_iso_follow": self.follow_cb,
"tools_iso_isotype": self.iso_type_radio
}
self.name2option = {
@@ -1787,6 +1908,13 @@ class ToolsDB2(QtWidgets.QWidget):
'gdb_p_method': "tools_paintmethod",
'gdb_p_connect': "tools_pathconnect",
'gdb_p_contour': "tools_paintcontour",
# Isolation
"gdb_i_passes": "tools_iso_passes",
"gdb_i_overlap": "tools_iso_overlap",
"gdb_i_milling_type": "tools_iso_milling_type",
"gdb_i_follow": "tools_iso_follow",
"gdb_i_iso_type": "tools_iso_isotype"
}
self.current_toolid = None
@@ -1939,21 +2067,23 @@ class ToolsDB2(QtWidgets.QWidget):
if self.db_tool_dict:
self.storage_to_form(self.db_tool_dict['1'])
# Enable GUI
# Enable AppGUI
self.basic_box.setEnabled(True)
self.advanced_box.setEnabled(True)
self.ncc_box.setEnabled(True)
self.paint_box.setEnabled(True)
self.iso_box.setEnabled(True)
self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0))
# self.tree_widget.setFocus()
else:
# Disable GUI
# Disable AppGUI
self.basic_box.setEnabled(False)
self.advanced_box.setEnabled(False)
self.ncc_box.setEnabled(False)
self.paint_box.setEnabled(False)
self.iso_box.setEnabled(False)
else:
self.storage_to_form(self.db_tool_dict[str(self.current_toolid)])
@@ -2006,6 +2136,13 @@ class ToolsDB2(QtWidgets.QWidget):
"tools_paintmethod": self.app.defaults["tools_paintmethod"],
"tools_pathconnect": self.app.defaults["tools_pathconnect"],
"tools_paintcontour": self.app.defaults["tools_paintcontour"],
# Isolation
"tools_iso_passes": int(self.app.defaults["tools_iso_passes"]),
"tools_iso_overlap": float(self.app.defaults["tools_iso_overlap"]),
"tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"],
"tools_iso_follow": self.app.defaults["tools_iso_follow"],
"tools_iso_isotype": self.app.defaults["tools_iso_isotype"],
})
dict_elem = {}
@@ -2117,7 +2254,7 @@ class ToolsDB2(QtWidgets.QWidget):
l_save=str(self.app.get_last_save_folder()),
n=_("Tools_Database"),
date=date),
filter=filter__)
ext_filter=filter__)
filename = str(filename)
@@ -2218,6 +2355,18 @@ class ToolsDB2(QtWidgets.QWidget):
self.app.tools_db_changed_flag = False
self.on_save_tools_db()
def on_calculate_tooldia(self):
if self.shape_combo.get_value() == 'V':
tip_dia = float(self.vdia_entry.get_value())
half_tip_angle = float(self.vangle_entry.get_value()) / 2.0
cut_z = float(self.cutz_entry.get_value())
cut_z = -cut_z if cut_z < 0 else cut_z
# calculated tool diameter so the cut_z parameter is obeyed
tool_dia = tip_dia + (2 * cut_z * math.tan(math.radians(half_tip_angle)))
self.dia_entry.set_value(tool_dia)
def ui_connect(self):
# make sure that we don't make multiple connections to the widgets
self.ui_disconnect()
@@ -2247,12 +2396,40 @@ class ToolsDB2(QtWidgets.QWidget):
if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner):
wdg.valueChanged.connect(self.update_storage)
# connect the calculate tooldia method to the controls
# if the tool shape is 'V' the tool dia will be calculated to obey Cut Z parameter
self.shape_combo.currentIndexChanged.connect(self.on_calculate_tooldia)
self.cutz_entry.valueChanged.connect(self.on_calculate_tooldia)
self.vdia_entry.valueChanged.connect(self.on_calculate_tooldia)
self.vangle_entry.valueChanged.connect(self.on_calculate_tooldia)
def ui_disconnect(self):
try:
self.name_entry.editingFinished.disconnect(self.update_tree_name)
except (TypeError, AttributeError):
pass
try:
self.shape_combo.currentIndexChanged.disconnect(self.on_calculate_tooldia)
except (TypeError, AttributeError):
pass
try:
self.cutz_entry.valueChanged.disconnect(self.on_calculate_tooldia)
except (TypeError, AttributeError):
pass
try:
self.vdia_entry.valueChanged.disconnect(self.on_calculate_tooldia)
except (TypeError, AttributeError):
pass
try:
self.vangle_entry.valueChanged.disconnect(self.on_calculate_tooldia)
except (TypeError, AttributeError):
pass
for key in self.form_fields:
wdg = self.form_fields[key]
@@ -2398,6 +2575,18 @@ class ToolsDB2(QtWidgets.QWidget):
elif wdg_name == "gdb_p_contour":
self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val
# Isolation Tool
elif wdg_name == "gdb_i_passes":
self.db_tool_dict[tool_id]['data']['tools_iso_passes'] = val
elif wdg_name == "gdb_i_overlap":
self.db_tool_dict[tool_id]['data']['tools_iso_overlap'] = val
elif wdg_name == "gdb_i_milling_type":
self.db_tool_dict[tool_id]['data']['tools_iso_milling_type'] = val
elif wdg_name == "gdb_i_follow":
self.db_tool_dict[tool_id]['data']['tools_iso_follow'] = val
elif wdg_name == "gdb_i_iso_type":
self.db_tool_dict[tool_id]['data']['tools_iso_isotype'] = val
self.callback_app()
def on_tool_requested_from_app(self):

View File

@@ -9,9 +9,9 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QSettings
from camlib import distance, arc, FlatCAMRTreeStorage
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
from flatcamParsers.ParseExcellon import Excellon
from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner
from AppEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
from AppParsers.ParseExcellon import Excellon
from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point
import shapely.affinity as affinity
@@ -26,7 +26,7 @@ import logging
from copy import deepcopy
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -2123,7 +2123,7 @@ class FlatCAMExcEditor(QtCore.QObject):
else:
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
else:
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_exc_editor')
@@ -2239,7 +2239,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# store the status of the editor so the Delete at object level will not work until the edit is finished
self.editor_active = False
log.debug("Initialization of the FlatCAM Excellon Editor is finished ...")
log.debug("Initialization of the Excellon Editor is finished ...")
def pool_recreated(self, pool):
self.shapes.pool = pool
@@ -2312,7 +2312,7 @@ class FlatCAMExcEditor(QtCore.QObject):
tool_dia = float('%.*f' % (self.decimals, v['C']))
self.tool2tooldia[int(k)] = tool_dia
# Init GUI
# Init AppGUI
self.addtool_entry.set_value(float(self.app.defaults['excellon_editor_newdia']))
self.drill_array_size_entry.set_value(int(self.app.defaults['excellon_editor_array_size']))
self.drill_axis_radio.set_value(self.app.defaults['excellon_editor_lin_dir'])
@@ -2819,10 +2819,8 @@ class FlatCAMExcEditor(QtCore.QObject):
self.tool_shape.enabled = True
# self.app.app_cursor.enabled = True
self.app.ui.snap_max_dist_entry.setEnabled(True)
self.app.ui.corner_snap_btn.setEnabled(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.exc_editor_menu.setDisabled(False)
self.app.ui.exc_editor_menu.menuAction().setVisible(True)
@@ -2832,12 +2830,11 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.exc_edit_toolbar.setDisabled(False)
self.app.ui.exc_edit_toolbar.setVisible(True)
# self.app.ui.snap_toolbar.setDisabled(False)
# self.app.ui.status_toolbar.setDisabled(False)
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.ui.on_grid_snap_triggered(state=True)
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -2869,30 +2866,8 @@ class FlatCAMExcEditor(QtCore.QObject):
self.clear()
self.app.ui.exc_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == 'standard':
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
else:
# self.app.ui.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
else:
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
# set the Editor Toolbar visibility to what was before entering in the Editor
self.app.ui.exc_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -3068,7 +3043,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.set_ui()
# now that we hava data, create the GUI interface and add it to the Tool Tab
# now that we hava data, create the AppGUI interface and add it to the Tool Tab
self.build_ui(first_run=True)
# we activate this after the initial build as we don't need to see the tool been populated
@@ -3361,15 +3336,17 @@ class FlatCAMExcEditor(QtCore.QObject):
with self.app.proc_container.new(_("Creating Excellon.")):
try:
edited_obj = self.app.new_object("excellon", outname, obj_init)
edited_obj = self.app.app_obj.new_object("excellon", outname, obj_init)
edited_obj.source_file = self.app.export_excellon(obj_name=edited_obj.options['name'],
local_use=edited_obj,
filename=None,
use_thread=False)
except Exception as e:
self.deactivate()
log.error("Error on Edited object creation: %s" % str(e))
return
self.deactivate()
self.app.inform.emit('[success] %s' % _("Excellon editing finished."))
def on_tool_select(self, tool):
@@ -3463,8 +3440,8 @@ class FlatCAMExcEditor(QtCore.QObject):
self.pos = (self.pos[0], self.pos[1])
if event.button == 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
# Selection with left mouse button
if self.active_tool is not None and event.button == 1:
@@ -3801,18 +3778,22 @@ class FlatCAMExcEditor(QtCore.QObject):
self.snap_x = x
self.snap_y = y
# update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (x, y))
if self.pos is None:
self.pos = (0, 0)
self.app.dx = x - self.pos[0]
self.app.dy = y - self.pos[1]
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
# # update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f&nbsp;" % (x, y))
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
units = self.app.defaults["units"].lower()
self.app.plotcanvas.text_hud.text = \
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
self.app.dx, units, self.app.dy, units, x, units, y, units)
# ## Utility geometry (animated)
self.update_utility_geometry(data=(x, y))
@@ -4045,7 +4026,7 @@ class FlatCAMExcEditor(QtCore.QObject):
def select_tool(self, toolname):
"""
Selects a drawing tool. Impacts the object and GUI.
Selects a drawing tool. Impacts the object and AppGUI.
:param toolname: Name of the tool.
:return: None

View File

@@ -15,11 +15,10 @@ from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, QSettings
from camlib import distance, arc, three_point_circle, Geometry, FlatCAMRTreeStorage
from FlatCAMTool import FlatCAMTool
from flatcamGUI.ObjectUI import RadioSet
from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree
from flatcamParsers.ParseFont import *
from AppTool import AppTool
from AppGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \
FCDoubleSpinner, FCButton, FCInputDialog, FCTree
from AppParsers.ParseFont import *
from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon
from shapely.ops import cascaded_union, unary_union, linemerge
@@ -34,7 +33,7 @@ from rtree import index as rtindex
from copy import deepcopy
# from vispy.io import read_png
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -42,7 +41,7 @@ if '_' not in builtins.__dict__:
_ = gettext.gettext
class BufferSelectionTool(FlatCAMTool):
class BufferSelectionTool(AppTool):
"""
Simple input for buffer distance.
"""
@@ -50,7 +49,7 @@ class BufferSelectionTool(FlatCAMTool):
toolName = "Buffer Selection"
def __init__(self, app, draw_app):
FlatCAMTool.__init__(self, app)
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
@@ -118,12 +117,12 @@ class BufferSelectionTool(FlatCAMTool):
self.buffer_int_button.clicked.connect(self.on_buffer_int)
self.buffer_ext_button.clicked.connect(self.on_buffer_ext)
# Init GUI
# Init AppGUI
self.buffer_distance_entry.set_value(0.01)
def run(self):
self.app.defaults.report_usage("Geo Editor ToolBuffer()")
FlatCAMTool.run(self)
AppTool.run(self)
# if the splitter us hidden, display it
if self.app.ui.splitter.sizes()[0] == 0:
@@ -187,7 +186,7 @@ class BufferSelectionTool(FlatCAMTool):
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
class TextInputTool(FlatCAMTool):
class TextInputTool(AppTool):
"""
Simple input for buffer distance.
"""
@@ -195,7 +194,7 @@ class TextInputTool(FlatCAMTool):
toolName = "Text Input Tool"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
AppTool.__init__(self, app)
self.app = app
self.text_path = []
@@ -340,7 +339,7 @@ class TextInputTool(FlatCAMTool):
def run(self):
self.app.defaults.report_usage("Geo Editor TextInputTool()")
FlatCAMTool.run(self)
AppTool.run(self)
# if the splitter us hidden, display it
if self.app.ui.splitter.sizes()[0] == 0:
@@ -405,7 +404,7 @@ class TextInputTool(FlatCAMTool):
self.app.ui.notebook.setTabText(2, _("Tool"))
class PaintOptionsTool(FlatCAMTool):
class PaintOptionsTool(AppTool):
"""
Inputs to specify how to paint the selected polygons.
"""
@@ -413,7 +412,7 @@ class PaintOptionsTool(FlatCAMTool):
toolName = "Paint Tool"
def __init__(self, app, fcdraw):
FlatCAMTool.__init__(self, app)
AppTool.__init__(self, app)
self.app = app
self.fcdraw = fcdraw
@@ -538,7 +537,7 @@ class PaintOptionsTool(FlatCAMTool):
def run(self):
self.app.defaults.report_usage("Geo Editor ToolPaint()")
FlatCAMTool.run(self)
AppTool.run(self)
# if the splitter us hidden, display it
if self.app.ui.splitter.sizes()[0] == 0:
@@ -547,7 +546,7 @@ class PaintOptionsTool(FlatCAMTool):
self.app.ui.notebook.setTabText(2, _("Paint Tool"))
def set_tool_ui(self):
# Init GUI
# Init AppGUI
if self.app.defaults["tools_painttooldia"]:
self.painttooldia_entry.set_value(self.app.defaults["tools_painttooldia"])
else:
@@ -599,7 +598,7 @@ class PaintOptionsTool(FlatCAMTool):
self.app.ui.splitter.setSizes([0, 1])
class TransformEditorTool(FlatCAMTool):
class TransformEditorTool(AppTool):
"""
Inputs to specify how to paint the selected polygons.
"""
@@ -612,7 +611,7 @@ class TransformEditorTool(FlatCAMTool):
offsetName = _("Offset")
def __init__(self, app, draw_app):
FlatCAMTool.__init__(self, app)
AppTool.__init__(self, app)
self.app = app
self.draw_app = draw_app
@@ -933,7 +932,7 @@ class TransformEditorTool(FlatCAMTool):
"the 'y' in (x, y) will be used when using Flip on Y.")
)
self.flip_ref_label.setFixedWidth(50)
self.flip_ref_entry = FCEntry("(0, 0)")
self.flip_ref_entry = FCEntry("0, 0")
self.flip_ref_button = FCButton()
self.flip_ref_button.set_value(_("Add"))
@@ -981,7 +980,7 @@ class TransformEditorTool(FlatCAMTool):
def run(self):
self.app.defaults.report_usage("Geo Editor Transform Tool()")
FlatCAMTool.run(self)
AppTool.run(self)
self.set_tool_ui()
# if the splitter us hidden, display it
@@ -991,7 +990,7 @@ class TransformEditorTool(FlatCAMTool):
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
def set_tool_ui(self):
# Initialize form
@@ -1048,7 +1047,7 @@ class TransformEditorTool(FlatCAMTool):
if self.app.defaults["tools_transform_mirror_point"]:
self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
else:
self.flip_ref_entry.set_value((0, 0))
self.flip_ref_entry.set_value("0, 0")
def template(self):
if not self.draw_app.selected:
@@ -3383,7 +3382,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
else:
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
@@ -3467,22 +3466,32 @@ class FlatCAMGeoEditor(QtCore.QObject):
:return:
"""
try:
self.options[opt] = float(entry.text())
text_value = entry.text()
if ',' in text_value:
text_value = text_value.replace(',', '.')
self.options[opt] = float(text_value)
except Exception as e:
entry.set_value(self.app.defaults[opt])
log.debug("FlatCAMGeoEditor.__init__().entry2option() --> %s" % str(e))
return
def gridx_changed(goption, gentry):
def grid_changed(goption, gentry):
"""
:param goption: String. Can be either 'global_gridx' or 'global_gridy'
:param gentry: A GUI element which text value is read and used
:return:
"""
if goption not in ['global_gridx', 'global_gridy']:
return
entry2option(opt=goption, entry=gentry)
# if the grid link is checked copy the value in the GridX field to GridY
try:
val = float(gentry.get_value())
text_value = gentry.text()
if ',' in text_value:
text_value = text_value.replace(',', '.')
val = float(text_value)
except ValueError:
return
@@ -3491,7 +3500,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_x_entry.textChanged.connect(
lambda: gridx_changed("global_gridx", self.app.ui.grid_gap_x_entry))
lambda: grid_changed("global_gridx", self.app.ui.grid_gap_x_entry))
self.app.ui.grid_gap_y_entry.setValidator(QtGui.QDoubleValidator())
self.app.ui.grid_gap_y_entry.textChanged.connect(
@@ -3542,7 +3551,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# store the status of the editor so the Delete at object level will not work until the edit is finished
self.editor_active = False
log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
log.debug("Initialization of the Geometry Editor is finished ...")
def pool_recreated(self, pool):
self.shapes.pool = pool
@@ -3559,14 +3568,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Remove anything else in the GUI Selected Tab
self.app.ui.selected_scroll_area.takeWidget()
# Put ourselves in the GUI Selected Tab
# Put ourselves in the AppGUI Selected Tab
self.app.ui.selected_scroll_area.setWidget(self.geo_edit_widget)
# Switch notebook to Selected page
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
def build_ui(self):
"""
Build the GUI in the Selected Tab for this editor
Build the AppGUI in the Selected Tab for this editor
:return:
"""
@@ -3644,10 +3653,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.tool_shape.enabled = True
self.app.app_cursor.enabled = True
self.app.ui.snap_max_dist_entry.setEnabled(True)
self.app.ui.corner_snap_btn.setEnabled(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.geo_editor_menu.setDisabled(False)
self.app.ui.geo_editor_menu.menuAction().setVisible(True)
@@ -3658,7 +3665,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_edit_toolbar.setDisabled(False)
self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_toolbar.setDisabled(False)
self.app.ui.status_toolbar.setDisabled(False)
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@@ -3675,7 +3682,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.item_selected.connect(self.on_geo_elem_selected)
# ## GUI Events
# ## AppGUI Events
self.tw.itemSelectionChanged.connect(self.on_tree_selection_change)
# self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
# self.tw.customContextMenuRequested.connect(self.on_menu_request)
@@ -3703,27 +3710,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == 'standard':
# self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
else:
# self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
else:
# self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
# set the Editor Toolbar visibility to what was before entering in the Editor
self.app.ui.geo_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -3757,7 +3745,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
pass
try:
# ## GUI Events
# ## AppGUI Events
self.tw.itemSelectionChanged.disconnect(self.on_tree_selection_change)
# self.tw.keyPressed.connect(self.app.ui.keyPressEvent)
# self.tw.customContextMenuRequested.connect(self.on_menu_request)
@@ -4100,7 +4088,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.ui.on_grid_snap_triggered(state=True)
def on_buffer_tool(self):
buff_tool = BufferSelectionTool(self.app, self)
@@ -4148,9 +4135,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
# make sure that the cursor shape is enabled/disabled, too
if self.options['grid_snap'] is True:
self.app.inform[str, bool].emit(_("Grid Snap enabled."), False)
self.app.app_cursor.enabled = True
else:
self.app.app_cursor.enabled = False
self.app.inform[str, bool].emit(_("Grid Snap disabled."), False)
def on_canvas_click(self, event):
"""
@@ -4173,8 +4162,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.pos = (self.pos[0], self.pos[1])
if event.button == 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
modifiers = QtWidgets.QApplication.keyboardModifiers()
# If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
@@ -4261,18 +4250,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.snap_y = y
self.app.mouse = [x, y]
# update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (x, y))
if self.pos is None:
self.pos = (0, 0)
self.app.dx = x - self.pos[0]
self.app.dy = y - self.pos[1]
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
# # update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f&nbsp;" % (x, y))
#
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
units = self.app.defaults["units"].lower()
self.app.plotcanvas.text_hud.text = \
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
self.app.dx, units, self.app.dy, units, x, units, y, units)
if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
pass
@@ -4665,7 +4659,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
def select_tool(self, toolname):
"""
Selects a drawing tool. Impacts the object and GUI.
Selects a drawing tool. Impacts the object and AppGUI.
:param toolname: Name of the tool.
:return: None
@@ -4776,6 +4770,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
new_geo = linemerge(new_geo)
fcgeometry.solid_geometry.append(new_geo)
self.deactivate()
def update_options(self, obj):
if self.paint_tooldia:
obj.options['cnctooldia'] = deepcopy(str(self.paint_tooldia))

View File

@@ -14,15 +14,13 @@ import shapely.affinity as affinity
from vispy.geometry import Rect
import threading
import time
from copy import copy, deepcopy
import logging
from camlib import distance, arc, three_point_circle
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
from AppGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \
EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
from FlatCAMTool import FlatCAMTool
from AppTool import AppTool
import numpy as np
from numpy.linalg import norm as numpy_norm
@@ -32,7 +30,7 @@ import math
# import pngcanvas
import traceback
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -1086,15 +1084,6 @@ class FCRegion(FCShapeTool):
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
self.draw_app.apertures_table.clearSelection()
self.draw_app.plot_all()
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
pass
def on_key(self, key):
# Jump to coords
if key == QtCore.Qt.Key_J or key == 'J':
@@ -1160,16 +1149,36 @@ class FCRegion(FCShapeTool):
return msg
def clean_up(self):
self.draw_app.selected = []
self.draw_app.apertures_table.clearSelection()
self.draw_app.plot_all()
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
pass
class FCTrack(FCRegion):
class FCTrack(FCShapeTool):
"""
Resulting type: Polygon
"""
def __init__(self, draw_app):
FCRegion.__init__(self, draw_app)
DrawTool.__init__(self, draw_app)
self.name = 'track'
self.draw_app = draw_app
self.steps_per_circle = self.draw_app.app.defaults["gerber_circle_steps"]
size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size'])
self.buf_val = (size_ap / 2) if size_ap > 0 else 0.0000001
self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
self.temp_points = []
self. final_click = False
try:
QtGui.QGuiApplication.restoreOverrideCursor()
except Exception as e:
@@ -1183,52 +1192,23 @@ class FCTrack(FCRegion):
self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...'))
def make(self):
new_geo_el = {}
if len(self.temp_points) == 1:
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
new_geo_el['follow'] = Point(self.temp_points)
else:
new_geo_el['solid'] = (LineString(self.temp_points).buffer(
self.buf_val, resolution=int(self.steps_per_circle / 4))).buffer(0)
new_geo_el['follow'] = LineString(self.temp_points)
self.geometry = DrawToolShape(new_geo_el)
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
self.draw_app.apertures_table.clearSelection()
self.draw_app.plot_all()
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
pass
def click(self, point):
self.draw_app.in_action = True
try:
if point != self.points[-1]:
if not self.points:
self.points.append(point)
except IndexError:
elif point != self.points[-1]:
self.points.append(point)
else:
return
new_geo_el = {}
if len(self.temp_points) == 1:
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
new_geo_el['follow'] = Point(self.temp_points)
else:
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
new_geo_el['follow'] = LineString(self.temp_points)
self.draw_app.add_gerber_shape(DrawToolShape(new_geo_el),
@@ -1241,23 +1221,25 @@ class FCTrack(FCRegion):
return ""
def update_grid_info(self):
self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value())
self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value())
def utility_geometry(self, data=None):
self.update_grid_info()
new_geo_el = {}
if len(self.points) == 0:
new_geo_el['solid'] = Point(data).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
if not self.points:
new_geo_el['solid'] = Point(data).buffer(self.buf_val, int(self.steps_per_circle))
return DrawToolUtilityShape(new_geo_el)
elif len(self.points) > 0:
self.temp_points = [self.points[-1]]
else:
old_x = self.points[-1][0]
old_y = self.points[-1][1]
x = data[0]
y = data[1]
self.temp_points = [self.points[-1]]
mx = abs(round((x - old_x) / self.gridx_size))
my = abs(round((y - old_y) / self.gridy_size))
@@ -1305,14 +1287,30 @@ class FCTrack(FCRegion):
self.temp_points.append(data)
if len(self.temp_points) == 1:
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
return DrawToolUtilityShape(new_geo_el)
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val,
resolution=int(self.steps_per_circle / 4))
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
return DrawToolUtilityShape(new_geo_el)
def make(self):
new_geo_el = {}
if len(self.temp_points) == 1:
new_geo_el['solid'] = Point(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
new_geo_el['follow'] = Point(self.temp_points)
else:
new_geo_el['solid'] = LineString(self.temp_points).buffer(self.buf_val, int(self.steps_per_circle))
new_geo_el['solid'] = new_geo_el['solid'].buffer(0) # try to clean the geometry
new_geo_el['follow'] = LineString(self.temp_points)
self.geometry = DrawToolShape(new_geo_el)
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.jump_signal.disconnect()
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def on_key(self, key):
if key == 'Backspace' or key == QtCore.Qt.Key_Backspace:
if len(self.points) > 0:
@@ -1405,6 +1403,15 @@ class FCTrack(FCRegion):
return msg
def clean_up(self):
self.draw_app.selected = []
self.draw_app.apertures_table.clearSelection()
self.draw_app.plot_all()
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
pass
class FCDisc(FCShapeTool):
"""
@@ -2955,7 +2962,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
# Init GUI
# Init AppGUI
self.apdim_lbl.hide()
self.apdim_entry.hide()
self.gerber_obj = None
@@ -2967,7 +2974,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.tool_shape = self.canvas.new_shape_collection(layers=1)
self.ma_annotation = self.canvas.new_text_group()
else:
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_grb_editor')
self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_grb_editor')
self.ma_annotation = ShapeCollectionLegacy(
@@ -3110,7 +3117,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.complete = True
self.set_ui()
log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
log.debug("Initialization of the Gerber Editor is finished ...")
def pool_recreated(self, pool):
self.shapes.pool = pool
@@ -3139,7 +3146,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
tt_aperture = self.sorted_apcode[i]
self.tid2apcode[i + 1] = tt_aperture
# Init GUI
# Init AppGUI
self.buffer_distance_entry.set_value(self.app.defaults["gerber_editor_buff_f"])
self.scale_factor_entry.set_value(self.app.defaults["gerber_editor_scale_f"])
@@ -3685,10 +3692,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.enabled = True
self.tool_shape.enabled = True
self.app.ui.snap_max_dist_entry.setEnabled(True)
self.app.ui.corner_snap_btn.setEnabled(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.grb_editor_menu.setDisabled(False)
self.app.ui.grb_editor_menu.menuAction().setVisible(True)
@@ -3698,12 +3703,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.grb_edit_toolbar.setDisabled(False)
self.app.ui.grb_edit_toolbar.setVisible(True)
# self.app.ui.snap_toolbar.setDisabled(False)
# self.app.ui.status_toolbar.setDisabled(False)
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.ui.on_grid_snap_triggered(state=True)
# adjust the visibility of some of the canvas context menu
self.app.ui.popmenu_edit.setVisible(False)
@@ -3736,29 +3740,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.grb_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == 'standard':
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
else:
# self.app.ui.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
else:
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
# set the Editor Toolbar visibility to what was before entering in the Editor
self.app.ui.grb_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
@@ -4210,7 +4193,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
def on_multiprocessing_finished(self):
self.app.proc_container.update_view_text(' %s' % _("Setting up the UI"))
self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the GUI"))
self.app.inform.emit('[success] %s.' % _("Adding geometry finished. Preparing the AppGUI"))
self.set_ui()
self.build_ui(first_run=True)
self.plot_all()
@@ -4245,6 +4228,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
new_grb_name = self.edited_obj_name + "_edit"
self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [new_grb_name, self.storage_dict]})
# self.new_edited_gerber(new_grb_name, self.storage_dict)
@staticmethod
def update_options(obj):
@@ -4269,6 +4253,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
:param outname: Name of the resulting object. None causes the name to be that of the file.
:type outname: str
:param aperture_storage: a dictionary that holds all the objects geometry
:type aperture_storage: dict
:return: None
"""
@@ -4360,26 +4345,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("There are no Aperture definitions in the file. Aborting Gerber creation."))
except Exception:
msg = '[ERROR] %s' % \
_("An internal error has occurred. See shell.\n")
msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
msg += traceback.format_exc()
app_obj.inform.emit(msg)
raise
grb_obj.source_file = self.app.export_gerber(obj_name=out_name, filename=None,
local_use=grb_obj, use_thread=False)
with self.app.proc_container.new(_("Creating Gerber.")):
try:
self.app.new_object("gerber", outname, obj_init)
self.app.app_obj.new_object("gerber", outname, obj_init)
except Exception as e:
log.error("Error on Edited object creation: %s" % str(e))
# make sure to clean the previous results
self.results = []
return
self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
# make sure to clean the previous results
self.results = []
self.deactivate_grb_editor()
self.app.inform.emit('[success] %s' % _("Done. Gerber editing finished."))
def on_tool_select(self, tool):
"""
@@ -4537,8 +4523,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.pos = (self.pos[0], self.pos[1])
if event.button == 1:
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
# Selection with left mouse button
if self.active_tool is not None:
@@ -4550,8 +4536,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.defaults["global_point_clipboard_format"] %
(self.decimals, self.pos[0], self.decimals, self.pos[1])
)
self.app.inform.emit('[success] %s' %
_("Coordinates copied to clipboard."))
self.app.inform.emit('[success] %s' % _("Coordinates copied to clipboard."))
return
# Dispatch event to active_tool
@@ -4562,6 +4547,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
if self.current_storage is not None:
self.on_grb_shape_complete(self.current_storage)
self.build_ui()
# MS: always return to the Select Tool if modifier key is not pressed
# else return to the current tool
key_modifier = QtWidgets.QApplication.keyboardModifiers()
@@ -4569,6 +4555,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
modifier_to_use = Qt.ControlModifier
else:
modifier_to_use = Qt.ShiftModifier
# if modifier key is pressed then we add to the selected list the current shape but if it's already
# in the selected list, we removed it. Therefore first click selects, second deselects.
if key_modifier == modifier_to_use:
@@ -4629,12 +4616,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
# if right click on canvas and the active tool need to be finished (like Path or Polygon)
# right mouse click will finish the action
if isinstance(self.active_tool, FCShapeTool):
if isinstance(self.active_tool, FCTrack):
self.active_tool.make()
else:
self.active_tool.click(self.app.geo_editor.snap(self.x, self.y))
self.active_tool.make()
if self.active_tool.complete:
self.on_grb_shape_complete()
self.app.inform.emit('[success] %s' %
_("Done."))
self.app.inform.emit('[success] %s' % _("Done."))
# MS: always return to the Select Tool if modifier key is not pressed
# else return to the current tool but not for FCTrack
@@ -4774,18 +4763,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.mouse = [x, y]
# update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (x, y))
if self.pos is None:
self.pos = (0, 0)
self.app.dx = x - self.pos[0]
self.app.dy = y - self.pos[1]
# update the reference position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
# # update the position label in the infobar since the APP mouse event handlers are disconnected
self.app.ui.position_label.setText("&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f&nbsp;" % (x, y))
#
# # update the reference position label in the infobar since the APP mouse event handlers are disconnected
# self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
# "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
units = self.app.defaults["units"].lower()
self.app.plotcanvas.text_hud.text = \
'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
self.app.dx, units, self.app.dy, units, x, units, y, units)
self.update_utility_geometry(data=(x, y))
@@ -5032,7 +5026,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
def select_tool(self, toolname):
"""
Selects a drawing tool. Impacts the object and GUI.
Selects a drawing tool. Impacts the object and AppGUI.
:param toolname: Name of the tool.
:return: None
@@ -5298,7 +5292,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
class TransformEditorTool(FlatCAMTool):
class TransformEditorTool(AppTool):
"""
Inputs to specify how to paint the selected polygons.
"""
@@ -5311,7 +5305,7 @@ class TransformEditorTool(FlatCAMTool):
offsetName = _("Offset")
def __init__(self, app, draw_app):
FlatCAMTool.__init__(self, app)
AppTool.__init__(self, app)
self.app = app
self.draw_app = draw_app
@@ -5633,7 +5627,7 @@ class TransformEditorTool(FlatCAMTool):
"the 'y' in (x, y) will be used when using Flip on Y.")
)
self.flip_ref_label.setMinimumWidth(50)
self.flip_ref_entry = EvalEntry2("(0, 0)")
self.flip_ref_entry = FCEntry()
self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
# self.flip_ref_entry.setFixedWidth(60)
@@ -5697,13 +5691,13 @@ class TransformEditorTool(FlatCAMTool):
except AttributeError:
pass
FlatCAMTool.run(self)
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs)
def set_tool_ui(self):
# Initialize form
@@ -5760,7 +5754,7 @@ class TransformEditorTool(FlatCAMTool):
if self.app.defaults["tools_transform_mirror_point"]:
self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
else:
self.flip_ref_entry.set_value((0, 0))
self.flip_ref_entry.set_value("0, 0")
def template(self):
if not self.draw_app.selected:

View File

@@ -5,7 +5,7 @@
# MIT Licence #
# ##########################################################
from flatcamGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
from AppGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber
from PyQt5 import QtPrintSupport, QtWidgets, QtCore, QtGui
from reportlab.platypus import SimpleDocTemplate, Paragraph
@@ -15,7 +15,7 @@ from reportlab.lib.units import inch, mm
# from io import StringIO
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -214,10 +214,10 @@ class TextEditor(QtWidgets.QWidget):
filename = str(FCFileSaveDialog.get_saved_filename(
caption=_("Export Code ..."),
directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
filter=_filter_
ext_filter=_filter_
)[0])
except TypeError:
filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), filter=_filter_)[0])
filename = str(FCFileSaveDialog.get_saved_filename(caption=_("Export Code ..."), ext_filter=_filter_)[0])
if filename == "":
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled."))

View File

@@ -20,9 +20,10 @@ from copy import copy
import re
import logging
import html
import sys
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
log = logging.getLogger('base')
@@ -569,9 +570,13 @@ class FCEntry3(FCEntry):
class EvalEntry(QtWidgets.QLineEdit):
def __init__(self, parent=None):
def __init__(self, border_color=None, parent=None):
super(EvalEntry, self).__init__(parent)
self.readyToEdit = True
if border_color:
self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
self.editingFinished.connect(self.on_edit_finished)
def on_edit_finished(self):
@@ -598,7 +603,6 @@ class EvalEntry(QtWidgets.QLineEdit):
def get_value(self):
raw = str(self.text()).strip(' ')
evaled = 0.0
try:
evaled = eval(raw)
except Exception as e:
@@ -638,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
def get_value(self):
raw = str(self.text()).strip(' ')
evaled = 0.0
try:
evaled = eval(raw)
except Exception as e:
@@ -655,6 +659,30 @@ class EvalEntry2(QtWidgets.QLineEdit):
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
class NumericalEvalEntry(EvalEntry):
"""
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
"""
def __init__(self, border_color=None):
super().__init__(border_color=border_color)
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
validator = QtGui.QRegExpValidator(regex, self)
self.setValidator(validator)
class NumericalEvalTupleEntry(FCEntry):
"""
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
"""
def __init__(self, border_color=None):
super().__init__(border_color=border_color)
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
validator = QtGui.QRegExpValidator(regex, self)
self.setValidator(validator)
class FCSpinner(QtWidgets.QSpinBox):
returnPressed = QtCore.pyqtSignal()
@@ -683,6 +711,8 @@ class FCSpinner(QtWidgets.QSpinBox):
self.setAlignment(align_val)
self.prev_readyToEdit = True
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
self.setSizePolicy(sizePolicy)
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.MouseButtonPress and self.prev_readyToEdit is True:
@@ -815,6 +845,8 @@ class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
self.setAlignment(align_val)
self.prev_readyToEdit = True
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
self.setSizePolicy(sizePolicy)
def on_edit_finished(self):
self.clearFocus()
@@ -1336,7 +1368,8 @@ class FCComboBox(QtWidgets.QComboBox):
return str(self.currentText())
def set_value(self, val):
self.setCurrentIndex(self.findText(str(val)))
idx = self.findText(str(val))
self.setCurrentIndex(idx)
@property
def is_last(self):
@@ -1480,9 +1513,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
From here:
https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
"""
tab_detached = QtCore.pyqtSignal(str)
tab_attached = QtCore.pyqtSignal(str)
def __init__(self, protect=None, protect_by_name=None, parent=None):
super().__init__()
super().__init__(parent=parent)
self.tabBar = self.FCTabBar(self)
self.tabBar.onMoveTabSignal.connect(self.moveTab)
@@ -1619,7 +1654,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
self.insertTab(toIndex, widget, icon, text)
self.setCurrentIndex(toIndex)
@pyqtSlot(int, QtCore.QPoint)
# @pyqtSlot(int, QtCore.QPoint)
def detachTab(self, index, point):
"""
Detach the tab by removing it's contents and placing them in
@@ -1656,6 +1691,8 @@ class FCDetachableTab(QtWidgets.QTabWidget):
# Create a reference to maintain access to the detached tab
self.detachedTabs[name] = detachedTab
self.tab_detached.emit(name)
def attachTab(self, contentWidget, name, icon, insertAt=None):
"""
Re-attach the tab by removing the content from the DetachedTab window,
@@ -1668,11 +1705,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
:return:
"""
old_name = name
# Make the content widget a child of this widget
contentWidget.setParent(self)
# Remove the reference
del self.detachedTabs[name]
# make sure that we strip the 'FlatCAM' part of the detached name otherwise the tab name will be too long
name = name.partition(' ')[2]
@@ -1712,6 +1749,9 @@ class FCDetachableTab(QtWidgets.QTabWidget):
else:
index = self.insertTab(insert_index, contentWidget, icon, name)
obj_name = contentWidget.objectName()
self.tab_attached.emit(obj_name)
# on reattaching the tab if protect is true then the closure button is not added
if self.protect_tab is True:
self.protectTab(index)
@@ -1727,6 +1767,14 @@ class FCDetachableTab(QtWidgets.QTabWidget):
if index > -1:
self.setCurrentIndex(insert_index) if self.use_old_index else self.setCurrentIndex(index)
# Remove the reference
# Unix-like OS's crash with segmentation fault after this. FOr whatever reason, they loose reference
if sys.platform == 'win32':
try:
del self.detachedTabs[old_name]
except KeyError:
pass
def removeTabByName(self, name):
"""
Remove the tab with the given name, even if it is detached
@@ -2180,10 +2228,13 @@ class OptionalHideInputSection:
Associates the a checkbox with a set of inputs.
:param cb: Checkbox that enables the optional inputs.
:type cb: QtWidgets.QCheckBox
:param optinputs: List of widgets that are optional.
:type optinputs: list
:param logic: When True the logic is normal, when False the logic is in reverse
It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
for logic=False, when the checkbox is checked the widgets are Disabled
:type logic: bool
:return:
"""
assert isinstance(cb, FCCheckBox), \
@@ -2215,6 +2266,7 @@ class OptionalHideInputSection:
class FCTable(QtWidgets.QTableWidget):
drag_drop_sig = QtCore.pyqtSignal()
lost_focus = QtCore.pyqtSignal()
def __init__(self, drag_drop=False, protected_rows=None, parent=None):
super(FCTable, self).__init__(parent)
@@ -2278,6 +2330,10 @@ class FCTable(QtWidgets.QTableWidget):
else:
QtWidgets.QTableWidget.mousePressEvent(self, event)
def focusOutEvent(self, event):
self.lost_focus.emit()
super().focusOutEvent(event)
def setupContextMenu(self):
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
@@ -2449,7 +2505,8 @@ class SpinBoxDelegate(QtWidgets.QItemDelegate):
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def setDecimals(self, spinbox, digits):
@staticmethod
def setDecimals(spinbox, digits):
spinbox.setDecimals(digits)
@@ -2497,7 +2554,7 @@ class DialogBoxRadio(QtWidgets.QDialog):
:param title: string with the window title
:param label: string with the message inside the dialog box
"""
super(DialogBoxRadio, self).__init__()
super(DialogBoxRadio, self).__init__(parent=parent)
if initial_text is None:
self.location = str((0, 0))
else:
@@ -2740,9 +2797,12 @@ class MyCompleter(QCompleter):
insertText = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
QCompleter.__init__(self)
QCompleter.__init__(self, parent=parent)
self.setCompletionMode(QCompleter.PopupCompletion)
self.highlighted.connect(self.setHighlighted)
self.lastSelected = ''
# self.popup().installEventFilter(self)
# def eventFilter(self, obj, event):
@@ -2898,9 +2958,9 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
super(FCFileSaveDialog, self).__init__(*args)
@staticmethod
def get_saved_filename(parent=None, caption='', directory='', filter='', initialFilter=''):
def get_saved_filename(parent=None, caption='', directory='', ext_filter='', initialFilter=''):
filename, _filter = QtWidgets.QFileDialog.getSaveFileName(parent=parent, caption=caption,
directory=directory, filter=filter,
directory=directory, filter=ext_filter,
initialFilter=initialFilter)
filename = str(filename)
@@ -2916,6 +2976,225 @@ class FCFileSaveDialog(QtWidgets.QFileDialog):
return filename, _filter
class FCDock(QtWidgets.QDockWidget):
def __init__(self, *args, **kwargs):
super(FCDock, self).__init__(*args)
self.close_callback = kwargs["close_callback"] if "close_callback" in kwargs else None
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
self.close_callback()
super().closeEvent(event)
def show(self) -> None:
if self.isFloating():
self.setFloating(False)
super().show()
class FlatCAMActivityView(QtWidgets.QWidget):
"""
This class create and control the activity icon displayed in the App status bar
"""
def __init__(self, app, parent=None):
super().__init__(parent=parent)
self.app = app
if self.app.defaults["global_activity_icon"] == "Ball green":
icon = self.app.resource_location + '/active_2_static.png'
movie = self.app.resource_location + "/active_2.gif"
elif self.app.defaults["global_activity_icon"] == "Ball black":
icon = self.app.resource_location + '/active_static.png'
movie = self.app.resource_location + "/active.gif"
elif self.app.defaults["global_activity_icon"] == "Arrow green":
icon = self.app.resource_location + '/active_3_static.png'
movie = self.app.resource_location + "/active_3.gif"
elif self.app.defaults["global_activity_icon"] == "Eclipse green":
icon = self.app.resource_location + '/active_4_static.png'
movie = self.app.resource_location + "/active_4.gif"
else:
icon = self.app.resource_location + '/active_static.png'
movie = self.app.resource_location + "/active.gif"
self.setMinimumWidth(200)
self.movie_path = movie
self.icon_path = icon
self.icon = FCLabel(self)
self.icon.setGeometry(0, 0, 16, 12)
self.movie = QtGui.QMovie(self.movie_path)
self.icon.setMovie(self.movie)
# self.movie.start()
layout = QtWidgets.QHBoxLayout()
layout.setContentsMargins(5, 0, 5, 0)
layout.setAlignment(QtCore.Qt.AlignLeft)
self.setLayout(layout)
layout.addWidget(self.icon)
self.text = QtWidgets.QLabel(self)
self.text.setText(_("Idle."))
self.icon.setPixmap(QtGui.QPixmap(self.icon_path))
layout.addWidget(self.text)
self.icon.clicked.connect(self.app.on_toolbar_replot)
def set_idle(self):
self.movie.stop()
self.text.setText(_("Idle."))
def set_busy(self, msg, no_movie=None):
if no_movie is not True:
self.icon.setMovie(self.movie)
self.movie.start()
self.text.setText(msg)
class FlatCAMInfoBar(QtWidgets.QWidget):
"""
This class create a place to display the App messages in the Status Bar
"""
def __init__(self, parent=None, app=None):
super(FlatCAMInfoBar, self).__init__(parent=parent)
self.app = app
self.icon = QtWidgets.QLabel(self)
self.icon.setGeometry(0, 0, 12, 12)
self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
self.icon.setPixmap(self.pmap)
self.lock_pmaps = False
layout = QtWidgets.QHBoxLayout()
layout.setContentsMargins(5, 0, 5, 0)
self.setLayout(layout)
layout.addWidget(self.icon)
self.text = QtWidgets.QLabel(self)
self.text.setText(_("Application started ..."))
self.text.setToolTip(_("Hello!"))
layout.addWidget(self.text)
layout.addStretch()
def set_text_(self, text, color=None):
self.text.setText(text)
self.text.setToolTip(text)
if color:
self.text.setStyleSheet('color: %s' % str(color))
def set_status(self, text, level="info"):
level = str(level)
if self.lock_pmaps is not True:
self.pmap.fill()
if level == "ERROR" or level == "ERROR_NOTCL":
self.pmap = QtGui.QPixmap(self.app.resource_location + '/redlight12.png')
elif level.lower() == "success":
self.pmap = QtGui.QPixmap(self.app.resource_location + '/greenlight12.png')
elif level == "WARNING" or level == "WARNING_NOTCL":
self.pmap = QtGui.QPixmap(self.app.resource_location + '/yellowlight12.png')
elif level.lower() == "selected":
self.pmap = QtGui.QPixmap(self.app.resource_location + '/bluelight12.png')
else:
self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png')
try:
self.set_text_(text)
self.icon.setPixmap(self.pmap)
except Exception as e:
log.debug("FlatCAMInfoBar.set_status() --> %s" % str(e))
class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon):
"""
This class create the Sys Tray icon for the app
"""
def __init__(self, app, icon, headless=None, parent=None):
# QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
super().__init__(icon, parent=parent)
self.app = app
menu = QtWidgets.QMenu(parent)
menu_runscript = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/script14.png'),
'%s' % _('Run Script ...'), self)
menu_runscript.setToolTip(
_("Will run the opened Tcl Script thus\n"
"enabling the automation of certain\n"
"functions of FlatCAM.")
)
menu.addAction(menu_runscript)
menu.addSeparator()
if headless is None:
self.menu_open = menu.addMenu(QtGui.QIcon(self.app.resource_location + '/folder32_bis.png'), _('Open'))
# Open Project ...
menu_openproject = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/folder16.png'),
_('Open Project ...'), self)
self.menu_open.addAction(menu_openproject)
self.menu_open.addSeparator()
# Open Gerber ...
menu_opengerber = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/flatcam_icon24.png'),
_('Open &Gerber ...\tCtrl+G'), self)
self.menu_open.addAction(menu_opengerber)
# Open Excellon ...
menu_openexcellon = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'),
_('Open &Excellon ...\tCtrl+E'), self)
self.menu_open.addAction(menu_openexcellon)
# Open G-Code ...
menu_opengcode = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/code.png'),
_('Open G-&Code ...'), self)
self.menu_open.addAction(menu_opengcode)
self.menu_open.addSeparator()
menu_openproject.triggered.connect(self.app.on_file_openproject)
menu_opengerber.triggered.connect(self.app.on_fileopengerber)
menu_openexcellon.triggered.connect(self.app.on_fileopenexcellon)
menu_opengcode.triggered.connect(self.app.on_fileopengcode)
exitAction = menu.addAction(_("Exit"))
exitAction.setIcon(QtGui.QIcon(self.app.resource_location + '/power16.png'))
self.setContextMenu(menu)
menu_runscript.triggered.connect(lambda: self.app.on_filerunscript(
silent=True if self.app.cmd_line_headless == 1 else False))
exitAction.triggered.connect(self.app.final_save)
def message_dialog(title, message, kind="info", parent=None):
"""
Builds and show a custom QMessageBox to be used in FlatCAM.
:param title: title of the QMessageBox
:param message: message to be displayed
:param kind: type of QMessageBox; will display a specific icon.
:param parent: parent
:return: None
"""
icon = {"info": QtWidgets.QMessageBox.Information,
"warning": QtWidgets.QMessageBox.Warning,
"error": QtWidgets.QMessageBox.Critical}[str(kind)]
dlg = QtWidgets.QMessageBox(icon, title, message, parent=parent)
dlg.setText(message)
dlg.exec_()
def rreplace(s, old, new, occurrence):
"""
Credits go here:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,13 +8,21 @@
from PyQt5 import QtCore
import logging
from flatcamGUI.VisPyCanvas import VisPyCanvas, Color
from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
from vispy.scene.visuals import InfiniteLine, Line
from AppGUI.VisPyCanvas import VisPyCanvas, Color
from AppGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
from vispy.scene.visuals import InfiniteLine, Line, Rectangle, Text
import gettext
import AppTranslation as fcTranslate
import builtins
import numpy as np
from vispy.geometry import Rect
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
log = logging.getLogger('base')
@@ -54,8 +62,12 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
if theme == 'white':
self.line_color = (0.3, 0.0, 0.0, 1.0)
self.rect_hud_color = Color('#0000FF10')
self.text_hud_color = 'black'
else:
self.line_color = (0.4, 0.4, 0.4, 1.0)
self.rect_hud_color = Color('#80808040')
self.text_hud_color = 'white'
# workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
# which might decrease performance
@@ -129,11 +141,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
parent=self.view.scene)
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
# all CNC have a limited workspace
if self.fcapp.defaults['global_workspace'] is True:
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
self.line_parent = None
if self.fcapp.defaults["global_cursor_color_enabled"]:
c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
@@ -146,13 +153,61 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
parent=self.line_parent)
# font size
qsettings = QtCore.QSettings("Open Source", "FlatCAM")
if qsettings.contains("hud_font_size"):
fsize = qsettings.value('hud_font_size', type=int)
else:
fsize = 8
# units
units = self.fcapp.defaults["units"].lower()
# coordinates and anchors
height = fsize * 11 # 90. Constant 11 is something that works
width = height * 2 # width is double the height = it is something that works
center_x = (width / 2) + 5
center_y = (height / 2) + 5
# text
self.text_hud = Text('', color=self.text_hud_color, pos=(10, center_y), method='gpu', anchor_x='left',
parent=None)
self.text_hud.font_size = fsize
self.text_hud.text = 'Dx:\t%s [%s]\nDy:\t%s [%s]\n\nX: \t%s [%s]\nY: \t%s [%s]' % \
('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
# rectangle
self.rect_hud = Rectangle(center=(center_x, center_y), width=width, height=height, radius=[5, 5, 5, 5],
border_color=self.rect_hud_color, color=self.rect_hud_color, parent=None)
self.rect_hud.set_gl_state(depth_test=False)
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
# all CNC have a limited workspace
if self.fcapp.defaults['global_workspace'] is True:
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
# HUD Display
self.hud_enabled = False
# enable the HUD if it is activated in FlatCAM Preferences
if self.fcapp.defaults['global_hud'] is True:
self.on_toggle_hud(state=True)
# Axis Display
self.axis_enabled = True
# enable Axis
self.on_toggle_axis(state=True)
# enable Grid lines
self.grid_lines_enabled = True
self.shape_collections = []
self.shape_collection = self.new_shape_collection()
self.fcapp.pool_recreated.connect(self.on_pool_recreated)
self.text_collection = self.new_text_collection()
# TODO: Should be setting to show/hide CNC job annotations (global or per object)
self.text_collection.enabled = True
self.c = None
@@ -163,6 +218,76 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
def on_toggle_axis(self, signal=None, state=None):
if state is None:
state = not self.axis_enabled
if state:
self.axis_enabled = True
self.v_line.parent = self.view.scene
self.h_line.parent = self.view.scene
self.fcapp.ui.axis_status_label.setStyleSheet("""
QLabel
{
color: black;
background-color: peachpuff;
}
""")
self.fcapp.inform[str, bool].emit(_("Axis enabled."), False)
else:
self.axis_enabled = False
self.v_line.parent = None
self.h_line.parent = None
self.fcapp.ui.axis_status_label.setStyleSheet("")
self.fcapp.inform[str, bool].emit(_("Axis disabled."), False)
def on_toggle_hud(self, signal=None, state=None):
if state is None:
state = not self.hud_enabled
if state:
self.hud_enabled = True
self.rect_hud.parent = self.view
self.text_hud.parent = self.view
self.fcapp.defaults['global_hud'] = True
self.fcapp.ui.hud_label.setStyleSheet("""
QLabel
{
color: black;
background-color: lightblue;
}
""")
self.fcapp.inform[str, bool].emit(_("HUD enabled."), False)
else:
self.hud_enabled = False
self.rect_hud.parent = None
self.text_hud.parent = None
self.fcapp.defaults['global_hud'] = False
self.fcapp.ui.hud_label.setStyleSheet("")
self.fcapp.inform[str, bool].emit(_("HUD disabled."), False)
def on_toggle_grid_lines(self):
state = not self.grid_lines_enabled
if state:
self.grid_lines_enabled = True
self.grid.parent = self.view.scene
self.fcapp.inform[str, bool].emit(_("Grid enabled."), False)
else:
self.grid_lines_enabled = False
self.grid.parent = None
self.fcapp.inform[str, bool].emit(_("Grid disabled."), False)
# HACK: enabling/disabling the cursor seams to somehow update the shapes on screen
# - perhaps is a bug in VisPy implementation
if self.fcapp.grid_status():
self.fcapp.app_cursor.enabled = False
self.fcapp.app_cursor.enabled = True
else:
self.fcapp.app_cursor.enabled = True
self.fcapp.app_cursor.enabled = False
def draw_workspace(self, workspace_size):
"""
Draw a rectangular shape on canvas to specify our valid workspace.
@@ -183,17 +308,30 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
a = np.array([(0, 0), (dims[0], 0), (dims[0], dims[1]), (0, dims[1])])
if not self.workspace_line:
# if not self.workspace_line:
# self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
# antialias=True, method='agg', parent=self.view.scene)
# else:
# self.workspace_line.parent = self.view.scene
self.workspace_line = Line(pos=np.array((a[0], a[1], a[2], a[3], a[0])), color=(0.70, 0.3, 0.3, 0.7),
antialias=True, method='agg', parent=self.view.scene)
else:
self.workspace_line.parent = self.view.scene
self.fcapp.ui.wplace_label.set_value(workspace_size[:3])
self.fcapp.ui.wplace_label.setToolTip(workspace_size)
self.fcapp.ui.wplace_label.setStyleSheet("""
QLabel
{
color: black;
background-color: lightgreen;
}
""")
def delete_workspace(self):
try:
self.workspace_line.parent = None
except Exception:
pass
self.fcapp.ui.wplace_label.setStyleSheet("")
# redraw the workspace lines on the plot by re adding them to the parent view.scene
def restore_workspace(self):
@@ -287,6 +425,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
def on_mouse_scroll(self, event):
# key modifiers
modifiers = event.modifiers
pan_delta_x = self.fcapp.defaults["global_gridx"]
pan_delta_y = self.fcapp.defaults["global_gridy"]
curr_pos = event.pos

View File

@@ -19,8 +19,10 @@ from shapely.geometry import Polygon, LineString, LinearRing
from copy import deepcopy
import logging
import numpy as np
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
# Prevent conflict with Qt5 and above.
@@ -29,13 +31,13 @@ mpl_use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.lines import Line2D
from matplotlib.offsetbox import AnchoredText
# from matplotlib.widgets import Cursor
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
log = logging.getLogger('base')
@@ -45,9 +47,9 @@ class CanvasCache(QtCore.QObject):
Case story #1:
1) No objects in the project.
2) Object is created (new_object() emits object_created(obj)).
2) Object is created (app_obj.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()
(ii) app_obj.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).
@@ -113,7 +115,7 @@ class CanvasCache(QtCore.QObject):
# Continue to update the cache.
# def on_new_object_available(self):
# def on_app_obj.new_object_available(self):
#
# log.debug("A new object is available. Should plot it!")
@@ -147,9 +149,13 @@ class PlotCanvasLegacy(QtCore.QObject):
if self.app.defaults['global_theme'] == 'white':
theme_color = '#FFFFFF'
tick_color = '#000000'
self.rect_hud_color = '#0000FF10'
self.text_hud_color = '#000000'
else:
theme_color = '#000000'
tick_color = '#FFFFFF'
self.rect_hud_color = '#80808040'
self.text_hud_color = '#FFFFFF'
# workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
# which might decrease performance
@@ -295,14 +301,163 @@ class PlotCanvasLegacy(QtCore.QObject):
# signal is the mouse is dragging
self.is_dragging = False
self.mouse_press_pos = None
# signal if there is a doubleclick
self.is_dblclk = False
self.hud_enabled = False
self.text_hud = self.Thud(plotcanvas=self)
# enable Grid lines
self.grid_lines_enabled = True
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
# all CNC have a limited workspace
if self.app.defaults['global_workspace'] is True:
self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
if self.app.defaults['global_hud'] is True:
self.on_toggle_hud(state=True)
# Axis Display
self.axis_enabled = True
# enable Axis
self.on_toggle_axis(state=True)
def on_toggle_axis(self, signal=None, state=None):
if state is None:
state = not self.axis_enabled
if state:
self.axis_enabled = True
if self.h_line not in self.axes.lines and self.v_line not in self.axes.lines:
self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
self.app.ui.axis_status_label.setStyleSheet("""
QLabel
{
color: black;
background-color: peachpuff;
}
""")
self.app.inform[str, bool].emit(_("Axis enabled."), False)
else:
self.axis_enabled = False
if self.h_line in self.axes.lines and self.v_line in self.axes.lines:
self.axes.lines.remove(self.h_line)
self.axes.lines.remove(self.v_line)
self.app.ui.axis_status_label.setStyleSheet("")
self.app.inform[str, bool].emit(_("Axis disabled."), False)
self.canvas.draw()
def on_toggle_hud(self, signal=None, state=None):
if state is None:
state = not self.hud_enabled
if state:
self.hud_enabled = True
self.text_hud.add_artist()
self.app.defaults['global_hud'] = True
self.app.ui.hud_label.setStyleSheet("""
QLabel
{
color: black;
background-color: lightblue;
}
""")
self.app.inform[str, bool].emit(_("HUD enabled."), False)
else:
self.hud_enabled = False
self.text_hud.remove_artist()
self.app.defaults['global_hud'] = False
self.app.ui.hud_label.setStyleSheet("")
self.app.inform[str, bool].emit(_("HUD disabled."), False)
self.canvas.draw()
class Thud(QtCore.QObject):
text_changed = QtCore.pyqtSignal(str)
def __init__(self, plotcanvas):
super().__init__()
self.p = plotcanvas
units = self.p.app.defaults['units']
self._text = 'Dx: %s [%s]\nDy: %s [%s]\n\nX: %s [%s]\nY: %s [%s]' % \
('0.0000', units, '0.0000', units, '0.0000', units, '0.0000', units)
# set font size
qsettings = QtCore.QSettings("Open Source", "FlatCAM")
if qsettings.contains("hud_font_size"):
# I multiply with 2.5 because this seems to be the difference between the value taken by the VisPy (3D)
# and Matplotlib (Legacy2D FlatCAM graphic engine)
fsize = int(qsettings.value('hud_font_size', type=int) * 2.5)
else:
fsize = 20
self.hud_holder = AnchoredText(self._text, prop=dict(size=fsize), frameon=True, loc='upper left')
self.hud_holder.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
fc_color = self.p.rect_hud_color[:-2]
fc_alpha = int(self.p.rect_hud_color[-2:], 16) / 255
text_color = self.p.text_hud_color
self.hud_holder.patch.set_facecolor(fc_color)
self.hud_holder.patch.set_alpha(fc_alpha)
self.hud_holder.patch.set_edgecolor((0, 0, 0, 0))
self. hud_holder.txt._text.set_color(color=text_color)
self.text_changed.connect(self.on_text_changed)
@property
def text(self):
return self._text
@text.setter
def text(self, val):
self.text_changed.emit(val)
self._text = val
def on_text_changed(self, txt):
try:
txt = txt.replace('\t', ' ')
self.hud_holder.txt.set_text(txt)
self.p.canvas.draw()
except Exception:
pass
def add_artist(self):
if self.hud_holder not in self.p.axes.artists:
self.p.axes.add_artist(self.hud_holder)
def remove_artist(self):
if self.hud_holder in self.p.axes.artists:
self.p.axes.artists.remove(self.hud_holder)
def on_toggle_grid_lines(self):
state = not self.grid_lines_enabled
if state:
self.grid_lines_enabled = True
self.axes.grid(True)
try:
self.canvas.draw()
except IndexError:
pass
self.app.inform[str, bool].emit(_("Grid enabled."), False)
else:
self.grid_lines_enabled = False
self.axes.grid(False)
try:
self.canvas.draw()
except IndexError:
pass
self.app.inform[str, bool].emit(_("Grid disabled."), False)
def draw_workspace(self, workspace_size):
"""
Draw a rectangular shape on canvas to specify our valid workspace.
@@ -329,12 +484,23 @@ class PlotCanvasLegacy(QtCore.QObject):
self.axes.add_line(self.workspace_line)
self.canvas.draw()
self.app.ui.wplace_label.set_value(workspace_size[:3])
self.app.ui.wplace_label.setToolTip(workspace_size)
self.fcapp.ui.wplace_label.setStyleSheet("""
QLabel
{
color: black;
background-color: lightgreen;
}
""")
def delete_workspace(self):
try:
self.axes.lines.remove(self.workspace_line)
self.canvas.draw()
except Exception:
pass
self.fcapp.ui.wplace_label.setStyleSheet("")
def graph_event_connect(self, event_name, callback):
"""
@@ -423,7 +589,7 @@ class PlotCanvasLegacy(QtCore.QObject):
if self.big_cursor is False:
try:
x, y = self.app.geo_editor.snap(x_pos, y_pos)
x, y = self.snap(x_pos, y_pos)
# Pointer (snapped)
# The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
@@ -781,6 +947,7 @@ class PlotCanvasLegacy(QtCore.QObject):
def on_mouse_press(self, event):
self.is_dragging = True
self.mouse_press_pos = (event.x, event.y)
# Check for middle mouse button press
if self.app.defaults["global_pan_button"] == '2':
@@ -806,6 +973,10 @@ class PlotCanvasLegacy(QtCore.QObject):
def on_mouse_release(self, event):
mouse_release_pos = (event.x, event.y)
delta = 0.05
if abs(self.distance(self.mouse_press_pos, mouse_release_pos)) < delta:
self.is_dragging = False
# Check for middle mouse button release to complete pan procedure
@@ -858,7 +1029,7 @@ class PlotCanvasLegacy(QtCore.QObject):
self.canvas.draw_idle()
# #### Temporary place-holder for cached update #####
self.update_screen_request.emit([0, 0, 0, 0, 0])
# self.update_screen_request.emit([0, 0, 0, 0, 0])
if self.app.defaults["global_cursor_color_enabled"] is True:
self.draw_cursor(x_pos=x, y_pos=y, color=self.app.cursor_color_3D)
@@ -910,6 +1081,59 @@ class PlotCanvasLegacy(QtCore.QObject):
return width / xpx, height / ypx
def snap(self, x, y):
"""
Adjusts coordinates to snap settings.
:param x: Input coordinate X
:param y: Input coordinate Y
:return: Snapped (x, y)
"""
snap_x, snap_y = (x, y)
snap_distance = np.Inf
# ### Grid snap
if self.app.grid_status():
if self.app.defaults["global_gridx"] != 0:
try:
snap_x_ = round(x / float(self.app.defaults["global_gridx"])) * \
float(self.app.defaults["global_gridx"])
except TypeError:
snap_x_ = x
else:
snap_x_ = x
# If the Grid_gap_linked on Grid Toolbar is checked then the snap distance on GridY entry will be ignored
# and it will use the snap distance from GridX entry
if self.app.ui.grid_gap_link_cb.isChecked():
if self.app.defaults["global_gridx"] != 0:
try:
snap_y_ = round(y / float(self.app.defaults["global_gridx"])) * \
float(self.app.defaults["global_gridx"])
except TypeError:
snap_y_ = y
else:
snap_y_ = y
else:
if self.app.defaults["global_gridy"] != 0:
try:
snap_y_ = round(y / float(self.app.defaults["global_gridy"])) * \
float(self.app.defaults["global_gridy"])
except TypeError:
snap_y_ = y
else:
snap_y_ = y
nearest_grid_distance = self.distance((x, y), (snap_x_, snap_y_))
if nearest_grid_distance < snap_distance:
snap_x, snap_y = (snap_x_, snap_y_)
return snap_x, snap_y
@staticmethod
def distance(pt1, pt2):
return np.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
class FakeCursor(QtCore.QObject):
"""

View File

@@ -13,6 +13,7 @@ import numpy as np
import vispy.scene as scene
from vispy.scene.cameras.base_camera import BaseCamera
# from vispy.scene.widgets import Widget as VisPyWidget
from vispy.color import Color
import time

View File

@@ -13,7 +13,7 @@ from vispy.color import Color
from shapely.geometry import Polygon, LineString, LinearRing
import threading
import numpy as np
from flatcamGUI.VisPyTesselators import GLUTess
from AppGUI.VisPyTesselators import GLUTess
class FlatCAMLineVisual(LineVisual):

View File

@@ -5,7 +5,7 @@ from defaults import FlatCAMDefaults
import logging
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -29,7 +29,7 @@ class PreferencesUIManager:
:param defaults: a dictionary storage where all the application settings are stored
:param data_path: a path to the file where all the preferences are stored for persistence
:param ui: reference to the FlatCAMGUI class which constructs the UI
:param ui: reference to the MainGUI class which constructs the UI
:param inform: a pyqtSignal used to display information's in the StatusBar of the GUI
"""
@@ -43,7 +43,7 @@ class PreferencesUIManager:
self.preferences_changed_flag = False
# when adding entries here read the comments in the method found below named:
# def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
# def app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
self.defaults_form_fields = {
# General App
"decimals_inch": self.ui.general_defaults_form.general_app_group.precision_inch_entry,
@@ -127,12 +127,6 @@ class PreferencesUIManager:
"gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry,
# Gerber Options
"gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry,
"gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry,
"gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry,
"gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb,
"gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.iso_scope_radio,
"gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio,
"gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry,
"gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb,
"gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry,
@@ -143,12 +137,6 @@ class PreferencesUIManager:
# "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry,
# "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry,
"gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb,
"gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio,
"gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner,
"gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner,
"gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner,
"gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio,
"gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio,
"gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb,
"gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner,
@@ -180,6 +168,7 @@ class PreferencesUIManager:
# Excellon General
"excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb,
"excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb,
"excellon_multicolored": self.ui.excellon_defaults_form.excellon_gen_group.multicolored_cb,
"excellon_format_upper_in":
self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry,
"excellon_format_lower_in":
@@ -271,6 +260,7 @@ class PreferencesUIManager:
# Geometry General
"geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb,
"geometry_multicolored": self.ui.geometry_defaults_form.geometry_gen_group.multicolored_cb,
"geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry,
"geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry,
"geometry_plot_line": self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry,
@@ -338,6 +328,28 @@ class PreferencesUIManager:
"cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp,
"cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry,
# Isolation Routing Tool
"tools_iso_tooldia": self.ui.tools_defaults_form.tools_iso_group.tool_dia_entry,
"tools_iso_order": self.ui.tools_defaults_form.tools_iso_group.order_radio,
"tools_iso_tool_type": self.ui.tools_defaults_form.tools_iso_group.tool_type_radio,
"tools_iso_tool_vtipdia": self.ui.tools_defaults_form.tools_iso_group.tipdia_entry,
"tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_entry,
"tools_iso_tool_cutz": self.ui.tools_defaults_form.tools_iso_group.cutz_entry,
"tools_iso_newdia": self.ui.tools_defaults_form.tools_iso_group.newdia_entry,
"tools_iso_passes": self.ui.tools_defaults_form.tools_iso_group.passes_entry,
"tools_iso_overlap": self.ui.tools_defaults_form.tools_iso_group.overlap_entry,
"tools_iso_milling_type": self.ui.tools_defaults_form.tools_iso_group.milling_type_radio,
"tools_iso_follow": self.ui.tools_defaults_form.tools_iso_group.follow_cb,
"tools_iso_isotype": self.ui.tools_defaults_form.tools_iso_group.iso_type_radio,
"tools_iso_rest": self.ui.tools_defaults_form.tools_iso_group.rest_cb,
"tools_iso_combine_passes": self.ui.tools_defaults_form.tools_iso_group.combine_passes_cb,
"tools_iso_isoexcept": self.ui.tools_defaults_form.tools_iso_group.except_cb,
"tools_iso_selection": self.ui.tools_defaults_form.tools_iso_group.select_combo,
"tools_iso_area_shape": self.ui.tools_defaults_form.tools_iso_group.area_shape_radio,
"tools_iso_plotting": self.ui.tools_defaults_form.tools_iso_group.plotting_radio,
# NCC Tool
"tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
"tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio,
@@ -351,13 +363,13 @@ class PreferencesUIManager:
"tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
"tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo,
"tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio,
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio,
"tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
"tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
"tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
"tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
"tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
"tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.plotting_radio,
# CutOut Tool
"tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
@@ -467,6 +479,12 @@ class PreferencesUIManager:
"tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo,
"tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb,
# Corner Markers Tool
"tools_corners_thickness": self.ui.tools_defaults_form.tools_corners_group.thick_entry,
"tools_corners_length": self.ui.tools_defaults_form.tools_corners_group.l_entry,
"tools_corners_margin": self.ui.tools_defaults_form.tools_corners_group.margin_entry,
# #######################################################################################################
# ########################################## TOOLS 2 ####################################################
# #######################################################################################################
@@ -650,7 +668,7 @@ class PreferencesUIManager:
def show_preferences_gui(self):
"""
Called to initialize and show the Preferences GUI
Called to initialize and show the Preferences AppGUI
:return: None
"""
@@ -913,7 +931,51 @@ class PreferencesUIManager:
# make sure we update the self.current_defaults dict used to undo changes to self.defaults
self.defaults.current_defaults.update(self.defaults)
if save_to_file:
# deal with theme change
theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
if theme_settings.contains("theme"):
theme = theme_settings.value('theme', type=str)
else:
theme = 'white'
should_restart = False
theme_new_val = self.ui.general_defaults_form.general_gui_group.theme_radio.get_value()
ge = self.defaults["global_graphic_engine"]
ge_val = self.ui.general_defaults_form.general_app_group.ge_radio.get_value()
if theme_new_val != theme or ge != ge_val:
msgbox = QtWidgets.QMessageBox()
msgbox.setText(_("Are you sure you want to continue?"))
msgbox.setWindowTitle(_("Application restart"))
msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/warning.png'))
msgbox.setIcon(QtWidgets.QMessageBox.Question)
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
msgbox.setDefaultButton(bt_yes)
msgbox.exec_()
response = msgbox.clickedButton()
if theme_new_val != theme:
if response == bt_yes:
theme_settings.setValue('theme', theme_new_val)
# This will write the setting to the platform specific storage.
del theme_settings
should_restart = True
else:
self.ui.general_defaults_form.general_gui_group.theme_radio.set_value(theme)
else:
if response == bt_yes:
self.defaults["global_graphic_engine"] = ge_val
should_restart = True
else:
self.ui.general_defaults_form.general_app_group.ge_radio.set_value(ge)
if save_to_file or should_restart is True:
self.save_defaults(silent=False)
# load the defaults so they are updated into the app
self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig'))
@@ -935,6 +997,10 @@ class PreferencesUIManager:
tb_fsize = self.ui.general_defaults_form.general_app_set_group.textbox_font_size_spinner.get_value()
settgs.setValue('textbox_font_size', tb_fsize)
# save the HUD font size
hud_fsize = self.ui.general_defaults_form.general_app_set_group.hud_font_size_spinner.get_value()
settgs.setValue('hud_font_size', hud_fsize)
settgs.setValue(
'machinist',
1 if self.ui.general_defaults_form.general_app_set_group.machinist_cb.get_value() else 0
@@ -951,6 +1017,9 @@ class PreferencesUIManager:
self.ui.plot_tab_area.closeTab(idx)
break
if should_restart is True:
self.ui.app.on_app_restart()
def on_pref_close_button(self):
# Preferences saved, update flag
self.preferences_changed_flag = False
@@ -1000,6 +1069,7 @@ class PreferencesUIManager:
:return: None
"""
self.defaults.report_usage("save_defaults")
log.debug("App.PreferencesUIManager.save_defaults()")
if data_path is None:
data_path = self.data_path
@@ -1036,7 +1106,7 @@ class PreferencesUIManager:
if self.ui.toolbarfile.isVisible():
tb_status += 1
if self.ui.toolbargeo.isVisible():
if self.ui.toolbaredit.isVisible():
tb_status += 2
if self.ui.toolbarview.isVisible():
@@ -1054,7 +1124,7 @@ class PreferencesUIManager:
if self.ui.grb_edit_toolbar.isVisible():
tb_status += 64
if self.ui.snap_toolbar.isVisible():
if self.ui.status_toolbar.isVisible():
tb_status += 128
if self.ui.toolbarshell.isVisible():
@@ -1118,6 +1188,7 @@ class PreferencesUIManager:
"Do you want to save the Preferences?"))
msgbox.setWindowTitle(_("Save Preferences"))
msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/save_as.png'))
msgbox.setIcon(QtWidgets.QMessageBox.Question)
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)

View File

@@ -1,6 +1,6 @@
from flatcamGUI.GUIElements import *
from AppGUI.GUIElements import *
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins

View File

@@ -1,10 +1,10 @@
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import QSettings, Qt
from flatcamGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCTextArea, FCCheckBox, FCComboBox, FCSpinner, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,10 +1,10 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCTextArea
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCTextArea
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,8 +1,8 @@
from PyQt5 import QtWidgets
from flatcamGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
from flatcamGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
from flatcamGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
from AppGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI
from AppGUI.preferences.cncjob.CNCJobOptPrefGroupUI import CNCJobOptPrefGroupUI
from AppGUI.preferences.cncjob.CNCJobGenPrefGroupUI import CNCJobGenPrefGroupUI
class CNCJobPreferencesUI(QtWidgets.QWidget):

View File

@@ -1,10 +1,10 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -60,7 +60,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
toolchange_xy_label.setToolTip(
_("Toolchange X,Y position.")
)
self.toolchangexy_entry = FCEntry()
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(toolchange_xy_label, 1, 0)
grid1.addWidget(self.toolchangexy_entry, 1, 1)
@@ -71,7 +71,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
_("Height of the tool just after start.\n"
"Delete the value if you don't need this feature.")
)
self.estartz_entry = FloatEntry()
self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
grid1.addWidget(startzlabel, 2, 0)
grid1.addWidget(self.estartz_entry, 2, 1)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,10 +1,10 @@
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import RadioSet, FCSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -3,10 +3,10 @@ import platform
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -36,22 +36,31 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
grid1 = QtWidgets.QGridLayout()
self.layout.addLayout(grid1)
# Plot CB
self.plot_cb = FCCheckBox(label=_('Plot'))
self.plot_cb.setToolTip(
"Plot (show) this object."
)
grid1.addWidget(self.plot_cb, 0, 0)
# Solid CB
self.solid_cb = FCCheckBox(label=_('Solid'))
self.solid_cb.setToolTip(
"Plot as solid circles."
)
grid1.addWidget(self.solid_cb, 0, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.")
)
grid1.addWidget(self.multicolored_cb, 0, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line, 1, 0, 1, 2)
grid1.addWidget(separator_line, 1, 0, 1, 3)
grid2 = QtWidgets.QGridLayout()
self.layout.addLayout(grid2)
@@ -341,6 +350,12 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
# Load the defaults values into the Excellon Format and Excellon Zeros fields
self.excellon_defaults_button.clicked.connect(self.on_excellon_defaults_button)
# Make sure that when the Excellon loading parameters are changed, the change is reflected in the
# Export Excellon parameters.
self.update_excellon_cb.stateChanged.connect(self.on_update_exc_export)
# call it once to make sure it is updated at startup
self.on_update_exc_export(state=self.app.defaults["excellon_update"])
def optimization_selection(self):
if self.excellon_optimization_radio.get_value() == 'M':
@@ -413,3 +428,105 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_mm"].set_value('3')
self.app.preferencesUiManager.defaults_form_fields["excellon_zeros"].set_value('L')
self.app.preferencesUiManager.defaults_form_fields["excellon_units"].set_value('INCH')
def on_update_exc_export(self, state):
"""
This is handling the update of Excellon Export parameters based on the ones in the Excellon General but only
if the update_excellon_cb checkbox is checked
:param state: state of the checkbox whose signals is tied to his slot
:return:
"""
if state:
# first try to disconnect
try:
self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
except TypeError:
pass
try:
self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
except TypeError:
pass
# the connect them
self.excellon_format_upper_in_entry.returnPressed.connect(self.on_excellon_format_changed)
self.excellon_format_lower_in_entry.returnPressed.connect(self.on_excellon_format_changed)
self.excellon_format_upper_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
self.excellon_format_lower_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
self.excellon_zeros_radio.activated_custom.connect(self.on_excellon_zeros_changed)
self.excellon_units_radio.activated_custom.connect(self.on_excellon_units_changed)
else:
# disconnect the signals
try:
self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
except TypeError:
pass
try:
self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
except TypeError:
pass
try:
self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
except TypeError:
pass
def on_excellon_format_changed(self):
"""
Slot activated when the user changes the Excellon format values in Preferences -> Excellon -> Excellon General
:return: None
"""
if self.excellon_units_radio.get_value().upper() == 'METRIC':
self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
self.excellon_format_upper_mm_entry.get_value())
self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
self.excellon_format_lower_mm_entry.get_value())
else:
self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
self.excellon_format_upper_in_entry.get_value())
self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
self.excellon_format_lower_in_entry.get_value())
def on_excellon_zeros_changed(self, val):
"""
Slot activated when the user changes the Excellon zeros values in Preferences -> Excellon -> Excellon General
:return: None
"""
self.app.ui.excellon_defaults_form.excellon_exp_group.zeros_radio.set_value(val + 'Z')
def on_excellon_units_changed(self, val):
"""
Slot activated when the user changes the Excellon unit values in Preferences -> Excellon -> Excellon General
:return: None
"""
self.app.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio.set_value(val)
self.on_excellon_format_changed()

View File

@@ -1,12 +1,12 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSettings
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
FCComboBox
from flatcamGUI.preferences import machinist_setting
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
FCComboBox, NumericalEvalTupleEntry
from AppGUI.preferences import machinist_setting
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -198,7 +198,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
"If no value is entered then there is no move\n"
"on X,Y plane at the end of the job.")
)
self.endxy_entry = FCEntry()
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid2.addWidget(endmove_xy_label, 9, 0)
grid2.addWidget(self.endxy_entry, 9, 1)

View File

@@ -1,14 +1,14 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
from flatcamGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
from flatcamGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
from flatcamGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
from flatcamGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
from AppGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI
from AppGUI.preferences.excellon.ExcellonExpPrefGroupUI import ExcellonExpPrefGroupUI
from AppGUI.preferences.excellon.ExcellonAdvOptPrefGroupUI import ExcellonAdvOptPrefGroupUI
from AppGUI.preferences.excellon.ExcellonOptPrefGroupUI import ExcellonOptPrefGroupUI
from AppGUI.preferences.excellon.ExcellonGenPrefGroupUI import ExcellonGenPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -31,7 +31,7 @@ class ExcellonPreferencesUI(QtWidgets.QWidget):
self.decimals = decimals
self.excellon_gen_group = ExcellonGenPrefGroupUI(decimals=self.decimals)
self.excellon_gen_group.setMinimumWidth(220)
self.excellon_gen_group.setMinimumWidth(240)
self.excellon_opt_group = ExcellonOptPrefGroupUI(decimals=self.decimals)
self.excellon_opt_group.setMinimumWidth(290)
self.excellon_exp_group = ExcellonExpPrefGroupUI(decimals=self.decimals)

View File

@@ -1,13 +1,13 @@
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, RadioSet, OptionalInputSection, FCSpinner, \
FCEntry
from flatcamGUI.preferences import settings
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.preferences import settings
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -177,14 +177,6 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
{'label': _('Landscape'), 'value': 'l'},
], stretch=False)
self.wks = OptionalInputSection(self.workspace_cb,
[
self.workspace_type_lbl,
self.wk_cb,
self.wk_orientation_label,
self.wk_orientation_radio
])
grid0.addWidget(self.wk_orientation_label, 8, 0)
grid0.addWidget(self.wk_orientation_radio, 8, 1)
@@ -201,7 +193,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
self.notebook_font_size_label = QtWidgets.QLabel('%s:' % _('Notebook'))
self.notebook_font_size_label.setToolTip(
_("This sets the font size for the elements found in the Notebook.\n"
"The notebook is the collapsible area in the left side of the GUI,\n"
"The notebook is the collapsible area in the left side of the AppGUI,\n"
"and include the Project, Selected and Tool tabs.")
)
@@ -240,7 +232,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
# TextBox Font Size
self.textbox_font_size_label = QtWidgets.QLabel('%s:' % _('Textbox'))
self.textbox_font_size_label.setToolTip(
_("This sets the font size for the Textbox GUI\n"
_("This sets the font size for the Textbox AppGUI\n"
"elements that are used in FlatCAM.")
)
@@ -257,10 +249,29 @@ class GeneralAPPSetGroupUI(OptionsGroupUI):
grid0.addWidget(self.textbox_font_size_label, 13, 0)
grid0.addWidget(self.textbox_font_size_spinner, 13, 1)
# HUD Font Size
self.hud_font_size_label = QtWidgets.QLabel('%s:' % _('HUD'))
self.hud_font_size_label.setToolTip(
_("This sets the font size for the Heads Up Display.")
)
self.hud_font_size_spinner = FCSpinner()
self.hud_font_size_spinner.set_range(8, 40)
self.hud_font_size_spinner.setWrapping(True)
qsettings = QSettings("Open Source", "FlatCAM")
if qsettings.contains("hud_font_size"):
self.hud_font_size_spinner.set_value(settings.value('hud_font_size', type=int))
else:
self.hud_font_size_spinner.set_value(8)
grid0.addWidget(self.hud_font_size_label, 14, 0)
grid0.addWidget(self.hud_font_size_spinner, 14, 1)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 14, 0, 1, 2)
grid0.addWidget(separator_line, 16, 0, 1, 2)
# -----------------------------------------------------------
# -------------- MOUSE SETTINGS -----------------------------

View File

@@ -3,12 +3,12 @@ import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
from AppGUI.GUIElements import RadioSet, FCSpinner, FCCheckBox, FCComboBox, FCButton, OptionalInputSection, \
FCDoubleSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -380,12 +380,11 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
def on_toggle_shell_from_settings(self, state):
"""
Toggle shell: if is visible close it, if it is closed then open it
Toggle shell ui: if is visible close it, if it is closed then open it
:return: None
"""
self.app.defaults.report_usage("on_toggle_shell_from_settings()")
if state is True:
if not self.app.ui.shell_dock.isVisible():
self.app.ui.shell_dock.show()

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QSettings, Qt
from flatcamGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCCheckBox, FCButton, FCComboBox, FCEntry, FCSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -56,13 +56,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.gray_icons_cb, 1, 0, 1, 3)
self.theme_button = FCButton(_("Apply Theme"))
self.theme_button.setToolTip(
_("Select a theme for FlatCAM.\n"
"It will theme the plot area.\n"
"The application will restart after change.")
)
grid0.addWidget(self.theme_button, 2, 0, 1, 3)
# self.theme_button = FCButton(_("Apply Theme"))
# self.theme_button.setToolTip(
# _("Select a theme for FlatCAM.\n"
# "It will theme the plot area.\n"
# "The application will restart after change.")
# )
# grid0.addWidget(self.theme_button, 2, 0, 1, 3)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
@@ -381,8 +381,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.layout.addStretch()
self.theme_button.clicked.connect(self.on_theme_change)
# #############################################################################
# ############################# GUI COLORS SIGNALS ############################
# #############################################################################
@@ -418,16 +416,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.layout_combo.activated.connect(self.on_layout)
def on_theme_change(self):
val = self.theme_radio.get_value()
qsettings = QSettings("Open Source", "FlatCAM")
qsettings.setValue('theme', val)
# This will write the setting to the platform specific storage.
del qsettings
self.app.on_app_restart()
@staticmethod
def handle_style(style):
# set current style
@@ -659,14 +647,13 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
# first remove the toolbars:
try:
self.app.ui.removeToolBar(self.app.ui.toolbarfile)
self.app.ui.removeToolBar(self.app.ui.toolbargeo)
self.app.ui.removeToolBar(self.app.ui.toolbaredit)
self.app.ui.removeToolBar(self.app.ui.toolbarview)
self.app.ui.removeToolBar(self.app.ui.toolbarshell)
self.app.ui.removeToolBar(self.app.ui.toolbartools)
self.app.ui.removeToolBar(self.app.ui.exc_edit_toolbar)
self.app.ui.removeToolBar(self.app.ui.geo_edit_toolbar)
self.app.ui.removeToolBar(self.app.ui.grb_edit_toolbar)
self.app.ui.removeToolBar(self.app.ui.snap_toolbar)
self.app.ui.removeToolBar(self.app.ui.toolbarshell)
except Exception:
pass
@@ -677,9 +664,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.app.ui.toolbarfile.setObjectName('File_TB')
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbarfile)
self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
self.app.ui.toolbargeo.setObjectName('Edit_TB')
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbargeo)
self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
self.app.ui.toolbaredit.setObjectName('Edit_TB')
self.app.ui.addToolBar(Qt.LeftToolBarArea, self.app.ui.toolbaredit)
self.app.ui.toolbarshell = QtWidgets.QToolBar('Shell Toolbar')
self.app.ui.toolbarshell.setObjectName('Shell_TB')
@@ -709,22 +696,15 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.app.ui.exc_edit_toolbar.setObjectName('ExcEditor_TB')
self.app.ui.addToolBar(Qt.RightToolBarArea, self.app.ui.exc_edit_toolbar)
self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
self.app.ui.snap_toolbar.setObjectName('Snap_TB')
self.app.ui.snap_toolbar.setMaximumHeight(30)
self.app.ui.splitter_left.addWidget(self.app.ui.snap_toolbar)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.snap_magnet.setVisible(True)
else:
# ## TOOLBAR INSTALLATION # ##
self.app.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar')
self.app.ui.toolbarfile.setObjectName('File_TB')
self.app.ui.addToolBar(self.app.ui.toolbarfile)
self.app.ui.toolbargeo = QtWidgets.QToolBar('Edit Toolbar')
self.app.ui.toolbargeo.setObjectName('Edit_TB')
self.app.ui.addToolBar(self.app.ui.toolbargeo)
self.app.ui.toolbaredit = QtWidgets.QToolBar('Edit Toolbar')
self.app.ui.toolbaredit.setObjectName('Edit_TB')
self.app.ui.addToolBar(self.app.ui.toolbaredit)
self.app.ui.toolbarview = QtWidgets.QToolBar('View Toolbar')
self.app.ui.toolbarview.setObjectName('View_TB')
@@ -755,18 +735,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.app.ui.grb_edit_toolbar.setObjectName('GrbEditor_TB')
self.app.ui.addToolBar(self.app.ui.grb_edit_toolbar)
self.app.ui.snap_toolbar = QtWidgets.QToolBar('Grid Toolbar')
self.app.ui.snap_toolbar.setObjectName('Snap_TB')
# self.app.ui.snap_toolbar.setMaximumHeight(30)
self.app.ui.addToolBar(self.app.ui.snap_toolbar)
self.app.ui.corner_snap_btn.setVisible(False)
self.app.ui.snap_magnet.setVisible(False)
if current_layout == 'minimal':
self.app.ui.toolbarview.setVisible(False)
self.app.ui.toolbarshell.setVisible(False)
self.app.ui.snap_toolbar.setVisible(False)
self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.grb_edit_toolbar.setVisible(False)
self.app.ui.exc_edit_toolbar.setVisible(False)
@@ -779,7 +750,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
self.app.connect_toolbar_signals()
self.app.ui.grid_snap_btn.setChecked(True)
self.app.ui.on_grid_snap_triggered(state=True)
self.app.ui.corner_snap_btn.setVisible(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))

View File

@@ -1,12 +1,12 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
from flatcamGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
from flatcamGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
from AppGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI
from AppGUI.preferences.general.GeneralAPPSetGroupUI import GeneralAPPSetGroupUI
from AppGUI.preferences.general.GeneralGUIPrefGroupUI import GeneralGUIPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,12 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
NumericalEvalEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -46,8 +47,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
toolchange_xy_label.setToolTip(
_("Toolchange X,Y position.")
)
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(toolchange_xy_label, 1, 0)
self.toolchangexy_entry = FCEntry()
grid1.addWidget(self.toolchangexy_entry, 1, 1)
# Start move Z
@@ -56,8 +58,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
_("Height of the tool just after starting the work.\n"
"Delete the value if you don't need this feature.")
)
self.gstartz_entry = NumericalEvalEntry(border_color='#0069A9')
grid1.addWidget(startzlabel, 2, 0)
self.gstartz_entry = FloatEntry()
grid1.addWidget(self.gstartz_entry, 2, 1)
# Feedrate rapids
@@ -186,6 +189,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
grid1.addWidget(segy_label, 11, 0)
grid1.addWidget(self.segy_entry, 11, 1)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line, 12, 0, 1, 2)
# -----------------------------
# --- Area Exclusion ----------
# -----------------------------
@@ -195,10 +203,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
"Those parameters are available only for\n"
"Advanced App. Level.")
)
grid1.addWidget(self.adv_label, 12, 0, 1, 2)
grid1.addWidget(self.adv_label, 13, 0, 1, 2)
# Exclusion Area CB
self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
self.exclusion_cb.setToolTip(
_(
"Include exclusion areas.\n"
@@ -206,7 +214,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
"is forbidden."
)
)
grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
# Area Selection shape
self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@@ -217,8 +225,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
grid1.addWidget(self.area_shape_label, 14, 0)
grid1.addWidget(self.area_shape_radio, 14, 1)
grid1.addWidget(self.area_shape_label, 15, 0)
grid1.addWidget(self.area_shape_radio, 15, 1)
# Chose Strategy
self.strategy_label = FCLabel('%s:' % _("Strategy"))
@@ -229,8 +237,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
{'label': _('Around'), 'value': 'around'}])
grid1.addWidget(self.strategy_label, 15, 0)
grid1.addWidget(self.strategy_radio, 15, 1)
grid1.addWidget(self.strategy_label, 16, 0)
grid1.addWidget(self.strategy_radio, 16, 1)
# Over Z
self.over_z_label = FCLabel('%s:' % _("Over Z"))

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, FCSpinner, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -31,12 +31,22 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
self.layout.addWidget(self.plot_options_label)
plot_hlay = QtWidgets.QHBoxLayout()
self.layout.addLayout((plot_hlay))
# Plot CB
self.plot_cb = FCCheckBox(label=_('Plot'))
self.plot_cb.setToolTip(
_("Plot (show) this object.")
)
self.layout.addWidget(self.plot_cb)
plot_hlay.addWidget(self.plot_cb)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label=_('M-Color'))
self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.")
)
plot_hlay.addWidget(self.multicolored_cb)
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)

View File

@@ -1,12 +1,13 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
from flatcamGUI.preferences import machinist_setting
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
NumericalEvalTupleEntry
from AppGUI.preferences import machinist_setting
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -176,7 +177,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
"If no value is entered then there is no move\n"
"on X,Y plane at the end of the job.")
)
self.endxy_entry = FCEntry()
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(endmove_xy_label, 7, 0)
grid1.addWidget(self.endxy_entry, 7, 1)

View File

@@ -1,13 +1,13 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
from flatcamGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
from flatcamGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
from flatcamGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
from AppGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI
from AppGUI.preferences.geometry.GeometryAdvOptPrefGroupUI import GeometryAdvOptPrefGroupUI
from AppGUI.preferences.geometry.GeometryOptPrefGroupUI import GeometryOptPrefGroupUI
from AppGUI.preferences.geometry.GeometryGenPrefGroupUI import GeometryGenPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -63,85 +63,6 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 2, 0, 1, 2)
# Tool Type
self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
self.tool_type_label.setToolTip(
_("Choose which tool to use for Gerber isolation:\n"
"'Circular' or 'V-shape'.\n"
"When the 'V-shape' is selected then the tool\n"
"diameter will depend on the chosen cut depth.")
)
self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
{'label': 'V-Shape', 'value': 'v'}])
grid0.addWidget(self.tool_type_label, 3, 0)
grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
self.tipdialabel.setToolTip(
_("The tip diameter for V-Shape Tool")
)
self.tipdia_spinner = FCDoubleSpinner()
self.tipdia_spinner.set_precision(self.decimals)
self.tipdia_spinner.set_range(-99.9999, 99.9999)
self.tipdia_spinner.setSingleStep(0.1)
self.tipdia_spinner.setWrapping(True)
grid0.addWidget(self.tipdialabel, 4, 0)
grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
self.tipanglelabel.setToolTip(
_("The tip angle for V-Shape Tool.\n"
"In degree.")
)
self.tipangle_spinner = FCSpinner()
self.tipangle_spinner.set_range(1, 180)
self.tipangle_spinner.set_step(5)
self.tipangle_spinner.setWrapping(True)
grid0.addWidget(self.tipanglelabel, 5, 0)
grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
# Cut Z
self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
self.cutzlabel.setToolTip(
_("Cutting depth (negative)\n"
"below the copper surface.")
)
self.cutz_spinner = FCDoubleSpinner()
self.cutz_spinner.set_precision(self.decimals)
self.cutz_spinner.set_range(-99.9999, 0.0000)
self.cutz_spinner.setSingleStep(0.1)
self.cutz_spinner.setWrapping(True)
grid0.addWidget(self.cutzlabel, 6, 0)
grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
_("Choose how the isolation will be executed:\n"
"- 'Full' -> complete isolation of polygons\n"
"- 'Ext' -> will isolate only on the outside\n"
"- 'Int' -> will isolate only on the inside\n"
"'Exterior' isolation is almost always possible\n"
"(with the right tool) but 'Interior'\n"
"isolation can be done only when there is an opening\n"
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
)
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Exterior'), 'value': 'ext'},
{'label': _('Interior'), 'value': 'int'}])
grid0.addWidget(self.iso_type_label, 7, 0,)
grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 8, 0, 1, 2)
# Buffering Type
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
buffering_label.setToolTip(

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -109,8 +109,9 @@ class GerberEditorPrefGroupUI(OptionsGroupUI):
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.adddim_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(self.adddim_label, 5, 0)
self.adddim_entry = FCEntry()
grid0.addWidget(self.adddim_entry, 5, 1)
self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import RadioSet, FCSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -34,26 +34,26 @@ class GerberGenPrefGroupUI(OptionsGroupUI):
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
# Plot CB
self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
self.plot_options_label.setToolTip(
_("Plot (show) this object.")
)
grid0.addWidget(self.plot_cb, 0, 0)
# Solid CB
self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
self.solid_cb.setToolTip(
_("Solid color polygons.")
)
grid0.addWidget(self.solid_cb, 0, 0)
grid0.addWidget(self.solid_cb, 0, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
self.multicolored_cb.setToolTip(
_("Draw polygons in different colors.")
)
grid0.addWidget(self.multicolored_cb, 0, 1)
# Plot CB
self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
self.plot_options_label.setToolTip(
_("Plot (show) this object.")
)
grid0.addWidget(self.plot_cb, 0, 2)
grid0.addWidget(self.multicolored_cb, 0, 2)
# Number of circle steps for circular aperture linear approximation
self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))

View File

@@ -0,0 +1,100 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox, FCComboBox
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("machinist"):
machinist_setting = settings.value('machinist', type=int)
else:
machinist_setting = 0
class GerberOptPrefGroupUI(OptionsGroupUI):
def __init__(self, decimals=4, parent=None):
# OptionsGroupUI.__init__(self, "Gerber Options Preferences", parent=parent)
super(GerberOptPrefGroupUI, self).__init__(self, parent=parent)
self.decimals = decimals
self.setTitle(str(_("Gerber Options")))
# ## Clear non-copper regions
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
self.clearcopper_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.clearcopper_label)
grid1 = QtWidgets.QGridLayout()
self.layout.addLayout(grid1)
# Margin
bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
bmlabel.setToolTip(
_("Specify the edge of the PCB\n"
"by drawing a box around all\n"
"objects with this minimum\n"
"distance.")
)
grid1.addWidget(bmlabel, 0, 0)
self.noncopper_margin_entry = FCDoubleSpinner()
self.noncopper_margin_entry.set_precision(self.decimals)
self.noncopper_margin_entry.setSingleStep(0.1)
self.noncopper_margin_entry.set_range(-9999, 9999)
grid1.addWidget(self.noncopper_margin_entry, 0, 1)
# Rounded corners
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
self.noncopper_rounded_cb.setToolTip(
_("Resulting geometry will have rounded corners.")
)
grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line, 2, 0, 1, 2)
# ## Bounding box
self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
self.layout.addWidget(self.boundingbox_label)
grid2 = QtWidgets.QGridLayout()
self.layout.addLayout(grid2)
bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
bbmargin.setToolTip(
_("Distance of the edges of the box\n"
"to the nearest polygon.")
)
self.bbmargin_entry = FCDoubleSpinner()
self.bbmargin_entry.set_precision(self.decimals)
self.bbmargin_entry.setSingleStep(0.1)
self.bbmargin_entry.set_range(-9999, 9999)
grid2.addWidget(bbmargin, 0, 0)
grid2.addWidget(self.bbmargin_entry, 0, 1)
self.bbrounded_cb = FCCheckBox(label='%s' % _("Rounded Geo"))
self.bbrounded_cb.setToolTip(
_("If the bounding box is \n"
"to have rounded corners\n"
"their radius is equal to\n"
"the margin.")
)
grid2.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
self.layout.addStretch()

View File

@@ -1,14 +1,14 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
from flatcamGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
from flatcamGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
from flatcamGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
from flatcamGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
from AppGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI
from AppGUI.preferences.gerber.GerberExpPrefGroupUI import GerberExpPrefGroupUI
from AppGUI.preferences.gerber.GerberAdvOptPrefGroupUI import GerberAdvOptPrefGroupUI
from AppGUI.preferences.gerber.GerberOptPrefGroupUI import GerberOptPrefGroupUI
from AppGUI.preferences.gerber.GerberGenPrefGroupUI import GerberGenPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -44,6 +44,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
self.vlay = QtWidgets.QVBoxLayout()
self.vlay.addWidget(self.gerber_opt_group)
self.vlay.addWidget(self.gerber_exp_group)
self.vlay.addStretch()
self.layout.addWidget(self.gerber_gen_group)
self.layout.addLayout(self.vlay)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -116,7 +116,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
"(x, y) point will be used,")
)
self.toolchange_xy_entry = FCEntry()
self.toolchange_xy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid_lay.addWidget(toolchangexy_lbl, 7, 0)
grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,18 +1,18 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
from flatcamGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
from flatcamGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
from flatcamGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
from flatcamGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
from flatcamGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
from flatcamGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
from flatcamGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
from flatcamGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
from AppGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI
from AppGUI.preferences.tools.Tools2PunchGerberPrefGroupUI import Tools2PunchGerberPrefGroupUI
from AppGUI.preferences.tools.Tools2EDrillsPrefGroupUI import Tools2EDrillsPrefGroupUI
from AppGUI.preferences.tools.Tools2CalPrefGroupUI import Tools2CalPrefGroupUI
from AppGUI.preferences.tools.Tools2FiducialsPrefGroupUI import Tools2FiducialsPrefGroupUI
from AppGUI.preferences.tools.Tools2CThievingPrefGroupUI import Tools2CThievingPrefGroupUI
from AppGUI.preferences.tools.Tools2QRCodePrefGroupUI import Tools2QRCodePrefGroupUI
from AppGUI.preferences.tools.Tools2OptimalPrefGroupUI import Tools2OptimalPrefGroupUI
from AppGUI.preferences.tools.Tools2RulesCheckPrefGroupUI import Tools2RulesCheckPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt, QSettings
from flatcamGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -205,3 +205,67 @@ class Tools2QRCodePrefGroupUI(OptionsGroupUI):
grid_lay.addWidget(self.sel_limit_label, 11, 0)
grid_lay.addWidget(self.sel_limit_entry, 11, 1)
# self.layout.addStretch()
# QRCode Tool
self.fill_color_entry.editingFinished.connect(self.on_qrcode_fill_color_entry)
self.fill_color_button.clicked.connect(self.on_qrcode_fill_color_button)
self.back_color_entry.editingFinished.connect(self.on_qrcode_back_color_entry)
self.back_color_button.clicked.connect(self.on_qrcode_back_color_button)
def on_qrcode_fill_color_entry(self):
self.app.defaults['tools_qrcode_fill_color'] = self.fill_color_entry.get_value()
self.fill_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(self.defaults['tools_qrcode_fill_color'])
)
def on_qrcode_fill_color_button(self):
current_color = QtGui.QColor(self.app.defaults['tools_qrcode_fill_color'])
c_dialog = QtWidgets.QColorDialog()
fill_color = c_dialog.getColor(initial=current_color)
if fill_color.isValid() is False:
return
# if new color is different then mark that the Preferences are changed
if fill_color != current_color:
self.app.preferencesUiManager.on_preferences_edited()
self.fill_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(fill_color.name())
)
new_val_sel = str(fill_color.name())
self.fill_color_entry.set_value(new_val_sel)
self.app.defaults['tools_qrcode_fill_color'] = new_val_sel
def on_qrcode_back_color_entry(self):
self.app.defaults['tools_qrcode_back_color'] = self.back_color_entry.get_value()
self.back_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(self.defaults['tools_qrcode_back_color'])
)
def on_qrcode_back_color_button(self):
current_color = QtGui.QColor(self.app.defaults['tools_qrcode_back_color'])
c_dialog = QtWidgets.QColorDialog()
back_color = c_dialog.getColor(initial=current_color)
if back_color.isValid() is False:
return
# if new color is different then mark that the Preferences are changed
if back_color != current_color:
self.app.preferencesUiManager.on_preferences_edited()
self.back_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(back_color.name())
)
new_val_sel = str(back_color.name())
self.back_color_entry.set_value(new_val_sel)
self.app.defaults['tools_qrcode_back_color'] = new_val_sel

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox, FCDoubleSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -0,0 +1,81 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCDoubleSpinner
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("machinist"):
machinist_setting = settings.value('machinist', type=int)
else:
machinist_setting = 0
class ToolsCornersPrefGroupUI(OptionsGroupUI):
def __init__(self, decimals=4, parent=None):
# OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent)
super(ToolsCornersPrefGroupUI, self).__init__(self, parent=parent)
self.setTitle(str(_("Corner Markers Options")))
self.decimals = decimals
grid0 = QtWidgets.QGridLayout()
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
self.layout.addLayout(grid0)
self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
self.param_label.setToolTip(
_("Parameters used for this tool.")
)
grid0.addWidget(self.param_label, 0, 0, 1, 2)
# Thickness #
self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
self.thick_label.setToolTip(
_("The thickness of the line that makes the corner marker.")
)
self.thick_entry = FCDoubleSpinner()
self.thick_entry.set_range(0.0000, 9.9999)
self.thick_entry.set_precision(self.decimals)
self.thick_entry.setWrapping(True)
self.thick_entry.setSingleStep(10 ** -self.decimals)
grid0.addWidget(self.thick_label, 1, 0)
grid0.addWidget(self.thick_entry, 1, 1)
# Length #
self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
self.l_label.setToolTip(
_("The length of the line that makes the corner marker.")
)
self.l_entry = FCDoubleSpinner()
self.l_entry.set_range(-9999.9999, 9999.9999)
self.l_entry.set_precision(self.decimals)
self.l_entry.setSingleStep(10 ** -self.decimals)
# Margin #
self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
self.margin_label.setToolTip(
_("Bounding box margin.")
)
self.margin_entry = FCDoubleSpinner()
self.margin_entry.set_range(-9999.9999, 9999.9999)
self.margin_entry.set_precision(self.decimals)
self.margin_entry.setSingleStep(0.1)
grid0.addWidget(self.margin_label, 2, 0)
grid0.addWidget(self.margin_entry, 2, 1)
grid0.addWidget(self.l_label, 4, 0)
grid0.addWidget(self.l_entry, 4, 1)
self.layout.addStretch()

View File

@@ -1,12 +1,12 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
from flatcamGUI.preferences import machinist_setting
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox
from AppGUI.preferences import machinist_setting
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtCore
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt, QSettings
from flatcamGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCEntry, FCDoubleSpinner, FCCheckBox, FCComboBox
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -314,3 +314,35 @@ class ToolsFilmPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.pagesize_combo, 17, 1)
self.layout.addStretch()
# Film Tool
self.film_color_entry.editingFinished.connect(self.on_film_color_entry)
self.film_color_button.clicked.connect(self.on_film_color_button)
def on_film_color_entry(self):
self.app.defaults['tools_film_color'] = self.film_color_entry.get_value()
self.film_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(self.defaults['tools_film_color'])
)
def on_film_color_button(self):
current_color = QtGui.QColor(self.app.defaults['tools_film_color'])
c_dialog = QtWidgets.QColorDialog()
film_color = c_dialog.getColor(initial=current_color)
if film_color.isValid() is False:
return
# if new color is different then mark that the Preferences are changed
if film_color != current_color:
self.app.preferencesUiManager.on_preferences_edited()
self.film_color_button.setStyleSheet(
"background-color:%s;"
"border-color: dimgray" % str(film_color.name())
)
new_val_sel = str(film_color.name())
self.film_color_entry.set_value(new_val_sel)
self.app.defaults['tools_film_color'] = new_val_sel

View File

@@ -0,0 +1,319 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("machinist"):
machinist_setting = settings.value('machinist', type=int)
else:
machinist_setting = 0
class ToolsISOPrefGroupUI(OptionsGroupUI):
def __init__(self, decimals=4, parent=None):
super(ToolsISOPrefGroupUI, self).__init__(self, parent=parent)
self.setTitle(str(_("Isolation Tool Options")))
self.decimals = decimals
# ## Clear non-copper regions
self.iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
self.iso_label.setToolTip(
_("Create a Geometry object with\n"
"toolpaths to cut around polygons.")
)
self.layout.addWidget(self.iso_label)
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
# Tool Dias
isotdlabel = QtWidgets.QLabel('<b><font color="green">%s:</font></b>' % _('Tools Dia'))
isotdlabel.setToolTip(
_("Diameters of the tools, separated by comma.\n"
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(isotdlabel, 0, 0)
grid0.addWidget(self.tool_dia_entry, 0, 1, 1, 2)
# Tool order Radio Button
self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
self.order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
"'No' --> means that the used order is the one in the tool table\n"
"'Forward' --> means that the tools will be ordered from small to big\n"
"'Reverse' --> means that the tools will ordered from big to small\n\n"
"WARNING: using rest machining will automatically set the order\n"
"in reverse and disable this control."))
self.order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
{'label': _('Forward'), 'value': 'fwd'},
{'label': _('Reverse'), 'value': 'rev'}])
grid0.addWidget(self.order_label, 1, 0)
grid0.addWidget(self.order_radio, 1, 1, 1, 2)
# Tool Type Radio Button
self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
self.tool_type_label.setToolTip(
_("Default tool type:\n"
"- 'V-shape'\n"
"- Circular")
)
self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
{'label': _('Circular'), 'value': 'C1'}])
self.tool_type_radio.setToolTip(
_("Default tool type:\n"
"- 'V-shape'\n"
"- Circular")
)
grid0.addWidget(self.tool_type_label, 2, 0)
grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
self.tipdialabel.setToolTip(
_("The tip diameter for V-Shape Tool"))
self.tipdia_entry = FCDoubleSpinner()
self.tipdia_entry.set_precision(self.decimals)
self.tipdia_entry.set_range(0, 1000)
self.tipdia_entry.setSingleStep(0.1)
grid0.addWidget(self.tipdialabel, 3, 0)
grid0.addWidget(self.tipdia_entry, 3, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
self.tipanglelabel.setToolTip(
_("The tip angle for V-Shape Tool.\n"
"In degrees."))
self.tipangle_entry = FCDoubleSpinner()
self.tipangle_entry.set_precision(self.decimals)
self.tipangle_entry.set_range(1, 180)
self.tipangle_entry.setSingleStep(5)
self.tipangle_entry.setWrapping(True)
grid0.addWidget(self.tipanglelabel, 4, 0)
grid0.addWidget(self.tipangle_entry, 4, 1, 1, 2)
# Cut Z entry
cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
cutzlabel.setToolTip(
_("Depth of cut into material. Negative value.\n"
"In FlatCAM units.")
)
self.cutz_entry = FCDoubleSpinner()
self.cutz_entry.set_precision(self.decimals)
self.cutz_entry.set_range(-9999.9999, 0.0000)
self.cutz_entry.setSingleStep(0.1)
self.cutz_entry.setToolTip(
_("Depth of cut into material. Negative value.\n"
"In FlatCAM units.")
)
grid0.addWidget(cutzlabel, 5, 0)
grid0.addWidget(self.cutz_entry, 5, 1, 1, 2)
# New Diameter
self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
self.newdialabel.setToolTip(
_("Diameter for the new tool to add in the Tool Table.\n"
"If the tool is V-shape type then this value is automatically\n"
"calculated from the other parameters.")
)
self.newdia_entry = FCDoubleSpinner()
self.newdia_entry.set_precision(self.decimals)
self.newdia_entry.set_range(0.0001, 9999.9999)
self.newdia_entry.setSingleStep(0.1)
grid0.addWidget(self.newdialabel, 6, 0)
grid0.addWidget(self.newdia_entry, 6, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 7, 0, 1, 3)
# Passes
passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
passlabel.setToolTip(
_("Width of the isolation gap in\n"
"number (integer) of tool widths.")
)
self.passes_entry = FCSpinner()
self.passes_entry.set_range(1, 999)
self.passes_entry.setObjectName("i_passes")
grid0.addWidget(passlabel, 8, 0)
grid0.addWidget(self.passes_entry, 8, 1, 1, 2)
# Overlap Entry
overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
overlabel.setToolTip(
_("How much (percentage) of the tool width to overlap each tool pass.")
)
self.overlap_entry = FCDoubleSpinner(suffix='%')
self.overlap_entry.set_precision(self.decimals)
self.overlap_entry.setWrapping(True)
self.overlap_entry.set_range(0.0000, 99.9999)
self.overlap_entry.setSingleStep(0.1)
self.overlap_entry.setObjectName("i_overlap")
grid0.addWidget(overlabel, 9, 0)
grid0.addWidget(self.overlap_entry, 9, 1, 1, 2)
# Milling Type Radio Button
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
self.milling_type_label.setToolTip(
_("Milling type when the selected tool is of type: 'iso_op':\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conventional'), 'value': 'cv'}])
self.milling_type_radio.setToolTip(
_("Milling type when the selected tool is of type: 'iso_op':\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
grid0.addWidget(self.milling_type_label, 10, 0)
grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
# Follow
self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
self.follow_label.setToolTip(
_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace.")
)
self.follow_cb = FCCheckBox()
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace."))
self.follow_cb.setObjectName("i_follow")
grid0.addWidget(self.follow_label, 11, 0)
grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
_("Choose how the isolation will be executed:\n"
"- 'Full' -> complete isolation of polygons\n"
"- 'Ext' -> will isolate only on the outside\n"
"- 'Int' -> will isolate only on the inside\n"
"'Exterior' isolation is almost always possible\n"
"(with the right tool) but 'Interior'\n"
"isolation can be done only when there is an opening\n"
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
)
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Ext'), 'value': 'ext'},
{'label': _('Int'), 'value': 'int'}])
self.iso_type_radio.setObjectName("i_type")
grid0.addWidget(self.iso_type_label, 12, 0)
grid0.addWidget(self.iso_type_radio, 12, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 13, 0, 1, 3)
# Rest machining CheckBox
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName("i_rest_machining")
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will isolate outside PCB features,\n"
"using the biggest tool and continue with the next tools,\n"
"from bigger to smaller, to isolate the copper features that\n"
"could not be cleared by previous tool, until there is\n"
"no more copper features to isolate or there are no more tools.\n"
"If not checked, use the standard algorithm.")
)
grid0.addWidget(self.rest_cb, 17, 0)
# Combine All Passes
self.combine_passes_cb = FCCheckBox(label=_('Combine'))
self.combine_passes_cb.setToolTip(
_("Combine all passes into one object")
)
self.combine_passes_cb.setObjectName("i_combine")
grid0.addWidget(self.combine_passes_cb, 17, 1)
# Exception Areas
self.except_cb = FCCheckBox(label=_('Except'))
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
"by checking this, the area of the object below\n"
"will be subtracted from the isolation geometry."))
self.except_cb.setObjectName("i_except")
grid0.addWidget(self.except_cb, 17, 2)
# Isolation Scope
self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
self.select_label.setToolTip(
_("Isolation scope. Choose what to isolate:\n"
"- 'All' -> Isolate all the polygons in the object\n"
"- 'Selection' -> Isolate a selection of polygons.\n"
"- 'Reference Object' - will process the area specified by another object.")
)
self.select_combo = FCComboBox()
self.select_combo.addItems(
[_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")]
)
self.select_combo.setObjectName("i_selection")
grid0.addWidget(self.select_label, 20, 0)
grid0.addWidget(self.select_combo, 20, 1, 1, 2)
# Area Shape
self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
self.area_shape_label.setToolTip(
_("The kind of selection shape used for area selection.")
)
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
grid0.addWidget(self.area_shape_label, 21, 0)
grid0.addWidget(self.area_shape_radio, 21, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 22, 0, 1, 3)
# ## Plotting type
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")
)
grid0.addWidget(plotting_label, 23, 0)
grid0.addWidget(self.plotting_radio, 23, 1, 1, 2)
self.layout.addStretch()

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -45,7 +45,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
"Valid values: 0.3, 1.0")
)
grid0.addWidget(ncctdlabel, 0, 0)
self.ncc_tool_dia_entry = FCEntry(border_color='#0069A9')
self.ncc_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.ncc_tool_dia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
@@ -227,7 +227,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
# ], orientation='vertical', stretch=False)
self.ncc_method_combo = FCComboBox()
self.ncc_method_combo.addItems(
[_("Standard"), _("Seed"), _("Lines")]
[_("Standard"), _("Seed"), _("Lines"), _("Combo")]
)
grid0.addWidget(methodlabel, 12, 0)
@@ -285,7 +285,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
grid0.addWidget(separator_line, 16, 0, 1, 2)
# Rest machining CheckBox
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
self.ncc_rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will clear copper outside PCB features,\n"
@@ -336,14 +336,14 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
grid0.addWidget(separator_line, 20, 0, 1, 2)
# ## Plotting type
self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the NCC job\n"
"- 'Progressive' - after each shape is generated it will be plotted.")
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")
)
grid0.addWidget(plotting_label, 21, 0)
grid0.addWidget(self.ncc_plotting_radio, 21, 1)
grid0.addWidget(self.plotting_radio, 21, 1)
self.layout.addStretch()

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -53,7 +53,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(ptdlabel, 0, 0)
self.painttooldia_entry = FCEntry(border_color='#0069A9')
self.painttooldia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.painttooldia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(self.painttooldia_entry, 0, 1)
@@ -241,8 +241,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 13, 0, 1, 2)
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.rest_cb.setObjectName(_("Rest Machining"))
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName(_("Rest"))
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will clear copper outside PCB features,\n"
@@ -277,7 +277,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
# )
self.selectmethod_combo = FCComboBox()
self.selectmethod_combo.addItems(
[_("Polygon Selection"), _("Area Selection"), _("All Polygons"), _("Reference Object")]
[_("Polygon Selection"), _("Area Selection"), _("All"), _("Reference Object")]
)
grid0.addWidget(selectlabel, 15, 0)
@@ -302,10 +302,10 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
# ## Plotting type
self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the Paint job\n"
"- 'Progressive' - after each shape is generated it will be plotted.")
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")
)
grid0.addWidget(plotting_label, 20, 0)
grid0.addWidget(self.paint_plotting_radio, 20, 1)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,19 +1,22 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
from flatcamGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
from flatcamGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
from flatcamGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
from flatcamGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
from flatcamGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
from flatcamGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
from flatcamGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
from flatcamGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
from flatcamGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
from AppGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI
from AppGUI.preferences.tools.ToolsSolderpastePrefGroupUI import ToolsSolderpastePrefGroupUI
from AppGUI.preferences.tools.ToolsCornersPrefGroupUI import ToolsCornersPrefGroupUI
from AppGUI.preferences.tools.ToolsTransformPrefGroupUI import ToolsTransformPrefGroupUI
from AppGUI.preferences.tools.ToolsCalculatorsPrefGroupUI import ToolsCalculatorsPrefGroupUI
from AppGUI.preferences.tools.ToolsPanelizePrefGroupUI import ToolsPanelizePrefGroupUI
from AppGUI.preferences.tools.ToolsFilmPrefGroupUI import ToolsFilmPrefGroupUI
from AppGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI
from AppGUI.preferences.tools.ToolsCutoutPrefGroupUI import ToolsCutoutPrefGroupUI
from AppGUI.preferences.tools.ToolsNCCPrefGroupUI import ToolsNCCPrefGroupUI
from AppGUI.preferences.tools.ToolsPaintPrefGroupUI import ToolsPaintPrefGroupUI
from AppGUI.preferences.tools.ToolsISOPrefGroupUI import ToolsISOPrefGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -35,6 +38,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
self.setLayout(self.layout)
self.decimals = decimals
self.tools_iso_group = ToolsISOPrefGroupUI(decimals=self.decimals)
self.tools_iso_group.setMinimumWidth(220)
self.tools_ncc_group = ToolsNCCPrefGroupUI(decimals=self.decimals)
self.tools_ncc_group.setMinimumWidth(220)
@@ -62,6 +68,9 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI(decimals=self.decimals)
self.tools_solderpaste_group.setMinimumWidth(200)
self.tools_corners_group = ToolsCornersPrefGroupUI(decimals=self.decimals)
self.tools_corners_group.setMinimumWidth(200)
self.tools_sub_group = ToolsSubPrefGroupUI(decimals=self.decimals)
self.tools_sub_group.setMinimumWidth(200)
@@ -71,7 +80,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
self.vlay1 = QtWidgets.QVBoxLayout()
self.vlay1.addWidget(self.tools_paint_group)
self.vlay1.addWidget(self.tools_panelize_group)
self.vlay1.addWidget(self.tools_iso_group)
self.vlay2 = QtWidgets.QVBoxLayout()
self.vlay2.addWidget(self.tools_transform_group)
@@ -84,6 +93,8 @@ class ToolsPreferencesUI(QtWidgets.QWidget):
self.vlay4 = QtWidgets.QVBoxLayout()
self.vlay4.addWidget(self.tools_solderpaste_group)
self.vlay4.addWidget(self.tools_corners_group)
self.vlay4.addWidget(self.tools_panelize_group)
self.layout.addLayout(self.vlay)
self.layout.addLayout(self.vlay1)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCEntry, FCDoubleSpinner, FCSpinner, FCComboBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -45,7 +45,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.nozzle_tool_dia_entry = FCEntry()
self.nozzle_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(nozzletdlabel, 0, 0)
grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
@@ -130,7 +130,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.z_toolchange_entry, 6, 1)
# X,Y Toolchange location
self.xy_toolchange_entry = FCEntry()
self.xy_toolchange_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
self.xy_toolchange_label.setToolTip(
_("The X,Y location for tool (nozzle) change.\n"

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCCheckBox
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCCheckBox
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, EvalEntry2
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -191,7 +191,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
"The 'x' in (x, y) will be used when using Flip on X and\n"
"the 'y' in (x, y) will be used when using Flip on Y and")
)
self.flip_ref_entry = EvalEntry2("(0, 0)")
self.flip_ref_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import VerticalScrollArea, FCButton, FCTextArea, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,11 +1,11 @@
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCButton, FCTextArea, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
from AppGUI.GUIElements import FCButton, FCTextArea, FCEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,9 +1,9 @@
from PyQt5 import QtWidgets
from flatcamGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
from flatcamGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
from flatcamGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
from flatcamGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
from AppGUI.preferences.utilities.AutoCompletePrefGroupUI import AutoCompletePrefGroupUI
from AppGUI.preferences.utilities.FAGrbPrefGroupUI import FAGrbPrefGroupUI
from AppGUI.preferences.utilities.FAGcoPrefGroupUI import FAGcoPrefGroupUI
from AppGUI.preferences.utilities.FAExcPrefGroupUI import FAExcPrefGroupUI
class UtilPreferencesUI(QtWidgets.QWidget):

394
AppObjects/AppObject.py Normal file
View File

@@ -0,0 +1,394 @@
# ###########################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://flatcam.org #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
# Modified by Marius Stanciu (2020) #
# ###########################################################
from PyQt5 import QtCore
from AppObjects.ObjectCollection import *
from AppObjects.FlatCAMCNCJob import CNCJobObject
from AppObjects.FlatCAMDocument import DocumentObject
from AppObjects.FlatCAMExcellon import ExcellonObject
from AppObjects.FlatCAMGeometry import GeometryObject
from AppObjects.FlatCAMGerber import GerberObject
from AppObjects.FlatCAMScript import ScriptObject
import time
import traceback
# FlatCAM Translation
import gettext
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class AppObject(QtCore.QObject):
# Emitted by app_obj.new_object() and passes the new object as argument, plot flag.
# on_object_created() adds the object to the collection, plots on appropriate flag
# and emits app_obj.new_object_available.
object_created = QtCore.pyqtSignal(object, bool, bool)
# Emitted when a object has been changed (like scaled, mirrored)
object_changed = QtCore.pyqtSignal(object)
# Emitted after object has been plotted.
# Calls 'on_zoom_fit' method to fit object in scene view in main thread to prevent drawing glitches.
object_plotted = QtCore.pyqtSignal(object)
plots_updated = QtCore.pyqtSignal()
def __init__(self, app):
super(AppObject, self).__init__()
self.app = app
self.inform = app.inform
# signals that are emitted when object state changes
self.object_created.connect(self.on_object_created)
self.object_changed.connect(self.on_object_changed)
self.object_plotted.connect(self.on_object_plotted)
self.plots_updated.connect(self.app.on_plots_updated)
def new_object(self, kind, name, initialize, plot=True, autoselected=True):
"""
Creates a new specialized FlatCAMObj and attaches it to the application,
this is, updates the GUI accordingly, any other records and plots it.
This method is thread-safe.
Notes:
* If the name is in use, the self.collection will modify it
when appending it to the collection. There is no need to handle
name conflicts here.
:param kind: The kind of object to create. One of 'gerber', 'excellon', 'cncjob' and 'geometry'.
:type kind: str
:param name: Name for the object.
:type name: str
:param initialize: Function to run after creation of the object but before it is attached to the application.
The function is called with 2 parameters: the new object and the App instance.
:type initialize: function
:param plot: If to plot the resulting object
:param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
self.collection
:return: None
:rtype: None
"""
log.debug("AppObject.new_object()")
obj_plot = plot
obj_autoselected = autoselected
t0 = time.time() # Debug
# ## Create object
classdict = {
"gerber": GerberObject,
"excellon": ExcellonObject,
"cncjob": CNCJobObject,
"geometry": GeometryObject,
"script": ScriptObject,
"document": DocumentObject
}
log.debug("Calling object constructor...")
# Object creation/instantiation
obj = classdict[kind](name)
obj.units = self.app.options["units"]
# IMPORTANT
# The key names in defaults and options dictionary's are not random:
# they have to have in name first the type of the object (geometry, excellon, cncjob and gerber) or how it's
# called here, the 'kind' followed by an underline. Above the App default values from self.defaults are
# copied to self.options. After that, below, depending on the type of
# object that is created, it will strip the name of the object and the underline (if the original key was
# let's say "excellon_toolchange", it will strip the excellon_) and to the obj.options the key will become
# "toolchange"
for option in self.app.options:
if option.find(kind + "_") == 0:
oname = option[len(kind) + 1:]
obj.options[oname] = self.app.options[option]
obj.isHovering = False
obj.notHovering = True
# Initialize as per user request
# User must take care to implement initialize
# in a thread-safe way as is is likely that we
# have been invoked in a separate thread.
t1 = time.time()
log.debug("%f seconds before initialize()." % (t1 - t0))
try:
return_value = initialize(obj, self.app)
except Exception as e:
msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n")
msg += _("Object ({kind}) failed because: {error} \n\n").format(kind=kind, error=str(e))
msg += traceback.format_exc()
self.app.inform.emit(msg)
return "fail"
t2 = time.time()
log.debug("%f seconds executing initialize()." % (t2 - t1))
if return_value == 'fail':
log.debug("Object (%s) parsing and/or geometry creation failed." % kind)
return "fail"
# Check units and convert if necessary
# This condition CAN be true because initialize() can change obj.units
if self.app.options["units"].upper() != obj.units.upper():
self.app.inform.emit('%s: %s' % (_("Converting units to "), self.app.options["units"]))
obj.convert_units(self.app.options["units"])
t3 = time.time()
log.debug("%f seconds converting units." % (t3 - t2))
# Create the bounding box for the object and then add the results to the obj.options
# But not for Scripts or for Documents
if kind != 'document' and kind != 'script':
try:
xmin, ymin, xmax, ymax = obj.bounds()
obj.options['xmin'] = xmin
obj.options['ymin'] = ymin
obj.options['xmax'] = xmax
obj.options['ymax'] = ymax
except Exception as e:
log.warning("AppObject.new_object() -> The object has no bounds properties. %s" % str(e))
return "fail"
try:
if kind == 'excellon':
obj.fill_color = self.app.defaults["excellon_plot_fill"]
obj.outline_color = self.app.defaults["excellon_plot_line"]
if kind == 'gerber':
obj.fill_color = self.app.defaults["gerber_plot_fill"]
obj.outline_color = self.app.defaults["gerber_plot_line"]
except Exception as e:
log.warning("AppObject.new_object() -> setting colors error. %s" % str(e))
# update the KeyWords list with the name of the file
self.app.myKeywords.append(obj.options['name'])
log.debug("Moving new object back to main thread.")
# Move the object to the main thread and let the app know that it is available.
obj.moveToThread(self.app.main_thread)
self.object_created.emit(obj, obj_plot, obj_autoselected)
return obj
def new_excellon_object(self):
"""
Creates a new, blank Excellon object.
:return: None
"""
self.new_object('excellon', 'new_exc', lambda x, y: None, plot=False)
def new_geometry_object(self):
"""
Creates a new, blank and single-tool Geometry object.
:return: None
"""
def initialize(obj, app):
obj.multitool = False
self.new_object('geometry', 'new_geo', initialize, plot=False)
def new_gerber_object(self):
"""
Creates a new, blank Gerber object.
:return: None
"""
def initialize(grb_obj, app):
grb_obj.multitool = False
grb_obj.source_file = []
grb_obj.multigeo = False
grb_obj.follow = False
grb_obj.apertures = {}
grb_obj.solid_geometry = []
try:
grb_obj.options['xmin'] = 0
grb_obj.options['ymin'] = 0
grb_obj.options['xmax'] = 0
grb_obj.options['ymax'] = 0
except KeyError:
pass
self.new_object('gerber', 'new_grb', initialize, plot=False)
def new_script_object(self):
"""
Creates a new, blank TCL Script object.
:return: None
"""
# commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
# "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
# "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
# "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
# "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
# "ListSys, MillDrills,\n" \
# "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
# "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
# "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
# "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
# "# SubtractRectangle, Version, WriteGCode\n"
new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
'# %s:\n' % _('TCL Tutorial is here') + \
'# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
'# %s:\n' % _("FlatCAM commands list")
new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
"(displayed in Tcl Shell).")
def initialize(obj, app):
obj.source_file = deepcopy(new_source_file)
outname = 'new_script'
self.new_object('script', outname, initialize, plot=False)
def new_document_object(self):
"""
Creates a new, blank Document object.
:return: None
"""
def initialize(obj, app):
obj.source_file = ""
self.new_object('document', 'new_document', initialize, plot=False)
def on_object_created(self, obj, plot, auto_select):
"""
Event callback for object creation.
It will add the new object to the collection. After that it will plot the object in a threaded way
:param obj: The newly created FlatCAM object.
:param plot: if the newly create object t obe plotted
:param auto_select: if the newly created object to be autoselected after creation
:return: None
"""
t0 = time.time() # DEBUG
log.debug("on_object_created()")
# The Collection might change the name if there is a collision
self.app.collection.append(obj)
# after adding the object to the collection always update the list of objects that are in the collection
self.app.all_objects_list = self.app.collection.get_list()
# self.app.inform.emit('[selected] %s created & selected: %s' %
# (str(obj.kind).capitalize(), str(obj.options['name'])))
if obj.kind == 'gerber':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='green',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'excellon':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='brown',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'cncjob':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='blue',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'geometry':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='red',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'script':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='orange',
name=str(obj.options['name']), tx=_("created/selected"))
)
elif obj.kind == 'document':
self.app.inform.emit('[selected] {kind} {tx}: <span style="color:{color};">{name}</span>'.format(
kind=obj.kind.capitalize(),
color='darkCyan',
name=str(obj.options['name']), tx=_("created/selected"))
)
# update the SHELL auto-completer model with the name of the new object
self.app.shell._edit.set_model_data(self.app.myKeywords)
if auto_select:
# select the just opened object but deselect the previous ones
self.app.collection.set_all_inactive()
self.app.collection.set_active(obj.options["name"])
else:
self.app.collection.set_all_inactive()
# here it is done the object plotting
def task(t_obj):
with self.app.proc_container.new(_("Plotting")):
if t_obj.kind == 'cncjob':
t_obj.plot(kind=self.app.defaults["cncjob_plot_kind"])
else:
t_obj.plot()
t1 = time.time() # DEBUG
log.debug("%f seconds adding object and plotting." % (t1 - t0))
self.object_plotted.emit(t_obj)
# Send to worker
# self.worker.add_task(worker_task, [self])
if plot is True:
self.app.worker_task.emit({'fcn': task, 'params': [obj]})
def on_object_changed(self, obj):
"""
Called whenever the geometry of the object was changed in some way.
This require the update of it's bounding values so it can be the selected on canvas.
Update the bounding box data from obj.options
:param obj: the object that was changed
:return: None
"""
try:
xmin, ymin, xmax, ymax = obj.bounds()
except TypeError:
return
obj.options['xmin'] = xmin
obj.options['ymin'] = ymin
obj.options['xmax'] = xmax
obj.options['ymax'] = ymax
log.debug("Object changed, updating the bounding box data on self.options")
# delete the old selection shape
self.app.delete_selection_shape()
self.app.should_we_save = True
def on_object_plotted(self):
"""
Callback called whenever the plotted object needs to be fit into the viewport (canvas)
:return: None
"""
self.app.on_zoom_fit()

View File

@@ -14,8 +14,8 @@ from copy import deepcopy
from io import StringIO
from datetime import datetime
from flatcamEditors.FlatCAMTextEditor import TextEditor
from flatcamObjects.FlatCAMObj import *
from AppEditors.FlatCAMTextEditor import TextEditor
from AppObjects.FlatCAMObj import *
from camlib import CNCjob
@@ -24,7 +24,7 @@ import sys
import math
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -506,10 +506,10 @@ class CNCJobObject(FlatCAMObj, CNCjob):
filename, _f = FCFileSaveDialog.get_saved_filename(
caption=_("Export Machine Code ..."),
directory=dir_file_to_save,
filter=_filter_
ext_filter=_filter_
)
except TypeError:
filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_)
filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), ext_filter=_filter_)
filename = str(filename)
@@ -564,7 +564,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
# delete the absolute and relative position and messages in the infobar
self.app.ui.position_label.setText("")
self.app.ui.rel_position_label.setText("")
# self.app.ui.rel_position_label.setText("")
# first clear previous text in text editor (if any)
self.gcode_editor_tab.code_editor.clear()
@@ -989,7 +989,6 @@ class CNCJobObject(FlatCAMObj, CNCjob):
for key in self.cnc_tools:
ppg = self.cnc_tools[key]['data']['ppname_g']
if 'toolchange_custom' not in str(ppg).lower():
print(ppg)
if self.ui.toolchange_cb.get_value():
self.ui.toolchange_cb.set_value(False)
self.app.inform.emit('[WARNING_NOTCL] %s' %
@@ -1107,7 +1106,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
except ValueError:
# we may have a tuple with only one element and a comma
dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0]
self.plot2(dia_plot, obj=self, visible=visible, kind=kind)
self.plot2(tooldia=dia_plot, obj=self, visible=visible, kind=kind)
else:
# multiple tools usage
if self.cnc_tools:
@@ -1117,10 +1116,14 @@ class CNCJobObject(FlatCAMObj, CNCjob):
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)
# TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed
# I do this so the travel lines thickness will reflect the tool diameter
# may work only for objects created within the app and not Gcode imported from elsewhere for which we
# don't know the origin
if self.origin_kind == "excellon":
if self.exc_cnc_tools:
for tooldia_key in self.exc_cnc_tools:
tooldia = float('%.*f' % (self.decimals, float(tooldia_key)))
# gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed']
# gcode_parsed = self.exc_cnc_tools[tooldia_key]['gcode_parsed']
gcode_parsed = self.gcode_parsed
self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind)

View File

@@ -10,11 +10,11 @@
# File modified by: Marius Stanciu #
# ##########################################################
from flatcamEditors.FlatCAMTextEditor import TextEditor
from flatcamObjects.FlatCAMObj import *
from AppEditors.FlatCAMTextEditor import TextEditor
from AppObjects.FlatCAMObj import *
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -284,6 +284,27 @@ class DocumentObject(FlatCAMObj):
self.ui.sel_color_entry.set_value(new_val)
self.app.defaults['document_sel_color'] = new_val
def mirror(self, axis, point):
pass
def offset(self, vect):
pass
def rotate(self, angle, point):
pass
def scale(self, xfactor, yfactor=None, point=None):
pass
def skew(self, angle_x, angle_y, point):
pass
def buffer(self, distance, join, factor=None):
pass
def bounds(self, flatten=False):
return None, None, None, None
def to_dict(self):
"""
Returns a representation of the object as a dictionary.

View File

@@ -15,13 +15,14 @@ from shapely.geometry import Point, LineString
from copy import deepcopy
from flatcamParsers.ParseExcellon import Excellon
from flatcamObjects.FlatCAMObj import *
from AppParsers.ParseExcellon import Excellon
from AppObjects.FlatCAMObj import *
import itertools
import numpy as np
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -31,7 +32,7 @@ if '_' not in builtins.__dict__:
class ExcellonObject(FlatCAMObj, Excellon):
"""
Represents Excellon/Drill code.
Represents Excellon/Drill code. An object stored in the FlatCAM objects collection (a dict)
"""
ui_type = ExcellonObjectUI
@@ -50,6 +51,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.options.update({
"plot": True,
"solid": False,
"multicolored": False,
"operation": "drill",
"milling_type": "drills",
@@ -125,6 +127,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.outline_color = self.app.defaults['excellon_plot_line']
self.alpha_level = 'bf'
# store here the state of the exclusion checkbox state to be restored after building the UI
# TODO add this in the sel.app.defaults dict and in Preferences
self.exclusion_area_cb_is_checked = False
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
@@ -143,7 +149,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
If only one object is in exc_list parameter then this function will copy that object in the exc_final
:param exc_list: List or one object of ExcellonObject Objects to join.
:type exc_list: list
:param exc_final: Destination ExcellonObject object.
:type exc_final: class
:return: None
"""
@@ -312,8 +320,23 @@ class ExcellonObject(FlatCAMObj, Excellon):
exc_final.create_geometry()
def build_ui(self):
"""
Will (re)build the Excellon UI updating it (the tool table)
:return: None
:rtype:
"""
FlatCAMObj.build_ui(self)
# Area Exception - exclusion shape added signal
# first disconnect it from any other object
try:
self.app.exc_areas.e_shape_modified.disconnect()
except (TypeError, AttributeError):
pass
# then connect it to the current build_ui() method
self.app.exc_areas.e_shape_modified.connect(self.update_exclusion_table)
self.units = self.app.defaults['units'].upper()
for row in range(self.ui.tools_table.rowCount()):
@@ -514,6 +537,58 @@ class ExcellonObject(FlatCAMObj, Excellon):
"<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
)
# Build Exclusion Areas section
e_len = len(self.app.exc_areas.exclusion_areas_storage)
self.ui.exclusion_table.setRowCount(e_len)
area_id = 0
for area in range(e_len):
area_id += 1
area_dict = self.app.exc_areas.exclusion_areas_storage[area]
area_id_item = QtWidgets.QTableWidgetItem('%d' % int(area_id))
area_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 0, area_id_item) # Area id
object_item = QtWidgets.QTableWidgetItem('%s' % area_dict["obj_type"])
object_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 1, object_item) # Origin Object
strategy_item = QtWidgets.QTableWidgetItem('%s' % area_dict["strategy"])
strategy_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 2, strategy_item) # Strategy
overz_item = QtWidgets.QTableWidgetItem('%s' % area_dict["overz"])
overz_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 3, overz_item) # Over Z
self.ui.exclusion_table.resizeColumnsToContents()
self.ui.exclusion_table.resizeRowsToContents()
area_vheader = self.ui.exclusion_table.verticalHeader()
area_vheader.hide()
self.ui.exclusion_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
area_hheader = self.ui.exclusion_table.horizontalHeader()
area_hheader.setMinimumSectionSize(10)
area_hheader.setDefaultSectionSize(70)
area_hheader.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
area_hheader.resizeSection(0, 20)
area_hheader.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
area_hheader.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
area_hheader.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
# area_hheader.setStretchLastSection(True)
self.ui.exclusion_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.ui.exclusion_table.setColumnWidth(0, 20)
self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight())
self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight())
self.ui_connect()
def set_ui(self, ui):
@@ -534,6 +609,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.form_fields.update({
"plot": self.ui.plot_cb,
"solid": self.ui.solid_cb,
"multicolored": self.ui.multicolored_cb,
"operation": self.ui.operation_radio,
"milling_type": self.ui.milling_type_radio,
@@ -567,7 +643,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
"ppname_g": self.ui.pp_geo_name_cb,
"z_pdepth": self.ui.pdepth_entry,
"feedrate_probe": self.ui.feedrate_probe_entry,
# "gcode_type": self.ui.excellon_gcode_type_radio
# "gcode_type": self.ui.excellon_gcode_type_radio,
"area_exclusion": self.ui.exclusion_cb,
"area_shape": self.ui.area_shape_radio,
"area_strategy": self.ui.strategy_radio,
"area_overz": self.ui.over_z_entry,
})
self.name2option = {
@@ -628,12 +708,24 @@ class ExcellonObject(FlatCAMObj, Excellon):
assert isinstance(self.ui, ExcellonObjectUI), \
"Expected a ExcellonObjectUI, got %s" % type(self.ui)
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click)
self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click)
# Exclusion areas signals
self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.exclusion_table_toggle_all)
self.ui.exclusion_table.lost_focus.connect(self.clear_selection)
self.ui.exclusion_table.itemClicked.connect(self.draw_sel_shape)
self.ui.add_area_button.clicked.connect(self.on_add_area_click)
self.ui.delete_area_button.clicked.connect(self.on_clear_area_click)
self.ui.delete_sel_area_button.clicked.connect(self.on_delete_sel_areas)
self.ui.strategy_radio.activated_custom.connect(self.on_strategy)
self.on_operation_type(val='drill')
self.ui.operation_radio.activated_custom.connect(self.on_operation_type)
@@ -650,6 +742,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui.operation_radio.setEnabled(False)
def ui_connect(self):
"""
Will connect all signals in the Excellon UI that needs to be connected
:return: None
:rtype:
"""
# selective plotting
for row in range(self.ui.tools_table.rowCount() - 2):
@@ -672,6 +770,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
current_widget.returnPressed.connect(self.form_to_storage)
def ui_disconnect(self):
"""
Will disconnect all signals in the Excellon UI that needs to be disconnected
:return: None
:rtype:
"""
# selective plotting
for row in range(self.ui.tools_table.rowCount()):
try:
@@ -714,6 +818,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
pass
def on_row_selection_change(self):
"""
Called when the user clicks on a row in Tools Table
:return: None
:rtype:
"""
self.ui_disconnect()
sel_rows = []
@@ -764,6 +874,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui_connect()
def storage_to_form(self, dict_storage):
"""
Will update the GUI with data from the "storage" in this case the dict self.tools
:param dict_storage: A dictionary holding the data relevant for gnerating Gcode from Excellon
:type dict_storage: dict
:return: None
:rtype:
"""
for form_key in self.form_fields:
for storage_key in dict_storage:
if form_key == storage_key and form_key not in \
@@ -775,6 +893,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
pass
def form_to_storage(self):
"""
Will update the 'storage' attribute which is the dict self.tools with data collected from GUI
:return: None
:rtype:
"""
if self.ui.tools_table.rowCount() == 0:
# there is no tool in tool table so we can't save the GUI elements values to storage
return
@@ -803,6 +927,14 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.ui_connect()
def on_operation_type(self, val):
"""
Called by a RadioSet activated_custom signal
:param val: Parameter passes by the signal that called this method
:type val: str
:return: None
:rtype:
"""
if val == 'mill':
self.ui.mill_type_label.show()
self.ui.milling_type_radio.show()
@@ -831,7 +963,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
def get_selected_tools_list(self):
"""
Returns the keys to the self.tools dictionary corresponding
to the selections on the tool list in the GUI.
to the selections on the tool list in the AppGUI.
:return: List of tools.
:rtype: list
@@ -872,7 +1004,21 @@ class ExcellonObject(FlatCAMObj, Excellon):
def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'):
"""
Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code
:return: has_slots and Excellon_code
:param whole: Integer part digits
:type whole: int
:param fract: Fractional part digits
:type fract: int
:param e_zeros: Excellon zeros suppression: LZ or TZ
:type e_zeros: str
:param form: Excellon format: 'dec',
:type form: str
:param factor: Conversion factor
:type factor: float
:param slot_type: How to treat slots: "routing" or "drilling"
:type slot_type: str
:return: A tuple: (has_slots, Excellon_code) -> (bool, str)
:rtype: tuple
"""
excellon_code = ''
@@ -1039,11 +1185,23 @@ class ExcellonObject(FlatCAMObj, Excellon):
def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
"""
Will generate an Geometry Object allowing to cut a drill hole instead of drilling it.
Note: This method is a good template for generic operations as
it takes it's options from parameters or otherwise from the
object's options and returns a (success, msg) tuple as feedback
for shell operations.
:param tools: A list of tools where the drills are to be milled or a string: "all"
:type tools:
:param outname: the name of the resulting Geometry object
:type outname: str
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
:type tooldia: float
:param plot: if to plot the resulting object
:type plot: bool
:param use_thread: if to use threading for creation of the Geometry object
:type use_thread: bool
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
"""
@@ -1088,6 +1246,15 @@ class ExcellonObject(FlatCAMObj, Excellon):
return False, "Error: Milling tool is larger than hole."
def geo_init(geo_obj, app_obj):
"""
:param geo_obj: New object
:type geo_obj: GeometryObject
:param app_obj: App
:type app_obj: FlatCAMApp.App
:return:
:rtype:
"""
assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
# ## Add properties to the object
@@ -1100,7 +1267,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1115,9 +1282,10 @@ class ExcellonObject(FlatCAMObj, Excellon):
else:
geo_obj.solid_geometry.append(
Point(hole['point']).buffer(buffer_value).exterior)
if use_thread:
def geo_thread(app_obj):
app_obj.new_object("geometry", outname, geo_init, plot=plot)
def geo_thread(a_obj):
a_obj.app_obj.new_object("geometry", outname, geo_init, plot=plot)
# Create a promise with the new name
self.app.collection.promise(outname)
@@ -1125,17 +1293,29 @@ class ExcellonObject(FlatCAMObj, Excellon):
# Send to worker
self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
else:
self.app.new_object("geometry", outname, geo_init, plot=plot)
self.app.app_obj.new_object("geometry", outname, geo_init, plot=plot)
return True, ""
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
"""
Will generate an Geometry Object allowing to cut/mill a slot hole.
Note: This method is a good template for generic operations as
it takes it's options from parameters or otherwise from the
object's options and returns a (success, msg) tuple as feedback
for shell operations.
:param tools: A list of tools where the drills are to be milled or a string: "all"
:type tools:
:param outname: the name of the resulting Geometry object
:type outname: str
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
:type tooldia: float
:param plot: if to plot the resulting object
:type plot: bool
:param use_thread: if to use threading for creation of the Geometry object
:type use_thread: bool
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
"""
@@ -1178,7 +1358,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
return False, "Error: Milling tool is larger than hole."
def geo_init(geo_obj, app_obj):
assert geo_obj.kind == 'geometry' "Initializer expected a GeometryObject, got %s" % type(geo_obj)
assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj)
# ## Add properties to the object
@@ -1190,7 +1370,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
@@ -1220,8 +1400,8 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.solid_geometry.append(poly)
if use_thread:
def geo_thread(app_obj):
app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
def geo_thread(a_obj):
a_obj.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
# Create a promise with the new name
self.app.collection.promise(outname)
@@ -1229,7 +1409,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
# Send to worker
self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]})
else:
self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot)
self.app.app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot)
return True, ""
@@ -1237,13 +1417,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.app.defaults.report_usage("excellon_on_create_milling_drills button")
self.read_form()
self.generate_milling_drills(use_thread=False)
self.generate_milling_drills(use_thread=False, plot=True)
def on_generate_milling_slots_button_click(self, *args):
self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
self.read_form()
self.generate_milling_slots(use_thread=False)
self.generate_milling_slots(use_thread=False, plot=True)
def on_pp_changed(self):
current_pp = self.ui.pp_excellon_name_cb.get_value()
@@ -1363,7 +1543,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
job_name = self.options["name"] + "_cnc"
pp_excellon_name = self.options["ppname_e"]
# Object initialization function for app.new_object()
# Object initialization function for app.app_obj.new_object()
def job_init(job_obj, app_obj):
assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
@@ -1426,9 +1606,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
job_obj.create_geometry()
# To be run in separate thread
def job_thread(app_obj):
def job_thread(a_obj):
with self.app.proc_container.new(_("Generating CNC Code")):
app_obj.new_object("cncjob", job_name, job_init)
a_obj.app_obj.new_object("cncjob", job_name, job_init)
# Create promise for the new name.
self.app.collection.promise(job_name)
@@ -1466,12 +1646,122 @@ class ExcellonObject(FlatCAMObj, Excellon):
# self.options['startz'] = float(self.options['startz']) * factor
# self.options['endz'] = float(self.options['endz']) * factor
def on_add_area_click(self):
shape_button = self.ui.area_shape_radio
overz_button = self.ui.over_z_entry
strategy_radio = self.ui.strategy_radio
cnc_button = self.ui.generate_cnc_button
solid_geo = self.solid_geometry
obj_type = self.kind
self.app.exc_areas.on_add_area_click(
shape_button=shape_button, overz_button=overz_button, cnc_button=cnc_button, strategy_radio=strategy_radio,
solid_geo=solid_geo, obj_type=obj_type)
def on_clear_area_click(self):
if not self.app.exc_areas.exclusion_areas_storage:
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. There are no exclusion areas to delete."))
return
self.app.exc_areas.on_clear_area_click()
self.app.exc_areas.e_shape_modified.emit()
def on_delete_sel_areas(self):
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
# so the duplicate rows will not be added
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
if not sel_rows:
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. Nothing is selected."))
return
self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows))
self.app.exc_areas.e_shape_modified.emit()
def draw_sel_shape(self):
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
self.delete_sel_shape()
if self.app.is_legacy is False:
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
else:
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.4 * 255)))[2:]
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
for row in sel_rows:
sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape']
self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0,
tolerance=None)
if self.app.is_legacy is True:
self.app.move_tool.sel_shapes.redraw()
def clear_selection(self):
self.app.delete_selection_shape()
# self.ui.exclusion_table.clearSelection()
def delete_sel_shape(self):
self.app.delete_selection_shape()
def update_exclusion_table(self):
self.exclusion_area_cb_is_checked = True if self.ui.exclusion_cb.isChecked() else False
self.build_ui()
self.ui.exclusion_cb.set_value(self.exclusion_area_cb_is_checked)
def on_strategy(self, val):
if val == 'around':
self.ui.over_z_label.setDisabled(True)
self.ui.over_z_entry.setDisabled(True)
else:
self.ui.over_z_label.setDisabled(False)
self.ui.over_z_entry.setDisabled(False)
def exclusion_table_toggle_all(self):
"""
will toggle the selection of all rows in Exclusion Areas table
:return:
"""
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
if sel_rows:
self.ui.exclusion_table.clearSelection()
self.delete_sel_shape()
else:
self.ui.exclusion_table.selectAll()
self.draw_sel_shape()
def on_solid_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('solid')
self.plot()
def on_multicolored_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('multicolored')
self.plot()
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
@@ -1545,6 +1835,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
if not FlatCAMObj.plot(self):
return
if self.app.is_legacy is False:
def random_color():
r_color = np.random.rand(4)
r_color[3] = 1
return r_color
else:
def random_color():
while True:
r_color = np.random.rand(4)
r_color[3] = 1
new_color = '#'
for idx in range(len(r_color)):
new_color += '%x' % int(r_color[idx] * 255)
# do it until a valid color is generated
# a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
# for a total of 9 chars
if len(new_color) == 9:
break
return new_color
# try:
# # Plot Excellon (All polygons?)
# if self.options["solid"]:
@@ -1576,12 +1887,26 @@ class ExcellonObject(FlatCAMObj, Excellon):
try:
# Plot Excellon (All polygons?)
if self.options["solid"]:
for geo in self.solid_geometry:
# for geo in self.solid_geometry:
# self.add_shape(shape=geo,
# color=self.outline_color,
# face_color=random_color() if self.options['multicolored'] else self.fill_color,
# visible=visible,
# layer=2)
# plot polygons for each tool separately
for tool in self.tools:
# set the color here so we have one color for each tool
geo_color = random_color()
# tool is a dict also
for geo in self.tools[tool]["solid_geometry"]:
self.add_shape(shape=geo,
color=self.outline_color,
face_color=self.fill_color,
color=geo_color if self.options['multicolored'] else self.outline_color,
face_color=geo_color if self.options['multicolored'] else self.fill_color,
visible=visible,
layer=2)
else:
for geo in self.solid_geometry:
self.add_shape(shape=geo.exterior, color='red', visible=visible)

View File

@@ -15,8 +15,7 @@ import shapely.affinity as affinity
from camlib import Geometry
from flatcamObjects.FlatCAMObj import *
import FlatCAMTool
from AppObjects.FlatCAMObj import *
import ezdxf
import math
@@ -25,7 +24,7 @@ from copy import deepcopy
import traceback
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -53,6 +52,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.options.update({
"plot": True,
"multicolored": False,
"cutz": -0.002,
"vtipdia": 0.1,
"vtipangle": 30,
@@ -151,17 +151,9 @@ class GeometryObject(FlatCAMObj, Geometry):
self.param_fields = {}
# Event signals disconnect id holders
self.mr = None
self.mm = None
self.kp = None
# variables to be used in area exclusion
self.cursor_pos = (0, 0)
self.exclusion_areas_list = []
self.first_click = False
self.points = []
self.poly_drawn = False
# store here the state of the exclusion checkbox state to be restored after building the UI
# TODO add this in the sel.app.defaults dict and in Preferences
self.exclusion_area_cb_is_checked = False
# Attributes to be included in serialization
# Always append to it because it carries contents
@@ -172,6 +164,15 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui_disconnect()
FlatCAMObj.build_ui(self)
# Area Exception - exclusion shape added signal
# first disconnect it from any other object
try:
self.app.exc_areas.e_shape_modified.disconnect()
except (TypeError, AttributeError):
pass
# then connect it to the current build_ui() method
self.app.exc_areas.e_shape_modified.connect(self.update_exclusion_table)
self.units = self.app.defaults['units']
tool_idx = 0
@@ -192,7 +193,6 @@ class GeometryObject(FlatCAMObj, Geometry):
# For INCH the decimals should be no more than 3. There are no tools under 10mils.
dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia'])))
dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
offset_item = FCComboBox()
@@ -305,6 +305,63 @@ class GeometryObject(FlatCAMObj, Geometry):
self.set_tool_offset_visibility(selected_row)
# -----------------------------
# Build Exclusion Areas section
# -----------------------------
e_len = len(self.app.exc_areas.exclusion_areas_storage)
self.ui.exclusion_table.setRowCount(e_len)
area_id = 0
for area in range(e_len):
area_id += 1
area_dict = self.app.exc_areas.exclusion_areas_storage[area]
area_id_item = QtWidgets.QTableWidgetItem('%d' % int(area_id))
area_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 0, area_id_item) # Area id
object_item = QtWidgets.QTableWidgetItem('%s' % area_dict["obj_type"])
object_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 1, object_item) # Origin Object
strategy_item = QtWidgets.QTableWidgetItem('%s' % area_dict["strategy"])
strategy_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 2, strategy_item) # Strategy
overz_item = QtWidgets.QTableWidgetItem('%s' % area_dict["overz"])
overz_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.exclusion_table.setItem(area, 3, overz_item) # Over Z
self.ui.exclusion_table.resizeColumnsToContents()
self.ui.exclusion_table.resizeRowsToContents()
area_vheader = self.ui.exclusion_table.verticalHeader()
area_vheader.hide()
self.ui.exclusion_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
area_hheader = self.ui.exclusion_table.horizontalHeader()
area_hheader.setMinimumSectionSize(10)
area_hheader.setDefaultSectionSize(70)
area_hheader.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
area_hheader.resizeSection(0, 20)
area_hheader.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
area_hheader.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
area_hheader.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
# area_hheader.setStretchLastSection(True)
self.ui.exclusion_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.ui.exclusion_table.setColumnWidth(0, 20)
self.ui.exclusion_table.setMinimumHeight(self.ui.exclusion_table.getHeight())
self.ui.exclusion_table.setMaximumHeight(self.ui.exclusion_table.getHeight())
# End Build Exclusion Areas
# -----------------------------
# HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
# done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
self.ui.name_entry.set_value(self.options['name'])
@@ -340,6 +397,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.form_fields.update({
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"cutz": self.ui.cutz_entry,
"vtipdia": self.ui.tipdia_entry,
"vtipangle": self.ui.tipangle_entry,
@@ -363,7 +421,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"endxy": self.ui.endxy_entry,
"cnctooldia": self.ui.addtool_entry,
"area_exclusion": self.ui.exclusion_cb,
"area_shape":self.ui.area_shape_radio,
"area_shape": self.ui.area_shape_radio,
"area_strategy": self.ui.strategy_radio,
"area_overz": self.ui.over_z_entry,
})
@@ -535,6 +593,8 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state))
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False))
self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False))
@@ -547,8 +607,14 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed)
# Exclusion areas signals
self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.exclusion_table_toggle_all)
self.ui.exclusion_table.lost_focus.connect(self.clear_selection)
self.ui.exclusion_table.itemClicked.connect(self.draw_sel_shape)
self.ui.add_area_button.clicked.connect(self.on_add_area_click)
self.ui.delete_area_button.clicked.connect(self.on_clear_area_click)
self.ui.delete_sel_area_button.clicked.connect(self.on_delete_sel_areas)
self.ui.strategy_radio.activated_custom.connect(self.on_strategy)
def on_cut_z_changed(self):
self.old_cutz = self.ui.cutz_entry.get_value()
@@ -595,8 +661,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.tool_offset_entry.get_value().replace(',', '.')
)
except ValueError:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("Wrong value format entered, use a number."))
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
return
def ui_connect(self):
@@ -604,6 +669,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# changes in geometry UI
for i in self.param_fields:
current_widget = self.param_fields[i]
if isinstance(current_widget, FCCheckBox):
current_widget.stateChanged.connect(self.gui_form_to_storage)
elif isinstance(current_widget, FCComboBox):
@@ -874,7 +940,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui_connect()
self.build_ui()
# if there is no tool left in the Tools Table, enable the parameters GUI
# if there is no tool left in the Tools Table, enable the parameters AppGUI
if self.ui.geo_tools_table.rowCount() != 0:
self.ui.geo_param_frame.setDisabled(False)
@@ -950,7 +1016,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui_connect()
self.build_ui()
# if there is no tool left in the Tools Table, enable the parameters GUI
# if there is no tool left in the Tools Table, enable the parameters AppGUI
if self.ui.geo_tools_table.rowCount() != 0:
self.ui.geo_param_frame.setDisabled(False)
@@ -1131,7 +1197,7 @@ class GeometryObject(FlatCAMObj, Geometry):
obj_active.options['xmax'] = 0
obj_active.options['ymax'] = 0
# if there is no tool left in the Tools Table, disable the parameters GUI
# if there is no tool left in the Tools Table, disable the parameters AppGUI
if self.ui.geo_tools_table.rowCount() == 0:
self.ui.geo_param_frame.setDisabled(True)
@@ -1149,8 +1215,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
)
"NB: a value of zero means that Tool Dia = 'V-tip Dia'")
)
self.ui.cutz_entry.setToolTip(
_("Disabled because the tool is V-shape.\n"
@@ -1159,8 +1224,7 @@ class GeometryObject(FlatCAMObj, Geometry):
"- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
)
"NB: a value of zero means that Tool Dia = 'V-tip Dia'")
)
self.update_cutz()
@@ -1172,8 +1236,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.cutz_entry.setDisabled(False)
self.ui.cutzlabel.setToolTip(
_("Cutting depth (negative)\n"
"below the copper surface."
)
"below the copper surface.")
)
self.ui.cutz_entry.setToolTip('')
@@ -1328,16 +1391,18 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui_connect()
def gui_form_to_storage(self):
self.ui_disconnect()
if self.ui.geo_tools_table.rowCount() == 0:
# there is no tool in tool table so we can't save the GUI elements values to storage
log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.")
return
self.ui_disconnect()
widget_changed = self.sender()
try:
widget_idx = self.ui.grid3.indexOf(widget_changed)
except Exception:
except Exception as e:
log.debug("GeometryObject.gui_form_to_storage() -- wdg index -> %s" % str(e))
return
# those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z
@@ -1690,15 +1755,11 @@ class GeometryObject(FlatCAMObj, Geometry):
The actual work is done by the target CNCJobObject object's
`generate_from_geometry_2()` method.
:param outname:
:param tools_dict: a dictionary that holds the whole data needed to create the Gcode
(including the solid_geometry)
:param tools_in_use: the tools that are used, needed by some preprocessors
:type list of lists, each list in the list is made out of row elements of tools table from GUI
:param outname:
:param tools_dict:
:param tools_in_use:
:type tools_in_use list of lists, each list in the list is made out of row elements of tools table from AppGUI
:param segx: number of segments on the X axis, for auto-levelling
:param segy: number of segments on the Y axis, for auto-levelling
:param plot: if True the generated object will be plotted; if False will not be plotted
@@ -1728,7 +1789,7 @@ class GeometryObject(FlatCAMObj, Geometry):
self.app.inform.emit(msg)
return
# Object initialization function for app.new_object()
# Object initialization function for app.app_obj.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init_single_geometry(job_obj, app_obj):
log.debug("Creating a CNCJob out of a single-geometry")
@@ -1742,7 +1803,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# count the tools
tool_cnt = 0
dia_cnc_dict = {}
# dia_cnc_dict = {}
# this turn on the FlatCAMCNCJob plot for multiple tools
job_obj.multitool = True
@@ -1868,7 +1929,7 @@ class GeometryObject(FlatCAMObj, Geometry):
})
dia_cnc_dict.clear()
# Object initialization function for app.new_object()
# Object initialization function for app.app_obj.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init_multi_geometry(job_obj, app_obj):
log.debug("Creating a CNCJob out of a multi-geometry")
@@ -1882,7 +1943,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# count the tools
tool_cnt = 0
dia_cnc_dict = {}
# dia_cnc_dict = {}
# this turn on the FlatCAMCNCJob plot for multiple tools
job_obj.multitool = True
@@ -2022,15 +2083,15 @@ class GeometryObject(FlatCAMObj, Geometry):
if use_thread:
# To be run in separate thread
def job_thread(app_obj):
def job_thread(a_obj):
if self.multigeo is False:
with self.app.proc_container.new(_("Generating CNC Code")):
if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
if a_obj.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail':
a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
else:
with self.app.proc_container.new(_("Generating CNC Code")):
if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
if a_obj.app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail':
a_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
# Create a promise with the name
self.app.collection.promise(outname)
@@ -2038,25 +2099,17 @@ class GeometryObject(FlatCAMObj, Geometry):
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
else:
if self.solid_geometry:
self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
self.app.app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot)
else:
self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
self.app.app_obj.new_object("cncjob", outname, job_init_multi_geometry, plot=plot)
def generatecncjob(
self, outname=None,
dia=None, offset=None,
z_cut=None, z_move=None,
feedrate=None, feedrate_z=None, feedrate_rapid=None,
spindlespeed=None, dwell=None, dwelltime=None,
multidepth=None, depthperpass=None,
toolchange=None, toolchangez=None, toolchangexy=None,
extracut=None, extracut_length=None, startz=None, endz=None,
pp=None,
segx=None, segy=None,
use_thread=True,
plot=True):
def generatecncjob(self, outname=None, dia=None, offset=None, z_cut=None, z_move=None,
feedrate=None, feedrate_z=None, feedrate_rapid=None, spindlespeed=None, dwell=None, dwelltime=None,
multidepth=None, dpp=None, toolchange=None, toolchangez=None, toolchangexy=None,
extracut=None, extracut_length=None, startz=None, endz=None, endxy=None, pp=None, segx=None, segy=None,
use_thread=True, plot=True):
"""
Only used for TCL Command.
Only used by the TCL Command Cncjob.
Creates a CNCJob out of this Geometry object. The actual
work is done by the target camlib.CNCjob
`generate_from_geometry_2()` method.
@@ -2073,14 +2126,17 @@ class GeometryObject(FlatCAMObj, Geometry):
:param dwell:
:param dwelltime:
:param multidepth:
:param depthperpass:
:param dpp: Depth for each pass when multidepth parameter is True
:param toolchange:
:param toolchangez:
:param toolchangexy:
:param toolchangexy: A sequence ox X,Y coordinates: a 2-length tuple or a string.
Coordinates in X,Y plane for the Toolchange event
:param extracut:
:param extracut_length:
:param startz:
:param endz:
:param endxy: A sequence ox X,Y coordinates: a 2-length tuple or a string.
Coordinates in X, Y plane for the last move after ending the job.
:param pp: Name of the preprocessor
:param segx:
:param segy:
@@ -2100,7 +2156,7 @@ class GeometryObject(FlatCAMObj, Geometry):
feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
multidepth = multidepth if multidepth is not None else self.options["multidepth"]
depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
depthperpass = dpp if dpp is not None else float(self.options["depthperpass"])
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
@@ -2110,10 +2166,21 @@ class GeometryObject(FlatCAMObj, Geometry):
startz = startz if startz is not None else self.options["startz"]
endz = endz if endz is not None else float(self.options["endz"])
endxy = self.options["endxy"]
endxy = endxy if endxy else self.options["endxy"]
if isinstance(endxy, str):
endxy = re.sub('[()\[\]]', '', endxy)
if endxy and endxy != '':
endxy = [float(eval(a)) for a in endxy.split(",")]
toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
if isinstance(toolchangexy, str):
toolchangexy = re.sub('[()\[\]]', '', toolchangexy)
if toolchangexy and toolchangexy != '':
toolchangexy = [float(eval(a)) for a in toolchangexy.split(",")]
toolchange = toolchange if toolchange else self.options["toolchange"]
offset = offset if offset else 0.0
@@ -2125,7 +2192,7 @@ class GeometryObject(FlatCAMObj, Geometry):
ppname_g = pp if pp else self.options["ppname_g"]
# Object initialization function for app.new_object()
# Object initialization function for app.app_obj.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init(job_obj, app_obj):
assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj)
@@ -2174,7 +2241,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# To be run in separate thread
def job_thread(app_obj):
with self.app.proc_container.new(_("Generating CNC Code")):
app_obj.new_object("cncjob", outname, job_init, plot=plot)
app_obj.app_obj.new_object("cncjob", outname, job_init, plot=plot)
app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
# Create a promise with the name
@@ -2182,7 +2249,7 @@ class GeometryObject(FlatCAMObj, Geometry):
# Send to worker
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
else:
self.app.new_object("cncjob", outname, job_init, plot=plot)
self.app.app_obj.new_object("cncjob", outname, job_init, plot=plot)
# def on_plot_cb_click(self, *args):
# if self.muted_ui:
@@ -2469,6 +2536,110 @@ class GeometryObject(FlatCAMObj, Geometry):
return factor
def on_add_area_click(self):
shape_button = self.ui.area_shape_radio
overz_button = self.ui.over_z_entry
strategy_radio = self.ui.strategy_radio
cnc_button = self.ui.generate_cnc_button
solid_geo = self.solid_geometry
obj_type = self.kind
self.app.exc_areas.on_add_area_click(
shape_button=shape_button, overz_button=overz_button, cnc_button=cnc_button, strategy_radio=strategy_radio,
solid_geo=solid_geo, obj_type=obj_type)
def on_clear_area_click(self):
if not self.app.exc_areas.exclusion_areas_storage:
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. There are no exclusion areas to delete."))
return
self.app.exc_areas.on_clear_area_click()
self.app.exc_areas.e_shape_modified.emit()
def on_delete_sel_areas(self):
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
# so the duplicate rows will not be added
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
if not sel_rows:
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Delete failed. Nothing is selected."))
return
self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows))
self.app.exc_areas.e_shape_modified.emit()
def draw_sel_shape(self):
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
self.delete_sel_shape()
if self.app.is_legacy is False:
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.2 * 255)))[2:]
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(0.8 * 255)))[2:]
else:
face = self.app.defaults['global_sel_fill'][:-2] + str(hex(int(0.4 * 255)))[2:]
outline = self.app.defaults['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
for row in sel_rows:
sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape']
self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0,
tolerance=None)
if self.app.is_legacy is True:
self.app.move_tool.sel_shapes.redraw()
def clear_selection(self):
self.app.delete_selection_shape()
# self.ui.exclusion_table.clearSelection()
def delete_sel_shape(self):
self.app.delete_selection_shape()
def update_exclusion_table(self):
self.exclusion_area_cb_is_checked = True if self.ui.exclusion_cb.isChecked() else False
self.build_ui()
self.ui.exclusion_cb.set_value(self.exclusion_area_cb_is_checked)
def on_strategy(self, val):
if val == 'around':
self.ui.over_z_label.setDisabled(True)
self.ui.over_z_entry.setDisabled(True)
else:
self.ui.over_z_label.setDisabled(False)
self.ui.over_z_entry.setDisabled(False)
def exclusion_table_toggle_all(self):
"""
will toggle the selection of all rows in Exclusion Areas table
:return:
"""
sel_model = self.ui.exclusion_table.selectionModel()
sel_indexes = sel_model.selectedIndexes()
# it will iterate over all indexes which means all items in all columns too but I'm interested only on rows
sel_rows = set()
for idx in sel_indexes:
sel_rows.add(idx.row())
if sel_rows:
self.ui.exclusion_table.clearSelection()
self.delete_sel_shape()
else:
self.ui.exclusion_table.selectAll()
self.draw_sel_shape()
def plot_element(self, element, color=None, visible=None):
if color is None:
@@ -2498,6 +2669,27 @@ class GeometryObject(FlatCAMObj, Geometry):
if not FlatCAMObj.plot(self):
return
if self.app.is_legacy is False:
def random_color():
r_color = np.random.rand(4)
r_color[3] = 1
return r_color
else:
def random_color():
while True:
r_color = np.random.rand(4)
r_color[3] = 1
new_color = '#'
for idx in range(len(r_color)):
new_color += '%x' % int(r_color[idx] * 255)
# do it until a valid color is generated
# a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
# for a total of 9 chars
if len(new_color) == 9:
break
return new_color
try:
# plot solid geometries found as members of self.tools attribute dict
# for MultiGeo
@@ -2505,7 +2697,8 @@ class GeometryObject(FlatCAMObj, Geometry):
for tooluid_key in self.tools:
solid_geometry = self.tools[tooluid_key]['solid_geometry']
self.plot_element(solid_geometry, visible=visible,
color=self.app.defaults["geometry_plot_line"])
color=random_color() if self.options['multicolored']
else self.app.defaults["geometry_plot_line"])
else:
# plot solid geometry that may be an direct attribute of the geometry object
# for SingleGeo
@@ -2573,218 +2766,11 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.plot_cb.setChecked(True)
self.ui_connect()
def on_add_area_click(self):
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
self.app.call_source = 'geometry'
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
else:
self.app.plotcanvas.graph_event_disconnect(self.app.mp)
self.app.plotcanvas.graph_event_disconnect(self.app.mm)
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
# self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
# To be called after clicking on the plot.
def on_mouse_release(self, event):
if self.app.is_legacy is False:
event_pos = event.pos
# event_is_dragging = event.is_dragging
right_button = 2
else:
event_pos = (event.xdata, event.ydata)
# event_is_dragging = self.app.plotcanvas.is_dragging
right_button = 3
event_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
else:
curr_pos = (event_pos[0], event_pos[1])
x1, y1 = curr_pos[0], curr_pos[1]
shape_type = self.ui.area_shape_radio.get_value()
# do clear area only for left mouse clicks
if event.button == 1:
if shape_type == "square":
if self.first_click is False:
self.first_click = True
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
else:
self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
self.app.delete_selection_shape()
x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
pt1 = (x0, y0)
pt2 = (x1, y0)
pt3 = (x1, y1)
pt4 = (x0, y1)
new_rectangle = Polygon([pt1, pt2, pt3, pt4])
self.exclusion_areas_list.append(new_rectangle)
# add a temporary shape on canvas
FlatCAMTool.FlatCAMTool.draw_tool_selection_shape(self, old_coords=(x0, y0), coords=(x1, y1))
self.first_click = False
def on_multicolored_cb_click(self, *args):
if self.muted_ui:
return
else:
self.points.append((x1, y1))
if len(self.points) > 1:
self.poly_drawn = True
self.app.inform.emit(_("Click on next Point or click right mouse button to complete ..."))
return ""
elif event.button == right_button and self.mouse_is_dragging is False:
shape_type = self.ui.area_shape_radio.get_value()
if shape_type == "square":
self.first_click = False
else:
# if we finish to add a polygon
if self.poly_drawn is True:
try:
# try to add the point where we last clicked if it is not already in the self.points
last_pt = (x1, y1)
if last_pt != self.points[-1]:
self.points.append(last_pt)
except IndexError:
pass
# we need to add a Polygon and a Polygon can be made only from at least 3 points
if len(self.points) > 2:
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
pol = Polygon(self.points)
# do not add invalid polygons even if they are drawn by utility geometry
if pol.is_valid:
self.exclusion_areas_list.append(pol)
FlatCAMTool.FlatCAMTool.draw_selection_shape_polygon(self, points=self.points)
self.app.inform.emit(
_("Zone added. Click to start adding next zone or right click to finish."))
self.points = []
self.poly_drawn = False
return
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self)
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
# self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
else:
self.app.plotcanvas.graph_event_disconnect(self.mr)
self.app.plotcanvas.graph_event_disconnect(self.mm)
# self.app.plotcanvas.graph_event_disconnect(self.kp)
self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
self.app.on_mouse_click_over_plot)
self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
self.app.on_mouse_move_over_plot)
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
self.app.on_mouse_click_release_over_plot)
self.app.call_source = 'app'
if len(self.exclusion_areas_list) == 0:
return
def area_disconnect(self):
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
else:
self.app.plotcanvas.graph_event_disconnect(self.mr)
self.app.plotcanvas.graph_event_disconnect(self.mm)
self.app.plotcanvas.graph_event_disconnect(self.kp)
self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
self.app.on_mouse_click_over_plot)
self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
self.app.on_mouse_move_over_plot)
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
self.app.on_mouse_click_release_over_plot)
self.points = []
self.poly_drawn = False
self.exclusion_areas_list = []
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
FlatCAMTool.FlatCAMTool.delete_tool_selection_shape(self)
self.app.call_source = "app"
self.app.inform.emit("[WARNING_NOTCL] %s" % _("Cancelled. Area exclusion drawing was interrupted."))
# called on mouse move
def on_mouse_move(self, event):
shape_type = self.ui.area_shape_radio.get_value()
if self.app.is_legacy is False:
event_pos = event.pos
event_is_dragging = event.is_dragging
# right_button = 2
else:
event_pos = (event.xdata, event.ydata)
event_is_dragging = self.app.plotcanvas.is_dragging
# right_button = 3
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
# detect mouse dragging motion
if event_is_dragging is True:
self.mouse_is_dragging = True
else:
self.mouse_is_dragging = False
# update the cursor position
if self.app.grid_status():
# Update cursor
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
symbol='++', edge_color=self.app.cursor_color_3D,
edge_width=self.app.defaults["global_cursor_width"],
size=self.app.defaults["global_cursor_size"])
# update the positions on status bar
self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
if self.cursor_pos is None:
self.cursor_pos = (0, 0)
self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (self.app.dx, self.app.dy))
# draw the utility geometry
if shape_type == "square":
if self.first_click:
self.app.delete_selection_shape()
self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
coords=(curr_pos[0], curr_pos[1]))
else:
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
FlatCAMTool.FlatCAMTool.draw_moving_selection_shape_poly(
self, points=self.points, data=(curr_pos[0], curr_pos[1]))
def on_clear_area_click(self):
self.exclusion_areas_list = []
FlatCAMTool.FlatCAMTool.delete_moving_selection_shape(self)
self.app.delete_selection_shape()
self.read_form_item('multicolored')
self.plot()
@staticmethod
def merge(geo_list, geo_final, multigeo=None):

View File

@@ -14,15 +14,15 @@
from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing
from shapely.ops import cascaded_union
from flatcamParsers.ParseGerber import Gerber
from flatcamObjects.FlatCAMObj import *
from AppParsers.ParseGerber import Gerber
from AppObjects.FlatCAMObj import *
import math
import numpy as np
from copy import deepcopy
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
"plot": True,
"multicolored": False,
"solid": False,
"tool_type": 'circular',
"vtipdia": 0.1,
"vtipangle": 30,
"vcutz": -0.05,
"isotooldia": 0.016,
"isopasses": 1,
"isooverlap": 15,
"milling_type": "cl",
"combine_passes": True,
"noncoppermargin": 0.0,
"noncopperrounded": False,
"bboxmargin": 0.0,
"bboxrounded": False,
"aperture_display": False,
"follow": False,
"iso_scope": 'all',
"iso_type": 'full'
})
# type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@@ -197,88 +186,44 @@ class GerberObject(FlatCAMObj, Gerber):
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"solid": self.ui.solid_cb,
"tool_type": self.ui.tool_type_radio,
"vtipdia": self.ui.tipdia_spinner,
"vtipangle": self.ui.tipangle_spinner,
"vcutz": self.ui.cutz_spinner,
"isotooldia": self.ui.iso_tool_dia_entry,
"isopasses": self.ui.iso_width_entry,
"isooverlap": self.ui.iso_overlap_entry,
"milling_type": self.ui.milling_type_radio,
"combine_passes": self.ui.combine_passes_cb,
"noncoppermargin": self.ui.noncopper_margin_entry,
"noncopperrounded": self.ui.noncopper_rounded_cb,
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb,
"aperture_display": self.ui.aperture_table_visibility_cb,
"follow": self.ui.follow_cb,
"iso_scope": self.ui.iso_scope_radio,
"iso_type": self.ui.iso_type_radio
"follow": self.ui.follow_cb
})
# Fill form fields only on object create
self.to_form()
assert isinstance(self.ui, GerberObjectUI)
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
# Tools
self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
# set the model for the Area Exception comboboxes
self.ui.obj_combo.setModel(self.app.collection)
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.ui.obj_combo.is_last = True
self.ui.obj_combo.obj_type = {
_("Gerber"): "Gerber", _("Geometry"): "Geometry"
}[self.ui.type_obj_combo.get_value()]
self.on_type_obj_index_changed()
self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
# establish visibility for the GUI elements found in the slot function
self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
# Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b':
self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
self.options['tool_type'] = 'circular'
self.ui.tool_type_label.hide()
self.ui.tool_type_radio.hide()
# override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
self.ui.tool_type_radio.set_value('circular')
self.ui.tipdialabel.hide()
self.ui.tipdia_spinner.hide()
self.ui.tipanglelabel.hide()
self.ui.tipangle_spinner.hide()
self.ui.cutzlabel.hide()
self.ui.cutz_spinner.hide()
self.ui.apertures_table_label.hide()
self.ui.aperture_table_visibility_cb.hide()
self.ui.milling_type_label.hide()
self.ui.milling_type_radio.hide()
self.ui.iso_type_label.hide()
self.ui.iso_type_radio.hide()
self.ui.follow_cb.hide()
self.ui.except_cb.setChecked(False)
self.ui.except_cb.hide()
else:
self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
if self.app.defaults["gerber_buffering"] == 'no':
self.ui.create_buffer_button.show()
@@ -296,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
self.build_ui()
self.units_found = self.app.defaults['units']
def on_calculate_tooldia(self):
try:
tdia = float(self.ui.tipdia_spinner.get_value())
except Exception:
return
try:
dang = float(self.ui.tipangle_spinner.get_value())
except Exception:
return
try:
cutz = float(self.ui.cutz_spinner.get_value())
except Exception:
return
cutz *= -1
if cutz < 0:
cutz *= -1
half_tip_angle = dang / 2
tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
self.ui.iso_tool_dia_entry.set_value(tool_diameter)
def on_type_obj_index_changed(self):
val = self.ui.type_obj_combo.get_value()
obj_type = {"Gerber": 0, "Geometry": 2}[val]
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
self.ui.obj_combo.setCurrentIndex(0)
self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
def on_tool_type_change(self, state):
if state == 'circular':
self.ui.tipdialabel.hide()
self.ui.tipdia_spinner.hide()
self.ui.tipanglelabel.hide()
self.ui.tipangle_spinner.hide()
self.ui.cutzlabel.hide()
self.ui.cutz_spinner.hide()
self.ui.iso_tool_dia_entry.setDisabled(False)
# update the value in the self.iso_tool_dia_entry once this is selected
self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
else:
self.ui.tipdialabel.show()
self.ui.tipdia_spinner.show()
self.ui.tipanglelabel.show()
self.ui.tipangle_spinner.show()
self.ui.cutzlabel.show()
self.ui.cutz_spinner.show()
self.ui.iso_tool_dia_entry.setDisabled(True)
# update the value in the self.iso_tool_dia_entry once this is selected
self.on_calculate_tooldia()
def build_ui(self):
FlatCAMObj.build_ui(self)
@@ -530,7 +423,7 @@ class GerberObject(FlatCAMObj, Gerber):
return "fail"
geo_obj.solid_geometry = non_copper
self.app.new_object("geometry", name, geo_init)
self.app.app_obj.new_object("geometry", name, geo_init)
def on_generatebb_button_click(self, *args):
self.app.defaults.report_usage("gerber_on_generatebb_button")
@@ -556,523 +449,7 @@ class GerberObject(FlatCAMObj, Gerber):
return "fail"
geo_obj.solid_geometry = bounding_box
self.app.new_object("geometry", name, geo_init)
def on_iso_button_click(self, *args):
obj = self.app.collection.get_active()
self.iso_type = 2
if self.ui.iso_type_radio.get_value() == 'ext':
self.iso_type = 0
if self.ui.iso_type_radio.get_value() == 'int':
self.iso_type = 1
def worker_task(iso_obj, app_obj):
with self.app.proc_container.new(_("Isolating...")):
if self.ui.follow_cb.get_value() is True:
iso_obj.follow_geo()
# in the end toggle the visibility of the origin object so we can see the generated Geometry
iso_obj.ui.plot_cb.toggle()
else:
app_obj.defaults.report_usage("gerber_on_iso_button")
self.read_form()
iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
def follow_geo(self, outname=None):
"""
Creates a geometry object "following" the gerber paths.
:return: None
"""
# default_name = self.options["name"] + "_follow"
# follow_name = outname or default_name
if outname is None:
follow_name = self.options["name"] + "_follow"
else:
follow_name = outname
def follow_init(follow_obj, app):
# Propagate options
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
follow_obj.solid_geometry = self.follow_geometry
# TODO: Do something if this is None. Offer changing name?
try:
self.app.new_object("geometry", follow_name, follow_init)
except Exception as e:
return "Operation failed: %s" % str(e)
def isolate_handler(self, iso_type, iso_scope):
if iso_scope == 'all':
self.isolate(iso_type=iso_type)
else:
# disengage the grid snapping since it may be hard to click on polygons with grid snapping on
if self.app.ui.grid_snap_btn.isChecked():
self.grid_status_memory = True
self.app.ui.grid_snap_btn.trigger()
else:
self.grid_status_memory = False
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
else:
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
def on_mouse_click_release(self, event):
if self.app.is_legacy is False:
event_pos = event.pos
right_button = 2
self.app.event_is_dragging = self.app.event_is_dragging
else:
event_pos = (event.xdata, event.ydata)
right_button = 3
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
try:
x = float(event_pos[0])
y = float(event_pos[1])
except TypeError:
return
event_pos = (x, y)
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
else:
curr_pos = (curr_pos[0], curr_pos[1])
if event.button == 1:
clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
if self.app.selection_type is not None:
self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
self.app.selection_type = None
elif clicked_poly:
if clicked_poly not in self.poly_dict.values():
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = clicked_poly
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
_("Click to add next polygon or right click to start isolation."))
)
else:
try:
for k, v in list(self.poly_dict.items()):
if v == clicked_poly:
self.app.tool_shapes.remove(k)
self.poly_dict.pop(k)
break
except TypeError:
return
self.app.inform.emit(
'%s. %s' % (_("Removed polygon"),
_("Click to add/remove next polygon or right click to start isolation."))
)
self.app.tool_shapes.redraw()
else:
self.app.inform.emit(_("No polygon detected under click position."))
elif event.button == right_button and self.app.event_is_dragging is False:
# restore the Grid snapping if it was active before
if self.grid_status_memory is True:
self.app.ui.grid_snap_btn.trigger()
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
else:
self.app.plotcanvas.graph_event_disconnect(self.mr)
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
self.app.on_mouse_click_release_over_plot)
self.app.tool_shapes.clear(update=True)
if self.poly_dict:
poly_list = deepcopy(list(self.poly_dict.values()))
self.isolate(iso_type=self.iso_type, geometry=poly_list)
self.poly_dict.clear()
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
def selection_area_handler(self, start_pos, end_pos, sel_type):
"""
:param start_pos: mouse position when the selection LMB click was done
:param end_pos: mouse position when the left mouse button is released
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
:return:
"""
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
# delete previous selection shape
self.app.delete_selection_shape()
added_poly_count = 0
try:
for geo in self.solid_geometry:
if geo not in self.poly_dict.values():
if sel_type is True:
if geo.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
else:
if poly_selection.intersects(geo):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
except TypeError:
if self.solid_geometry not in self.poly_dict.values():
if sel_type is True:
if self.solid_geometry.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
else:
if poly_selection.intersects(self.solid_geometry):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
if added_poly_count > 0:
self.app.tool_shapes.redraw()
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"),
int(added_poly_count),
_("Click to add next polygon or right click to start isolation."))
)
else:
self.app.inform.emit(_("No polygon in selection."))
def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
milling_type=None, follow=None, plot=True):
"""
Creates an isolation routing geometry object in the project.
:param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
:param geometry: specific geometry to isolate
:param dia: Tool diameter
:param passes: Number of tool widths to cut
:param overlap: Overlap between passes in fraction of tool diameter
:param outname: Base name of the output object
:param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
:param milling_type: type of milling: conventional or climbing
:param follow: Boolean: if to generate a 'follow' geometry
:param plot: Boolean: if to plot the resulting geometry object
:return: None
"""
if geometry is None:
work_geo = self.follow_geometry if follow is True else self.solid_geometry
else:
work_geo = geometry
if dia is None:
dia = float(self.options["isotooldia"])
if passes is None:
passes = int(self.options["isopasses"])
if overlap is None:
overlap = float(self.options["isooverlap"])
overlap /= 100.0
combine = self.options["combine_passes"] if combine is None else bool(combine)
if milling_type is None:
milling_type = self.options["milling_type"]
if iso_type is None:
iso_t = 2
else:
iso_t = iso_type
base_name = self.options["name"]
if combine:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
geo_obj.solid_geometry = []
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
for nr_pass in range(passes):
iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia)
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow, nr_passes=nr_pass)
if geom == 'fail':
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry.append(geom)
# update the geometry in the tools
geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list) and \
not isinstance(geo_obj.solid_geometry, MultiPolygon):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
# even if combine is checked, one pass is still single-geo
geo_obj.multigeo = True if passes > 1 else False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.new_object("geometry", iso_name, iso_init, plot=plot)
else:
for i in range(passes):
offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
if passes > 1:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso" + str(i + 1)
elif self.iso_type == 1:
iso_name = base_name + "_int_iso" + str(i + 1)
else:
iso_name = base_name + "_iso" + str(i + 1)
else:
iso_name = outname
else:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
if self.ui.tool_type_radio.get_value() == 'v':
geo_obj.tool_type = 'V'
else:
geo_obj.tool_type = 'C1'
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow,
nr_passes=i)
if geom == 'fail':
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry = geom
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
# even if the resulting geometry is not multigeo we add the tools dict which will hold the data
# required to be transfered to the Geometry object
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
app_obj.inform.emit('[success] %s: %s' %
(_("Isolation geometry created"), geo_obj.options["name"]))
geo_obj.multigeo = False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.new_object("geometry", iso_name, iso_init, plot=plot)
self.app.app_obj.new_object("geometry", name, geo_init)
def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
# isolation_geometry produces an envelope that is going on the left of the geometry
@@ -1114,64 +491,28 @@ class GerberObject(FlatCAMObj, Gerber):
return 'fail'
return geom
def area_subtraction(self, geo, subtractor_geo=None):
def follow_geo(self, outname=None):
"""
Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
Creates a geometry object "following" the gerber paths.
:param geo: target geometry from which to subtract
:param subtractor_geo: geometry that acts as subtractor
:return:
:return: None
"""
new_geometry = []
target_geo = geo
if subtractor_geo:
sub_union = cascaded_union(subtractor_geo)
if outname is None:
follow_name = self.options["name"] + "_follow"
else:
name = self.ui.obj_combo.currentText()
subtractor_obj = self.app.collection.get_by_name(name)
sub_union = cascaded_union(subtractor_obj.solid_geometry)
follow_name = outname
def follow_init(follow_obj, app):
# Propagate options
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
follow_obj.solid_geometry = self.follow_geometry
# TODO: Do something if this is None. Offer changing name?
try:
for geo_elem in target_geo:
if isinstance(geo_elem, Polygon):
for ring in self.poly2rings(geo_elem):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiPolygon):
for poly in geo_elem:
for ring in self.poly2rings(poly):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, LineString):
new_geo = geo_elem.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiLineString):
for line_elem in geo_elem:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
except TypeError:
if isinstance(target_geo, Polygon):
for ring in self.poly2rings(target_geo):
new_geo = ring.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, LineString):
new_geo = target_geo.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, MultiLineString):
for line_elem in target_geo:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
return new_geometry
self.app.app_obj.new_object("geometry", follow_name, follow_init)
except Exception as e:
return "Operation failed: %s" % str(e)
def on_plot_cb_click(self, *args):
if self.muted_ui:

View File

@@ -12,15 +12,15 @@
import inspect # TODO: For debugging only.
from flatcamGUI.ObjectUI import *
from AppGUI.ObjectUI import *
from FlatCAMCommon import LoudDict
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
from Common import LoudDict
from AppGUI.PlotCanvasLegacy import ShapeCollectionLegacy
import sys
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -43,7 +43,7 @@ class ValidationError(Exception):
class FlatCAMObj(QtCore.QObject):
"""
Base type of objects handled in FlatCAM. These become interactive
in the GUI, can be plotted, and their options can be modified
in the AppGUI, can be plotted, and their options can be modified
by the user in their respective forms.
"""
@@ -186,11 +186,9 @@ class FlatCAMObj(QtCore.QObject):
def build_ui(self):
"""
Sets up the UI/form for this object. Show the UI
in the App.
Sets up the UI/form for this object. Show the UI in the App.
:return: None
:rtype: None
"""
self.muted_ui = True
@@ -247,14 +245,14 @@ class FlatCAMObj(QtCore.QObject):
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.object_changed.emit(self)
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
def on_scale_button_click(self):
self.read_form()
try:
factor = float(eval(self.ui.scale_entry.get_value()))
factor = float(self.ui.scale_entry.get_value())
except Exception as e:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed."))
log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e))
@@ -278,7 +276,7 @@ class FlatCAMObj(QtCore.QObject):
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.object_changed.emit(self)
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
@@ -294,7 +292,7 @@ class FlatCAMObj(QtCore.QObject):
self.app.proc_container.update_view_text('')
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.object_changed.emit(self)
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': worker_task, 'params': []})
@@ -308,8 +306,8 @@ class FlatCAMObj(QtCore.QObject):
for option in self.options:
try:
self.set_form_item(option)
except Exception:
self.app.log.warning("Unexpected error:", sys.exc_info())
except Exception as err:
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()), str(err))
def read_form(self):
"""
@@ -323,7 +321,7 @@ class FlatCAMObj(QtCore.QObject):
try:
self.read_form_item(option)
except Exception:
self.app.log.warning("Unexpected error:", sys.exc_info())
self.app.log.warning("Unexpected error: %s" % str(sys.exc_info()))
def set_form_item(self, option):
"""
@@ -374,7 +372,7 @@ class FlatCAMObj(QtCore.QObject):
def plot_task():
with self.app.proc_container.new('%s...' % _("Plotting")):
self.plot()
self.app.object_changed.emit(self)
self.app.app_obj.object_changed.emit(self)
self.app.worker_task.emit({'fcn': plot_task, 'params': []})
@@ -461,8 +459,13 @@ class FlatCAMObj(QtCore.QObject):
def visible(self, value, threaded=True):
log.debug("FlatCAMObj.visible()")
def worker_task(app_obj):
self.shapes.visible = value
# self.shapes.visible = value # maybe this is slower in VisPy? use enabled property?
if self.shapes.visible is True:
if value is False:
self.shapes.visible = False
else:
if value is True:
self.shapes.visible = True
if self.app.is_legacy is False:
# Not all object types has annotations
@@ -471,11 +474,6 @@ class FlatCAMObj(QtCore.QObject):
except Exception:
pass
if threaded is False:
worker_task(app_obj=self.app)
else:
self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
@property
def drawing_tolerance(self):
self.units = self.app.defaults['units'].upper()

View File

@@ -10,16 +10,16 @@
# File modified by: Marius Stanciu #
# ##########################################################
from flatcamEditors.FlatCAMTextEditor import TextEditor
from flatcamObjects.FlatCAMObj import *
from flatcamGUI.ObjectUI import *
from AppEditors.FlatCAMTextEditor import TextEditor
from AppObjects.FlatCAMObj import *
from AppGUI.ObjectUI import *
import tkinter as tk
import sys
from copy import deepcopy
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -183,7 +183,13 @@ class ScriptObject(FlatCAMObj):
if self.app.ui.shell_dock.isHidden():
self.app.ui.shell_dock.show()
self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText())
self.app.shell.open_processing() # Disables input box.
# make sure that the pixmaps are not updated when running this as they will crash
# TODO find why the pixmaps load crash when run from this object (perhaps another thread?)
self.app.ui.fcinfo.lock_pmaps = True
self.script_code = self.script_editor_tab.code_editor.toPlainText()
old_line = ''
for tcl_command_line in self.script_code.splitlines():
@@ -202,8 +208,6 @@ class ScriptObject(FlatCAMObj):
# execute the actual Tcl command
try:
self.app.shell.open_processing() # Disables input box.
result = self.app.shell.tcl.eval(str(new_command))
if result != 'None':
self.app.shell.append_output(result + '\n')
@@ -220,6 +224,7 @@ class ScriptObject(FlatCAMObj):
log.error("Exec command Exception: %s\n" % result)
self.app.shell.append_error('ERROR: %s\n '% result)
self.app.ui.fcinfo.lock_pmaps = False
self.app.shell.close_processing()
def on_autocomplete_changed(self, state):
@@ -228,6 +233,27 @@ class ScriptObject(FlatCAMObj):
else:
self.script_editor_tab.code_editor.completer_enable = False
def mirror(self, axis, point):
pass
def offset(self, vect):
pass
def rotate(self, angle, point):
pass
def scale(self, xfactor, yfactor=None, point=None):
pass
def skew(self, angle_x, angle_y, point):
pass
def buffer(self, distance, join, factor=None):
pass
def bounds(self, flatten=False):
return None, None, None, None
def to_dict(self):
"""
Returns a representation of the object as a dictionary.

View File

@@ -16,13 +16,13 @@ from PyQt5.QtCore import Qt, QSettings
from PyQt5.QtGui import QColor
# from PyQt5.QtCore import QModelIndex
from flatcamObjects.FlatCAMObj import FlatCAMObj
from flatcamObjects.FlatCAMCNCJob import CNCJobObject
from flatcamObjects.FlatCAMDocument import DocumentObject
from flatcamObjects.FlatCAMExcellon import ExcellonObject
from flatcamObjects.FlatCAMGeometry import GeometryObject
from flatcamObjects.FlatCAMGerber import GerberObject
from flatcamObjects.FlatCAMScript import ScriptObject
from AppObjects.FlatCAMObj import FlatCAMObj
from AppObjects.FlatCAMCNCJob import CNCJobObject
from AppObjects.FlatCAMDocument import DocumentObject
from AppObjects.FlatCAMExcellon import ExcellonObject
from AppObjects.FlatCAMGeometry import GeometryObject
from AppObjects.FlatCAMGerber import GerberObject
from AppObjects.FlatCAMScript import ScriptObject
import inspect # TODO: Remove
@@ -32,7 +32,7 @@ from copy import deepcopy
from numpy import Inf
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
@@ -50,12 +50,13 @@ class KeySensitiveListView(QtWidgets.QTreeView):
def __init__(self, app, parent=None):
super(KeySensitiveListView, self).__init__(parent)
self.setHeaderHidden(True)
self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked)
# self.setEditTriggers(QtWidgets.QTreeView.SelectedClicked) # allow Edit on Tree
self.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
# self.setRootIsDecorated(False)
# self.setExpandsOnDoubleClick(False)
# Enable dragging and dropping onto the GUI
# Enable dragging and dropping onto the AppGUI
self.setAcceptDrops(True)
self.filename = ""
self.app = app
@@ -334,6 +335,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.click_modifier = None
self.update_list_signal.connect(self.on_update_list_signal)
self.view.activated.connect(self.on_row_activated)
self.item_selected.connect(self.on_row_selected)
def promise(self, obj_name):
log.debug("Object %s has been promised." % obj_name)
@@ -382,8 +385,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if type(obj) != GeometryObject:
self.app.ui.menuprojectgeneratecnc.setVisible(False)
if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject:
self.app.ui.menuprojectedit.setVisible(False)
# if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject or \
# type(obj) != CNCJobObject:
# self.app.ui.menuprojectedit.setVisible(False)
if type(obj) != GerberObject and type(obj) != ExcellonObject and type(obj) != CNCJobObject:
self.app.ui.menuprojectviewsource.setVisible(False)
if type(obj) != GerberObject and type(obj) != GeometryObject and type(obj) != ExcellonObject and \
@@ -550,7 +554,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
while name in self.get_names():
# ## Create a new name
# Ends with number?
log.debug("new_object(): Object name (%s) exists, changing." % name)
log.debug("app_obj.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 ''
@@ -985,7 +989,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
def get_list(self):
"""
Will return a list of all objects currently opened.
Will return a list of all objects currently opened. Except FlatCAMScript and FlatCAMDocuments
:return:
"""
@@ -998,3 +1002,199 @@ class ObjectCollection(QtCore.QAbstractItemModel):
def update_view(self):
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
def on_row_activated(self, index):
if index.isValid():
if index.internalPointer().parent_item != self.root_item:
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
self.on_item_activated(index)
def on_row_selected(self, obj_name):
"""
This is a special string; when received it will make all Menu -> Objects entries unchecked
It mean we clicked outside of the items and deselected all
:param obj_name:
:return:
"""
if obj_name == 'none':
for act in self.app.ui.menuobjects.actions():
act.setChecked(False)
return
# get the name of the selected objects and add them to a list
name_list = []
for obj in self.get_selected():
name_list.append(obj.options['name'])
# set all actions as unchecked but the ones selected make them checked
for act in self.app.ui.menuobjects.actions():
act.setChecked(False)
if act.text() in name_list:
act.setChecked(True)
def on_collection_updated(self, obj, state, old_name):
"""
Create a menu from the object loaded in the collection.
:param obj: object that was changed (added, deleted, renamed)
:param state: what was done with the object. Can be: added, deleted, delete_all, renamed
:param old_name: the old name of the object before the action that triggered this slot happened
:return: None
"""
icon_files = {
"gerber": self.app.resource_location + "/flatcam_icon16.png",
"excellon": self.app.resource_location + "/drill16.png",
"cncjob": self.app.resource_location + "/cnc16.png",
"geometry": self.app.resource_location + "/geometry16.png",
"script": self.app.resource_location + "/script_new16.png",
"document": self.app.resource_location + "/notes16_1.png"
}
if state == 'append':
for act in self.app.ui.menuobjects.actions():
try:
act.triggered.disconnect()
except TypeError:
pass
self.app.ui.menuobjects.clear()
gerber_list = []
exc_list = []
cncjob_list = []
geo_list = []
script_list = []
doc_list = []
for name in self.get_names():
obj_named = self.get_by_name(name)
if obj_named.kind == 'gerber':
gerber_list.append(name)
elif obj_named.kind == 'excellon':
exc_list.append(name)
elif obj_named.kind == 'cncjob':
cncjob_list.append(name)
elif obj_named.kind == 'geometry':
geo_list.append(name)
elif obj_named.kind == 'script':
script_list.append(name)
elif obj_named.kind == 'document':
doc_list.append(name)
def add_act(o_name):
obj_for_icon = self.get_by_name(o_name)
add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
add_action.setCheckable(True)
add_action.setText(o_name)
add_action.setIcon(QtGui.QIcon(icon_files[obj_for_icon.kind]))
add_action.triggered.connect(
lambda: self.set_active(o_name) if add_action.isChecked() is True else
self.set_inactive(o_name))
self.app.ui.menuobjects.addAction(add_action)
for name in gerber_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
for name in exc_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
for name in cncjob_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
for name in geo_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
for name in script_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
for name in doc_list:
add_act(name)
self.app.ui.menuobjects.addSeparator()
self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
QtGui.QIcon(self.app.resource_location + '/select_all.png'),
_('Select All')
)
self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
_('Deselect All')
)
self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
elif state == 'delete':
for act in self.app.ui.menuobjects.actions():
if act.text() == obj.options['name']:
try:
act.triggered.disconnect()
except TypeError:
pass
self.app.ui.menuobjects.removeAction(act)
break
elif state == 'rename':
for act in self.app.ui.menuobjects.actions():
if act.text() == old_name:
add_action = QtWidgets.QAction(parent=self.app.ui.menuobjects)
add_action.setText(obj.options['name'])
add_action.setIcon(QtGui.QIcon(icon_files[obj.kind]))
add_action.triggered.connect(
lambda: self.set_active(obj.options['name']) if add_action.isChecked() is True else
self.set_inactive(obj.options['name']))
self.app.ui.menuobjects.insertAction(act, add_action)
try:
act.triggered.disconnect()
except TypeError:
pass
self.app.ui.menuobjects.removeAction(act)
break
elif state == 'delete_all':
for act in self.app.ui.menuobjects.actions():
try:
act.triggered.disconnect()
except TypeError:
pass
self.app.ui.menuobjects.clear()
self.app.ui.menuobjects.addSeparator()
self.app.ui.menuobjects_selall = self.app.ui.menuobjects.addAction(
QtGui.QIcon(self.app.resource_location + '/select_all.png'),
_('Select All')
)
self.app.ui.menuobjects_unselall = self.app.ui.menuobjects.addAction(
QtGui.QIcon(self.app.resource_location + '/deselect_all32.png'),
_('Deselect All')
)
self.app.ui.menuobjects_selall.triggered.connect(lambda: self.on_objects_selection(True))
self.app.ui.menuobjects_unselall.triggered.connect(lambda: self.on_objects_selection(False))
def on_objects_selection(self, on_off):
obj_list = self.get_names()
if on_off is True:
self.set_all_active()
for act in self.app.ui.menuobjects.actions():
try:
act.setChecked(True)
except Exception:
pass
if obj_list:
self.app.inform[str, bool].emit('[selected] %s' % _("All objects are selected."), False)
else:
self.set_all_inactive()
for act in self.app.ui.menuobjects.actions():
try:
act.setChecked(False)
except Exception:
pass
if obj_list:
self.app.inform[str, bool].emit('%s' % _("Objects selection is cleared."), False)
else:
self.app.inform[str, bool].emit('', False)

View File

@@ -12,8 +12,8 @@ import logging
log = logging.getLogger('base2')
from flatcamParsers.ParseFont import *
from flatcamParsers.ParseDXF_Spline import *
from AppParsers.ParseFont import *
from AppParsers.ParseDXF_Spline import *
def distance(pt1, pt2):

View File

@@ -6,7 +6,7 @@
# MIT Licence #
# ########################################################## ##
from camlib import Geometry
from camlib import Geometry, grace
import shapely.affinity as affinity
from shapely.geometry import Point, LineString
@@ -17,8 +17,7 @@ import logging
import traceback
from copy import deepcopy
import FlatCAMTranslation as fcTranslate
from FlatCAMCommon import GracefulException as grace
# import AppTranslation as fcTranslate
import gettext
import builtins
@@ -967,7 +966,7 @@ class Excellon(Geometry):
:return: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry()")
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry()")
self.solid_geometry = []
try:
# clear the solid_geometry in self.tools
@@ -982,7 +981,7 @@ class Excellon(Geometry):
_("Excellon.create_geometry() -> a drill location was skipped "
"due of not having a tool associated.\n"
"Check the resulting GCode."))
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
"due of not having a tool associated")
continue
tooldia = self.tools[drill['tool']]['C']
@@ -1006,7 +1005,7 @@ class Excellon(Geometry):
self.tools[tool_in_slots]['solid_geometry'].append(poly)
self.tools[tool_in_slots]['data'] = deepcopy(self.default_data)
except Exception as e:
log.debug("flatcamParsers.ParseExcellon.Excellon.create_geometry() -> "
log.debug("AppParsers.ParseExcellon.Excellon.create_geometry() -> "
"Excellon geometry creation failed due of ERROR: %s" % str(e))
return "fail"
@@ -1018,10 +1017,10 @@ class Excellon(Geometry):
:param flatten: No used
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()")
log.debug("AppParsers.ParseExcellon.Excellon.bounds()")
if self.solid_geometry is None or not self.tools:
log.debug("flatcamParsers.ParseExcellon.Excellon -> solid_geometry is None")
log.debug("AppParsers.ParseExcellon.Excellon -> solid_geometry is None")
return 0, 0, 0, 0
def bounds_rec(obj):
@@ -1069,7 +1068,7 @@ class Excellon(Geometry):
This function first convert to the the units found in the Excellon file but it converts tools that
are not there yet so it has no effect other than it signal that the units are the ones in the file.
On object creation, in new_object(), true conversion is done because this is done at the end of the
On object creation, in app_obj.new_object(), true conversion is done because this is done at the end of the
Excellon file parsing, the tools are inside and self.tools is really converted from the units found
inside the file to the FlatCAM units.
@@ -1092,7 +1091,7 @@ class Excellon(Geometry):
else:
log.error("Unsupported units: %s" % str(obj_units))
factor = 1.0
log.debug("flatcamParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
log.debug("AppParsers.ParseExcellon.Excellon.convert_units() --> Factor: %s" % str(factor))
self.units = obj_units
self.scale(factor, factor)
@@ -1118,7 +1117,7 @@ class Excellon(Geometry):
:return: None
:rtype: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.scale()")
log.debug("AppParsers.ParseExcellon.Excellon.scale()")
if yfactor is None:
yfactor = xfactor
@@ -1183,7 +1182,7 @@ class Excellon(Geometry):
:type vect: tuple
:return: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.offset()")
log.debug("AppParsers.ParseExcellon.Excellon.offset()")
dx, dy = vect
@@ -1243,7 +1242,7 @@ class Excellon(Geometry):
:type point: list
:return: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()")
log.debug("AppParsers.ParseExcellon.Excellon.mirror()")
px, py = point
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
@@ -1309,7 +1308,7 @@ class Excellon(Geometry):
See shapely manual for more information:
http://toblerity.org/shapely/manual.html#affine-transformations
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.skew()")
log.debug("AppParsers.ParseExcellon.Excellon.skew()")
if angle_x is None:
angle_x = 0.0
@@ -1396,7 +1395,7 @@ class Excellon(Geometry):
:param point: tuple of coordinates (x, y)
:return: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()")
log.debug("AppParsers.ParseExcellon.Excellon.rotate()")
if angle == 0:
return
@@ -1479,7 +1478,7 @@ class Excellon(Geometry):
:param join: The type of line joint used by the shapely buffer method: round, square, bevel
:return: None
"""
log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
log.debug("AppParsers.ParseExcellon.Excellon.buffer()")
if distance == 0:
return

View File

@@ -22,7 +22,7 @@ from fontTools import ttLib
import logging
import gettext
import FlatCAMTranslation as fcTranslate
import AppTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')

View File

@@ -1,5 +1,5 @@
from PyQt5 import QtWidgets
from camlib import Geometry, arc, arc_angle, ApertureMacro
from camlib import Geometry, arc, arc_angle, ApertureMacro, grace
import numpy as np
import re
@@ -14,9 +14,8 @@ import shapely.affinity as affinity
from shapely.geometry import box as shply_box, Polygon, LineString, Point, MultiPolygon
from lxml import etree as ET
from flatcamParsers.ParseSVG import svgparselength, getsvggeo
from FlatCAMCommon import GracefulException as grace
import FlatCAMTranslation as fcTranslate
from AppParsers.ParseSVG import svgparselength, getsvggeo
import AppTranslation as fcTranslate
import gettext
import builtins
@@ -1477,7 +1476,7 @@ class Gerber(Geometry):
sol_geo_length = 1
try:
if buff_length == 0 and sol_geo_length in [0, 1]:
if buff_length == 0 and sol_geo_length in [0, 1] and self.solid_geometry.area == 0:
log.error("Object is not Gerber file or empty. Aborting Object creation.")
return 'fail'
except TypeError as e:
@@ -1763,7 +1762,7 @@ class Gerber(Geometry):
:return: None
"""
log.debug("flatcamParsers.ParseGerber.Gerber.import_svg()")
log.debug("AppParsers.ParseGerber.Gerber.import_svg()")
# Parse into list of shapely objects
svg_tree = ET.parse(filename)
@@ -2389,7 +2388,7 @@ class Gerber(Geometry):
geo_p = shply_box(minx, miny, maxx, maxy)
new_geo_el['solid'] = geo_p
else:
log.debug("flatcamParsers.ParseGerber.Gerber.buffer() --> "
log.debug("AppParsers.ParseGerber.Gerber.buffer() --> "
"ap type not supported")
else:
new_geo_el['solid'] = geo_el['follow'].buffer(

View File

@@ -6,7 +6,7 @@
# MIT Licence #
# ############################################################
from camlib import arc, three_point_circle
from camlib import arc, three_point_circle, grace
import numpy as np
import re
@@ -18,8 +18,7 @@ import sys
from shapely.ops import unary_union
from shapely.geometry import LineString, Point
from FlatCAMCommon import GracefulException as grace
import FlatCAMTranslation as fcTranslate
# import AppTranslation as fcTranslate
import gettext
import builtins

View File

@@ -5,50 +5,27 @@
# MIT Licence #
# ##########################################################
from PyQt5 import QtWidgets, QtCore
from PyQt5 import QtCore
from FlatCAMTool import FlatCAMTool
from FlatCAMCommon import GracefulException as grace
from Common import GracefulException as grace
from shapely.geometry import Point, Polygon, LineString, MultiPolygon
from shapely.ops import unary_union
from shapely.geometry import Polygon, LineString, MultiPolygon
from copy import copy, deepcopy
import numpy as np
import zlib
import re
import time
import logging
import traceback
import gettext
import FlatCAMTranslation as fcTranslate
import builtins
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
log = logging.getLogger('base')
class ToolPDF(FlatCAMTool):
"""
Parse a PDF file.
Reference here: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/pdf_reference_archives/PDFReference.pdf
Return a list of geometries
"""
toolName = _("PDF Import Tool")
class PdfParser(QtCore.QObject):
def __init__(self, app):
FlatCAMTool.__init__(self, app)
super().__init__()
self.app = app
self.decimals = self.app.decimals
self.step_per_circles = self.app.defaults["gerber_circle_steps"]
self.stream_re = re.compile(b'.*?FlateDecode.*?stream(.*?)endstream', re.S)
# detect stroke color change; it means a new object to be created
self.stroke_color_re = re.compile(r'^\s*(\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)\s*RG$')
@@ -112,73 +89,10 @@ class ToolPDF(FlatCAMTool):
self.gs['transform'] = []
self.gs['line_width'] = [] # each element is a float
self.pdf_decompressed = {}
# key = file name and extension
# value is a dict to store the parsed content of the PDF
self.pdf_parsed = {}
# QTimer for periodic check
self.check_thread = QtCore.QTimer()
# Every time a parser is started we add a promise; every time a parser finished we remove a promise
# when empty we start the layer rendering
self.parsing_promises = []
# conversion factor to INCH
self.point_to_unit_factor = 0.01388888888
def run(self, toggle=True):
self.app.defaults.report_usage("ToolPDF()")
self.set_tool_ui()
self.on_open_pdf_click()
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='Ctrl+Q', **kwargs)
def set_tool_ui(self):
pass
def on_open_pdf_click(self):
"""
File menu callback for opening an PDF file.
:return: None
"""
self.app.defaults.report_usage("ToolPDF.on_open_pdf_click()")
self.app.log.debug("ToolPDF.on_open_pdf_click()")
_filter_ = "Adobe PDF Files (*.pdf);;" \
"All Files (*.*)"
try:
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"),
directory=self.app.get_last_folder(),
filter=_filter_)
except TypeError:
filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open PDF"), filter=_filter_)
if len(filenames) == 0:
self.app.inform.emit('[WARNING_NOTCL] %s.' % _("Open PDF cancelled"))
else:
# start the parsing timer with a period of 1 second
self.periodic_check(1000)
for filename in filenames:
if filename != '':
self.app.worker_task.emit({'fcn': self.open_pdf,
'params': [filename]})
def open_pdf(self, filename):
short_name = filename.split('/')[-1].split('\\')[-1]
self.parsing_promises.append(short_name)
self.pdf_parsed[short_name] = {}
self.pdf_parsed[short_name]['pdf'] = {}
self.pdf_parsed[short_name]['filename'] = filename
self.pdf_decompressed[short_name] = ''
def parse_pdf(self, pdf_content):
# the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
if self.app.defaults['units'].upper() == 'MM':
@@ -188,252 +102,6 @@ class ToolPDF(FlatCAMTool):
# 1 inch = 72 points => 1 point = 1 / 72 = 0.01388888888 inch
self.point_to_unit_factor = 1 / 72
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
with self.app.proc_container.new(_("Parsing PDF file ...")):
with open(filename, "rb") as f:
pdf = f.read()
stream_nr = 0
for s in re.findall(self.stream_re, pdf):
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
stream_nr += 1
log.debug(" PDF STREAM: %d\n" % stream_nr)
s = s.strip(b'\r\n')
try:
self.pdf_decompressed[short_name] += (zlib.decompress(s).decode('UTF-8') + '\r\n')
except Exception as e:
log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
self.pdf_parsed[short_name]['pdf'] = self.parse_pdf(pdf_content=self.pdf_decompressed[short_name])
# we used it, now we delete it
self.pdf_decompressed[short_name] = ''
# removal from list is done in a multithreaded way therefore not always the removal can be done
# try to remove until it's done
try:
while True:
self.parsing_promises.remove(short_name)
time.sleep(0.1)
except Exception as e:
log.debug("ToolPDF.open_pdf() --> %s" % str(e))
self.app.inform.emit('[success] %s: %s' % (_("Opened"), str(filename)))
def layer_rendering_as_excellon(self, filename, ap_dict, layer_nr):
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
# store the points here until reconstitution:
# keys are diameters and values are list of (x,y) coords
points = {}
def obj_init(exc_obj, app_obj):
clear_geo = [geo_el['clear'] for geo_el in ap_dict['0']['geometry']]
for geo in clear_geo:
xmin, ymin, xmax, ymax = geo.bounds
center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
# for drill bits, even in INCH, it's enough 3 decimals
correction_factor = 0.974
dia = (xmax - xmin) * correction_factor
dia = round(dia, 3)
if dia in points:
points[dia].append(center)
else:
points[dia] = [center]
sorted_dia = sorted(points.keys())
name_tool = 0
for dia in sorted_dia:
name_tool += 1
# create tools dictionary
spec = {"C": dia, 'solid_geometry': []}
exc_obj.tools[str(name_tool)] = spec
# create drill list of dictionaries
for dia_points in points:
if dia == dia_points:
for pt in points[dia_points]:
exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
break
ret = exc_obj.create_geometry()
if ret == 'fail':
log.debug("Could not create geometry for Excellon object.")
return "fail"
for tool in exc_obj.tools:
if exc_obj.tools[tool]['solid_geometry']:
return
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
(_("No geometry found in file"), outname))
return "fail"
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
ret_val = self.app.new_object("excellon", outname, obj_init, autoselected=False)
if ret_val == 'fail':
self.app.inform.emit('[ERROR_NOTCL] %s' %
_('Open PDF file failed.'))
return
# Register recent file
self.app.file_opened.emit("excellon", filename)
# GUI feedback
self.app.inform.emit('[success] %s: %s' %
(_("Rendered"), outname))
def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr):
outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr)
def obj_init(grb_obj):
grb_obj.apertures = ap_dict
poly_buff = []
follow_buf = []
for ap in grb_obj.apertures:
for k in grb_obj.apertures[ap]:
if k == 'geometry':
for geo_el in ap_dict[ap][k]:
if 'solid' in geo_el:
poly_buff.append(geo_el['solid'])
if 'follow' in geo_el:
follow_buf.append(geo_el['follow'])
poly_buff = unary_union(poly_buff)
if '0' in grb_obj.apertures:
global_clear_geo = []
if 'geometry' in grb_obj.apertures['0']:
for geo_el in ap_dict['0']['geometry']:
if 'clear' in geo_el:
global_clear_geo.append(geo_el['clear'])
if global_clear_geo:
solid = []
for apid in grb_obj.apertures:
if 'geometry' in grb_obj.apertures[apid]:
for elem in grb_obj.apertures[apid]['geometry']:
if 'solid' in elem:
solid_geo = deepcopy(elem['solid'])
for clear_geo in global_clear_geo:
# Make sure that the clear_geo is within the solid_geo otherwise we loose
# the solid_geometry. We want for clear_geometry just to cut into solid_geometry
# not to delete it
if clear_geo.within(solid_geo):
solid_geo = solid_geo.difference(clear_geo)
if solid_geo.is_empty:
solid_geo = elem['solid']
try:
for poly in solid_geo:
solid.append(poly)
except TypeError:
solid.append(solid_geo)
poly_buff = deepcopy(MultiPolygon(solid))
follow_buf = unary_union(follow_buf)
try:
poly_buff = poly_buff.buffer(0.0000001)
except ValueError:
pass
try:
poly_buff = poly_buff.buffer(-0.0000001)
except ValueError:
pass
grb_obj.solid_geometry = deepcopy(poly_buff)
grb_obj.follow_geometry = deepcopy(follow_buf)
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % int(layer_nr)):
ret = self.app.new_object('gerber', outname, obj_init, autoselected=False)
if ret == 'fail':
self.app.inform.emit('[ERROR_NOTCL] %s' %
_('Open PDF file failed.'))
return
# Register recent file
self.app.file_opened.emit('gerber', filename)
# GUI feedback
self.app.inform.emit('[success] %s: %s' % (_("Rendered"), outname))
def periodic_check(self, check_period):
"""
This function starts an QTimer and it will periodically check if parsing was done
:param check_period: time at which to check periodically if all plots finished to be plotted
:return:
"""
# self.plot_thread = threading.Thread(target=lambda: self.check_plot_finished(check_period))
# self.plot_thread.start()
log.debug("ToolPDF --> Periodic Check started.")
try:
self.check_thread.stop()
except TypeError:
pass
self.check_thread.setInterval(check_period)
try:
self.check_thread.timeout.disconnect(self.periodic_check_handler)
except (TypeError, AttributeError):
pass
self.check_thread.timeout.connect(self.periodic_check_handler)
self.check_thread.start(QtCore.QThread.HighPriority)
def periodic_check_handler(self):
"""
If the parsing worker finished then start multithreaded rendering
:return:
"""
# log.debug("checking parsing --> %s" % str(self.parsing_promises))
try:
if not self.parsing_promises:
self.check_thread.stop()
# parsing finished start the layer rendering
if self.pdf_parsed:
obj_to_delete = []
for object_name in self.pdf_parsed:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
filename = deepcopy(self.pdf_parsed[object_name]['filename'])
pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf'])
obj_to_delete.append(object_name)
for k in pdf_content:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
ap_dict = pdf_content[k]
if ap_dict:
layer_nr = k
if k == 0:
self.app.worker_task.emit({'fcn': self.layer_rendering_as_excellon,
'params': [filename, ap_dict, layer_nr]})
else:
self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber,
'params': [filename, ap_dict, layer_nr]})
# delete the object already processed so it will not be processed again for other objects
# that were opened at the same time; like in drag & drop on GUI
for obj_name in obj_to_delete:
if obj_name in self.pdf_parsed:
self.pdf_parsed.pop(obj_name)
log.debug("ToolPDF --> Periodic check finished.")
except Exception:
traceback.print_exc()
def parse_pdf(self, pdf_content):
path = {}
path['lines'] = [] # it's a list of lines subpaths
path['bezier'] = [] # it's a list of bezier arcs subpaths
@@ -503,7 +171,7 @@ class ToolPDF(FlatCAMTool):
if match:
color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
log.debug(
"ToolPDF.parse_pdf() --> STROKE Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
"parse_pdf() --> STROKE Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
(line_nr, color[0], color[1], color[2]))
if color[0] == old_color[0] and color[1] == old_color[1] and color[2] == old_color[2]:
@@ -526,7 +194,7 @@ class ToolPDF(FlatCAMTool):
if match:
fill_color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
log.debug(
"ToolPDF.parse_pdf() --> FILL Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
"parse_pdf() --> FILL Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
(line_nr, fill_color[0], fill_color[1], fill_color[2]))
# if the color is white we are seeing 'clear_geometry' that can't be seen. It may be that those
# geometries are actually holes from which we can make an Excellon file
@@ -545,7 +213,7 @@ class ToolPDF(FlatCAMTool):
# sometimes they combine save_to_graphics_stack with the transformation on the same line
if match.group(1) == 'q':
log.debug(
"ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
"parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
@@ -555,7 +223,7 @@ class ToolPDF(FlatCAMTool):
if (float(match.group(3)) == 0 and float(match.group(4)) == 0) and \
(float(match.group(6)) != 0 or float(match.group(7)) != 0):
log.debug(
"ToolPDF.parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
"parse_pdf() --> OFFSET transformation found on line: %s --> %s" % (line_nr, pline))
offset_geo[0] += float(match.group(6))
offset_geo[1] += float(match.group(7))
@@ -564,7 +232,7 @@ class ToolPDF(FlatCAMTool):
# transformation = SCALING
if float(match.group(2)) != 1 and float(match.group(5)) != 1:
log.debug(
"ToolPDF.parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
"parse_pdf() --> SCALE transformation found on line: %s --> %s" % (line_nr, pline))
scale_geo[0] *= float(match.group(2))
scale_geo[1] *= float(match.group(5))
@@ -576,7 +244,7 @@ class ToolPDF(FlatCAMTool):
match = self.save_gs_re.search(pline)
if match:
log.debug(
"ToolPDF.parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
"parse_pdf() --> Save to GS found on line: %s --> offset=[%f, %f] ||| scale=[%f, %f]" %
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
self.gs['transform'].append(deepcopy([offset_geo, scale_geo]))
self.gs['line_width'].append(deepcopy(size))
@@ -590,18 +258,18 @@ class ToolPDF(FlatCAMTool):
scale_geo = restored_transform[1]
except IndexError:
# nothing to remove
log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
log.debug("parse_pdf() --> Nothing to restore")
pass
try:
size = self.gs['line_width'].pop(-1)
except IndexError:
log.debug("ToolPDF.parse_pdf() --> Nothing to restore")
log.debug("parse_pdf() --> Nothing to restore")
# nothing to remove
pass
log.debug(
"ToolPDF.parse_pdf() --> Restore from GS found on line: %s --> "
"parse_pdf() --> Restore from GS found on line: %s --> "
"restored_offset=[%f, %f] ||| restored_scale=[%f, %f]" %
(line_nr, offset_geo[0], offset_geo[1], scale_geo[0], scale_geo[1]))
# log.debug("Restored Offset= [%f, %f]" % (offset_geo[0], offset_geo[1]))
@@ -712,9 +380,9 @@ class ToolPDF(FlatCAMTool):
width = (float(match.group(3)) + offset_geo[0]) * self.point_to_unit_factor * scale_geo[0]
height = (float(match.group(4)) + offset_geo[1]) * self.point_to_unit_factor * scale_geo[1]
pt1 = (x, y)
pt2 = (x+width, y)
pt3 = (x+width, y+height)
pt4 = (x, y+height)
pt2 = (x + width, y)
pt3 = (x + width, y + height)
pt4 = (x, y + height)
subpath['rectangle'] += [pt1, pt2, pt3, pt4, pt1]
current_point = pt1
continue
@@ -948,7 +616,8 @@ class ToolPDF(FlatCAMTool):
geo += self.bezier_to_points(start=b[0], c1=b[1], c2=b[2], stop=b[3])
# close the subpath if it was not closed already
if close_subpath is False:
geo.append(geo[0])
new_g = geo[0]
geo.append(new_g)
try:
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el)

View File

@@ -27,7 +27,7 @@ from shapely.geometry import LineString, LinearRing, MultiLineString
from shapely.affinity import skew, affine_transform, rotate
import numpy as np
from flatcamParsers.ParseFont import *
from AppParsers.ParseFont import *
log = logging.getLogger('base2')

Some files were not shown because too many files have changed in this diff Show More