- added to GUI new options for the Gerber object related to area subtraction

- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)
This commit is contained in:
Marius Stanciu 2019-09-07 15:13:40 +03:00 committed by Marius
parent 2c2bdf5002
commit f164dae7a9
5 changed files with 294 additions and 122 deletions

View File

@ -1090,9 +1090,9 @@ class App(QtCore.QObject):
if user_defaults: if user_defaults:
self.load_defaults(filename='current_defaults') self.load_defaults(filename='current_defaults')
# ########################### # #############################################################
# #### APPLY APP LANGUAGE ### # ############## APPLY APP LANGUAGE ###########################
# ########################### # #############################################################
ret_val = fcTranslate.apply_language('strings') ret_val = fcTranslate.apply_language('strings')
@ -1104,9 +1104,9 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_app_group.language_cb.setCurrentText(ret_val) self.ui.general_defaults_form.general_app_group.language_cb.setCurrentText(ret_val)
log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize()) log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize())
# ################################## # ##############################################################
# ### CREATE UNIQUE SERIAL NUMBER ## # ################# CREATE UNIQUE SERIAL NUMBER ################
# ################################## # ##############################################################
chars = 'abcdefghijklmnopqrstuvwxyz0123456789' chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10: if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10:
@ -1495,20 +1495,27 @@ class App(QtCore.QObject):
# ############### Signal handling ################ # ############### Signal handling ################
# ################################################ # ################################################
# ### Custom signals ### # ############# Custom signals ##################
# signal for displaying messages in status bar
self.inform.connect(self.info) self.inform.connect(self.info)
# signal to be called when the app is quiting
self.app_quit.connect(self.quit_application) self.app_quit.connect(self.quit_application)
self.message.connect(self.message_dialog) self.message.connect(self.message_dialog)
self.progress.connect(self.set_progress_bar) self.progress.connect(self.set_progress_bar)
# signals that are emitted when object state changes
self.object_created.connect(self.on_object_created) self.object_created.connect(self.on_object_created)
self.object_changed.connect(self.on_object_changed) self.object_changed.connect(self.on_object_changed)
self.object_plotted.connect(self.on_object_plotted) self.object_plotted.connect(self.on_object_plotted)
self.plots_updated.connect(self.on_plots_updated) self.plots_updated.connect(self.on_plots_updated)
# signals emitted when file state change
self.file_opened.connect(self.register_recent) self.file_opened.connect(self.register_recent)
self.file_opened.connect(lambda kind, filename: self.register_folder(filename)) self.file_opened.connect(lambda kind, filename: self.register_folder(filename))
self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename)) self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename))
# ### Standard signals # ############# Standard signals ###################
# ### Menu # ### Menu
self.ui.menufilenewproject.triggered.connect(self.on_file_new_click) self.ui.menufilenewproject.triggered.connect(self.on_file_new_click)
self.ui.menufilenewgeo.triggered.connect(self.new_geometry_object) self.ui.menufilenewgeo.triggered.connect(self.new_geometry_object)
@ -1675,9 +1682,9 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect(
lambda: self.on_toggle_units(no_pref=False)) lambda: self.on_toggle_units(no_pref=False))
# ############################## # ###############################################################
# ### GUI PREFERENCES SIGNALS ## # ################### GUI COLORS SIGNALS ########################
# ############################## # ###############################################################
# Setting plot colors signals # Setting plot colors signals
self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect( self.ui.general_defaults_form.general_gui_group.pf_color_entry.editingFinished.connect(
@ -1739,11 +1746,13 @@ class App(QtCore.QObject):
self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect( self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.clicked.connect(
self.on_proj_color_dis_button) self.on_proj_color_dis_button)
# ########## workspace setting signals ###########
self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified) self.ui.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace) self.ui.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace)
self.ui.general_defaults_form.general_gui_set_group.layout_combo.activated.connect(self.on_layout) self.ui.general_defaults_form.general_gui_set_group.layout_combo.activated.connect(self.on_layout)
# ########## CNC Job related signals #############
self.ui.cncjob_defaults_form.cncjob_adv_opt_group.tc_variable_combo.currentIndexChanged[str].connect( self.ui.cncjob_defaults_form.cncjob_adv_opt_group.tc_variable_combo.currentIndexChanged[str].connect(
self.on_cnc_custom_parameters) self.on_cnc_custom_parameters)
self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_entry.editingFinished.connect( self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_entry.editingFinished.connect(
@ -1751,7 +1760,7 @@ class App(QtCore.QObject):
self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_button.clicked.connect( self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_fontcolor_button.clicked.connect(
self.on_annotation_fontcolor_button) self.on_annotation_fontcolor_button)
# Modify G-CODE Plot Area TAB # ########## Modify G-CODE Plot Area TAB ###########
self.ui.code_editor.textChanged.connect(self.handleTextChanged) self.ui.code_editor.textChanged.connect(self.handleTextChanged)
self.ui.buttonOpen.clicked.connect(self.handleOpen) self.ui.buttonOpen.clicked.connect(self.handleOpen)
self.ui.buttonSave.clicked.connect(self.handleSaveGCode) self.ui.buttonSave.clicked.connect(self.handleSaveGCode)
@ -1760,7 +1769,7 @@ class App(QtCore.QObject):
self.ui.buttonFind.clicked.connect(self.handleFindGCode) self.ui.buttonFind.clicked.connect(self.handleFindGCode)
self.ui.buttonReplace.clicked.connect(self.handleReplaceGCode) self.ui.buttonReplace.clicked.connect(self.handleReplaceGCode)
# portability changed # portability changed signal
self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked) self.ui.general_defaults_form.general_app_group.portability_cb.stateChanged.connect(self.on_portable_checked)
# Object list # Object list
@ -1791,8 +1800,15 @@ class App(QtCore.QObject):
# connect the abort_all_tasks related slots to the related signals # connect the abort_all_tasks related slots to the related signals
self.proc_container.idle_flag.connect(self.app_is_idle) self.proc_container.idle_flag.connect(self.app_is_idle)
# #####################################################################################
# ########### FINISHED CONNECTING SIGNALS #############################################
# #####################################################################################
self.log.debug("Finished connecting Signals.") self.log.debug("Finished connecting Signals.")
# #####################################################################################
# ########################## Other setups #############################################
# #####################################################################################
# this is a flag to signal to other tools that the ui tooltab is locked and not accessible # this is a flag to signal to other tools that the ui tooltab is locked and not accessible
self.tool_tab_locked = False self.tool_tab_locked = False
@ -1802,18 +1818,14 @@ class App(QtCore.QObject):
else: else:
self.ui.splitter.setSizes([0, 1]) self.ui.splitter.setSizes([0, 1])
# ###########################################
# ################# Other setups ############
# ###########################################
# Sets up FlatCAMObj, FCProcess and FCProcessContainer. # Sets up FlatCAMObj, FCProcess and FCProcessContainer.
self.setup_obj_classes() self.setup_obj_classes()
self.setup_recent_items() self.setup_recent_items()
self.setup_component_editor() self.setup_component_editor()
# ########################################### # #####################################################################################
# #######Auto-complete KEYWORDS ############# # ######################### Auto-complete KEYWORDS ####################################
# ########################################### # #####################################################################################
self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle', self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout', 'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout',
'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow', 'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow',
@ -2028,9 +2040,9 @@ class App(QtCore.QObject):
self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
# ########################################### # ####################################################################################
# ########### Shell SETUP ################### # ####################### Shell SETUP ################################################
# ########################################### # ####################################################################################
self.shell = FCShell(self, version=self.version) self.shell = FCShell(self, version=self.version)
self.shell._edit.set_model_data(self.myKeywords) self.shell._edit.set_model_data(self.myKeywords)
@ -2058,9 +2070,9 @@ class App(QtCore.QObject):
else: else:
self.ui.shell_dock.hide() self.ui.shell_dock.hide()
# ########################################### # ##################################################################################
# ######### Tools and Plugins ############### # ###################### Tools and Plugins #########################################
# ########################################### # ##################################################################################
self.dblsidedtool = None self.dblsidedtool = None
self.measurement_tool = None self.measurement_tool = None
@ -2086,9 +2098,9 @@ class App(QtCore.QObject):
# self.f_parse = ParseFont(self) # self.f_parse = ParseFont(self)
# self.parse_system_fonts() # self.parse_system_fonts()
# ############################################### # #####################################################################################
# ######## START-UP ARGUMENTS ################### # ########################## START-UP ARGUMENTS #######################################
# ############################################### # #####################################################################################
# test if the program was started with a script as parameter # test if the program was started with a script as parameter
if self.cmd_line_shellfile: if self.cmd_line_shellfile:
@ -2100,9 +2112,9 @@ class App(QtCore.QObject):
print("ERROR: ", ext) print("ERROR: ", ext)
sys.exit(2) sys.exit(2)
# ############################################### # #####################################################################################
# ############# Check for updates ############### # ######################## Check for updates ##########################################
# ############################################### # #####################################################################################
# Separate thread (Not worker) # Separate thread (Not worker)
# Check for updates on startup but only if the user consent and the app is not in Beta version # Check for updates on startup but only if the user consent and the app is not in Beta version
@ -2115,9 +2127,9 @@ class App(QtCore.QObject):
'params': []}) 'params': []})
self.thr2.start(QtCore.QThread.LowPriority) self.thr2.start(QtCore.QThread.LowPriority)
# ################################################ # #####################################################################################
# ######### Variables for global usage ########### # ###################### Variables for global usage ###################################
# ################################################ # #####################################################################################
# register files with FlatCAM; it works only for Windows for now # register files with FlatCAM; it works only for Windows for now
if sys.platform == 'win32' and self.defaults["first_run"] is True: if sys.platform == 'win32' and self.defaults["first_run"] is True:
@ -2199,10 +2211,10 @@ class App(QtCore.QObject):
# when True, the app has to return from any thread # when True, the app has to return from any thread
self.abort_flag = False self.abort_flag = False
# ######################################################### # ###############################################################################
# ### Save defaults to factory_defaults.FlatConfig file ### # ############# Save defaults to factory_defaults.FlatConfig file ###############
# ### It's done only once after install ################### # ############# It's done only once after install ###############
# ######################################################### # ###############################################################################
factory_file = open(self.data_path + '/factory_defaults.FlatConfig') factory_file = open(self.data_path + '/factory_defaults.FlatConfig')
fac_def_from_file = factory_file.read() fac_def_from_file = factory_file.read()
factory_defaults = json.loads(fac_def_from_file) factory_defaults = json.loads(fac_def_from_file)
@ -2223,9 +2235,9 @@ class App(QtCore.QObject):
filename_factory = self.data_path + '/factory_defaults.FlatConfig' filename_factory = self.data_path + '/factory_defaults.FlatConfig'
os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH) os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH)
#################################################### # ###############################################################################
# ### ADDING FlatCAM EDITORS section ############### # ################# ADDING FlatCAM EDITORS section ##############################
#################################################### # ###############################################################################
# watch out for the position of the editors instantiation ... if it is done before a save of the default values # watch out for the position of the editors instantiation ... if it is done before a save of the default values
# at the first launch of the App , the editors will not be functional. # at the first launch of the App , the editors will not be functional.
@ -2234,18 +2246,16 @@ class App(QtCore.QObject):
self.grb_editor = FlatCAMGrbEditor(self) self.grb_editor = FlatCAMGrbEditor(self)
self.log.debug("Finished adding FlatCAM Editor's.") self.log.debug("Finished adding FlatCAM Editor's.")
# Post-GUI initialization: Experimental attempt
# to perform unit tests on the GUI.
# if post_gui is not None:
# post_gui(self)
App.log.debug("END of constructor. Releasing control.")
self.set_ui_title(name=_("New Project - Not saved")) self.set_ui_title(name=_("New Project - Not saved"))
# after the first run, this object should be False # after the first run, this object should be False
self.defaults["first_run"] = False self.defaults["first_run"] = False
# ###############################################################################
# ####################### Finished the CONSTRUCTOR ##############################
# ###############################################################################
App.log.debug("END of constructor. Releasing control.")
# accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts
# the path/file_name must be enclosed in quotes if it contain spaces # the path/file_name must be enclosed in quotes if it contain spaces
if App.args: if App.args:

View File

@ -339,6 +339,10 @@ class FlatCAMObj(QtCore.QObject):
key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, **kwargs) key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, **kwargs)
return key return key
@staticmethod
def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors]
@property @property
def visible(self): def visible(self):
return self.shapes.visible return self.shapes.visible
@ -551,6 +555,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change) 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) 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.setCurrentIndex(1)
self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
# Show/Hide Advanced Options # Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b': if self.app.defaults["global_app_level"] == 'b':
self.ui.level.setText(_( self.ui.level.setText(_(
@ -563,12 +572,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.ui.generate_ext_iso_button.hide() self.ui.generate_ext_iso_button.hide()
self.ui.generate_int_iso_button.hide() self.ui.generate_int_iso_button.hide()
self.ui.follow_cb.hide() self.ui.follow_cb.hide()
self.ui.padding_area_label.show() self.ui.except_cb.setChecked(False)
self.ui.except_cb.hide()
else: else:
self.ui.level.setText(_( self.ui.level.setText(_(
'<span style="color:red;"><b>Advanced</b></span>' '<span style="color:red;"><b>Advanced</b></span>'
)) ))
self.ui.padding_area_label.hide()
# add the shapes storage for marking apertures # add the shapes storage for marking apertures
for ap_code in self.apertures: for ap_code in self.apertures:
@ -579,6 +588,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.build_ui() self.build_ui()
def on_type_obj_index_changed(self, index):
obj_type = self.ui.type_obj_combo.currentIndex()
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
self.ui.obj_combo.setCurrentIndex(0)
def build_ui(self): def build_ui(self):
FlatCAMObj.build_ui(self) FlatCAMObj.build_ui(self)
@ -881,23 +895,22 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if invert: if invert:
try: try:
try: pl = []
pl = [] for p in geom:
for p in geom: if p is not None:
if p is not None: if isinstance(p, Polygon):
if isinstance(p, Polygon): pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
pl.append(Polygon(p.exterior.coords[::-1], p.interiors)) elif isinstance(p, LinearRing):
elif isinstance(p, LinearRing): pl.append(Polygon(p.coords[::-1]))
pl.append(Polygon(p.coords[::-1])) geom = MultiPolygon(pl)
geom = MultiPolygon(pl) except TypeError:
except TypeError: if isinstance(geom, Polygon) and geom is not None:
if isinstance(geom, Polygon) and geom is not None: geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
geom = Polygon(geom.exterior.coords[::-1], geom.interiors) elif isinstance(geom, LinearRing) and geom is not None:
elif isinstance(geom, LinearRing) and geom is not None: geom = Polygon(geom.coords[::-1])
geom = Polygon(geom.coords[::-1]) else:
else: log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" %
log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" % type(geom))
type(geom))
except Exception as e: except Exception as e:
log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e)) log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e))
return 'fail' return 'fail'
@ -924,6 +937,54 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# if float(self.options["isotooldia"]) < 0: # if float(self.options["isotooldia"]) < 0:
# self.options["isotooldia"] = -self.options["isotooldia"] # self.options["isotooldia"] = -self.options["isotooldia"]
def area_subtraction(geo):
new_geometry = []
name = self.ui.obj_combo.currentText()
subtractor_obj = self.app.collection.get_by_name(name)
sub_union = cascaded_union(subtractor_obj.solid_geometry)
try:
for geo_elem in 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(geo, Polygon):
for ring in self.poly2rings(geo):
new_geo = ring.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo, LineString):
new_geo = geo.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo, MultiLineString):
for line_elem in 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
if combine: if combine:
if self.iso_type == 0: if self.iso_type == 0:
iso_name = self.options["name"] + "_ext_iso" iso_name = self.options["name"] + "_ext_iso"
@ -1001,21 +1062,24 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
for g in geo_obj.solid_geometry: for g in geo_obj.solid_geometry:
if g: if g:
app_obj.inform.emit(_(
"[success] Isolation geometry created: %s"
) % geo_obj.options["name"])
break break
else: else:
empty_cnt += 1 empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry): if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None) raise ValidationError("Empty Geometry", None)
# even if combine is checked, one pass is still singlegeo
if passes > 1:
geo_obj.multigeo = True
else: else:
geo_obj.multigeo = False 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():
geo_obj.solid_geometry = area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name? # TODO: Do something if this is None. Offer changing name?
self.app.new_object("geometry", iso_name, iso_init) self.app.new_object("geometry", iso_name, iso_init)
@ -1065,16 +1129,23 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
for g in geo_obj.solid_geometry: for g in geo_obj.solid_geometry:
if g: if g:
app_obj.inform.emit(_(
"[success] Isolation geometry created: %s"
) % geo_obj.options["name"])
break break
else: else:
empty_cnt += 1 empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry): if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None) raise ValidationError("Empty Geometry", None)
else:
app_obj.inform.emit('[success] %s: %s' %
(_("Isolation geometry created"), geo_obj.options["name"]))
geo_obj.multigeo = False geo_obj.multigeo = False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
geo_obj.solid_geometry = area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name? # TODO: Do something if this is None. Offer changing name?
self.app.new_object("geometry", iso_name, iso_init) self.app.new_object("geometry", iso_name, iso_init)

View File

@ -14,6 +14,8 @@ CAD program, and create G-Code for Isolation routing.
- added a method to gracefully exit from threaded tasks and implemented it for the NCC Tool and for the Paint Tool - added a method to gracefully exit from threaded tasks and implemented it for the NCC Tool and for the Paint Tool
- modified the on_about() function to reflect the reality in 2019 - FlatCAM it is an Open Source contributed software - modified the on_about() function to reflect the reality in 2019 - FlatCAM it is an Open Source contributed software
- remade the handlers for the Enable/Disable Project Tree context menu so they are threaded and activity is shown in the lower right corner of the main window - remade the handlers for the Enable/Disable Project Tree context menu so they are threaded and activity is shown in the lower right corner of the main window
- added to GUI new options for the Gerber object related to area subtraction
- added new feature in the Gerber object isolation allowing for the isolation to avoid an area defined by another object (Gerber or Geometry)
6.09.2019 6.09.2019

View File

@ -1524,6 +1524,45 @@ class OptionalInputSection:
widget.setEnabled(True) widget.setEnabled(True)
class OptionalHideInputSection:
def __init__(self, cb, optinputs, logic=True):
"""
Associates the a checkbox with a set of inputs.
:param cb: Checkbox that enables the optional inputs.
:param optinputs: List of widgets that are optional.
: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
:return:
"""
assert isinstance(cb, FCCheckBox), \
"Expected an FCCheckBox, got %s" % type(cb)
self.cb = cb
self.optinputs = optinputs
self.logic = logic
self.on_cb_change()
self.cb.stateChanged.connect(self.on_cb_change)
def on_cb_change(self):
if self.cb.checkState():
for widget in self.optinputs:
if self.logic is True:
widget.show()
else:
widget.hide()
else:
for widget in self.optinputs:
if self.logic is True:
widget.hide()
else:
widget.show()
class FCTable(QtWidgets.QTableWidget): class FCTable(QtWidgets.QTableWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(FCTable, self).__init__(parent) super(FCTable, self).__init__(parent)

View File

@ -254,8 +254,13 @@ class GerberObjectUI(ObjectUI):
) )
self.custom_box.addWidget(self.isolation_routing_label) self.custom_box.addWidget(self.isolation_routing_label)
# ###########################################
# ########## NEW GRID #######################
# ###########################################
grid1 = QtWidgets.QGridLayout() grid1 = QtWidgets.QGridLayout()
self.custom_box.addLayout(grid1) self.custom_box.addLayout(grid1)
tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia')) tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
tdlabel.setToolTip( tdlabel.setToolTip(
_("Diameter of the cutting tool.\n" _("Diameter of the cutting tool.\n"
@ -265,9 +270,9 @@ class GerberObjectUI(ObjectUI):
"this parameter.") "this parameter.")
) )
tdlabel.setMinimumWidth(90) tdlabel.setMinimumWidth(90)
grid1.addWidget(tdlabel, 0, 0)
self.iso_tool_dia_entry = LengthEntry() self.iso_tool_dia_entry = LengthEntry()
grid1.addWidget(self.iso_tool_dia_entry, 0, 1) grid1.addWidget(tdlabel, 0, 0)
grid1.addWidget(self.iso_tool_dia_entry, 0, 1, 1, 2)
passlabel = QtWidgets.QLabel('%s:' % _('# Passes')) passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
passlabel.setToolTip( passlabel.setToolTip(
@ -275,10 +280,10 @@ class GerberObjectUI(ObjectUI):
"number (integer) of tool widths.") "number (integer) of tool widths.")
) )
passlabel.setMinimumWidth(90) passlabel.setMinimumWidth(90)
grid1.addWidget(passlabel, 1, 0)
self.iso_width_entry = FCSpinner() self.iso_width_entry = FCSpinner()
self.iso_width_entry.setRange(1, 999) self.iso_width_entry.setRange(1, 999)
grid1.addWidget(self.iso_width_entry, 1, 1) grid1.addWidget(passlabel, 1, 0)
grid1.addWidget(self.iso_width_entry, 1, 1, 1, 2)
overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap')) overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
overlabel.setToolTip( overlabel.setToolTip(
@ -287,9 +292,9 @@ class GerberObjectUI(ObjectUI):
"A value here of 0.25 means an overlap of 25% from the tool diameter found above.") "A value here of 0.25 means an overlap of 25% from the tool diameter found above.")
) )
overlabel.setMinimumWidth(90) overlabel.setMinimumWidth(90)
grid1.addWidget(overlabel, 2, 0)
self.iso_overlap_entry = FloatEntry() self.iso_overlap_entry = FloatEntry()
grid1.addWidget(self.iso_overlap_entry, 2, 1) grid1.addWidget(overlabel, 2, 0)
grid1.addWidget(self.iso_overlap_entry, 2, 1, 1, 2)
# Milling Type Radio Button # Milling Type Radio Button
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type')) self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
@ -298,27 +303,69 @@ class GerberObjectUI(ObjectUI):
"- climb / best for precision milling and to reduce tool usage\n" "- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation") "- conventional / useful when there is no backlash compensation")
) )
grid1.addWidget(self.milling_type_label, 3, 0)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'}, self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conv.'), 'value': 'cv'}]) {'label': _('Conv.'), 'value': 'cv'}])
grid1.addWidget(self.milling_type_radio, 3, 1) grid1.addWidget(self.milling_type_label, 3, 0)
grid1.addWidget(self.milling_type_radio, 3, 1, 1, 2)
# combine all passes CB # combine all passes CB
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes')) self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
self.combine_passes_cb.setToolTip( self.combine_passes_cb.setToolTip(
_("Combine all passes into one object") _("Combine all passes into one object")
) )
grid1.addWidget(self.combine_passes_cb, 4, 0)
# generate follow # generate follow
self.follow_cb = FCCheckBox(label=_('"Follow"')) self.follow_cb = FCCheckBox(label=_('"Follow"'))
self.follow_cb.setToolTip( self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
_("Generate a 'Follow' geometry.\n" "This means that it will cut through\n"
"This means that it will cut through\n" "the middle of the trace."))
"the middle of the trace.")
) # avoid an area from isolation
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 bellow\n"
"will be subtracted from the isolation geometry."))
grid1.addWidget(self.combine_passes_cb, 4, 0)
grid1.addWidget(self.follow_cb, 4, 1) grid1.addWidget(self.follow_cb, 4, 1)
grid1.addWidget(self.except_cb, 4, 2)
# ## Form Layout
form_layout = QtWidgets.QFormLayout()
grid1.addLayout(form_layout, 5, 0, 1, 3)
# ################################################
# ##### Type of object to be excepted ############
# ################################################
self.type_obj_combo = QtWidgets.QComboBox()
self.type_obj_combo.addItem("Gerber")
self.type_obj_combo.addItem("Excellon")
self.type_obj_combo.addItem("Geometry")
# we get rid of item1 ("Excellon") as it is not suitable
self.type_obj_combo.view().setRowHidden(1, True)
self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
self.type_obj_combo_label.setToolTip(
_("Specify the type of object to be excepted from isolation.\n"
"It can be of type: Gerber or Geometry.\n"
"What is selected here will dictate the kind\n"
"of objects that will populate the 'Object' combobox.")
)
# self.type_obj_combo_label.setMinimumWidth(60)
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
# ################################################
# ##### The object to be excepted ################
# ################################################
self.obj_combo = QtWidgets.QComboBox()
self.obj_label = QtWidgets.QLabel('%s:' % _("Object"))
self.obj_label.setToolTip(_("Object whose area will be removed from isolation geometry."))
form_layout.addRow(self.obj_label, self.obj_combo)
self.gen_iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Generate Isolation Geometry")) self.gen_iso_label = QtWidgets.QLabel("<b>%s:</b>" % _("Generate Isolation Geometry"))
self.gen_iso_label.setToolTip( self.gen_iso_label.setToolTip(
@ -332,14 +379,7 @@ class GerberObjectUI(ObjectUI):
"inside the actual Gerber feature, use a negative tool\n" "inside the actual Gerber feature, use a negative tool\n"
"diameter above.") "diameter above.")
) )
self.custom_box.addWidget(self.gen_iso_label) grid1.addWidget(self.gen_iso_label, 6, 0, 1, 3)
hlay_1 = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(hlay_1)
self.padding_area_label = QtWidgets.QLabel('')
self.padding_area_label.setMinimumWidth(90)
hlay_1.addWidget(self.padding_area_label)
self.generate_iso_button = QtWidgets.QPushButton(_('FULL Geo')) self.generate_iso_button = QtWidgets.QPushButton(_('FULL Geo'))
self.generate_iso_button.setToolTip( self.generate_iso_button.setToolTip(
@ -347,10 +387,10 @@ class GerberObjectUI(ObjectUI):
"for isolation routing. It contains both\n" "for isolation routing. It contains both\n"
"the interiors and exteriors geometry.") "the interiors and exteriors geometry.")
) )
self.generate_iso_button.setMinimumWidth(90) grid1.addWidget(self.generate_iso_button, 7, 0)
hlay_1.addWidget(self.generate_iso_button, alignment=Qt.AlignLeft)
# hlay_1.addStretch() hlay_1 = QtWidgets.QHBoxLayout()
grid1.addLayout(hlay_1, 7, 1, 1, 2)
self.generate_ext_iso_button = QtWidgets.QPushButton(_('Ext Geo')) self.generate_ext_iso_button = QtWidgets.QPushButton(_('Ext Geo'))
self.generate_ext_iso_button.setToolTip( self.generate_ext_iso_button.setToolTip(
@ -370,14 +410,28 @@ class GerberObjectUI(ObjectUI):
# self.generate_ext_iso_button.setMinimumWidth(90) # self.generate_ext_iso_button.setMinimumWidth(90)
hlay_1.addWidget(self.generate_int_iso_button) hlay_1.addWidget(self.generate_int_iso_button)
self.ohis_iso = OptionalHideInputSection(
self.except_cb,
[self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
logic=True
)
# when the follow checkbox is checked then the exteriors and interiors isolation generation buttons # when the follow checkbox is checked then the exteriors and interiors isolation generation buttons
# are disabled as is doesn't make sense to have them enabled due of the nature of "follow" # are disabled as is doesn't make sense to have them enabled due of the nature of "follow"
self.ois_iso = OptionalInputSection(self.follow_cb, self.ois_iso = OptionalInputSection(self.follow_cb,
[self.generate_int_iso_button, self.generate_ext_iso_button], logic=False) [self.generate_int_iso_button, self.generate_ext_iso_button], logic=False)
grid1.addWidget(QtWidgets.QLabel(''), 8, 0)
# ###########################################
# ########## NEW GRID #######################
# ###########################################
grid2 = QtWidgets.QGridLayout() grid2 = QtWidgets.QGridLayout()
self.custom_box.addLayout(grid2) self.custom_box.addLayout(grid2)
self.tool_lbl = QtWidgets.QLabel('<b>%s</b>:' % _("TOOLS"))
grid2.addWidget(self.tool_lbl, 0, 0, 1, 2)
# ## Clear non-copper regions # ## Clear non-copper regions
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Clear N-copper")) self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Clear N-copper"))
self.clearcopper_label.setToolTip( self.clearcopper_label.setToolTip(
@ -385,14 +439,14 @@ class GerberObjectUI(ObjectUI):
"toolpaths to cut all non-copper regions.") "toolpaths to cut all non-copper regions.")
) )
self.clearcopper_label.setMinimumWidth(90) self.clearcopper_label.setMinimumWidth(90)
grid2.addWidget(self.clearcopper_label, 0, 0)
self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool')) self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool'))
self.generate_ncc_button.setToolTip( self.generate_ncc_button.setToolTip(
_("Create the Geometry Object\n" _("Create the Geometry Object\n"
"for non-copper routing.") "for non-copper routing.")
) )
grid2.addWidget(self.generate_ncc_button, 0, 1) grid2.addWidget(self.clearcopper_label, 1, 0)
grid2.addWidget(self.generate_ncc_button, 1, 1)
# ## Board cutout # ## Board cutout
self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Board cutout")) self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Board cutout"))
@ -401,14 +455,14 @@ class GerberObjectUI(ObjectUI):
"the PCB and separate it from\n" "the PCB and separate it from\n"
"the original board.") "the original board.")
) )
grid2.addWidget(self.board_cutout_label, 1, 0)
self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool')) self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool'))
self.generate_cutout_button.setToolTip( self.generate_cutout_button.setToolTip(
_("Generate the geometry for\n" _("Generate the geometry for\n"
"the board cutout.") "the board cutout.")
) )
grid2.addWidget(self.generate_cutout_button, 1, 1) grid2.addWidget(self.board_cutout_label, 2, 0)
grid2.addWidget(self.generate_cutout_button, 2, 1)
# ## Non-copper regions # ## Non-copper regions
self.noncopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions")) self.noncopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
@ -419,10 +473,8 @@ class GerberObjectUI(ObjectUI):
"object. Can be used to remove all\n" "object. Can be used to remove all\n"
"copper from a specified region.") "copper from a specified region.")
) )
self.custom_box.addWidget(self.noncopper_label)
grid4 = QtWidgets.QGridLayout() grid2.addWidget(self.noncopper_label, 3, 0, 1, 2)
self.custom_box.addLayout(grid4)
# Margin # Margin
bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin')) bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
@ -433,9 +485,9 @@ class GerberObjectUI(ObjectUI):
"distance.") "distance.")
) )
bmlabel.setMinimumWidth(90) bmlabel.setMinimumWidth(90)
grid4.addWidget(bmlabel, 0, 0)
self.noncopper_margin_entry = LengthEntry() self.noncopper_margin_entry = LengthEntry()
grid4.addWidget(self.noncopper_margin_entry, 0, 1) grid2.addWidget(bmlabel, 4, 0)
grid2.addWidget(self.noncopper_margin_entry, 4, 1)
# Rounded corners # Rounded corners
self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo")) self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
@ -443,10 +495,10 @@ class GerberObjectUI(ObjectUI):
_("Resulting geometry will have rounded corners.") _("Resulting geometry will have rounded corners.")
) )
self.noncopper_rounded_cb.setMinimumWidth(90) self.noncopper_rounded_cb.setMinimumWidth(90)
grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo')) self.generate_noncopper_button = QtWidgets.QPushButton(_('Generate Geo'))
grid4.addWidget(self.generate_noncopper_button, 1, 1) grid2.addWidget(self.noncopper_rounded_cb, 5, 0)
grid2.addWidget(self.generate_noncopper_button, 5, 1)
# ## Bounding box # ## Bounding box
self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box')) self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
@ -454,10 +506,8 @@ class GerberObjectUI(ObjectUI):
_("Create a geometry surrounding the Gerber object.\n" _("Create a geometry surrounding the Gerber object.\n"
"Square shape.") "Square shape.")
) )
self.custom_box.addWidget(self.boundingbox_label)
grid5 = QtWidgets.QGridLayout() grid2.addWidget(self.boundingbox_label, 6, 0, 1, 2)
self.custom_box.addLayout(grid5)
bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin')) bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
bbmargin.setToolTip( bbmargin.setToolTip(
@ -465,9 +515,9 @@ class GerberObjectUI(ObjectUI):
"to the nearest polygon.") "to the nearest polygon.")
) )
bbmargin.setMinimumWidth(90) bbmargin.setMinimumWidth(90)
grid5.addWidget(bbmargin, 0, 0)
self.bbmargin_entry = LengthEntry() self.bbmargin_entry = LengthEntry()
grid5.addWidget(self.bbmargin_entry, 0, 1) grid2.addWidget(bbmargin, 7, 0)
grid2.addWidget(self.bbmargin_entry, 7, 1)
self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo")) self.bbrounded_cb = FCCheckBox(label=_("Rounded Geo"))
self.bbrounded_cb.setToolTip( self.bbrounded_cb.setToolTip(
@ -477,13 +527,13 @@ class GerberObjectUI(ObjectUI):
"the margin.") "the margin.")
) )
self.bbrounded_cb.setMinimumWidth(90) self.bbrounded_cb.setMinimumWidth(90)
grid5.addWidget(self.bbrounded_cb, 1, 0)
self.generate_bb_button = QtWidgets.QPushButton(_('Generate Geo')) self.generate_bb_button = QtWidgets.QPushButton(_('Generate Geo'))
self.generate_bb_button.setToolTip( self.generate_bb_button.setToolTip(
_("Generate the Geometry object.") _("Generate the Geometry object.")
) )
grid5.addWidget(self.generate_bb_button, 1, 1) grid2.addWidget(self.bbrounded_cb, 8, 0)
grid2.addWidget(self.generate_bb_button, 8, 1)
class ExcellonObjectUI(ObjectUI): class ExcellonObjectUI(ObjectUI):