diff --git a/README.md b/README.md index ce3f27b9..3f2d75a0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ CAD program, and create G-Code for Isolation routing. - each CNCJob object has now it's own text_collection for the annotations which allow for the individual enabling and disabling of the annotations - added new menu category in File -> Backup with two menu entries that duplicate the functions of the export/import preferences buttons from the bottom of the Preferences window - in Excellon Editor fixed the display of the number of slots in the Tool Table after the resize done with the Resize tool -- in Excellon editor -> Resize tool, made sure that when the slot is resized, it's length remain the same, because the tool should influence only the 'thickness' of the slot. Since I don't know anything but the geometry and tool diameter, this is only an approximation and computationally intensive +- in Excellon Editor -> Resize tool, made sure that when the slot is resized, it's length remain the same, because the tool should influence only the 'thickness' of the slot. Since I don't know anything but the geometry and tool diameters (old and new), this is only an approximation and computationally intensive +- in Excellon Editor -> remade the Tool edit made by editing the diameter values in the Tools Table to work for slots too 15.08.2019 diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py index 800ca9ac..0ab26157 100644 --- a/flatcamEditors/FlatCAMExcEditor.py +++ b/flatcamEditors/FlatCAMExcEditor.py @@ -907,11 +907,11 @@ class FCDrillResize(FCShapeTool): origin='center'))) elif isinstance(select_shape.geo, Polygon): # I don't have any info regarding the angle of the slot geometry, nor how thick it is or - # how long it is given the angle. SO I will have to make an approximation because + # how long it is given the angle. So I will have to make an approximation because # we need to conserve the slot length, we only resize the diameter for the tool # Therefore scaling won't work and buffering will not work either. - # First we get the Linestring that is one that the original slot is built around with the4 + # First we get the Linestring that is one that the original slot is built around with the # tool having the diameter sel_dia poly = select_shape.geo xmin, ymin, xmax, ymax = poly.bounds @@ -940,16 +940,17 @@ class FCDrillResize(FCShapeTool): # around them start_pt = Point(cut_line_with_max_length_coords[0]) stop_pt = Point(cut_line_with_max_length_coords[1]) - start_cut_geo = start_pt.buffer(new_dia) - stop_cut_geo = stop_pt.buffer(new_dia) + start_cut_geo = start_pt.buffer(new_dia / 2) + stop_cut_geo = stop_pt.buffer(new_dia / 2) # and we cut the above circle polygons from our line and get in this way a line around # which we can build the new slot by buffering with the new tool diameter new_line = cut_line_with_max_length.difference(start_cut_geo) new_line = new_line.difference(stop_cut_geo) - # create the geometry for the resized slot by buffering with the new diameter value, new_dia - new_poly = new_line.buffer(new_dia) + # create the geometry for the resized slot by buffering with half of the + # new diameter value, new_dia + new_poly = new_line.buffer(new_dia / 2) self.geometry.append(DrawToolShape(new_poly)) else: @@ -985,7 +986,6 @@ class FCDrillResize(FCShapeTool): sel_shapes_to_be_deleted.append(select_shape) - self.draw_app.on_exc_shape_complete(self.destination_storage) # a hack to make the tool_table display more drills/slots per diameter when shape(drill/slot) # is added. @@ -1009,8 +1009,6 @@ class FCDrillResize(FCShapeTool): else: self.draw_app.slot_points_edit[new_dia].append((0, 0)) - self.geometry = [] - for dia_key in list(self.draw_app.storage_dict.keys()): # if following the resize of the drills there will be no more drills for some of the tools then # delete those tools @@ -1034,9 +1032,15 @@ class FCDrillResize(FCShapeTool): for shp in sel_shapes_to_be_deleted: self.draw_app.selected.remove(shp) + # add the new geometry to storage + self.draw_app.on_exc_shape_complete(self.destination_storage) + self.draw_app.build_ui() self.draw_app.replot() + # empty the self.geometry + self.geometry = [] + # we reactivate the signals after the after the tool editing self.draw_app.tools_table_exc.itemChanged.connect(self.draw_app.on_tool_edit) @@ -2467,93 +2471,149 @@ class FlatCAMExcEditor(QtCore.QObject): self.build_ui() def on_tool_edit(self, item_changed): - # if connected, disconnect the signal from the slot on item_changed as it creates issues - self.tools_table_exc.itemChanged.disconnect() - self.tools_table_exc.cellPressed.disconnect() + try: + self.tools_table_exc.itemChanged.disconnect() + except TypeError: + pass + try: + self.tools_table_exc.cellPressed.disconnect() + except TypeError: + pass # self.tools_table_exc.selectionModel().currentChanged.disconnect() self.is_modified = True - current_table_dia_edited = None + new_dia = None if self.tools_table_exc.currentItem() is not None: try: - current_table_dia_edited = float(self.tools_table_exc.currentItem().text()) + new_dia = float(self.tools_table_exc.currentItem().text()) except ValueError as e: log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e)) self.tools_table_exc.setCurrentItem(None) return row_of_item_changed = self.tools_table_exc.currentRow() - # rows start with 0, tools start with 1 so we adjust the value by 1 key_in_tool2tooldia = row_of_item_changed + 1 + old_dia = self.tool2tooldia[key_in_tool2tooldia] - dia_changed = self.tool2tooldia[key_in_tool2tooldia] + # SOURCE storage + source_storage = self.storage_dict[old_dia] + # DESTINATION storage # tool diameter is not used so we create a new tool with the desired diameter - if current_table_dia_edited not in self.olddia_newdia.values(): - # update the dict that holds as keys our initial diameters and as values the edited diameters - self.olddia_newdia[dia_changed] = current_table_dia_edited - # update the dict that holds tool_no as key and tool_dia as value - self.tool2tooldia[key_in_tool2tooldia] = current_table_dia_edited + if new_dia not in self.olddia_newdia: + destination_storage = FlatCAMGeoEditor.make_storage() + self.storage_dict[new_dia] = destination_storage - # update the tool offset - modified_offset = self.exc_obj.tool_offset.pop(dia_changed, None) - if modified_offset is not None: - self.exc_obj.tool_offset[current_table_dia_edited] = modified_offset - - self.replot() + # self.olddia_newdia dict keeps the evidence on current tools diameters as keys and gets updated on values + # each time a tool diameter is edited or added + self.olddia_newdia[new_dia] = new_dia else: # tool diameter is already in use so we move the drills from the prior tool to the new tool - factor = current_table_dia_edited / dia_changed - geometry = [] + destination_storage = self.storage_dict[new_dia] - scaled_geo = [] - for shape_exc in self.storage_dict[dia_changed].get_objects(): - geo_list = [] - if isinstance(shape_exc.geo, MultiLineString) or isinstance(shape_exc.geo, MultiPolygon): - for subgeo in shape_exc.geo: - geo_list.append(affinity.scale(subgeo, xfact=factor, yfact=factor, origin='center')) - scaled_geo = MultiLineString(geo_list) - elif isinstance(shape_exc.geo, Polygon): - scaled_geo = geo_list.append(affinity.scale(shape_exc.geo, - xfact=factor, yfact=factor, origin='center')) + # since we add a new tool, we update also the intial state of the tool_table through it's dictionary + # we add a new entry in the tool2tooldia dict + self.tool2tooldia[len(self.olddia_newdia)] = new_dia - if isinstance(shape_exc.geo, MultiLineString): - # add bogus drill points (for total count of drills) but only if the shape is a MultiLineString - # because the drills are MultiLineString - for k, v in self.olddia_newdia.items(): - if v == current_table_dia_edited: - self.points_edit[k].append((0, 0)) - break + # CHANGE the elements geometry according to the new diameter + factor = new_dia / old_dia + new_geo = Polygon() + for shape_exc in source_storage.get_objects(): + geo_list = [] + if isinstance(shape_exc.geo, MultiLineString): + for subgeo in shape_exc.geo: + geo_list.append(affinity.scale(subgeo, xfact=factor, yfact=factor, origin='center')) + new_geo = MultiLineString(geo_list) + elif isinstance(shape_exc.geo, Polygon): + # I don't have any info regarding the angle of the slot geometry, nor how thick it is or + # how long it is given the angle. So I will have to make an approximation because + # we need to conserve the slot length, we only resize the diameter for the tool + # Therefore scaling won't work and buffering will not work either. + + # First we get the Linestring that is one that the original slot is built around with the + # tool having the diameter sel_dia + poly = shape_exc.geo + xmin, ymin, xmax, ymax = poly.bounds + # a line that is certain to be bigger than our slot because it's the diagonal + # of it's bounding box + poly_diagonal = LineString([(xmin, ymin), (xmax, ymax)]) + poly_centroid = poly.centroid + # center of the slot geometry + poly_center = (poly_centroid.x, poly_centroid.y) + + # make a list of intersections with the rotated line + list_of_cuttings = [] + for angle in range(0, 359, 1): + rot_poly_diagonal = affinity.rotate(poly_diagonal, angle=angle, origin=poly_center) + cut_line = rot_poly_diagonal.intersection(poly) + cut_line_len = cut_line.length + list_of_cuttings.append( + (cut_line_len, cut_line) + ) + # find the cut_line with the maximum length which is the LineString for which the start + # and stop point are the start and stop point of the slot as in the Gerber file + cut_line_with_max_length = max(list_of_cuttings, key=lambda i: i[0])[1] + # find the coordinates of this line + cut_line_with_max_length_coords = list(cut_line_with_max_length.coords) + # extract the first and last point of the line and build some buffered polygon circles + # around them + start_pt = Point(cut_line_with_max_length_coords[0]) + stop_pt = Point(cut_line_with_max_length_coords[1]) + start_cut_geo = start_pt.buffer(new_dia / 2) + stop_cut_geo = stop_pt.buffer(new_dia / 2) + + # and we cut the above circle polygons from our line and get in this way a line around + # which we can build the new slot by buffering with the new tool diameter + new_line = cut_line_with_max_length.difference(start_cut_geo) + new_line = new_line.difference(stop_cut_geo) + + # create the geometry for the resized slot by buffering with half of the + # new diameter value: new_dia + new_geo = new_line.buffer(new_dia / 2) + + try: + self.points_edit.pop(old_dia, None) + except KeyError: + pass + try: + self.slot_points_edit.pop(old_dia, None) + except KeyError: + pass + + # add bogus drill/slots points (for total count of drills/slots) + # for drills + if isinstance(shape_exc.geo, MultiLineString): + if new_dia not in self.points_edit: + self.points_edit[new_dia] = [(0, 0)] else: - # the shape is a Polygon or MultiPolygon therefore we have to increase the slot numbers - for k, v in self.olddia_newdia.items(): - if v == current_table_dia_edited: - self.slot_points_edit[k].append((0, 0)) - break + self.points_edit[new_dia].append((0, 0)) - geometry.append(DrawToolShape(scaled_geo)) + # for slots + if isinstance(shape_exc.geo, Polygon): + if new_dia not in self.slot_points_edit: + self.slot_points_edit[new_dia] = [(0, 0)] + else: + self.slot_points_edit[new_dia].append((0, 0)) - # search for the old dia that correspond to the new dia and add the drills/slots in it's storage - # everything will be sorted out later, when the edited Excellon is updated - for k, v in self.olddia_newdia.items(): - if v == current_table_dia_edited: - self.add_exc_shape(geometry, self.storage_dict[k]) - break + self.add_exc_shape(shape=DrawToolShape(new_geo), storage=destination_storage) - # delete the old tool from which we moved the drills - self.on_tool_delete(dia=dia_changed) + # update the UI and the CANVAS + self.build_ui() + self.replot() - # delete the tool offset - self.exc_obj.tool_offset.pop(dia_changed, None) + # delete the old tool + self.on_tool_delete(dia=old_dia) # we reactivate the signals after the after the tool editing self.tools_table_exc.itemChanged.connect(self.on_tool_edit) self.tools_table_exc.cellPressed.connect(self.on_row_selected) + self.app.inform.emit(_("[success] Done. Tool edit completed.")) + # self.tools_table_exc.selectionModel().currentChanged.connect(self.on_row_selected) def on_name_activate(self):