diff --git a/FlatCAMApp.py b/FlatCAMApp.py index a9c3b139..0639a168 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1090,9 +1090,9 @@ class App(QtCore.QObject): if user_defaults: self.load_defaults(filename='current_defaults') - # ########################### - # #### APPLY APP LANGUAGE ### - # ########################### + # ############################################################# + # ############## APPLY APP LANGUAGE ########################### + # ############################################################# 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) log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize()) - # ################################## - # ### CREATE UNIQUE SERIAL NUMBER ## - # ################################## + # ############################################################## + # ################# CREATE UNIQUE SERIAL NUMBER ################ + # ############################################################## chars = 'abcdefghijklmnopqrstuvwxyz0123456789' if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10: @@ -1495,20 +1495,27 @@ class App(QtCore.QObject): # ############### Signal handling ################ # ################################################ - # ### Custom signals ### + # ############# Custom signals ################## + + # signal for displaying messages in status bar self.inform.connect(self.info) + # signal to be called when the app is quiting self.app_quit.connect(self.quit_application) self.message.connect(self.message_dialog) 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_changed.connect(self.on_object_changed) self.object_plotted.connect(self.on_object_plotted) 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(lambda kind, filename: self.register_folder(filename)) self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename)) - # ### Standard signals + # ############# Standard signals ################### # ### Menu self.ui.menufilenewproject.triggered.connect(self.on_file_new_click) 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( lambda: self.on_toggle_units(no_pref=False)) - # ############################## - # ### GUI PREFERENCES SIGNALS ## - # ############################## + # ############################################################### + # ################### GUI COLORS SIGNALS ######################## + # ############################################################### # Setting plot colors signals 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.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.workspace_cb.stateChanged.connect(self.on_workspace) 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.on_cnc_custom_parameters) 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.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.buttonOpen.clicked.connect(self.handleOpen) self.ui.buttonSave.clicked.connect(self.handleSaveGCode) @@ -1760,7 +1769,7 @@ class App(QtCore.QObject): self.ui.buttonFind.clicked.connect(self.handleFindGCode) 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) # Object list @@ -1791,8 +1800,15 @@ class App(QtCore.QObject): # connect the abort_all_tasks related slots to the related signals self.proc_container.idle_flag.connect(self.app_is_idle) + # ##################################################################################### + # ########### 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 self.tool_tab_locked = False @@ -1802,18 +1818,14 @@ class App(QtCore.QObject): else: self.ui.splitter.setSizes([0, 1]) - # ########################################### - # ################# Other setups ############ - # ########################################### - # Sets up FlatCAMObj, FCProcess and FCProcessContainer. self.setup_obj_classes() self.setup_recent_items() self.setup_component_editor() - # ########################################### - # #######Auto-complete KEYWORDS ############# - # ########################################### + # ##################################################################################### + # ######################### Auto-complete KEYWORDS #################################### + # ##################################################################################### self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle', 'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout', '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 - # ########################################### - # ########### Shell SETUP ################### - # ########################################### + # #################################################################################### + # ####################### Shell SETUP ################################################ + # #################################################################################### self.shell = FCShell(self, version=self.version) self.shell._edit.set_model_data(self.myKeywords) @@ -2058,9 +2070,9 @@ class App(QtCore.QObject): else: self.ui.shell_dock.hide() - # ########################################### - # ######### Tools and Plugins ############### - # ########################################### + # ################################################################################## + # ###################### Tools and Plugins ######################################### + # ################################################################################## self.dblsidedtool = None self.measurement_tool = None @@ -2086,9 +2098,9 @@ class App(QtCore.QObject): # self.f_parse = ParseFont(self) # self.parse_system_fonts() - # ############################################### - # ######## START-UP ARGUMENTS ################### - # ############################################### + # ##################################################################################### + # ########################## START-UP ARGUMENTS ####################################### + # ##################################################################################### # test if the program was started with a script as parameter if self.cmd_line_shellfile: @@ -2100,9 +2112,9 @@ class App(QtCore.QObject): print("ERROR: ", ext) sys.exit(2) - # ############################################### - # ############# Check for updates ############### - # ############################################### + # ##################################################################################### + # ######################## Check for updates ########################################## + # ##################################################################################### # Separate thread (Not worker) # 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': []}) 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 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 self.abort_flag = False - # ######################################################### - # ### Save defaults to factory_defaults.FlatConfig file ### - # ### It's done only once after install ################### - # ######################################################### + # ############################################################################### + # ############# Save defaults to factory_defaults.FlatConfig file ############### + # ############# It's done only once after install ############### + # ############################################################################### factory_file = open(self.data_path + '/factory_defaults.FlatConfig') fac_def_from_file = factory_file.read() factory_defaults = json.loads(fac_def_from_file) @@ -2223,9 +2235,9 @@ class App(QtCore.QObject): filename_factory = self.data_path + '/factory_defaults.FlatConfig' 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 # 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.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")) # after the first run, this object should be 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 # the path/file_name must be enclosed in quotes if it contain spaces if App.args: diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 1c4de08f..d926ea92 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -339,6 +339,10 @@ class FlatCAMObj(QtCore.QObject): key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, **kwargs) return key + @staticmethod + def poly2rings(poly): + return [poly.exterior] + [interior for interior in poly.interiors] + @property def visible(self): 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.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 if self.app.defaults["global_app_level"] == 'b': self.ui.level.setText(_( @@ -563,12 +572,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber): self.ui.generate_ext_iso_button.hide() self.ui.generate_int_iso_button.hide() self.ui.follow_cb.hide() - self.ui.padding_area_label.show() + self.ui.except_cb.setChecked(False) + self.ui.except_cb.hide() else: self.ui.level.setText(_( 'Advanced' )) - self.ui.padding_area_label.hide() # add the shapes storage for marking apertures for ap_code in self.apertures: @@ -579,6 +588,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber): 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): FlatCAMObj.build_ui(self) @@ -881,23 +895,22 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if invert: try: - try: - pl = [] - for p in geom: - if p is not None: - if isinstance(p, Polygon): - pl.append(Polygon(p.exterior.coords[::-1], p.interiors)) - elif isinstance(p, LinearRing): - pl.append(Polygon(p.coords[::-1])) - geom = MultiPolygon(pl) - except TypeError: - if isinstance(geom, Polygon) and geom is not None: - geom = Polygon(geom.exterior.coords[::-1], geom.interiors) - elif isinstance(geom, LinearRing) and geom is not None: - geom = Polygon(geom.coords[::-1]) - else: - log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" % - type(geom)) + pl = [] + for p in geom: + if p is not None: + if isinstance(p, Polygon): + pl.append(Polygon(p.exterior.coords[::-1], p.interiors)) + elif isinstance(p, LinearRing): + pl.append(Polygon(p.coords[::-1])) + geom = MultiPolygon(pl) + except TypeError: + if isinstance(geom, Polygon) and geom is not None: + geom = Polygon(geom.exterior.coords[::-1], geom.interiors) + elif isinstance(geom, LinearRing) and geom is not None: + geom = Polygon(geom.coords[::-1]) + else: + log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" % + type(geom)) except Exception as e: log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e)) return 'fail' @@ -924,6 +937,54 @@ class FlatCAMGerber(FlatCAMObj, Gerber): # if float(self.options["isotooldia"]) < 0: # 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 self.iso_type == 0: iso_name = self.options["name"] + "_ext_iso" @@ -1001,21 +1062,24 @@ class FlatCAMGerber(FlatCAMObj, Gerber): for g in geo_obj.solid_geometry: if g: - app_obj.inform.emit(_( - "[success] Isolation geometry created: %s" - ) % geo_obj.options["name"]) break else: empty_cnt += 1 if empty_cnt == len(geo_obj.solid_geometry): raise ValidationError("Empty Geometry", None) - - # even if combine is checked, one pass is still singlegeo - if passes > 1: - geo_obj.multigeo = True 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? self.app.new_object("geometry", iso_name, iso_init) @@ -1065,16 +1129,23 @@ class FlatCAMGerber(FlatCAMObj, Gerber): for g in geo_obj.solid_geometry: if g: - app_obj.inform.emit(_( - "[success] Isolation geometry created: %s" - ) % geo_obj.options["name"]) 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(): + geo_obj.solid_geometry = 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) diff --git a/README.md b/README.md index 2f15c867..14ef7d38 100644 --- a/README.md +++ b/README.md @@ -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 - 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 +- 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 diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index a1b85f98..d0bfbbab 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -1524,6 +1524,45 @@ class OptionalInputSection: 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): def __init__(self, parent=None): super(FCTable, self).__init__(parent) diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index d0e178ae..c9c6d598 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -254,8 +254,13 @@ class GerberObjectUI(ObjectUI): ) self.custom_box.addWidget(self.isolation_routing_label) + # ########################################### + # ########## NEW GRID ####################### + # ########################################### + grid1 = QtWidgets.QGridLayout() self.custom_box.addLayout(grid1) + tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia')) tdlabel.setToolTip( _("Diameter of the cutting tool.\n" @@ -265,9 +270,9 @@ class GerberObjectUI(ObjectUI): "this parameter.") ) tdlabel.setMinimumWidth(90) - grid1.addWidget(tdlabel, 0, 0) 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.setToolTip( @@ -275,10 +280,10 @@ class GerberObjectUI(ObjectUI): "number (integer) of tool widths.") ) passlabel.setMinimumWidth(90) - grid1.addWidget(passlabel, 1, 0) self.iso_width_entry = FCSpinner() 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.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.") ) overlabel.setMinimumWidth(90) - grid1.addWidget(overlabel, 2, 0) 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 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" "- 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'}, {'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 self.combine_passes_cb = FCCheckBox(label=_('Combine Passes')) self.combine_passes_cb.setToolTip( _("Combine all passes into one object") ) - grid1.addWidget(self.combine_passes_cb, 4, 0) # generate follow self.follow_cb = FCCheckBox(label=_('"Follow"')) - 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.setToolTip(_("Generate a 'Follow' geometry.\n" + "This means that it will cut through\n" + "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.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("%s:" % _("Generate Isolation Geometry")) self.gen_iso_label.setToolTip( @@ -332,14 +379,7 @@ class GerberObjectUI(ObjectUI): "inside the actual Gerber feature, use a negative tool\n" "diameter above.") ) - self.custom_box.addWidget(self.gen_iso_label) - - 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) + grid1.addWidget(self.gen_iso_label, 6, 0, 1, 3) self.generate_iso_button = QtWidgets.QPushButton(_('FULL Geo')) self.generate_iso_button.setToolTip( @@ -347,10 +387,10 @@ class GerberObjectUI(ObjectUI): "for isolation routing. It contains both\n" "the interiors and exteriors geometry.") ) - self.generate_iso_button.setMinimumWidth(90) - hlay_1.addWidget(self.generate_iso_button, alignment=Qt.AlignLeft) + grid1.addWidget(self.generate_iso_button, 7, 0) - # 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.setToolTip( @@ -370,14 +410,28 @@ class GerberObjectUI(ObjectUI): # self.generate_ext_iso_button.setMinimumWidth(90) 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 # 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.generate_int_iso_button, self.generate_ext_iso_button], logic=False) + grid1.addWidget(QtWidgets.QLabel(''), 8, 0) + + # ########################################### + # ########## NEW GRID ####################### + # ########################################### + grid2 = QtWidgets.QGridLayout() self.custom_box.addLayout(grid2) + self.tool_lbl = QtWidgets.QLabel('%s:' % _("TOOLS")) + grid2.addWidget(self.tool_lbl, 0, 0, 1, 2) + # ## Clear non-copper regions self.clearcopper_label = QtWidgets.QLabel("%s:" % _("Clear N-copper")) self.clearcopper_label.setToolTip( @@ -385,14 +439,14 @@ class GerberObjectUI(ObjectUI): "toolpaths to cut all non-copper regions.") ) self.clearcopper_label.setMinimumWidth(90) - grid2.addWidget(self.clearcopper_label, 0, 0) self.generate_ncc_button = QtWidgets.QPushButton(_('NCC Tool')) self.generate_ncc_button.setToolTip( _("Create the Geometry Object\n" "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 self.board_cutout_label = QtWidgets.QLabel("%s:" % _("Board cutout")) @@ -401,14 +455,14 @@ class GerberObjectUI(ObjectUI): "the PCB and separate it from\n" "the original board.") ) - grid2.addWidget(self.board_cutout_label, 1, 0) self.generate_cutout_button = QtWidgets.QPushButton(_('Cutout Tool')) self.generate_cutout_button.setToolTip( _("Generate the geometry for\n" "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 self.noncopper_label = QtWidgets.QLabel("%s:" % _("Non-copper regions")) @@ -419,10 +473,8 @@ class GerberObjectUI(ObjectUI): "object. Can be used to remove all\n" "copper from a specified region.") ) - self.custom_box.addWidget(self.noncopper_label) - grid4 = QtWidgets.QGridLayout() - self.custom_box.addLayout(grid4) + grid2.addWidget(self.noncopper_label, 3, 0, 1, 2) # Margin bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin')) @@ -433,9 +485,9 @@ class GerberObjectUI(ObjectUI): "distance.") ) bmlabel.setMinimumWidth(90) - grid4.addWidget(bmlabel, 0, 0) 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 self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo")) @@ -443,10 +495,10 @@ class GerberObjectUI(ObjectUI): _("Resulting geometry will have rounded corners.") ) self.noncopper_rounded_cb.setMinimumWidth(90) - grid4.addWidget(self.noncopper_rounded_cb, 1, 0) 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 self.boundingbox_label = QtWidgets.QLabel('%s:' % _('Bounding Box')) @@ -454,10 +506,8 @@ class GerberObjectUI(ObjectUI): _("Create a geometry surrounding the Gerber object.\n" "Square shape.") ) - self.custom_box.addWidget(self.boundingbox_label) - grid5 = QtWidgets.QGridLayout() - self.custom_box.addLayout(grid5) + grid2.addWidget(self.boundingbox_label, 6, 0, 1, 2) bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin')) bbmargin.setToolTip( @@ -465,9 +515,9 @@ class GerberObjectUI(ObjectUI): "to the nearest polygon.") ) bbmargin.setMinimumWidth(90) - grid5.addWidget(bbmargin, 0, 0) 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.setToolTip( @@ -477,13 +527,13 @@ class GerberObjectUI(ObjectUI): "the margin.") ) self.bbrounded_cb.setMinimumWidth(90) - grid5.addWidget(self.bbrounded_cb, 1, 0) self.generate_bb_button = QtWidgets.QPushButton(_('Generate Geo')) self.generate_bb_button.setToolTip( _("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):