From 251dfd3fb79b9a0e0cf0ffcabaa3f0687dd63948 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 27 Jan 2020 04:12:46 +0200 Subject: [PATCH] - in Geometry Editor made sure that on final save, for MultiLineString geometry all the connected lines are merged into one LineString to minimize the number of vertical movements in GCode - more work in Punch Gerber Tool --- README.md | 5 ++ flatcamEditors/FlatCAMGeoEditor.py | 23 +++-- flatcamTools/ToolPunchGerber.py | 137 +++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fa51f802..5d35f12f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +27.01.2020 + +- in Geometry Editor made sure that on final save, for MultiLineString geometry all the connected lines are merged into one LineString to minimize the number of vertical movements in GCode +- more work in Punch Gerber Tool + 24.02.2020 - small changes to the Toolchange manual preprocessor diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index 8e12c935..4dbf6389 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -23,7 +23,7 @@ from flatcamParsers.ParseFont import * import FlatCAMApp from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon -from shapely.ops import cascaded_union, unary_union +from shapely.ops import cascaded_union, unary_union, linemerge import shapely.affinity as affinity from shapely.geometry.polygon import orient @@ -2498,6 +2498,9 @@ class FCSelect(DrawTool): raise return "" + def clean_up(self): + pass + class FCExplode(FCShapeTool): def __init__(self, draw_app): @@ -3739,8 +3742,7 @@ class FlatCAMGeoEditor(QtCore.QObject): ) ) else: - geo_to_edit = self.flatten(geometry=fcgeometry.solid_geometry, - orient_val=milling_type) + geo_to_edit = self.flatten(geometry=fcgeometry.solid_geometry, orient_val=milling_type) for shape in geo_to_edit: if shape is not None: # TODO: Make flatten never create a None @@ -4396,13 +4398,24 @@ class FlatCAMGeoEditor(QtCore.QObject): fcgeometry.tools[self.multigeo_tool]['solid_geometry'] = [] # for shape in self.shape_buffer: for shape in self.storage.get_objects(): - fcgeometry.tools[self.multigeo_tool]['solid_geometry'].append(shape.geo) + new_geo = shape.geo + + # simplify the MultiLineString + if isinstance(new_geo, MultiLineString): + new_geo = linemerge(new_geo) + + fcgeometry.tools[self.multigeo_tool]['solid_geometry'].append(new_geo) self.multigeo_tool = None fcgeometry.solid_geometry = [] # for shape in self.shape_buffer: for shape in self.storage.get_objects(): - fcgeometry.solid_geometry.append(shape.geo) + new_geo = shape.geo + + # simplify the MultiLineString + if isinstance(new_geo, MultiLineString): + new_geo = linemerge(new_geo) + fcgeometry.solid_geometry.append(new_geo) def update_options(self, obj): if self.paint_tooldia: diff --git a/flatcamTools/ToolPunchGerber.py b/flatcamTools/ToolPunchGerber.py index 3a1dcf79..b52058ef 100644 --- a/flatcamTools/ToolPunchGerber.py +++ b/flatcamTools/ToolPunchGerber.py @@ -379,6 +379,7 @@ class ToolPunchGerber(FlatCAMTool): # ## Signals self.method_punch.activated_custom.connect(self.on_method) self.reset_button.clicked.connect(self.set_tool_ui) + self.punch_object_button.clicked.connect(self.on_generate_object) self.circular_cb.stateChanged.connect( lambda state: @@ -493,6 +494,142 @@ class ToolPunchGerber(FlatCAMTool): except (AttributeError, TypeError): pass + def on_generate_object(self): + + # get the Gerber file who is the source of the punched Gerber + selection_index = self.gerber_object_combo.currentIndex() + model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex()) + + try: + grb_obj = model_index.internalPointer().obj + except Exception: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) + return + + name = grb_obj.options['name'].rpartition('.')[0] + outname = name + "_punched" + + punch_method = self.method_punch.get_value() + + if punch_method == 'exc': + + # get the Excellon file whose geometry will create the punch holes + selection_index = self.exc_combo.currentIndex() + model_index = self.app.collection.index(selection_index, 0, self.exc_combo.rootModelIndex()) + + try: + exc_obj = model_index.internalPointer().obj + except Exception: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ...")) + return + + # this is the punching geometry + exc_solid_geometry = MultiPolygon(exc_obj.solid_geometry) + if isinstance(grb_obj.solid_geometry, list): + grb_solid_geometry = MultiPolygon(grb_obj.solid_geometry) + else: + grb_solid_geometry = grb_obj.solid_geometry + + # create the punched Gerber solid_geometry + punched_solid_geometry = grb_solid_geometry.difference(exc_solid_geometry) + + new_apertures = dict() + new_apertures = deepcopy(grb_obj.apertures) + + holes_apertures = dict() + + for apid, val in new_apertures.items(): + for elem in val['geometry']: + # make it work only for Gerber Flashes who are Points in 'follow' + if 'solid' in elem and isinstance(elem['follow'], Point): + for drill in exc_obj.drills: + clear_apid = exc_obj.tools[drill['tool']]['C'] + exc_poly = drill['point'].buffer(clear_apid / 2.0) + if exc_poly.within(elem['solid']): + + if clear_apid not in holes_apertures or holes_apertures[clear_apid]['type'] != 'C': + holes_apertures[clear_apid] = dict() + holes_apertures[clear_apid]['type'] = 'C' + holes_apertures[clear_apid]['size'] = clear_apid + holes_apertures[clear_apid]['geometry'] = list() + geo_elem = dict() + geo_elem['clear'] = exc_poly + geo_elem['follow'] = exc_poly.centroid + holes_apertures[clear_apid]['geometry'].append(deepcopy(geo_elem)) + + elem['clear'] = exc_poly.centroid + + for apid, val in new_apertures.items(): + for clear_apid, clear_val in holes_apertures.items(): + if round(clear_apid, self.decimals) == round(val['size'], self.decimals): + geo_elem = dict() + + val['geometry'].append(geo_elem) + + def init_func(new_obj, app_obj): + new_obj.options.update(grb_obj.options) + new_obj.options['name'] = outname + new_obj.fill_color = deepcopy(grb_obj.fill_color) + new_obj.outline_color = deepcopy(grb_obj.outline_color) + + new_obj.apertures = deepcopy(new_apertures) + + new_obj.solid_geometry = deepcopy(punched_solid_geometry) + new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None, + local_use=new_obj, use_thread=False) + + self.app.new_object('gerber', outname, init_func) + elif punch_method == 'fixed': + punch_size = float(self.dia_entry.get_value()) + + punching_geo = list() + for apid in grb_obj.apertures: + if grb_obj.apertures[apid]['type'] == 'C': + if punch_size >= float(grb_obj.apertures[apid]['size']): + self.app.inform.emit('[ERROR_NOTCL] %s' % + _(" Could not generate punched hole Gerber because the punch hole size" + "is bigger than some of the apertures in the Gerber object.")) + return 'fail' + else: + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(punch_size / 2)) + else: + if punch_size >= float(grb_obj.apertures[apid]['width']) or \ + punch_size >= float(grb_obj.apertures[apid]['height']): + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Could not generate punched hole Gerber because the punch hole size" + "is bigger than some of the apertures in the Gerber object.")) + return 'fail' + else: + for elem in grb_obj.apertures[apid]['geometry']: + if 'follow' in elem: + if isinstance(elem['follow'], Point): + punching_geo.append(elem['follow'].buffer(punch_size / 2)) + + punching_geo = MultiPolygon(punching_geo) + if isinstance(grb_obj.solid_geometry, list): + temp_solid_geometry = MultiPolygon(grb_obj.solid_geometry) + else: + temp_solid_geometry = grb_obj.solid_geometry + punched_solid_geometry = temp_solid_geometry.difference(punching_geo) + + if punched_solid_geometry == temp_solid_geometry: + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("Could not generate punched hole Gerber because the newly created object " + "geometry is the same as the one in the source object geometry...")) + return 'fail' + + def init_func(new_obj, app_obj): + new_obj.solid_geometry = deepcopy(punched_solid_geometry) + + self.app.new_object('gerber', outname, init_func) + elif punch_method == 'ring': + pass + elif punch_method == 'prop': + pass + def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))