diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index a8fe3591..86559660 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool): def on_buffer(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer(buffer_distance, join_style) def on_buffer_int(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer_int(buffer_distance, join_style) def on_buffer_ext(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer_ext(buffer_distance, join_style) diff --git a/README.md b/README.md index 967ce074..17c50f26 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ CAD program, and create G-Code for Isolation routing. - combined the geocutout and cutout_any TCL commands - work in progress - added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) - fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences +- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects) 5.02.3019 diff --git a/camlib.py b/camlib.py index b85eb553..3e89a412 100644 --- a/camlib.py +++ b/camlib.py @@ -494,7 +494,7 @@ class Geometry(object): # # return self.flat_geometry, self.flat_geometry_rtree - def isolation_geometry(self, offset, iso_type=2): + def isolation_geometry(self, offset, iso_type=2, corner=None): """ Creates contours around geometry at a given offset distance. @@ -503,6 +503,7 @@ class Geometry(object): :type offset: float :param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete) :type integer + :param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends) :return: The buffered geometry. :rtype: Shapely.MultiPolygon or Shapely.Polygon """ @@ -537,7 +538,11 @@ class Geometry(object): if offset == 0: geo_iso = self.solid_geometry else: - geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)) + if corner is None: + geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)) + else: + geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner) + # end of replaced block if iso_type == 2: diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index 84ac3d23..db49be02 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -55,6 +55,8 @@ class TclCommandGeoCutout(TclCommandSignaled): " geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"] } + flat_geometry = [] + def execute(self, args, unnamed_args): """ @@ -63,10 +65,62 @@ class TclCommandGeoCutout(TclCommandSignaled): :return: """ + def subtract_rectangle(obj_, x0, y0, x1, y1): pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] obj_.subtract_polygon(pts) + def substract_rectangle_geo(geo, x0, y0, x1, y1): + pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] + + + def flatten(geometry=None, reset=True, pathonly=False): + """ + Creates a list of non-iterable linear geometry objects. + Polygons are expanded into its exterior and interiors if specified. + + Results are placed in flat_geometry + + :param geometry: Shapely type or list or list of list of such. + :param reset: Clears the contents of self.flat_geometry. + :param pathonly: Expands polygons into linear elements. + """ + + if reset: + self.flat_geometry = [] + + ## If iterable, expand recursively. + try: + for geo in geometry: + if geo is not None: + flatten(geometry=geo, + reset=False, + pathonly=pathonly) + + ## Not iterable, do the actual indexing and add. + except TypeError: + if pathonly and type(geometry) == Polygon: + self.flat_geometry.append(geometry.exterior) + flatten(geometry=geometry.interiors, + reset=False, + pathonly=True) + else: + self.flat_geometry.append(geometry) + + return self.flat_geometry + + flat_geometry = flatten(geo, pathonly=True) + + polygon = Polygon(pts) + toolgeo = cascaded_union(polygon) + diffs = [] + for target in flat_geometry: + if type(target) == LineString or type(target) == LinearRing: + diffs.append(target.difference(toolgeo)) + else: + log.warning("Not implemented.") + return cascaded_union(diffs) + if 'name' in args: name = args['name'] else: @@ -116,20 +170,105 @@ class TclCommandGeoCutout(TclCommandSignaled): lenghtx = (xmax - xmin) + (margin * 2) lenghty = (ymax - ymin) + (margin * 2) - gapsize = gapsize + (dia / 2) + gapsize = gapsize / 2 + (dia / 2) + + try: + gaps_u = int(gaps) + except ValueError: + gaps_u = gaps if isinstance(cutout_obj, FlatCAMGeometry): # rename the obj name so it can be identified as cutout cutout_obj.options["name"] += "_cutout" + + if gaps_u == 8 or gaps_u == '2lr': + subtract_rectangle(cutout_obj, + xmin - gapsize, # botleft_x + py - gapsize + lenghty / 4, # botleft_y + xmax + gapsize, # topright_x + py + gapsize + lenghty / 4) # topright_y + subtract_rectangle(cutout_obj, + xmin - gapsize, + py - gapsize - lenghty / 4, + xmax + gapsize, + py + gapsize - lenghty / 4) + + if gaps_u == 8 or gaps_u == '2tb': + subtract_rectangle(cutout_obj, + px - gapsize + lenghtx / 4, + ymin - gapsize, + px + gapsize + lenghtx / 4, + ymax + gapsize) + subtract_rectangle(cutout_obj, + px - gapsize - lenghtx / 4, + ymin - gapsize, + px + gapsize - lenghtx / 4, + ymax + gapsize) + + if gaps_u == 4 or gaps_u == 'lr': + subtract_rectangle(cutout_obj, + xmin - gapsize, + py - gapsize, + xmax + gapsize, + py + gapsize) + + if gaps_u == 4 or gaps_u == 'tb': + subtract_rectangle(cutout_obj, + px - gapsize, + ymin - gapsize, + px + gapsize, + ymax + gapsize) + + cutout_obj.plot() + self.app.inform.emit("[success]Any-form Cutout operation finished.") elif isinstance(cutout_obj, FlatCAMGerber): def geo_init(geo_obj, app_obj): try: - geo_obj.solid_geometry = cutout_obj.isolation_geometry((dia / 2), iso_type=0) + geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2) except Exception as e: log.debug("TclCommandGeoCutout.execute() --> %s" % str(e)) return 'fail' + if gaps_u == 8 or gaps_u == '2lr': + geo = substract_rectangle_geo(geo, + xmin - gapsize, # botleft_x + py - gapsize + lenghty / 4, # botleft_y + xmax + gapsize, # topright_x + py + gapsize + lenghty / 4) # topright_y + geo = substract_rectangle_geo(geo, + xmin - gapsize, + py - gapsize - lenghty / 4, + xmax + gapsize, + py + gapsize - lenghty / 4) + + if gaps_u == 8 or gaps_u == '2tb': + geo = substract_rectangle_geo(geo, + px - gapsize + lenghtx / 4, + ymin - gapsize, + px + gapsize + lenghtx / 4, + ymax + gapsize) + geo = substract_rectangle_geo(geo, + px - gapsize - lenghtx / 4, + ymin - gapsize, + px + gapsize - lenghtx / 4, + ymax + gapsize) + + if gaps_u == 4 or gaps_u == 'lr': + geo = substract_rectangle_geo(geo, + xmin - gapsize, + py - gapsize, + xmax + gapsize, + py + gapsize) + + if gaps_u == 4 or gaps_u == 'tb': + geo = substract_rectangle_geo(geo, + px - gapsize, + ymin - gapsize, + px + gapsize, + ymax + gapsize) + geo_obj.solid_geometry = geo + outname = cutout_obj.options["name"] + "_cutout" self.app.new_object('geometry', outname, geo_init) @@ -138,48 +277,6 @@ class TclCommandGeoCutout(TclCommandSignaled): self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.") return - try: - gaps_u = int(gaps) - except ValueError: - gaps_u = gaps - if gaps_u == 8 or gaps_u == '2lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, # botleft_x - py - gapsize + lenghty / 4, # botleft_y - xmax + gapsize, # topright_x - py + gapsize + lenghty / 4) # topright_y - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize - lenghty / 4, - xmax + gapsize, - py + gapsize - lenghty / 4) - if gaps_u == 8 or gaps_u == '2tb': - subtract_rectangle(cutout_obj, - px - gapsize + lenghtx / 4, - ymin - gapsize, - px + gapsize + lenghtx / 4, - ymax + gapsize) - subtract_rectangle(cutout_obj, - px - gapsize - lenghtx / 4, - ymin - gapsize, - px + gapsize - lenghtx / 4, - ymax + gapsize) - if gaps_u == 4 or gaps_u == 'lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize, - xmax + gapsize, - py + gapsize) - - if gaps_u == 4 or gaps_u == 'tb': - subtract_rectangle(cutout_obj, - px - gapsize, - ymin - gapsize, - px + gapsize, - ymax + gapsize) - - cutout_obj.plot() - self.app.inform.emit("[success]Any-form Cutout operation finished.")