- 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
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
91
camlib.py
91
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
|
||||
|
||||
Reference in New Issue
Block a user