diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 8b937f77..e8b8fab0 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -4306,6 +4306,9 @@ class FlatCAMExcEditor(QtCore.QObject): spec = {"C": float(tool_dia[0])} self.new_tools[name] = spec + # add in self.tools the 'solid_geometry' key, the value (a list) is populated bellow + self.new_tools[name]['solid_geometry'] = [] + # create the self.drills for the new Excellon object (the one with edited content) for point in tool_dia[1]: self.new_drills.append( @@ -4314,6 +4317,9 @@ class FlatCAMExcEditor(QtCore.QObject): 'tool': str(current_tool) } ) + # repopulate the 'solid_geometry' for each tool + poly = Point(point).buffer(float(tool_dia[0]) / 2.0, int(int(exc_obj.geo_steps_per_circle) / 4)) + self.new_tools[name]['solid_geometry'].append(poly) if self.is_modified is True: if "_edit" in self.edited_obj_name: diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 306f78ea..16d417e5 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1134,10 +1134,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): t_offset = self.app.defaults['excellon_offset'] tool_offset_item = QtWidgets.QTableWidgetItem('%s' % str(t_offset)) + plot_item = FCCheckBox() + plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) + if self.ui.plot_cb.isChecked(): + plot_item.setChecked(True) + self.ui.tools_table.setItem(self.tool_row, 1, dia) # Diameter self.ui.tools_table.setItem(self.tool_row, 2, drill_count) # Number of drills per tool self.ui.tools_table.setItem(self.tool_row, 3, slot_count) # Number of drills per tool self.ui.tools_table.setItem(self.tool_row, 4, tool_offset_item) # Tool offset + self.ui.tools_table.setCellWidget(self.tool_row, 5, plot_item) self.tool_row += 1 @@ -1201,12 +1207,28 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) horizontal_header = self.ui.tools_table.horizontalHeader() - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(5, 17) + self.ui.tools_table.setColumnWidth(5, 17) + # horizontal_header.setStretchLastSection(True) + + + + # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents) + + # horizontal_header.setStretchLastSection(True) + self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.ui.tools_table.setSortingEnabled(False) self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight()) @@ -1233,6 +1255,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated self.ui.tools_table.itemChanged.connect(self.on_tool_offset_edit) + self.ui_connect() + def set_ui(self, ui): """ Configures the user interface for this object. @@ -1297,6 +1321,24 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed) + def ui_connect(self): + + for row in range(self.ui.tools_table.rowCount() - 2): + self.ui.tools_table.cellWidget(row, 5).clicked.connect(self.on_plot_cb_click_table) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + + def ui_disconnect(self): + for row in range(self.ui.tools_table.rowCount()): + try: + self.ui.tools_table.cellWidget(row, 5).clicked.disconnect() + except: + pass + + try: + self.ui.plot_cb.stateChanged.disconnect() + except: + pass + def on_tool_offset_edit(self): # if connected, disconnect the signal from the slot on item_changed as it creates issues self.ui.tools_table.itemChanged.disconnect() @@ -1351,8 +1393,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): """ table_tools_items = [] for x in self.ui.tools_table.selectedItems(): + # from the columnCount we subtract a value of 1 which represent the last column (plot column) + # which does not have text table_tools_items.append([self.ui.tools_table.item(x.row(), column).text() - for column in range(0, self.ui.tools_table.columnCount())]) + for column in range(0, self.ui.tools_table.columnCount() - 1)]) for item in table_tools_items: item[0] = str(item[0]) return table_tools_items @@ -1839,17 +1883,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # self.app.worker.add_task(job_thread, [self.app]) self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - def on_plot_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('plot') - - def on_solid_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('solid') - self.plot() - def convert_units(self, units): factor = Excellon.convert_units(self, units) @@ -1875,6 +1908,89 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): self.options['startz'] = float(self.options['startz']) * factor self.options['endz'] = float(self.options['endz']) * factor + def on_solid_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('solid') + self.plot() + + def on_plot_cb_click(self, *args): + if self.muted_ui: + return + self.plot() + self.read_form_item('plot') + + self.ui_disconnect() + cb_flag = self.ui.plot_cb.isChecked() + for row in range(self.ui.tools_table.rowCount() - 2): + table_cb = self.ui.tools_table.cellWidget(row, 5) + if cb_flag: + table_cb.setChecked(True) + else: + table_cb.setChecked(False) + + self.ui_connect() + + def on_plot_cb_click_table(self): + # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) + self.ui_disconnect() + # cw = self.sender() + # cw_index = self.ui.tools_table.indexAt(cw.pos()) + # cw_row = cw_index.row() + check_row = 0 + + self.shapes.clear(update=True) + for tool_key in self.tools: + solid_geometry = self.tools[tool_key]['solid_geometry'] + + # find the geo_tool_table row associated with the tool_key + for row in range(self.ui.tools_table.rowCount()): + tool_item = int(self.ui.tools_table.item(row, 0).text()) + if tool_item == int(tool_key): + check_row = row + break + if self.ui.tools_table.cellWidget(check_row, 5).isChecked(): + self.options['plot'] = True + # self.plot_element(element=solid_geometry, visible=True) + # Plot excellon (All polygons?) + if self.options["solid"]: + for geo in solid_geometry: + self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', + visible=self.options['plot'], + layer=2) + else: + for geo in solid_geometry: + self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot']) + for ints in geo.interiors: + self.add_shape(shape=ints, color='green', visible=self.options['plot']) + self.shapes.redraw() + + # make sure that the general plot is disabled if one of the row plot's are disabled and + # if all the row plot's are enabled also enable the general plot checkbox + cb_cnt = 0 + total_row = self.ui.tools_table.rowCount() + for row in range(total_row - 2): + if self.ui.tools_table.cellWidget(row, 5).isChecked(): + cb_cnt += 1 + else: + cb_cnt -= 1 + if cb_cnt < total_row - 2: + self.ui.plot_cb.setChecked(False) + else: + self.ui.plot_cb.setChecked(True) + self.ui_connect() + + # def plot_element(self, element, color='red', visible=None, layer=None): + # + # visible = visible if visible else self.options['plot'] + # + # try: + # for sub_el in element: + # self.plot_element(sub_el) + # + # except TypeError: # Element is not iterable... + # self.add_shape(shape=element, color=color, visible=visible, layer=0) + def plot(self): # Does all the required setup and returns False @@ -3971,62 +4087,65 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', 'endz', 'toolchangez'] - temp_tools_dict = {} - tool_dia_copy = {} - data_copy = {} - for tooluid_key, tooluid_value in self.tools.items(): - for dia_key, dia_value in tooluid_value.items(): - if dia_key == 'tooldia': - dia_value *= factor - dia_value = float('%.4f' % dia_value) - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset_value': - dia_value *= factor - tool_dia_copy[dia_key] = dia_value + if isinstance(self, FlatCAMGeometry): + temp_tools_dict = {} + tool_dia_copy = {} + data_copy = {} + for tooluid_key, tooluid_value in self.tools.items(): + for dia_key, dia_value in tooluid_value.items(): + if dia_key == 'tooldia': + dia_value *= factor + dia_value = float('%.4f' % dia_value) + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset_value': + dia_value *= factor + tool_dia_copy[dia_key] = dia_value - # convert the value in the Custom Tool Offset entry in UI - try: - custom_offset = float(self.ui.tool_offset_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return + # convert the value in the Custom Tool Offset entry in UI + custom_offset = None try: - custom_offset = float(self.ui.tool_offset_entry.get_value().replace(',', '.') - ) + custom_offset = float(self.ui.tool_offset_entry.get_value()) except ValueError: - self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " - "use a number.") - return + # try to convert comma to decimal point. if it's still not working error message and return + try: + custom_offset = float(self.ui.tool_offset_entry.get_value().replace(',', '.') + ) + except ValueError: + self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " + "use a number.") + return + except TypeError: + pass - if custom_offset: - custom_offset *= factor - self.ui.tool_offset_entry.set_value(custom_offset) + if custom_offset: + custom_offset *= factor + self.ui.tool_offset_entry.set_value(custom_offset) - if dia_key == 'type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'tool_type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'data': - for data_key, data_value in dia_value.items(): - # convert the form fields that are convertible - for param in param_list: - if data_key == param and data_value is not None: - data_copy[data_key] = data_value * factor - # copy the other dict entries that are not convertible - if data_key not in param_list: - data_copy[data_key] = data_value - tool_dia_copy[dia_key] = copy.deepcopy(data_copy) - data_copy.clear() + if dia_key == 'type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'tool_type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'data': + for data_key, data_value in dia_value.items(): + # convert the form fields that are convertible + for param in param_list: + if data_key == param and data_value is not None: + data_copy[data_key] = data_value * factor + # copy the other dict entries that are not convertible + if data_key not in param_list: + data_copy[data_key] = data_value + tool_dia_copy[dia_key] = copy.deepcopy(data_copy) + data_copy.clear() - temp_tools_dict.update({ - tooluid_key: copy.deepcopy(tool_dia_copy) - }) - tool_dia_copy.clear() + temp_tools_dict.update({ + tooluid_key: copy.deepcopy(tool_dia_copy) + }) + tool_dia_copy.clear() - - self.tools.clear() - self.tools = copy.deepcopy(temp_tools_dict) + self.tools.clear() + self.tools = copy.deepcopy(temp_tools_dict) # if there is a value in the new tool field then convert that one too tooldia = self.ui.addtool_entry.get_value() @@ -4188,6 +4307,27 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): ''' self.cnc_tools = {} + ''' + This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the + diameter of the tools and the value is another dict that will hold the data under the following form: + {tooldia: { + 'tool': int, + 'nr_drills': int, + 'nr_slots': int, + 'offset': float, + 'data': {} # a dict to hold the parameters + 'gcode': "" # a string with the actual GCODE + 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move) + 'solid_geometry': [] + }, + ... + } + It is populated in the FlatCAMExcellon.on_create_cncjob_click() but actually + it's done in camlib.Excellon.generate_from_excellon_by_tool() + BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ... + ''' + self.exc_cnc_tools = {} + # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool # (like the one in the TCL Command), False self.multitool = False @@ -4223,10 +4363,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it if self.cnc_tools: self.ui.cnc_tools_table.show() - self.ui.plot_options_label.show() else: self.ui.cnc_tools_table.hide() - self.ui.plot_options_label.hide() + offset = 0 tool_idx = 0 diff --git a/ObjectUI.py b/ObjectUI.py index 00116f40..746c5de3 100644 --- a/ObjectUI.py +++ b/ObjectUI.py @@ -388,16 +388,12 @@ class ExcellonObjectUI(ObjectUI): grid0 = QtWidgets.QGridLayout() self.custom_box.addLayout(grid0) - self.plot_cb = FCCheckBox(label='Plot') - self.plot_cb.setToolTip( - "Plot (show) this object." - ) - grid0.addWidget(self.plot_cb, 0, 0) + self.solid_cb = FCCheckBox(label='Solid') self.solid_cb.setToolTip( "Solid circles." ) - grid0.addWidget(self.solid_cb, 0, 1) + grid0.addWidget(self.solid_cb, 0, 0) # add a frame and inside add a vertical box layout. Inside this vbox layout I add all the Drills widgets # this way I can hide/show the frame @@ -408,19 +404,31 @@ class ExcellonObjectUI(ObjectUI): self.tools_box.setContentsMargins(0, 0, 0, 0) self.drills_frame.setLayout(self.tools_box) + hlay_plot = QtWidgets.QHBoxLayout() + self.tools_box.addLayout(hlay_plot) + #### Tools Drills #### self.tools_table_label = QtWidgets.QLabel('Tools Table') self.tools_table_label.setToolTip( "Tools in this Excellon object\n" "when are used for drilling." ) - self.tools_box.addWidget(self.tools_table_label) + hlay_plot.addWidget(self.tools_table_label) + + # Plot CB + self.plot_cb = FCCheckBox('Plot Object') + self.plot_cb.setToolTip( + "Plot (show) this object." + ) + self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft) + hlay_plot.addStretch() + hlay_plot.addWidget(self.plot_cb) self.tools_table = FCTable() self.tools_box.addWidget(self.tools_table) - self.tools_table.setColumnCount(5) - self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S', 'Offset']) + self.tools_table.setColumnCount(6) + self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S', 'Offset', 'P']) self.tools_table.setSortingEnabled(False) self.tools_table.horizontalHeaderItem(0).setToolTip( @@ -440,6 +448,8 @@ class ExcellonObjectUI(ObjectUI): "Some drill bits (the larger ones) need to drill deeper\n" "to create the desired exit hole diameter due of the tip shape.\n" "The value here can compensate the Cut Z parameter.") + self.tools_table.horizontalHeaderItem(5).setToolTip( + "Toggle display of the drills for the current tool.") self.empty_label = QtWidgets.QLabel('') self.tools_box.addWidget(self.empty_label) diff --git a/README.md b/README.md index 374f770f..89c765af 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,16 @@ CAD program, and create G-Code for Isolation routing. 12.02.2019 - whenever a FlatCAM tool is activated, if the notebook side is hidden it will be unhidden -- reactivated the Voronoi classed +- reactivated the Voronoi classes - added a new parameter named Offset in the Excellon tool table - work in progress - finished work on Offset parameter in Excellon Object (Excellon Editor, camlib, FlatCAMObj updated to take this param in consideration) - fixed a bug where in Excellon editor when editing a file, a tool was automatically added. That is supposed to happen only for empty newly created Excellon Objects. - starting to work on storing the solid_geometry for each tool in part in Excellon Object +- stored solid_geometry of Excellon object in the self.tools dictionary +- finished the solid_geometry restore after edit in Excellon Editor +- finished plotting selection for each tool in the Excellon Tool Table +- fixed the camlib.Excellon.bounds() function for the new type of Excellon geometry therefore fixed the canvas selection, too + 10.02.2019 diff --git a/camlib.py b/camlib.py index d9e968db..17443ee8 100644 --- a/camlib.py +++ b/camlib.py @@ -4073,7 +4073,12 @@ class Excellon(Geometry): :return: None """ self.solid_geometry = [] + try: + # clear the solid_geometry in self.tools + for tool in self.tools: + self.tools[tool]['solid_geometry'][:] = [] + for drill in self.drills: # poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0) if drill['tool'] is '': @@ -4096,7 +4101,7 @@ class Excellon(Geometry): lines_string = LineString([start, stop]) poly = lines_string.buffer(slot_tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4)) # self.solid_geometry.append(poly) - self.tools[drill['tool']]['solid_geometry'].append(poly) + self.tools[slot['tool']]['solid_geometry'].append(poly) except Exception as e: log.debug("Excellon geometry creation failed due of ERROR: %s" % str(e)) @@ -4139,9 +4144,9 @@ class Excellon(Geometry): # now it can get bounds for nested lists of objects log.debug("Excellon() -> bounds()") - if self.solid_geometry is None: - log.debug("solid_geometry is None") - return 0, 0, 0, 0 + # if self.solid_geometry is None: + # log.debug("solid_geometry is None") + # return 0, 0, 0, 0 def bounds_rec(obj): if type(obj) is list: @@ -4169,8 +4174,19 @@ class Excellon(Geometry): # it's a Shapely object, return it's bounds return obj.bounds - bounds_coords = bounds_rec(self.solid_geometry) - return bounds_coords + minx_list = [] + miny_list = [] + maxx_list = [] + maxy_list = [] + + for tool in self.tools: + minx, miny, maxx, maxy = bounds_rec(self.tools[tool]['solid_geometry']) + minx_list.append(minx) + miny_list.append(miny) + maxx_list.append(maxx) + maxy_list.append(maxy) + + return (min(minx_list), min(miny_list), max(maxx_list), max(maxy_list)) def convert_units(self, units): """ @@ -5535,7 +5551,7 @@ class CNCjob(Geometry): return "fail" gobj = self.codes_split(line) - + print(gobj) ## Units if 'G' in gobj and (gobj['G'] == 20.0 or gobj['G'] == 21.0): self.units = {20.0: "IN", 21.0: "MM"}[gobj['G']]