From 28372c1e08d36157fd03c46fe118438322fcd153 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 31 Jan 2022 21:19:37 +0200 Subject: [PATCH] - updated the `isolate`, `cutout` and `geocutout` Tcl commands and now they yield multigeo Geometry objects --- CHANGELOG.md | 1 + appObjects/FlatCAMGeometry.py | 2 +- appObjects/FlatCAMGerber.py | 6 +- tclCommands/TclCommandCutout.py | 29 +++- tclCommands/TclCommandGeoCutout.py | 252 +++++++++++------------------ 5 files changed, 121 insertions(+), 169 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef6cd38f..378593f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ CHANGELOG for FlatCAM beta - another fix for Shapely 2.0 deprecation warning - yet another fix for Shapely 2.0 deprecation warnings - updated the `join_geometry` and `join_excellon` Tcl commands format to follow the format of other commands +- updated the `isolate`, `cutout` and `geocutout` Tcl commands and now they yield multigeo Geometry objects 30.01.2022 diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py index 3484b13f..53266795 100644 --- a/appObjects/FlatCAMGeometry.py +++ b/appObjects/FlatCAMGeometry.py @@ -110,7 +110,7 @@ class GeometryObject(FlatCAMObj, Geometry): tool_id of the tools and the value is another dict that will hold the data under the following form: {tooluid: { 'tooldia': 1, - 'data': self.default_tool_data + 'data': self.default_tool_data, 'solid_geometry': [] } } diff --git a/appObjects/FlatCAMGerber.py b/appObjects/FlatCAMGerber.py index 0a540bbb..0a10dc18 100644 --- a/appObjects/FlatCAMGerber.py +++ b/appObjects/FlatCAMGerber.py @@ -579,6 +579,7 @@ class GerberObject(FlatCAMObj, Gerber): # Propagate options geo_obj.options["tools_mill_tooldia"] = str(dia) geo_obj.tool_type = self.app.defaults["tools_iso_tool_shape"] + geo_obj.multigeo = True geo_obj.solid_geometry = [] @@ -639,9 +640,6 @@ class GerberObject(FlatCAMObj, Gerber): msg = '[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]) app_obj.inform.emit(msg) - # even if combine is checked, one pass is still single-geo - geo_obj.multigeo = True if passes > 1 else False - # ############################################################ # ########## AREA SUBTRACTION ################################ # ############################################################ @@ -679,6 +677,7 @@ class GerberObject(FlatCAMObj, Gerber): # Propagate options geo_obj.options["tools_mill_tooldia"] = str(dia) geo_obj.tool_type = self.app.defaults["tools_iso_tool_shape"] + geo_obj.multigeo = True # if milling type is climb then the move is counter-clockwise around features mill_dir = 1 if milling_type == 'cl' else 0 @@ -731,7 +730,6 @@ class GerberObject(FlatCAMObj, Gerber): elif plot: msg = '[success] %s: %s' % (_("Isolation geometry created"), geo_obj.options["name"]) app_obj.inform.emit(msg) - geo_obj.multigeo = False # ############################################################ # ########## AREA SUBTRACTION ################################ diff --git a/tclCommands/TclCommandCutout.py b/tclCommands/TclCommandCutout.py index d01ee010..e662421e 100644 --- a/tclCommands/TclCommandCutout.py +++ b/tclCommands/TclCommandCutout.py @@ -2,6 +2,7 @@ from tclCommands.TclCommand import TclCommand import collections import logging +from copy import deepcopy from shapely.ops import unary_union from shapely.geometry import LineString @@ -107,7 +108,7 @@ class TclCommandCutout(TclCommand): return "fail" def geo_init_me(geo_obj, app_obj): - geo_obj.multigeo = False + geo_obj.multigeo = True margin = margin_par + dia_par / 2 gap_size = dia_par + gapsize_par @@ -152,8 +153,30 @@ class TclCommandCutout(TclCommand): cuts = cases[gaps_par] geo_obj.solid_geometry = unary_union([LineString(segment) for segment in cuts]) + if not geo_obj.solid_geometry: + app_obj.log("TclCommandCutout.execute(). No geometry after cutout.") + return "fail" + + default_tool_data = self.app.options.copy() + + geo_obj.tools = { + 1: { + 'tooldia': dia_par, + 'data': default_tool_data, + 'solid_geometry': deepcopy(geo_obj.solid_geometry) + } + } + geo_obj.tools[1]['data']['tools_cutout_tooldia'] = dia_par + geo_obj.tools[1]['data']['tools_cutout_gaps_ff'] = gaps_par + geo_obj.tools[1]['data']['tools_cutout_margin'] = margin_par + geo_obj.tools[1]['data']['tools_cutout_gapsize'] = gapsize_par + try: - self.app.app_obj.new_object("geometry", outname, geo_init_me, plot=False) + ret = self.app.app_obj.new_object("geometry", outname, geo_init_me, plot=False) + if ret == 'fail': + self.app.log.error("Could not create a cutout Geometry object." ) + return "fail" self.app.inform.emit("[success] Rectangular-form Cutout operation finished.") except Exception as e: - return "Operation failed: %s" % str(e) + self.app.log.error("Cutout operation failed: %s" % str(e)) + return "fail" diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index fab7df21..2c2ba576 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -4,7 +4,7 @@ import logging import collections from copy import deepcopy from shapely.ops import unary_union -from shapely.geometry import Polygon, LineString, LinearRing +from shapely.geometry import Polygon, LineString, LinearRing, MultiPolygon, MultiLineString import gettext import appTranslation as fcTranslate @@ -110,19 +110,15 @@ class TclCommandGeoCutout(TclCommandSignaled): # If iterable, expand recursively. try: - for geo_el in geometry: + w_geo = geometry.geoms if isinstance(geometry, (MultiPolygon, MultiLineString)) else geometry + for geo_el in w_geo: if geo_el is not None: - flatten(geometry=geo_el, - reset=False, - pathonly=pathonly) - - # Not iterable, do the actual indexing and add. + flatten(geometry=geo_el, reset=False, pathonly=pathonly) except TypeError: + # Not iterable, do the actual indexing and add. if pathonly and type(geometry) == Polygon: self.flat_geometry.append(geometry.exterior) - flatten(geometry=geometry.interiors, - reset=False, - pathonly=True) + flatten(geometry=geometry.interiors, reset=False, pathonly=True) else: self.flat_geometry.append(geometry) @@ -137,14 +133,14 @@ class TclCommandGeoCutout(TclCommandSignaled): if type(target) == LineString or type(target) == LinearRing: diffs.append(target.difference(toolgeo)) else: - self.app.log.warning("Not implemented.") + self.app.log.warning("TclCommandGeoCutout.execute(). Not implemented.") return unary_union(diffs) if 'name' in args: name = args['name'] else: - self.app.inform.emit( - "[WARNING] %s" % _("The name of the object for which cutout is done is missing. Add it and retry.")) + msg = "[WARNING] %s" % _("The name of the object for which cutout is done is missing. Add it and retry.") + self.app.inform.emit(msg) return "fail" if 'margin' in args: @@ -210,155 +206,89 @@ class TclCommandGeoCutout(TclCommandSignaled): gaps_u = gaps if cutout_obj.kind == 'geometry': - # 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) - - def geo_init(geo_obj, app_obj): - geo = deepcopy(cutout_obj.solid_geometry) - - 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 = deepcopy(geo) - geo_obj.options['xmin'] = cutout_obj.options['xmin'] - geo_obj.options['ymin'] = cutout_obj.options['ymin'] - geo_obj.options['xmax'] = cutout_obj.options['xmax'] - geo_obj.options['ymax'] = cutout_obj.options['ymax'] - - app_obj.disable_plots(objects=[cutout_obj]) - - app_obj.inform.emit("[success] %s" % _("Any-form Cutout operation finished.")) - - self.app.app_obj.new_object('geometry', outname, geo_init, plot=False) - + geo_to_cutout = deepcopy(cutout_obj.solid_geometry) elif cutout_obj.kind == 'gerber': - - def geo_init(geo_obj, app_obj): - try: - geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2) - except Exception as exc: - self.app.log.error("TclCommandGeoCutout.execute() --> %s" % str(exc)) - 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 = deepcopy(geo) - geo_obj.options['xmin'] = cutout_obj.options['xmin'] - geo_obj.options['ymin'] = cutout_obj.options['ymin'] - geo_obj.options['xmax'] = cutout_obj.options['xmax'] - geo_obj.options['ymax'] = cutout_obj.options['ymax'] - app_obj.inform.emit("[success] %s" % _("Any-form Cutout operation finished.")) - - self.app.app_obj.new_object('geometry', outname, geo_init, plot=False) - - cutout_obj = self.app.collection.get_by_name(outname) + try: + geo_to_cutout = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2) + except Exception as exc: + self.app.log.error("TclCommandGeoCutout.execute() --> %s" % str(exc)) + return 'fail' else: self.app.inform.emit("[ERROR] %s" % _("Cancelled. Object type is not supported.")) return "fail" + + def geo_init(geo_obj, app_obj): + geo_obj.multigeo = True + geo = geo_to_cutout + + 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 = deepcopy(geo) + geo_obj.options['xmin'] = cutout_obj.options['xmin'] + geo_obj.options['ymin'] = cutout_obj.options['ymin'] + geo_obj.options['xmax'] = cutout_obj.options['xmax'] + geo_obj.options['ymax'] = cutout_obj.options['ymax'] + + if not geo_obj.solid_geometry: + app_obj.log("TclCommandGeoCutout.execute(). No geometry after geo-cutout.") + return "fail" + + default_tool_data = self.app.options.copy() + + geo_obj.tools = { + 1: { + 'tooldia': dia, + 'data': default_tool_data, + 'solid_geometry': deepcopy(geo_obj.solid_geometry) + } + } + geo_obj.tools[1]['data']['tools_cutout_tooldia'] = dia + geo_obj.tools[1]['data']['tools_cutout_gaps_ff'] = gaps + geo_obj.tools[1]['data']['tools_cutout_margin'] = margin + geo_obj.tools[1]['data']['tools_cutout_gapsize'] = gapsize + + app_obj.disable_plots(objects=[cutout_obj]) + + ret = self.app.app_obj.new_object('geometry', outname, geo_init, plot=False) + if ret == 'fail': + msg = "Could not create a geo-cutout Geometry object from a %s object." % cutout_obj.kind.capialize() + self.app.log.error(msg) + return "fail" + else: + self.app.inform.emit("[success] %s" % _("Any-form Cutout operation finished."))