From a9b26b291ffc0897aa5da16ad575013a043d385b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 19 Jan 2022 17:45:59 +0200 Subject: [PATCH] - the Editors require an Object UI build before launching; I've added the required code so the shortcut keys work even if the Properties tab is not current --- CHANGELOG.md | 1 + appParsers/ParseGerber.py | 34 ++++++------ appPlugins/ToolImage.py | 111 ++++++++++++++++++++++++++++++++++++-- camlib.py | 91 ------------------------------- 4 files changed, 126 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89531c2f..70572520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ CHANGELOG for FlatCAM beta - fixed the generatecncjob() method default parameters to reflect new data structure names - in Geometry object the default self.options dictionary is updated with keys that reflect new data structure - added comments in the `default` preprocessors which will help other people create their own preprocessors +- remade the Image Import plugin in an attempt to fix issues with the latest packages of Gdal and Rasterio but it looks that it is a conflict between the Rasterio 1.2.10, Gdal 3.4.1 and Shapely 1.8.0 18.01.2022 diff --git a/appParsers/ParseGerber.py b/appParsers/ParseGerber.py index cb3cd310..a4bb76a1 100644 --- a/appParsers/ParseGerber.py +++ b/appParsers/ParseGerber.py @@ -523,7 +523,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) path = [path[-1]] @@ -744,7 +744,7 @@ class Gerber(Geometry): if 'geometry' not in self.tools[current_aperture]: self.tools[current_aperture]['geometry'] = [] - self.tools[current_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[current_aperture]['geometry'].append(geo_dict) except IndexError: self.app.log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline)) @@ -804,7 +804,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) path = [path[-1]] continue @@ -857,7 +857,7 @@ class Gerber(Geometry): geo_dict['solid'] = pol if not pol.is_empty: - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) except TypeError: if not geo_s.is_empty: # is it possible that simplification creates an Empty Geometry ????? @@ -871,7 +871,7 @@ class Gerber(Geometry): geo_dict['solid'] = geo_s if not geo_s.is_empty: - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) path = [path[-1]] @@ -922,7 +922,7 @@ class Gerber(Geometry): geo_dict['solid'] = geo_s if geo_s or geo_f: - self.tools[0]['geometry'].append(deepcopy(geo_dict)) + self.tools[0]['geometry'].append(geo_dict) path = [[current_x, current_y]] # Start new path @@ -982,7 +982,7 @@ class Gerber(Geometry): geo_dict['solid'] = pol if not pol.is_empty: - self.tools[0]['geometry'].append(deepcopy(geo_dict)) + self.tools[0]['geometry'].append(geo_dict) except TypeError: # is it possible that simplification creates an Empty Geometry ????? if self.app.defaults['gerber_simplification']: @@ -1001,7 +1001,7 @@ class Gerber(Geometry): geo_dict['solid'] = region_s if not region_s.is_empty: - self.tools[0]['geometry'].append(deepcopy(geo_dict)) + self.tools[0]['geometry'].append(geo_dict) else: # is it possible that simplification creates an Empty Geometry ????? if self.app.defaults['gerber_simplification']: @@ -1020,7 +1020,7 @@ class Gerber(Geometry): geo_dict['solid'] = region_s if not region_s.is_empty: - self.tools[0]['geometry'].append(deepcopy(geo_dict)) + self.tools[0]['geometry'].append(geo_dict) path = [[current_x, current_y]] # Start new path continue @@ -1117,7 +1117,7 @@ class Gerber(Geometry): self.tools[current_aperture] = {} if 'geometry' not in self.tools[current_aperture]: self.tools[current_aperture]['geometry'] = [] - self.tools[current_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[current_aperture]['geometry'].append(geo_dict) if making_region is False: # if the aperture is rectangle then add a rectangular shape having as parameters the @@ -1153,7 +1153,7 @@ class Gerber(Geometry): self.tools[current_aperture] = {} if 'geometry' not in self.tools[current_aperture]: self.tools[current_aperture]['geometry'] = [] - self.tools[current_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[current_aperture]['geometry'].append(geo_dict) except Exception: pass last_path_aperture = current_aperture @@ -1261,7 +1261,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) # if linear_x or linear_y are None, ignore those if linear_x is not None and linear_y is not None: @@ -1328,7 +1328,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) # Reset path starting point path = [[linear_x, linear_y]] @@ -1363,7 +1363,7 @@ class Gerber(Geometry): self.tools[current_aperture] = {} if 'geometry' not in self.tools[current_aperture]: self.tools[current_aperture]['geometry'] = [] - self.tools[current_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[current_aperture]['geometry'].append(geo_dict) # maybe those lines are not exactly needed but it is easier to read the program as those coordinates # are used in case that circular interpolation is encountered within the Gerber file @@ -1476,7 +1476,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) current_x = circular_x current_y = circular_y @@ -1629,7 +1629,7 @@ class Gerber(Geometry): self.tools[last_path_aperture] = {} if 'geometry' not in self.tools[last_path_aperture]: self.tools[last_path_aperture]['geometry'] = [] - self.tools[last_path_aperture]['geometry'].append(deepcopy(geo_dict)) + self.tools[last_path_aperture]['geometry'].append(geo_dict) # ########################################################################################################## # Creating the FINAL GEOMETRY @@ -2058,7 +2058,7 @@ class Gerber(Geometry): for pol in flat_geo: new_el = {'solid': pol, 'follow': pol} - self.tools[0]['geometry'].append(deepcopy(new_el)) + self.tools[0]['geometry'].append(new_el) def scale(self, xfactor, yfactor=None, point=None): """ diff --git a/appPlugins/ToolImage.py b/appPlugins/ToolImage.py index 1ddfb154..3059db95 100644 --- a/appPlugins/ToolImage.py +++ b/appPlugins/ToolImage.py @@ -4,14 +4,24 @@ # Date: 3/10/2019 # # MIT Licence # # ########################################################## +import sys from PyQt6 import QtGui, QtWidgets +import os + +from shapely.geometry import shape +from shapely.ops import unary_union +from shapely.affinity import scale, translate + +import numpy as np + +# import rasterio +from rasterio import open as rasterio_open +from rasterio.features import shapes from appTool import AppTool from appGUI.GUIElements import RadioSet, FCComboBox, FCSpinner, FCLabel, VerticalScrollArea, FCGridLayout -import os - import gettext import appTranslation as fcTranslate import builtins @@ -184,8 +194,27 @@ class ToolImage(AppTool): def obj_init(geo_obj, app_obj): app_obj.log.debug("ToolIamge.import_image() -> importing image as geometry") - geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask) + image_geo = self.import_image_handler(filename, units=units, dpi=dpi, mode=mode, mask=mask) + + # # Add to object + # if self.solid_geometry is None: + # self.solid_geometry = [] + # + # if type(self.solid_geometry) is list: + # # self.solid_geometry.append(unary_union(geos)) + # if type(geos) is list: + # self.solid_geometry += geos + # else: + # self.solid_geometry.append(geos) + # else: # It's shapely geometry + # self.solid_geometry = [self.solid_geometry, geos] + + # flatten the geo_obj.solid_geometry list + geo_obj.solid_geometry = list(self.flatten_list(image_geo)) + geo_obj.solid_geometry = unary_union(geo_obj.solid_geometry) + geo_obj.multigeo = False + geo_obj.multitool = False with self.app.proc_container.new('%s ...' % _("Importing")): @@ -201,6 +230,82 @@ class ToolImage(AppTool): # GUI feedback self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + def import_image_handler(self, filename, flip=True, units='MM', dpi=96, mode='black', mask=None): + """ + Imports shapes from an IMAGE file into the object's geometry. + + :param filename: Path to the IMAGE file. + :type filename: str + :param flip: Flip the object vertically. + :type flip: bool + :param units: FlatCAM units + :type units: str + :param dpi: dots per inch on the imported image + :param mode: how to import the image: as 'black' or 'color' + :type mode: str + :param mask: level of detail for the import + :return: None + """ + if mask is None: + mask = [128, 128, 128, 128] + + scale_factor = 25.4 / dpi if units.lower() == 'mm' else 1 / dpi + + geos = [] + unscaled_geos = [] + + with rasterio_open(filename) as src: + # if filename.lower().rpartition('.')[-1] == 'bmp': + # red = green = blue = src.read(1) + # print("BMP") + # elif filename.lower().rpartition('.')[-1] == 'png': + # red, green, blue, alpha = src.read() + # elif filename.lower().rpartition('.')[-1] == 'jpg': + # red, green, blue = src.read() + + red = green = blue = src.read(1) + + try: + green = src.read(2) + except Exception: + pass + + try: + blue = src.read(3) + except Exception: + pass + + if mode == 'black': + mask_setting = red <= mask[0] + total = red + self.app.log.debug("Image import as monochrome.") + else: + mask_setting = (red <= mask[1]) + (green <= mask[2]) + (blue <= mask[3]) + total = np.zeros(red.shape, dtype=np.float32) + for band in red, green, blue: + total += band + total /= 3 + self.app.log.debug("Image import as colored. Thresholds are: R = %s , G = %s, B = %s" % + (str(mask[1]), str(mask[2]), str(mask[3]))) + + for geom, val in shapes(total, mask=mask_setting): + unscaled_geos.append(shape(geom)) + + for g in unscaled_geos: + geos.append(scale(g, scale_factor, scale_factor, origin=(0, 0))) + + if flip: + geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0))) for g in geos] + + return geos + + def flatten_list(self, obj_list): + for item in obj_list: + if hasattr(item, '__iter__') and not isinstance(item, (str, bytes)): + yield from self.flatten_list(item) + else: + yield item + class ImageUI: diff --git a/camlib.py b/camlib.py index e7eae7b0..993371f6 100644 --- a/camlib.py +++ b/camlib.py @@ -30,7 +30,6 @@ import shapely.affinity as affinity from shapely.wkt import loads as sloads from shapely.wkt import dumps as sdumps from shapely.geometry.base import BaseGeometry -from shapely.geometry import shape # --------------------------------------- # NEEDED for Legacy mode @@ -40,8 +39,6 @@ from descartes.patch import PolygonPatch from collections.abc import Iterable -import rasterio -from rasterio.features import shapes import ezdxf from appCommon.Common import GracefulException as grace @@ -1385,94 +1382,6 @@ class Geometry(object): # geos_text_f = [] # self.solid_geometry = [self.solid_geometry, geos_text_f] - def import_image(self, filename, flip=True, units='MM', dpi=96, mode='black', mask=None): - """ - Imports shapes from an IMAGE file into the object's geometry. - - :param filename: Path to the IMAGE file. - :type filename: str - :param flip: Flip the object vertically. - :type flip: bool - :param units: FlatCAM units - :type units: str - :param dpi: dots per inch on the imported image - :param mode: how to import the image: as 'black' or 'color' - :type mode: str - :param mask: level of detail for the import - :return: None - """ - if mask is None: - mask = [128, 128, 128, 128] - - scale_factor = 25.4 / dpi if units.lower() == 'mm' else 1 / dpi - - geos = [] - unscaled_geos = [] - - with rasterio.open(filename) as src: - # if filename.lower().rpartition('.')[-1] == 'bmp': - # red = green = blue = src.read(1) - # print("BMP") - # elif filename.lower().rpartition('.')[-1] == 'png': - # red, green, blue, alpha = src.read() - # elif filename.lower().rpartition('.')[-1] == 'jpg': - # red, green, blue = src.read() - - red = green = blue = src.read(1) - - try: - green = src.read(2) - except Exception: - pass - - try: - blue = src.read(3) - except Exception: - pass - - if mode == 'black': - mask_setting = red <= mask[0] - total = red - log.debug("Image import as monochrome.") - else: - mask_setting = (red <= mask[1]) + (green <= mask[2]) + (blue <= mask[3]) - total = np.zeros(red.shape, dtype=np.float32) - for band in red, green, blue: - total += band - total /= 3 - log.debug("Image import as colored. Thresholds are: R = %s , G = %s, B = %s" % - (str(mask[1]), str(mask[2]), str(mask[3]))) - - for geom, val in shapes(total, mask=mask_setting): - unscaled_geos.append(shape(geom)) - - for g in unscaled_geos: - geos.append(scale(g, scale_factor, scale_factor, origin=(0, 0))) - - if flip: - geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0))) for g in geos] - - # Add to object - if self.solid_geometry is None: - self.solid_geometry = [] - - if type(self.solid_geometry) is list: - # self.solid_geometry.append(unary_union(geos)) - if type(geos) is list: - self.solid_geometry += geos - else: - self.solid_geometry.append(geos) - else: # It's shapely geometry - self.solid_geometry = [self.solid_geometry, geos] - - # flatten the self.solid_geometry list for import_svg() to import SVG as Gerber - self.solid_geometry = list(self.flatten_list(self.solid_geometry)) - self.solid_geometry = unary_union(self.solid_geometry) - - # self.solid_geometry = MultiPolygon(self.solid_geometry) - # self.solid_geometry = self.solid_geometry.buffer(0.00000001) - # self.solid_geometry = self.solid_geometry.buffer(-0.00000001) - def size(self): """ Returns (width, height) of rectangular