Merge branch 'Beta' into preferences-refactoring

This commit is contained in:
David Robertson 2020-05-09 15:07:30 +01:00
commit 4fc98482de
16 changed files with 455 additions and 251 deletions

View File

@ -7,6 +7,24 @@ CHANGELOG for FlatCAM beta
=================================================
9.05.2020
- modified the GUI for Exclusion areas; now the shapes are displayed in a Table where they can be selected and deleted. Modification applied for Geometry Objects only (for now).
- fixed and error when converting units, error that acted when in those fields that accept lists of tools only one tool was added
8.05.2020
- added a parameter to the FlatCAMDefaults class, whenever a value in the self.defaults dict change it will call a callback function and send to it the modified key
- optimized and fixed some issues in the self.on_toggle_units() method
- the Exclusion areas will have all the orange color but the color of the outline will differ according to the type of the object from where it was added (cosmetic use only as the Exclusion areas will be applied globally)
- removed the Apply theme button in the Preferences; it is now replaced by the more general buttons (either Save or Apply)
- added a confirmation/warning message when applying a new theme
7.05.2020
- added a fix so the app close is now clean, with exit code 0 as set
- added the ability to add exclusion areas from the Excellon object too. Now there is a difference in color to differentiate from which type of object the exclusion areas were added but they all serve the same purpose
6.05.2020
- wip in adding Exclusion areas in Geometry object; each Geometry object has now a storage for shapes (exclusion shapes, should I make them more general?)

View File

@ -3599,14 +3599,13 @@ class App(QtCore.QObject):
# try to quit the Socket opened by ArgsThread class
try:
self.new_launch.thread_exit = True
self.new_launch.listener.close()
self.new_launch.stop.emit()
except Exception as err:
log.debug("App.quit_application() --> %s" % str(err))
# try to quit the QThread that run ArgsThread class
try:
self.th.terminate()
self.th.quit()
except Exception as e:
log.debug("App.quit_application() --> %s" % str(e))
@ -3616,7 +3615,6 @@ class App(QtCore.QObject):
# quit app by signalling for self.kill_app() method
# self.close_app_signal.emit()
QtWidgets.qApp.quit()
# QtCore.QCoreApplication.exit()
# When the main event loop is not started yet in which case the qApp.quit() will do nothing
# we use the following command
@ -3628,7 +3626,6 @@ class App(QtCore.QObject):
@staticmethod
def kill_app():
# QtCore.QCoreApplication.quit()
QtWidgets.qApp.quit()
# When the main event loop is not started yet in which case the qApp.quit() will do nothing
# we use the following command
@ -4248,10 +4245,10 @@ class App(QtCore.QObject):
# If option is the same, then ignore
if new_units == self.defaults["units"].upper():
self.log.debug("on_toggle_units(): Same as defaults, so ignoring.")
self.log.debug("on_toggle_units(): Same as previous, ignoring.")
return
# Options to scale
# Keys in self.defaults for which to scale their values
dimensions = ['gerber_isotooldia', 'gerber_noncoppermargin', 'gerber_bboxmargin',
"gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f", "gerber_vtipdia",
"gerber_vcutz", "gerber_editor_newdim", "gerber_editor_ma_low",
@ -4325,149 +4322,62 @@ class App(QtCore.QObject):
def scale_defaults(sfactor):
for dim in dimensions:
if dim == 'gerber_editor_newdim':
if self.defaults["gerber_editor_newdim"] is None or self.defaults["gerber_editor_newdim"] == '':
continue
coordinates = self.defaults["gerber_editor_newdim"].split(",")
coords_xy = [float(eval(a)) for a in coordinates if a != '']
coords_xy[0] *= sfactor
coords_xy[1] *= sfactor
self.defaults['gerber_editor_newdim'] = "%.*f, %.*f" % (self.decimals, coords_xy[0],
self.decimals, coords_xy[1])
if dim == 'excellon_toolchangexy':
if self.defaults["excellon_toolchangexy"] is None or self.defaults["excellon_toolchangexy"] == '':
continue
coordinates = self.defaults["excellon_toolchangexy"].split(",")
coords_xy = [float(eval(a)) for a in coordinates if a != '']
coords_xy[0] *= sfactor
coords_xy[1] *= sfactor
self.defaults['excellon_toolchangexy'] = "%.*f, %.*f" % (self.decimals, coords_xy[0],
self.decimals, coords_xy[1])
elif dim == 'geometry_toolchangexy':
if self.defaults["geometry_toolchangexy"] is None or self.defaults["geometry_toolchangexy"] == '':
continue
coordinates = self.defaults["geometry_toolchangexy"].split(",")
coords_xy = [float(eval(a)) for a in coordinates if a != '']
coords_xy[0] *= sfactor
coords_xy[1] *= sfactor
self.defaults['geometry_toolchangexy'] = "%.*f, %.*f" % (self.decimals, coords_xy[0],
self.decimals, coords_xy[1])
elif dim == 'excellon_endxy':
if self.defaults["excellon_endxy"] is None or self.defaults["excellon_endxy"] == '':
if dim in [
'gerber_editor_newdim', 'excellon_toolchangexy', 'geometry_toolchangexy', 'excellon_endxy',
'geometry_endxy', 'tools_solderpaste_xy_toolchange', 'tools_cal_toolchange_xy',
'tools_transform_mirror_point'
]:
if self.defaults[dim] is None or self.defaults[dim] == '':
continue
coordinates = self.defaults["excellon_endxy"].split(",")
end_coords_xy = [float(eval(a)) for a in coordinates if a != '']
end_coords_xy[0] *= sfactor
end_coords_xy[1] *= sfactor
self.defaults['excellon_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0],
self.decimals, end_coords_xy[1])
elif dim == 'geometry_endxy':
if self.defaults["geometry_endxy"] is None or self.defaults["geometry_endxy"] == '':
continue
coordinates = self.defaults["geometry_endxy"].split(",")
end_coords_xy = [float(eval(a)) for a in coordinates if a != '']
end_coords_xy[0] *= sfactor
end_coords_xy[1] *= sfactor
self.defaults['geometry_endxy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0],
self.decimals, end_coords_xy[1])
try:
coordinates = self.defaults[dim].split(",")
coords_xy = [float(eval(a)) for a in coordinates if a != '']
coords_xy[0] *= sfactor
coords_xy[1] *= sfactor
self.defaults[dim] = "%.*f, %.*f" % (
self.decimals, coords_xy[0], self.decimals, coords_xy[1])
except Exception as e:
log.debug("App.on_toggle_units.scale_defaults() --> 'string tuples': %s" % str(e))
elif dim == 'geometry_cnctooldia':
if self.defaults["geometry_cnctooldia"] is None or self.defaults["geometry_cnctooldia"] == '':
elif dim in [
'geometry_cnctooldia', 'tools_ncctools', 'tools_solderpaste_tools'
]:
if self.defaults[dim] is None or self.defaults[dim] == '':
continue
if type(self.defaults["geometry_cnctooldia"]) is float:
tools_diameters = [self.defaults["geometry_cnctooldia"]]
else:
try:
self.defaults[dim] = float(self.defaults[dim])
tools_diameters = [self.defaults[dim]]
except ValueError:
try:
tools_string = self.defaults["geometry_cnctooldia"].split(",")
tools_string = self.defaults[dim].split(",")
tools_diameters = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
self.defaults['geometry_cnctooldia'] = ''
for t in range(len(tools_diameters)):
tools_diameters[t] *= sfactor
self.defaults['geometry_cnctooldia'] += "%.*f," % (self.decimals, tools_diameters[t])
elif dim == 'tools_ncctools':
if self.defaults["tools_ncctools"] is None or self.defaults["tools_ncctools"] == '':
continue
if type(self.defaults["tools_ncctools"]) == float:
ncctools = [self.defaults["tools_ncctools"]]
self.defaults[dim] = ''
td_len = len(tools_diameters)
if td_len > 1:
for t in range(td_len):
tools_diameters[t] *= sfactor
self.defaults[dim] += "%.*f," % (self.decimals, tools_diameters[t])
else:
try:
tools_string = self.defaults["tools_ncctools"].split(",")
ncctools = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
tools_diameters[0] *= sfactor
self.defaults[dim] += "%.*f" % (self.decimals, tools_diameters[0])
self.defaults['tools_ncctools'] = ''
for t in range(len(ncctools)):
ncctools[t] *= sfactor
self.defaults['tools_ncctools'] += "%.*f," % (self.decimals, ncctools[t])
elif dim == 'tools_solderpaste_tools':
if self.defaults["tools_solderpaste_tools"] is None or \
self.defaults["tools_solderpaste_tools"] == '':
continue
if type(self.defaults["tools_solderpaste_tools"]) == float:
sptools = [self.defaults["tools_solderpaste_tools"]]
else:
try:
tools_string = self.defaults["tools_solderpaste_tools"].split(",")
sptools = [eval(a) for a in tools_string if a != '']
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
continue
self.defaults['tools_solderpaste_tools'] = ""
for t in range(len(sptools)):
sptools[t] *= sfactor
self.defaults['tools_solderpaste_tools'] += "%.*f," % (self.decimals, sptools[t])
elif dim == 'tools_solderpaste_xy_toolchange':
if self.defaults["tools_solderpaste_xy_toolchange"] is None or \
self.defaults["tools_solderpaste_xy_toolchange"] == '':
continue
elif dim in ['global_gridx', 'global_gridy']:
# format the number of decimals to the one specified in self.decimals
try:
coordinates = self.defaults["tools_solderpaste_xy_toolchange"].split(",")
sp_coords = [float(eval(a)) for a in coordinates if a != '']
sp_coords[0] *= sfactor
sp_coords[1] *= sfactor
self.defaults['tools_solderpaste_xy_toolchange'] = "%.*f, %.*f" % (self.decimals, sp_coords[0],
self.decimals, sp_coords[1])
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug("App.on_toggle_units().scale_options() --> %s" % str(e))
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
continue
elif dim == 'tools_cal_toolchange_xy':
if self.defaults["tools_cal_toolchange_xy"] is None or \
self.defaults["tools_cal_toolchange_xy"] == '':
continue
coordinates = self.defaults["tools_cal_toolchange_xy"].split(",")
end_coords_xy = [float(eval(a)) for a in coordinates if a != '']
end_coords_xy[0] *= sfactor
end_coords_xy[1] *= sfactor
self.defaults['tools_cal_toolchange_xy'] = "%.*f, %.*f" % (self.decimals, end_coords_xy[0],
self.decimals, end_coords_xy[1])
elif dim == 'global_gridx' or dim == 'global_gridy':
if new_units == 'IN':
try:
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
continue
self.defaults[dim] = float('%.*f' % (self.decimals, val))
else:
try:
val = float(self.defaults[dim]) * sfactor
except Exception as e:
log.debug('App.on_toggle_units().scale_defaults() --> %s' % str(e))
continue
self.defaults[dim] = float('%.*f' % (self.decimals, val))
self.defaults[dim] = float('%.*f' % (self.decimals, val))
else:
# the number of decimals for the rest is kept unchanged
if self.defaults[dim]:
try:
val = float(self.defaults[dim]) * sfactor
@ -4530,10 +4440,11 @@ class App(QtCore.QObject):
# replot all objects
self.plot_all()
# set the status labels to reflect the current FlatCAM units
self.set_screen_units(new_units)
# signal to the app that we changed the object properties and it shoud save the project
# signal to the app that we changed the object properties and it should save the project
self.should_we_save = True
self.inform.emit('[success] %s: %s' % (_("Converted units to"), new_units))
@ -10890,6 +10801,7 @@ class App(QtCore.QObject):
class ArgsThread(QtCore.QObject):
open_signal = pyqtSignal(list)
start = pyqtSignal()
stop = pyqtSignal()
if sys.platform == 'win32':
address = (r'\\.\pipe\NPtest', 'AF_PIPE')
@ -10902,6 +10814,7 @@ class ArgsThread(QtCore.QObject):
self.thread_exit = False
self.start.connect(self.run)
self.stop.connect(self.close_listener)
def my_loop(self, address):
try:
@ -10945,4 +10858,9 @@ class ArgsThread(QtCore.QObject):
def run(self):
self.my_loop(self.address)
@pyqtSlot()
def close_listener(self):
self.thread_exit = True
self.listener.close()
# end of file

View File

@ -10,6 +10,7 @@
# File Modified (major mod): Marius Adrian Stanciu #
# Date: 11/4/2019 #
# ##########################################################
from PyQt5 import QtCore
from shapely.geometry import Polygon, MultiPolygon
@ -17,7 +18,6 @@ from flatcamGUI.VisPyVisuals import ShapeCollection
from FlatCAMTool import FlatCAMTool
import numpy as np
import re
import gettext
import FlatCAMTranslation as fcTranslate
@ -129,9 +129,13 @@ def color_variant(hex_color, bright_factor=1):
return "#" + "".join([i for i in new_rgb])
class ExclusionAreas:
class ExclusionAreas(QtCore.QObject):
e_shape_modified = QtCore.pyqtSignal()
def __init__(self, app):
super().__init__()
self.app = app
# Storage for shapes, storage that can be used by FlatCAm tools for utility geometry
@ -275,11 +279,18 @@ class ExclusionAreas:
}
self.exclusion_areas_storage.append(new_el)
if self.obj_type == 'excellon':
color = "#FF7400"
face_color = "#FF7400BF"
else:
color = "#098a8f"
face_color = "#FF7400BF"
# add a temporary shape on canvas
FlatCAMTool.draw_tool_selection_shape(
self, old_coords=(x0, y0), coords=(x1, y1),
color="#FF7400",
face_color="#FF7400BF",
color=color,
face_color=face_color,
shapes_storage=self.exclusion_shapes)
self.first_click = False
@ -328,10 +339,18 @@ class ExclusionAreas:
"overz": self.over_z
}
self.exclusion_areas_storage.append(new_el)
if self.obj_type == 'excellon':
color = "#FF7400"
face_color = "#FF7400BF"
else:
color = "#098a8f"
face_color = "#FF7400BF"
FlatCAMTool.draw_selection_shape_polygon(
self, points=self.points,
color="#FF7400",
face_color="#FF7400BF",
color=color,
face_color=face_color,
shapes_storage=self.exclusion_shapes)
self.app.inform.emit(
_("Zone added. Click to start adding next zone or right click to finish."))
@ -386,6 +405,7 @@ class ExclusionAreas:
'%s %s' % (_("Generate the CNC Job object."), _("With Exclusion areas."))
)
self.e_shape_modified.emit()
for k in self.exclusion_areas_storage:
print(k)
@ -456,20 +476,28 @@ class ExclusionAreas:
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))
if self.obj_type == 'excellon':
color = "#FF7400"
face_color = "#FF7400BF"
else:
color = "#098a8f"
face_color = "#FF7400BF"
# 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]),
color="#FF7400",
face_color="#FF7400BF",
color=color,
face_color=face_color,
coords=(curr_pos[0], curr_pos[1]))
else:
FlatCAMTool.delete_moving_selection_shape(self)
FlatCAMTool.draw_moving_selection_shape_poly(
self, points=self.points,
color="#FF7400",
face_color="#FF7400BF",
color=color,
face_color=face_color,
data=(curr_pos[0], curr_pos[1]))
def on_clear_area_click(self):
@ -491,27 +519,52 @@ class ExclusionAreas:
FlatCAMTool.delete_moving_selection_shape(self)
self.app.delete_selection_shape()
FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))
def delete_sel_shapes(self, idxs):
"""
class InvertHexColor:
"""
Will invert a hex color made out of 3 chars or 6 chars
From here: http://code.activestate.com/recipes/527747-invert-css-hex-colors/
"""
def __init__(self):
self.p6 = re.compile("#[0-9a-f]{6};", re.IGNORECASE)
self.p3 = re.compile("#[0-9a-f]{3};", re.IGNORECASE)
:param idxs: list of indexes in self.exclusion_areas_storage list to be deleted
:return:
"""
def modify(self, original_color=3):
code = {}
l1 = "#;0123456789abcdef"
l2 = "#;fedcba9876543210"
# delete all plotted shapes
FlatCAMTool.delete_tool_selection_shape(self, shapes_storage=self.exclusion_shapes)
for i in range(len(l1)):
code[l1[i]] = l2[i]
inverted = ""
# delete shapes
for idx in sorted(idxs, reverse=True):
del self.exclusion_areas_storage[idx]
content = p6.sub(modify, content)
content = p3.sub(modify, content)
return inverted
# re-add what's left after deletion in first step
if self.obj_type == 'excellon':
color = "#FF7400"
face_color = "#FF7400BF"
else:
color = "#098a8f"
face_color = "#FF7400BF"
face_alpha = 0.3
color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:]
for geo_el in self.exclusion_areas_storage:
if isinstance(geo_el['shape'], Polygon):
self.exclusion_shapes.add(
geo_el['shape'], color=color, face_color=color_t, update=True, layer=0, tolerance=None)
if self.app.is_legacy is True:
self.exclusion_shapes.redraw()
if self.exclusion_areas_storage:
self.app.inform.emit('[success] %s' % _("Selected exclusion zones deleted."))
else:
# restore the default StyleSheet
self.cnc_button.setStyleSheet("")
# update the StyleSheet
self.cnc_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.cnc_button.setToolTip('%s' % _("Generate the CNC Job object."))
self.app.inform.emit('[success] %s' % _("All exclusion zones deleted."))

View File

@ -186,8 +186,7 @@ def restart_program(app, ask=None):
# try to quit the Socket opened by ArgsThread class
try:
app.new_launch.thread_exit = True
app.new_launch.listener.close()
app.new_launch.stop.emit()
except Exception as err:
log.debug("FlatCAMTranslation.restart_program() --> %s" % str(err))

View File

@ -263,6 +263,10 @@ class FlatCAMDefaults:
"excellon_tooldia": 0.8,
"excellon_slot_tooldia": 1.8,
"excellon_gcode_type": "drills",
"excellon_area_exclusion": False,
"excellon_area_shape": "polygon",
"excellon_area_strategy": "over",
"excellon_area_overz": 1.0,
# Excellon Advanced Options
"excellon_offset": 0.0,
@ -491,7 +495,7 @@ class FlatCAMDefaults:
"tools_transform_offset_x": 0.0,
"tools_transform_offset_y": 0.0,
"tools_transform_mirror_reference": False,
"tools_transform_mirror_point": (0, 0),
"tools_transform_mirror_point": "0.0, 0.0",
"tools_transform_buffer_dis": 0.0,
"tools_transform_buffer_factor": 100.0,
"tools_transform_buffer_corner": True,
@ -693,13 +697,19 @@ class FlatCAMDefaults:
except Exception as e:
log.error("save_factory_defaults() -> %s" % str(e))
def __init__(self):
def __init__(self, callback=lambda x: None):
"""
:param callback: A method called each time that one of the values are changed in the self.defaults LouDict
"""
self.defaults = LoudDict()
self.defaults.update(self.factory_defaults)
self.current_defaults = {} # copy used for restoring after cancelled prefs changes
self.current_defaults.update(self.factory_defaults)
self.old_defaults_found = False
self.defaults.set_change_callback(callback)
# #### Pass-through to the defaults LoudDict #####
def __len__(self):
return self.defaults.__len__()

View File

@ -933,7 +933,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"))
@ -1048,7 +1048,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

@ -5633,7 +5633,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)
@ -5760,7 +5760,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

@ -1292,10 +1292,89 @@ class ExcellonObjectUI(ObjectUI):
self.grid5.addWidget(pp_geo_label, 16, 0)
self.grid5.addWidget(self.pp_geo_name_cb, 16, 1)
# Exclusion Areas
self.exclusion_cb = FCCheckBox('%s' % _("Add exclusion areas"))
self.exclusion_cb.setToolTip(
_(
"Include exclusion areas.\n"
"In those areas the travel of the tools\n"
"is forbidden."
)
)
self.grid5.addWidget(self.exclusion_cb, 17, 0, 1, 2)
# ------------------------------------------------------------------------------------------------------------
# ------------------------- EXCLUSION AREAS ------------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------------
self.exclusion_frame = QtWidgets.QFrame()
self.exclusion_frame.setContentsMargins(0, 0, 0, 0)
self.grid5.addWidget(self.exclusion_frame, 18, 0, 1, 2)
self.exclusion_box = QtWidgets.QVBoxLayout()
self.exclusion_box.setContentsMargins(0, 0, 0, 0)
self.exclusion_frame.setLayout(self.exclusion_box)
h_lay = QtWidgets.QHBoxLayout()
self.exclusion_box.addLayout(h_lay)
# Button Add Area
self.add_area_button = QtWidgets.QPushButton(_('Add area'))
self.add_area_button.setToolTip(_("Add an Exclusion Area."))
h_lay.addWidget(self.add_area_button)
# Button Delete Area
self.delete_area_button = QtWidgets.QPushButton(_('Clear areas'))
self.delete_area_button.setToolTip(_("Delete all exclusion areas."))
h_lay.addWidget(self.delete_area_button)
grid_l = QtWidgets.QGridLayout()
grid_l.setColumnStretch(0, 0)
grid_l.setColumnStretch(1, 1)
self.exclusion_box.addLayout(grid_l)
# Area Selection 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'}])
grid_l.addWidget(self.area_shape_label, 0, 0)
grid_l.addWidget(self.area_shape_radio, 0, 1)
# Chose Strategy
self.strategy_label = FCLabel('%s:' % _("Strategy"))
self.strategy_label.setToolTip(_("The strategy followed when encountering an exclusion area.\n"
"Can be:\n"
"- Over -> when encountering the area, the tool will go to a set height\n"
"- Around -> will avoid the exclusion area by going around the area"))
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
{'label': _('Around'), 'value': 'around'}])
grid_l.addWidget(self.strategy_label, 1, 0)
grid_l.addWidget(self.strategy_radio, 1, 1)
# Over Z
self.over_z_label = FCLabel('%s:' % _("Over Z"))
self.over_z_label.setToolTip(_("The height Z to which the tool will rise in order to avoid\n"
"an interdiction area."))
self.over_z_entry = FCDoubleSpinner()
self.over_z_entry.set_range(0.000, 9999.9999)
self.over_z_entry.set_precision(self.decimals)
grid_l.addWidget(self.over_z_label, 2, 0)
grid_l.addWidget(self.over_z_entry, 2, 1)
# -------------------------- EXCLUSION AREAS END -------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------------
self.ois_exclusion_geo = OptionalHideInputSection(self.exclusion_cb, [self.exclusion_frame])
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.grid5.addWidget(separator_line, 17, 0, 1, 2)
self.grid5.addWidget(separator_line, 19, 0, 1, 2)
# #################################################################
# ################# GRID LAYOUT 6 ###############################
@ -2025,7 +2104,7 @@ class GeometryObjectUI(ObjectUI):
# grid4.addWidget(QtWidgets.QLabel(''), 12, 0, 1, 2)
# Exclusion Areas
self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
self.exclusion_cb = FCCheckBox('%s' % _("Add exclusion areas"))
self.exclusion_cb.setToolTip(
_(
"Include exclusion areas.\n"
@ -2046,35 +2125,29 @@ class GeometryObjectUI(ObjectUI):
self.exclusion_box.setContentsMargins(0, 0, 0, 0)
self.exclusion_frame.setLayout(self.exclusion_box)
h_lay = QtWidgets.QHBoxLayout()
self.exclusion_box.addLayout(h_lay)
self.exclusion_table = FCTable()
self.exclusion_box.addWidget(self.exclusion_table)
self.exclusion_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
# Button Add Area
self.add_area_button = QtWidgets.QPushButton(_('Add area'))
self.add_area_button.setToolTip(_("Add an Exclusion Area."))
h_lay.addWidget(self.add_area_button)
self.exclusion_table.setColumnCount(4)
self.exclusion_table.setColumnWidth(0, 20)
self.exclusion_table.setHorizontalHeaderLabels(['#', _('Object'), _('Strategy'), _('Over Z')])
# Button Delete Area
self.delete_area_button = QtWidgets.QPushButton(_('Clear areas'))
self.delete_area_button.setToolTip(_("Delete all exclusion areas."))
h_lay.addWidget(self.delete_area_button)
self.exclusion_table.horizontalHeaderItem(0).setToolTip(_("This is the Area ID."))
self.exclusion_table.horizontalHeaderItem(1).setToolTip(
_("Type of the object where the exclusion area was added."))
self.exclusion_table.horizontalHeaderItem(2).setToolTip(
_("The strategy used for exclusion area. Go around the exclusion areas or over it."))
self.exclusion_table.horizontalHeaderItem(3).setToolTip(
_("If the strategy is to go over the area then this is the height at which the tool will go to avoid the "
"exclusion area."))
grid_l = QtWidgets.QGridLayout()
grid_l.setColumnStretch(0, 0)
grid_l.setColumnStretch(1, 1)
self.exclusion_box.addLayout(grid_l)
self.exclusion_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# Area Selection 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'}])
grid_l.addWidget(self.area_shape_label, 0, 0)
grid_l.addWidget(self.area_shape_radio, 0, 1)
grid_a1 = QtWidgets.QGridLayout()
grid_a1.setColumnStretch(0, 0)
grid_a1.setColumnStretch(1, 1)
self.exclusion_box.addLayout(grid_a1)
# Chose Strategy
self.strategy_label = FCLabel('%s:' % _("Strategy"))
@ -2085,8 +2158,8 @@ class GeometryObjectUI(ObjectUI):
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
{'label': _('Around'), 'value': 'around'}])
grid_l.addWidget(self.strategy_label, 1, 0)
grid_l.addWidget(self.strategy_radio, 1, 1)
grid_a1.addWidget(self.strategy_label, 1, 0)
grid_a1.addWidget(self.strategy_radio, 1, 1)
# Over Z
self.over_z_label = FCLabel('%s:' % _("Over Z"))
@ -2096,12 +2169,40 @@ class GeometryObjectUI(ObjectUI):
self.over_z_entry.set_range(0.000, 9999.9999)
self.over_z_entry.set_precision(self.decimals)
grid_l.addWidget(self.over_z_label, 2, 0)
grid_l.addWidget(self.over_z_entry, 2, 1)
grid_a1.addWidget(self.over_z_label, 2, 0)
grid_a1.addWidget(self.over_z_entry, 2, 1)
# Button Add Area
self.add_area_button = QtWidgets.QPushButton(_('Add area'))
self.add_area_button.setToolTip(_("Add an Exclusion Area."))
# Area Selection shape
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
self.area_shape_radio.setToolTip(
_("The kind of selection shape used for area selection.")
)
grid_a1.addWidget(self.add_area_button, 4, 0)
grid_a1.addWidget(self.area_shape_radio, 4, 1)
h_lay_1 = QtWidgets.QHBoxLayout()
self.exclusion_box.addLayout(h_lay_1)
# Button Delete All Areas
self.delete_area_button = QtWidgets.QPushButton(_('Clear areas'))
self.delete_area_button.setToolTip(_("Delete all exclusion areas."))
# Button Delete Selected Areas
self.delete_sel_area_button = QtWidgets.QPushButton(_('Delete Selected'))
self.delete_sel_area_button.setToolTip(_("Delete all exclusion areas that are selected in the table."))
h_lay_1.addWidget(self.delete_area_button)
h_lay_1.addWidget(self.delete_sel_area_button)
# -------------------------- EXCLUSION AREAS END -------------------------------------------------------------
# ------------------------------------------------------------------------------------------------------------
self.ois_exclusion_geo = OptionalInputSection(self.exclusion_cb, [self.exclusion_frame])
self.ois_exclusion_geo = OptionalHideInputSection(self.exclusion_cb, [self.exclusion_frame])
warning_lbl = QtWidgets.QLabel(
_(

View File

@ -287,6 +287,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

@ -714,7 +714,40 @@ 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
val = self.get_form_field("global_theme").get_value()
if val != theme:
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'))
bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.NoRole)
msgbox.setDefaultButton(bt_yes)
msgbox.exec_()
response = msgbox.clickedButton()
if response == bt_yes:
theme_settings.setValue('theme', 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)
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'))
@ -752,6 +785,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

View File

@ -25,8 +25,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI2):
self.layout_field.activated.connect(self.on_layout)
self.theme_field = self.option_dict()["global_theme"].get_field()
self.theme_apply_button = self.option_dict()["__button_apply_theme"].get_field()
self.theme_apply_button.clicked.connect(self.on_theme_change)
self.style_field = self.option_dict()["style"].get_field()
current_style_index = self.style_field.findText(QtWidgets.qApp.style().objectName(), QtCore.Qt.MatchFixedString)
@ -58,13 +56,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI2):
label_text="Use Gray Icons",
label_tooltip="Check this box to use a set of icons with\na lighter (gray) color. To be used when a\nfull dark theme is applied."
),
FullWidthButtonOptionUI(
option="__button_apply_theme",
label_text="Apply Theme",
label_tooltip="Select a theme for FlatCAM.\n"
"It will theme the plot area.\n"
"The application will restart after change."
),
SeparatorOptionUI(),
ComboboxOptionUI(
@ -181,24 +172,6 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI2):
),
]
def on_theme_change(self):
# FIXME: this should be moved out to a view model
val = self.theme_field.get_value()
theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
if theme_settings.contains("theme"):
theme = theme_settings.value('theme', type=str)
else:
theme = 'white'
if val != theme:
theme_settings.setValue("theme", val)
# This will write the setting to the platform specific storage.
del theme_settings
self.app.on_app_restart()
def on_layout(self, index=None, lay=None):
if lay:
current_layout = lay

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, EvalEntry2
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry
from flatcamGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -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 = FCEntry()
grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)

View File

@ -567,7 +567,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 = {
@ -634,6 +638,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
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)
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.on_operation_type(val='drill')
self.ui.operation_radio.activated_custom.connect(self.on_operation_type)
@ -1467,6 +1474,21 @@ 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):
self.app.exc_areas.on_clear_area_click()
def on_solid_cb_click(self, *args):
if self.muted_ui:
return

View File

@ -159,6 +159,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.build_ui)
self.units = self.app.defaults['units']
tool_idx = 0
@ -179,7 +188,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()
@ -310,6 +318,58 @@ class GeometryObject(FlatCAMObj, Geometry):
"<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())
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
@ -534,8 +594,11 @@ 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
self.ui.exclusion_table.horizontalHeader().sectionClicked.connect(self.ui.exclusion_table.selectAll)
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)
def on_cut_z_changed(self):
self.old_cutz = self.ui.cutz_entry.get_value()
@ -1674,15 +1737,11 @@ class GeometryObject(FlatCAMObj, Geometry):
The actual work is done by the target CNCJobObject object's
`generate_from_geometry_2()` method.
: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:
: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 tools_in_use list of lists, each list in the list is made out of row elements of tools table from GUI
: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
@ -1726,7 +1785,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
@ -1866,7 +1925,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
@ -2040,7 +2099,7 @@ class GeometryObject(FlatCAMObj, Geometry):
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.
@ -2467,6 +2526,20 @@ class GeometryObject(FlatCAMObj, Geometry):
def on_clear_area_click(self):
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())
self.app.exc_areas.delete_sel_shapes(idxs=list(sel_rows))
self.app.exc_areas.e_shape_modified.emit()
def plot_element(self, element, color=None, visible=None):

View File

@ -308,8 +308,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 +323,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):
"""

View File

@ -7,7 +7,7 @@
from PyQt5 import QtWidgets
from FlatCAMTool import FlatCAMTool
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2
from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, FCEntry
import gettext
import FlatCAMTranslation as fcTranslate
@ -300,7 +300,7 @@ class ToolTransform(FlatCAMTool):
"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.")
)
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(70)
@ -533,7 +533,7 @@ class ToolTransform(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")
if self.app.defaults["tools_transform_buffer_dis"]:
self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"])