Merge branch 'new_aperture_storage' into beta_8.916

# Conflicts:
#	README.md
This commit is contained in:
Marius Stanciu 2019-05-11 03:49:53 +03:00
commit 5287cbd8de
4 changed files with 715 additions and 752 deletions

View File

@ -1155,10 +1155,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
self.app.progress.emit(30)
try:
if aperture_to_plot_mark in self.apertures:
if type(self.apertures[aperture_to_plot_mark]['solid_geometry']) is not list:
self.apertures[aperture_to_plot_mark]['solid_geometry'] = \
[self.apertures[aperture_to_plot_mark]['solid_geometry']]
for geo in self.apertures[aperture_to_plot_mark]['solid_geometry']:
for elem in self.apertures[aperture_to_plot_mark]['geometry']:
if 'solid' in elem:
geo = elem['solid']
if type(geo) == Polygon or type(geo) == LineString:
self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
face_color=color, visible=visibility)

View File

@ -11,9 +11,14 @@ CAD program, and create G-Code for Isolation routing.
10.05.2019
- Gerber Editor - working in conversion to the new data format
- made sure that only units toggle done in Edit -> Preferences will toggle the data in Preferences. THe menu entry Edit -> Toggle Units and the shortcut key 'Q' will change only the display units in the app
- optimized Transform tool
9.05.2019
- rework the Gerber parser
8.05.2019
- added zoom fit for Set Origin command

647
camlib.py
View File

@ -1920,14 +1920,19 @@ class Gerber (Geometry):
'''
apertures = {
'id':{
'type':chr,
'type':string,
'size':float,
'width':float,
'height':float,
'solid_geometry': [],
'follow_geometry': [],
'geometry': [],
}
}
apertures['geometry'] list elements are dicts
dict = {
'solid': [],
'follow': [],
'clear': []
}
'''
# aperture storage
@ -2181,8 +2186,8 @@ class Gerber (Geometry):
# store here the follow geometry
follow_buffer = []
last_path_aperture = None
current_aperture = None
last_path_aperture = '0'
current_aperture = '0'
# 1,2 or 3 from "G01", "G02" or "G03"
current_interpolation_mode = None
@ -2227,7 +2232,7 @@ class Gerber (Geometry):
### Cleanup
gline = gline.strip(' \r\n')
# log.debug("Line=%3s %s" % (line_num, gline))
log.debug("Line=%3s %s" % (line_num, gline))
#### Ignored lines
## Comments
@ -2244,37 +2249,30 @@ class Gerber (Geometry):
new_polarity = match.group(1)
# log.info("Polarity CHANGE, LPC = %s, poly_buff = %s" % (self.is_lpc, poly_buffer))
self.is_lpc = True if new_polarity == 'C' else False
if len(path) > 1 and current_polarity != new_polarity:
# finish the current path and add it to the storage
# --- Buffered ----
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['follow_geometry'] = []
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
if path:
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
path = [path[-1]]
@ -2437,28 +2435,25 @@ class Gerber (Geometry):
flash = Gerber.create_flash_geometry(
Point(current_x, current_y), self.apertures[current_aperture],
int(self.steps_per_circle))
if not flash.is_empty:
poly_buffer.append(flash)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(flash)
else:
try:
self.apertures[current_aperture]['follow_geometry'].append(Point(
current_x, current_y))
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(Point(
current_x, current_y))
if not flash.is_empty:
geo_f = Point(current_x, current_y)
geo_s = flash
# follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
self.apertures[current_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
self.apertures[current_aperture]['geometry'] = []
self.apertures[current_aperture]['geometry'].append(geo_dict)
except IndexError:
log.warning("Line %d: %s -> Nothing there to flash!" % (line_num, gline))
@ -2482,37 +2477,25 @@ class Gerber (Geometry):
# Take care of the current path with the previous tool
if len(path) > 1:
if self.apertures[last_path_aperture]["type"] == 'R':
# do nothing because 'R' type moving aperture is none at once
pass
else:
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['follow_geometry'] = []
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
# --- Buffered ----
if self.apertures[last_path_aperture]["type"] != 'R':
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
path = [path[-1]]
@ -2522,34 +2505,24 @@ class Gerber (Geometry):
if self.regionon_re.search(gline):
if len(path) > 1:
# Take care of what is left in the path
## --- Buffered ---
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
path = [path[-1]]
@ -2564,94 +2537,61 @@ class Gerber (Geometry):
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
self.apertures['0']['geometry'] = []
# if D02 happened before G37 we now have a path with 1 element only so we have to add the current
# geo to the poly_buffer otherwise we loose it
if current_operation_code == 2:
if geo:
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures['0']['follow_geometry'].append(geo)
except KeyError:
self.apertures['0']['follow_geometry'] = []
self.apertures['0']['follow_geometry'].append(geo)
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures['0']['clear_geometry'].append(geo)
except KeyError:
self.apertures['0']['clear_geometry'] = []
self.apertures['0']['clear_geometry'].append(geo)
else:
try:
self.apertures['0']['solid_geometry'].append(geo)
except KeyError:
self.apertures['0']['solid_geometry'] = []
self.apertures['0']['solid_geometry'].append(geo)
continue
# if current_operation_code == 2:
# if geo:
# if not geo.is_empty:
# follow_buffer.append(geo)
# try:
# self.apertures['0']['follow_geometry'].append(geo)
# except KeyError:
# self.apertures['0']['follow_geometry'] = []
# self.apertures['0']['follow_geometry'].append(geo)
#
# poly_buffer.append(geo)
# if self.is_lpc is True:
# try:
# self.apertures['0']['clear_geometry'].append(geo)
# except KeyError:
# self.apertures['0']['clear_geometry'] = []
# self.apertures['0']['clear_geometry'].append(geo)
# else:
# try:
# self.apertures['0']['solid_geometry'].append(geo)
# except KeyError:
# self.apertures['0']['solid_geometry'] = []
# self.apertures['0']['solid_geometry'].append(geo)
# continue
# Only one path defines region?
# This can happen if D02 happened before G37 and
# is not and error.
if len(path) < 3:
# print "ERROR: Path contains less than 3 points:"
# print path
# print "Line (%d): " % line_num, gline
# path = []
#path = [[current_x, current_y]]
continue
# For regions we may ignore an aperture that is None
# self.regions.append({"polygon": Polygon(path),
# "aperture": last_path_aperture})
geo_f = LineString(path)
geo_s = Polygon(path)
if not geo_s.is_valid:
geo_s = geo_s.buffer(0, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
if not geo_s.is_empty:
poly_buffer.append(geo_s)
# --- Buffered ---
region = Polygon()
if not region.is_empty:
follow_buffer.append(region)
try:
self.apertures['0']['follow_geometry'].append(region)
except KeyError:
self.apertures['0']['follow_geometry'] = []
self.apertures['0']['follow_geometry'].append(region)
region = Polygon(path)
if not region.is_valid:
region = region.buffer(0, int(self.steps_per_circle / 4))
if not region.is_empty:
poly_buffer.append(region)
# we do this for the case that a region is done without having defined any aperture
# Allegro does that
# if current_aperture:
# used_aperture = current_aperture
# elif last_path_aperture:
# used_aperture = last_path_aperture
# else:
# if '0' not in self.apertures:
# self.apertures['0'] = {}
# self.apertures['0']['size'] = 0.0
# self.apertures['0']['type'] = 'REG'
# self.apertures['0']['solid_geometry'] = []
# used_aperture = '0'
used_aperture = '0'
if self.is_lpc is True:
try:
self.apertures[used_aperture]['clear_geometry'].append(region)
except KeyError:
self.apertures[used_aperture]['clear_geometry'] = []
self.apertures[used_aperture]['clear_geometry'].append(region)
geo_dict = dict()
geo_dict['follow'] = geo_f
if not geo_s.is_empty:
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[used_aperture]['solid_geometry'].append(region)
self.apertures['0']['geometry'].append(geo_dict)
except KeyError:
self.apertures[used_aperture]['solid_geometry'] = []
self.apertures[used_aperture]['solid_geometry'].append(region)
self.apertures['0']['geometry'] = []
self.apertures['0']['geometry'].append(geo_dict)
path = [[current_x, current_y]] # Start new path
continue
@ -2680,6 +2620,14 @@ class Gerber (Geometry):
# NOTE: Letting it continue allows it to react to the
# operation code.
if current_aperture is None:
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['geometry'] = []
current_aperture = '0'
# Parse coordinates
if match.group(2) is not None:
linear_x = parse_gerber_number(match.group(2),
@ -2705,6 +2653,7 @@ class Gerber (Geometry):
# only add the point if it's a new one otherwise skip it (harder to process)
if path[-1] != [current_x, current_y]:
path.append([current_x, current_y])
if making_region is False:
# if the aperture is rectangle then add a rectangular shape having as parameters the
# coordinates of the start and end point and also the width and height
@ -2717,136 +2666,79 @@ class Gerber (Geometry):
maxx = max(path[0][0], path[1][0]) + width / 2
miny = min(path[0][1], path[1][1]) - height / 2
maxy = max(path[0][1], path[1][1]) + height / 2
log.debug("Coords: %s - %s - %s - %s" % (minx, miny, maxx, maxy))
# log.debug("Coords: %s - %s - %s - %s" % (minx, miny, maxx, maxy))
geo = shply_box(minx, miny, maxx, maxy)
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(geo)
r_x = maxx - minx
r_y = maxy - miny
geo_f = Point(r_x, r_y)
geo_s = shply_box(minx, miny, maxx, maxy)
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures[current_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(geo)
self.apertures[current_aperture]['geometry'] = []
self.apertures[current_aperture]['geometry'].append(geo_dict)
except:
pass
last_path_aperture = current_aperture
# we do this for the case that a region is done without having defined any aperture
# Allegro does that
if last_path_aperture is None:
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
last_path_aperture = '0'
else:
self.app.inform.emit(_("[WARNING] Coordinates missing, line ignored: %s") % str(gline))
self.app.inform.emit(_("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!"))
elif current_operation_code == 2:
# finish current path
if len(path) > 1:
geo = None
# --- BUFFERED ---
# this treats the case when we are storing geometry as paths only
if making_region:
# we do this for the case that a region is done without having defined any aperture
# Allegro does that
if last_path_aperture is None:
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
self.apertures['0']['geometry'] = []
last_path_aperture = '0'
geo = Polygon()
width = 0
else:
geo = LineString(path)
width = self.apertures[last_path_aperture]["size"]
try:
if self.apertures[last_path_aperture]["type"] != 'R':
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
except Exception as e:
log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
if path and self.apertures[last_path_aperture]["type"] != 'R':
geo_f = LineString(path)
geo_s = None
# this treats the case when we are storing geometry as solids
if making_region:
# we do this for the case that a region is done without having defined any aperture
# Allegro does that
if last_path_aperture is None:
if '0' not in self.apertures:
self.apertures['0'] = {}
self.apertures['0']['type'] = 'REG'
self.apertures['0']['size'] = 0.0
self.apertures['0']['solid_geometry'] = []
last_path_aperture = '0'
# elem = [current_x, current_y]
# if elem != path[-1]:
# path.append([current_x, current_y])
try:
geo = Polygon(path)
geo_s = Polygon(path)
poly_buffer.append(geo_s)
except ValueError:
log.warning("Problem %s %s" % (gline, line_num))
self.app.inform.emit(_("[ERROR] Region does not have enough points. "
"File will be processed but there are parser errors. "
"Line number: %s") % str(line_num))
else:
if last_path_aperture is None:
log.warning("No aperture defined for curent path. (%d)" % line_num)
width = self.apertures[last_path_aperture]["size"] # TODO: WARNING this should fail!
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
poly_buffer.append(geo_s)
follow_buffer.append(geo_f)
try:
if self.apertures[last_path_aperture]["type"] != 'R':
if not geo.is_empty:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
geo_dict = dict()
geo_dict['follow'] = geo_f
if geo_s:
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except Exception as e:
log.debug("camlib.Gerber.parse_lines() --> %s" % str(e))
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[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:
@ -2859,98 +2751,53 @@ class Gerber (Geometry):
# Not allowed in region mode.
elif current_operation_code == 3:
# Create path draw so far.
if len(path) > 1:
# --- Buffered ----
# this treats the case when we are storing geometry as paths
geo = LineString(path)
if not geo.is_empty:
try:
if self.apertures[last_path_aperture]["type"] != 'R':
follow_buffer.append(geo)
try:
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['follow_geometry'] = []
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except Exception as e:
log.debug("camlib.Gerber.parse_lines() --> G01 match D03 --> %s" % str(e))
follow_buffer.append(geo)
try:
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['follow_geometry'] = []
self.apertures[last_path_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
try:
if self.apertures[last_path_aperture]["type"] != 'R':
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
else:
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
# finish the path draw until now
if len(path) > 1 and self.apertures[last_path_aperture]["type"] != 'R':
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
# Reset path starting point
path = [[linear_x, linear_y]]
# --- BUFFERED ---
# Draw the flash
# this treats the case when we are storing geometry as paths
geo_flash = Point([linear_x, linear_y])
follow_buffer.append(geo_flash)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo_flash)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo_flash)
# this treats the case when we are storing geometry as solids
flash = Gerber.create_flash_geometry(
Point( [linear_x, linear_y]),
# Draw the flash
geo_f = Point(linear_x, linear_y)
geo_s = Gerber.create_flash_geometry(
Point([linear_x, linear_y]),
self.apertures[current_aperture],
int(self.steps_per_circle)
)
if not flash.is_empty:
poly_buffer.append(flash)
if self.is_lpc is True:
try:
self.apertures[current_aperture]['clear_geometry'].append(flash)
except KeyError:
self.apertures[current_aperture]['clear_geometry'] = []
self.apertures[current_aperture]['clear_geometry'].append(flash)
follow_buffer.append(geo_f)
if not geo_s.is_empty:
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if not geo_s.is_empty:
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[current_aperture]['solid_geometry'].append(flash)
self.apertures[current_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[current_aperture]['solid_geometry'] = []
self.apertures[current_aperture]['solid_geometry'].append(flash)
self.apertures[current_aperture]['geometry'] = []
self.apertures[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
@ -3024,6 +2871,8 @@ class Gerber (Geometry):
# Nothing created! Pen Up.
if current_operation_code == 2:
log.warning("Arc with D2. (%d)" % line_num)
# if we have something drawn until this moment, add it
if len(path) > 1:
if last_path_aperture is None:
log.warning("No aperture defined for curent path. (%d)" % line_num)
@ -3031,32 +2880,24 @@ class Gerber (Geometry):
# --- BUFFERED ---
width = self.apertures[last_path_aperture]["size"]
# this treats the case when we are storing geometry as paths
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo_s.is_empty:
follow_buffer.append(geo_f)
poly_buffer.append(geo_s)
# this treats the case when we are storing geometry as solids
buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
if not buffered.is_empty:
poly_buffer.append(buffered)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(buffered)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(buffered)
geo_dict = dict()
geo_dict['follow'] = geo_f
if not geo_s.is_empty:
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(buffered)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
current_x = circular_x
current_y = circular_y
@ -3168,39 +3009,28 @@ class Gerber (Geometry):
# In case that G01 (moving) aperture is rectangular, there is no need to still create
# another geo since we already created a shapely box using the start and end coordinates found in
# path variable. We do it only for other apertures than 'R' type
if self.apertures[last_path_aperture]["type"] == 'R':
pass
else:
if self.apertures[last_path_aperture]["type"] != 'R':
# EOF, create shapely LineString if something still in path
## --- Buffered ---
# this treats the case when we are storing geometry as paths
geo = LineString(path)
if not geo.is_empty:
follow_buffer.append(geo)
try:
self.apertures[current_aperture]['follow_geometry'].append(geo)
except KeyError:
self.apertures[current_aperture]['follow_geometry'] = []
self.apertures[current_aperture]['follow_geometry'].append(geo)
# this treats the case when we are storing geometry as solids
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
if not geo.is_empty:
poly_buffer.append(geo)
if self.is_lpc is True:
try:
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
except KeyError:
self.apertures[last_path_aperture]['clear_geometry'] = []
self.apertures[last_path_aperture]['clear_geometry'].append(geo)
geo_f = LineString(path)
geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
follow_buffer.append(geo_f)
if not geo_s.is_empty:
poly_buffer.append(geo_s)
geo_dict = dict()
geo_dict['follow'] = geo_f
if not geo_s.is_empty:
if self.is_lpc:
geo_dict['clear'] = geo_s
else:
geo_dict['solid'] = geo_s
try:
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
except KeyError:
self.apertures[last_path_aperture]['solid_geometry'] = []
self.apertures[last_path_aperture]['solid_geometry'].append(geo)
self.apertures[last_path_aperture]['geometry'] = []
self.apertures[last_path_aperture]['geometry'].append(geo_dict)
# TODO: make sure to keep track of units changes because right now it seems to happen in a weird way
# find out the conversion factor used to convert inside the self.apertures keys: size, width, height
@ -3215,31 +3045,23 @@ class Gerber (Geometry):
global_clear_geo = []
for apid in self.apertures:
# first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
if 'clear_geometry' in self.apertures[apid]:
for pol in self.apertures[apid]['clear_geometry']:
global_clear_geo.append(pol)
self.apertures[apid].pop('clear_geometry', None)
if 'geometry' in self.apertures[apid]:
for elem in self.apertures[apid]['geometry']:
if 'clear' in elem:
global_clear_geo.append(elem['clear'])
log.warning("Found %d clear polygons." % len(global_clear_geo))
temp_geo = []
for apid in self.apertures:
if 'solid_geometry' in self.apertures[apid]:
for solid_geo in self.apertures[apid]['solid_geometry']:
if 'geometry' in self.apertures[apid]:
for elem in self.apertures[apid]['geometry']:
if 'solid' in elem:
for clear_geo in global_clear_geo:
# Make sure that the clear_geo is within the solid_geo otherwise we loose
# the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to
# delete it
if clear_geo.within(solid_geo):
solid_geo = solid_geo.difference(clear_geo)
try:
for poly in solid_geo:
temp_geo.append(poly)
except TypeError:
temp_geo.append(solid_geo)
if clear_geo.within(elem['solid']):
elem['solid'] = elem['solid'].difference(clear_geo)
self.apertures[apid]['solid_geometry'] = deepcopy(temp_geo)
temp_geo = []
log.warning("Polygon difference done for %d apertures." % len(self.apertures))
for apid in self.apertures:
@ -3249,6 +3071,9 @@ class Gerber (Geometry):
self.apertures[apid][k] = v * conversion_factor
# -------------------------------------------------------------
# for t in self.apertures:
# print(t, self.apertures[t])
# --- Apply buffer ---
# this treats the case when we are storing geometry as paths
self.follow_geometry = follow_buffer

View File

@ -14,7 +14,6 @@ from copy import copy, deepcopy
from camlib import *
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
from FlatCAMObj import FlatCAMGerber
from FlatCAMTool import FlatCAMTool
@ -32,6 +31,148 @@ if '_' not in builtins.__dict__:
_ = gettext.gettext
class DrawToolShape(object):
"""
Encapsulates "shapes" under a common class.
"""
tolerance = None
@staticmethod
def get_pts(o):
"""
Returns a list of all points in the object, where
the object can be a Polygon, Not a polygon, or a list
of such. Search is done recursively.
:param: geometric object
:return: List of points
:rtype: list
"""
pts = []
## Iterable: descend into each item.
try:
for subo in o:
pts += DrawToolShape.get_pts(subo)
## Non-iterable
except TypeError:
if o is not None:
## DrawToolShape: descend into .geo.
if isinstance(o, DrawToolShape):
pts += DrawToolShape.get_pts(o.geo)
## Descend into .exerior and .interiors
elif type(o) == Polygon:
pts += DrawToolShape.get_pts(o.exterior)
for i in o.interiors:
pts += DrawToolShape.get_pts(i)
elif type(o) == MultiLineString:
for line in o:
pts += DrawToolShape.get_pts(line)
## Has .coords: list them.
else:
if DrawToolShape.tolerance is not None:
pts += list(o.simplify(DrawToolShape.tolerance).coords)
else:
pts += list(o.coords)
else:
return
return pts
def __init__(self, geo={}):
# Shapely type or list of such
self.geo = geo
self.utility = False
class DrawToolUtilityShape(DrawToolShape):
"""
Utility shapes are temporary geometry in the editor
to assist in the creation of shapes. For example it
will show the outline of a rectangle from the first
point to the current mouse pointer before the second
point is clicked and the final geometry is created.
"""
def __init__(self, geo={}):
super(DrawToolUtilityShape, self).__init__(geo=geo)
self.utility = True
class DrawTool(object):
"""
Abstract Class representing a tool in the drawing
program. Can generate geometry, including temporary
utility geometry that is updated on user clicks
and mouse motion.
"""
def __init__(self, draw_app):
self.draw_app = draw_app
self.complete = False
self.points = []
self.geometry = None # DrawToolShape or None
def click(self, point):
"""
:param point: [x, y] Coordinate pair.
"""
return ""
def click_release(self, point):
"""
:param point: [x, y] Coordinate pair.
"""
return ""
def on_key(self, key):
return None
def utility_geometry(self, data=None):
return None
def bounds(self, obj):
def bounds_rec(o):
if type(o) is list:
minx = Inf
miny = Inf
maxx = -Inf
maxy = -Inf
for k in o:
try:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
except Exception as e:
log.debug("camlib.Gerber.bounds() --> %s" % str(e))
return
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
else:
# it's a Shapely object, return it's bounds
return o.geo.bounds
bounds_coords = bounds_rec(obj)
return bounds_coords
class FCShapeTool(DrawTool):
"""
Abstract class for tools that create a shape.
"""
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
def make(self):
pass
class FCPad(FCShapeTool):
"""
Resulting type: Polygon
@ -2159,9 +2300,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
# holds flattened geometry
self.flat_geometry = []
# Init GUI
self.apdim_lbl.hide()
self.apdim_entry.hide()
@ -2180,7 +2318,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.enabled = False
self.tool_shape.enabled = False
## List of selected shapes.
## List of selected geometric elements.
self.selected = []
self.key = None # Currently pressed key
@ -2493,8 +2631,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apsize_entry.set_value(size_val)
self.storage_dict[ap_id]['size'] = size_val
self.storage_dict[ap_id]['solid_geometry'] = []
self.storage_dict[ap_id]['follow_geometry'] = []
self.storage_dict[ap_id]['geometry'] = []
# self.olddia_newdia dict keeps the evidence on current aperture codes as keys and gets updated on values
# each time a aperture code is edited or added
@ -2535,8 +2672,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
return
self.storage_dict[ap_id]['size'] = size_val
self.storage_dict[ap_id]['solid_geometry'] = []
self.storage_dict[ap_id]['follow_geometry'] = []
self.storage_dict[ap_id]['geometry'] = []
# self.olddia_newdia dict keeps the evidence on current aperture codes as keys and gets updated on values
# each time a aperture code is edited or added
@ -2662,11 +2798,19 @@ class FlatCAMGrbEditor(QtCore.QObject):
else:
# aperture code is already in use so we move the pads from the prior tool to the new tool
factor = current_table_dia_edited / dia_changed
for shape in self.storage_dict[dia_changed].get_objects():
geometry.append(DrawToolShape(
MultiLineString([affinity.scale(subgeo, xfact=factor, yfact=factor) for subgeo in shape.geo])))
geometry = []
for geo_el in self.storage_dict[dia_changed]:
geometric_data = geo_el.geo
new_geo_el = dict()
if 'solid' in geometric_data:
new_geo_el['solid'] = deepcopy(geometric_data['solid'])
if 'follow' in geometric_data:
new_geo_el['follow'] = deepcopy(geometric_data['follow'])
if 'clear' in geometric_data:
new_geo_el['clear'] = deepcopy(geometric_data['clear'])
# geometry.append(DrawToolShape(
# MultiLineString([affinity.scale(subgeo, xfact=factor, yfact=factor) for subgeo in shape.geo])))
self.points_edit[current_table_dia_edited].append((0, 0))
self.add_gerber_shape(geometry, self.storage_dict[current_table_dia_edited])
self.on_aperture_delete(apid=dia_changed)
@ -2916,37 +3060,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.shapes.clear(update=True)
self.tool_shape.clear(update=True)
def flatten(self, geometry=None, reset=True, pathonly=False):
"""
Creates a list of non-iterable linear geometry objects.
Polygons are expanded into its exterior pathonly param 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 from the exterior attribute.
"""
if reset:
self.flat_geometry = []
## If iterable, expand recursively.
try:
for geo in geometry:
if geo is not None:
self.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)
self.flatten(geometry=geometry.interiors,
reset=False,
pathonly=True)
else:
self.flat_geometry.append(geometry)
return self.flat_geometry
def edit_fcgerber(self, orig_grb_obj):
"""
Imports the geometry found in self.apertures from the given FlatCAM Gerber object
@ -2978,28 +3091,19 @@ class FlatCAMGrbEditor(QtCore.QObject):
def job_thread(self, apid):
with self.app.proc_container.new(_("Adding aperture: %s geo ...") % str(apid)):
solid_storage_elem = []
follow_storage_elem = []
storage_elem = []
self.storage_dict[apid] = {}
# add the Gerber geometry to editor storage
for k, v in self.gerber_obj.apertures[apid].items():
try:
if k == 'solid_geometry':
for geo in v:
if geo:
self.add_gerber_shape(DrawToolShape(geo), solid_storage_elem)
self.storage_dict[apid][k] = solid_storage_elem
elif k == 'follow_geometry':
for geo in v:
if geo is not None:
self.add_gerber_shape(DrawToolShape(geo), follow_storage_elem)
self.storage_dict[apid][k] = follow_storage_elem
elif k == 'clear_geometry':
continue
if k == 'geometry':
for geo_el in v:
if geo_el:
self.add_gerber_shape(DrawToolShape(geo_el), storage_elem)
self.storage_dict[apid][k] = storage_elem
else:
self.storage_dict[apid][k] = v
self.storage_dict[apid][k] = self.gerber_obj.apertures[apid][k]
except Exception as e:
log.debug("FlatCAMGrbEditor.edit_fcgerber().job_thread() --> %s" % str(e))
# Check promises and clear if exists
@ -3104,26 +3208,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
grb_obj.apertures[storage_apid] = {}
for k, v in storage_val.items():
if k == 'solid_geometry':
if k == 'geometry':
grb_obj.apertures[storage_apid][k] = []
for geo in v:
new_geo = deepcopy(geo.geo)
grb_obj.apertures[storage_apid][k].append(new_geo)
poly_buffer.append(new_geo)
elif k == 'follow_geometry':
grb_obj.apertures[storage_apid][k] = []
for geo_f in v:
if isinstance(geo_f.geo, Polygon):
for geo_el in v:
new_geo = dict()
geometric_data = geo_el.geo
for key in geometric_data:
if key == 'solid':
new_geo[key] = geometric_data['solid']
poly_buffer.append(deepcopy(new_geo['solid']))
if key == 'follow':
if isinstance(geometric_data[key], Polygon):
buff_val = -(int(storage_apid) / 2)
geo_f = geo_f.geo.buffer(buff_val).exterior
new_geo = deepcopy(geo_f)
geo_f = geo_el.geo.buffer(buff_val).exterior
new_geo[key] = geo_f
else:
new_geo = deepcopy(geo_f.geo)
grb_obj.apertures[storage_apid][k].append(new_geo)
follow_buffer.append(new_geo)
else:
grb_obj.apertures[storage_apid][k] = deepcopy(v)
new_geo[key] = geometric_data[key]
follow_buffer.append(deepcopy(new_geo['follow']))
if key == 'clear':
new_geo[key] = geometric_data['clear']
grb_obj.apertures[storage_apid][k].append(deepcopy(new_geo))
grb_obj.aperture_macros = deepcopy(self.gerber_obj.aperture_macros)
@ -3221,7 +3326,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
selected_apid = self.apertures_table.item(row, 1).text()
self.last_aperture_selected = copy(selected_apid)
for obj in self.storage_dict[selected_apid]['solid_geometry']:
for obj in self.storage_dict[selected_apid]['geometry']:
self.selected.append(obj)
except Exception as e:
self.app.log.debug(str(e))
@ -3233,7 +3338,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
return self.options[key]
def on_grb_shape_complete(self, storage=None, specific_shape=None, noplot=False):
self.app.log.debug("on_shape_complete()")
self.app.log.debug("on_grb_shape_complete()")
if specific_shape:
geo = specific_shape
@ -3246,7 +3351,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Add shape
self.add_gerber_shape(geo, storage)
else:
stora = self.storage_dict[self.last_aperture_selected]['solid_geometry']
stora = self.storage_dict[self.last_aperture_selected]['geometry']
self.add_gerber_shape(geo, storage=stora)
# Remove any utility shapes
@ -3257,35 +3362,36 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Replot and reset tool.
self.plot_all()
def add_gerber_shape(self, shape, storage):
def add_gerber_shape(self, shape_element, storage):
"""
Adds a shape to the shape storage.
:param shape: Shape to be added.
:type shape: DrawToolShape
:param shape_element: Shape to be added.
:type shape_element: DrawToolShape or DrawToolUtilityShape Geometry is stored as a dict with keys: solid,
follow, clear, each value being a list of Shapely objects. The dict can have at least one of the mentioned keys
:return: None
"""
# List of DrawToolShape?
if isinstance(shape, list):
for subshape in shape:
if isinstance(shape_element, list):
for subshape in shape_element:
self.add_gerber_shape(subshape, storage)
return
assert isinstance(shape, DrawToolShape), \
"Expected a DrawToolShape, got %s" % str(type(shape))
assert isinstance(shape_element, DrawToolShape), \
"Expected a DrawToolShape, got %s" % str(type(shape_element))
assert shape.geo is not None, \
assert shape_element.geo is not None, \
"Shape object has empty geometry (None)"
assert (isinstance(shape.geo, list) and len(shape.geo) > 0) or \
not isinstance(shape.geo, list), \
assert (isinstance(shape_element.geo, list) and len(shape_element.geo) > 0) or \
not isinstance(shape_element.geo, list), \
"Shape objects has empty geometry ([])"
if isinstance(shape, DrawToolUtilityShape):
self.utility.append(shape)
if isinstance(shape_element, DrawToolUtilityShape):
self.utility.append(shape_element)
else:
storage.append(shape) # TODO: Check performance
storage.append(shape_element)
def on_canvas_click(self, event):
"""
@ -3440,9 +3546,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.delete_selection_shape()
for storage in self.storage_dict:
try:
for obj in self.storage_dict[storage]['solid_geometry']:
if (sel_type is True and poly_selection.contains(obj.geo)) or \
(sel_type is False and poly_selection.intersects(obj.geo)):
for obj in self.storage_dict[storage]['geometry']:
geometric_data = obj.geo['solid']
if (sel_type is True and poly_selection.contains(geometric_data)) or \
(sel_type is False and poly_selection.intersects(geometric_data)):
if self.key == self.app.defaults["global_mselect_key"]:
if obj in self.selected:
self.selected.remove(obj)
@ -3562,15 +3669,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
def draw_utility_geometry(self, geo):
if type(geo.geo) == list:
for el in geo.geo:
geometric_data = el['solid']
# Add the new utility shape
self.tool_shape.add(
shape=el, color=(self.app.defaults["global_draw_color"] + '80'),
shape=geometric_data, color=(self.app.defaults["global_draw_color"] + '80'),
# face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None)
else:
geometric_data = geo.geo['solid']
# Add the new utility shape
self.tool_shape.add(
shape=geo.geo,
shape=geometric_data,
color=(self.app.defaults["global_draw_color"] + '80'),
# face_color=self.app.defaults['global_alt_sel_fill'],
update=False, layer=0, tolerance=None)
@ -3590,32 +3699,34 @@ class FlatCAMGrbEditor(QtCore.QObject):
for storage in self.storage_dict:
try:
for shape in self.storage_dict[storage]['solid_geometry']:
if shape.geo is None:
for elem in self.storage_dict[storage]['geometry']:
geometric_data = elem.geo['solid']
if geometric_data is None:
continue
if shape in self.selected:
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'],
linewidth=2)
if elem in self.selected:
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_sel_draw_color'])
continue
self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'])
self.plot_shape(geometry=geometric_data,
color=self.app.defaults['global_draw_color'])
except KeyError:
pass
for shape in self.utility:
self.plot_shape(geometry=shape.geo, linewidth=1)
for elem in self.utility:
geometric_data = elem.geo['solid']
self.plot_shape(geometry=geometric_data)
continue
self.shapes.redraw()
def plot_shape(self, geometry=None, color='black', linewidth=1):
def plot_shape(self, geometry=None, color='black'):
"""
Plots a geometric object or list of objects without rendering. Plotted objects
are returned as a list. This allows for efficient/animated rendering.
:param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such)
:param color: Shape color
:param linewidth: Width of lines in # of pixels.
:return: List of plotted elements.
"""
# plot_elements = []
@ -3671,20 +3782,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
except Exception:
traceback.print_exc()
def on_shape_complete(self):
self.app.log.debug("on_shape_complete()")
# Add shape
self.add_gerber_shape(self.active_tool.geometry)
# Remove any utility shapes
self.delete_utility_geometry()
self.tool_shape.clear(update=True)
# Replot and reset tool.
self.plot_all()
# self.active_tool = type(self.active_tool)(self)
def get_selected(self):
"""
Returns list of shapes that are selected in the editor.
@ -3708,28 +3805,28 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.build_ui()
self.app.inform.emit(_("[success] Done. Apertures geometry deleted."))
def delete_shape(self, shape):
def delete_shape(self, geo_el):
self.is_modified = True
if shape in self.utility:
self.utility.remove(shape)
if geo_el in self.utility:
self.utility.remove(geo_el)
return
for storage in self.storage_dict:
try:
if shape in self.storage_dict[storage]['solid_geometry']:
self.storage_dict[storage]['solid_geometry'].remove(shape)
if geo_el in self.storage_dict[storage]['geometry']:
self.storage_dict[storage]['geometry'].remove(geo_el)
except KeyError:
pass
if shape in self.selected:
self.selected.remove(shape) # TODO: Check performance
if geo_el in self.selected:
self.selected.remove(geo_el) # TODO: Check performance
def delete_utility_geometry(self):
# for_deletion = [shape for shape in self.shape_buffer if shape.utility]
# for_deletion = [shape for shape in self.storage.get_objects() if shape.utility]
for_deletion = [shape for shape in self.utility]
for shape in for_deletion:
self.delete_shape(shape)
for_deletion = [geo_el for geo_el in self.utility]
for geo_el in for_deletion:
self.delete_shape(geo_el)
self.tool_shape.clear(update=True)
self.tool_shape.redraw()
@ -3748,17 +3845,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.tools_gerber[toolname]["button"].setChecked(True)
self.on_tool_select(toolname)
def set_selected(self, shape):
def set_selected(self, geo_el):
# Remove and add to the end.
if shape in self.selected:
self.selected.remove(shape)
if geo_el in self.selected:
self.selected.remove(geo_el)
self.selected.append(shape)
self.selected.append(geo_el)
def set_unselected(self, shape):
if shape in self.selected:
self.selected.remove(shape)
def set_unselected(self, geo_el):
if geo_el in self.selected:
self.selected.remove(geo_el)
def on_array_type_combo(self):
if self.array_type_combo.currentIndex() == 0:
@ -3827,17 +3924,28 @@ class FlatCAMGrbEditor(QtCore.QObject):
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
def buffer_recursion(geom, selection):
if type(geom) == list or type(geom) is MultiPolygon:
def buffer_recursion(geom_el, selection):
if type(geom_el) == list:
geoms = list()
for local_geom in geom:
for local_geom in geom_el:
geoms.append(buffer_recursion(local_geom, selection=selection))
return geoms
else:
if geom in selection:
return DrawToolShape(geom.geo.buffer(buff_value, join_style=join_style))
if geom_el in selection:
geometric_data = geom_el.geo
buffered_geom_el = dict()
if 'solid' in geom_el:
buffered_geom_el['solid'] = DrawToolShape(geometric_data['solid'].buffer(buff_value,
join_style=join_style))
if 'follow' in geom_el:
buffered_geom_el['follow'] = DrawToolShape(geometric_data['follow'].buffer(buff_value,
join_style=join_style))
if 'clear' in geom_el:
buffered_geom_el['clear'] = DrawToolShape(geometric_data['clear'].buffer(buff_value,
join_style=join_style))
return buffered_geom_el
else:
return geom
return geom_el
if not self.apertures_table.selectedItems():
self.app.inform.emit(_(
@ -3849,9 +3957,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
try:
apid = self.apertures_table.item(x.row(), 1).text()
temp_storage = deepcopy(buffer_recursion(self.storage_dict[apid]['solid_geometry'], self.selected))
self.storage_dict[apid]['solid_geometry'] = []
self.storage_dict[apid]['solid_geometry'] = temp_storage
temp_storage = deepcopy(buffer_recursion(self.storage_dict[apid]['geometry'], self.selected))
self.storage_dict[apid]['geometry'] = []
self.storage_dict[apid]['geometry'] = temp_storage
except Exception as e:
log.debug("FlatCAMGrbEditor.buffer() --> %s" % str(e))
@ -3874,17 +3982,29 @@ class FlatCAMGrbEditor(QtCore.QObject):
"Add it and retry."))
return
def scale_recursion(geom, selection):
if type(geom) == list or type(geom) is MultiPolygon:
def scale_recursion(geom_el, selection):
if type(geom_el) == list:
geoms = list()
for local_geom in geom:
for local_geom in geom_el:
geoms.append(scale_recursion(local_geom, selection=selection))
return geoms
else:
if geom in selection:
return DrawToolShape(affinity.scale(geom.geo, scale_factor, scale_factor, origin='center'))
if geom_el in selection:
geometric_data = geom_el.geo
scaled_geom_el = dict()
if 'solid' in geom_el:
scaled_geom_el['solid'] = DrawToolShape(
affinity.scale(geometric_data['solid'], scale_factor, scale_factor, origin='center'))
if 'follow' in geom_el:
scaled_geom_el['follow'] = DrawToolShape(
affinity.scale(geometric_data['follow'], scale_factor, scale_factor, origin='center'))
if 'clear' in geom_el:
scaled_geom_el['clear'] = DrawToolShape(
affinity.scale(geometric_data['clear'], scale_factor, scale_factor, origin='center'))
return scaled_geom_el
else:
return geom
return geom_el
if not self.apertures_table.selectedItems():
self.app.inform.emit(_(
@ -3896,9 +4016,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
try:
apid = self.apertures_table.item(x.row(), 1).text()
temp_storage = deepcopy(scale_recursion(self.storage_dict[apid]['solid_geometry'], self.selected))
self.storage_dict[apid]['solid_geometry'] = []
self.storage_dict[apid]['solid_geometry'] = temp_storage
temp_storage = deepcopy(scale_recursion(self.storage_dict[apid]['geometry'], self.selected))
self.storage_dict[apid]['geometry'] = []
self.storage_dict[apid]['geometry'] = temp_storage
except Exception as e:
log.debug("FlatCAMGrbEditor.on_scale() --> %s" % str(e))
@ -4581,21 +4701,23 @@ class TransformEditorTool(FlatCAMTool):
return
def on_rotate_action(self, num):
shape_list = self.draw_app.selected
elem_list = self.draw_app.selected
xminlist = []
yminlist = []
xmaxlist = []
ymaxlist = []
if not shape_list:
if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to rotate!"))
return
else:
with self.app.proc_container.new(_("Appying Rotate")):
try:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
# first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
# bounding box
for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin)
yminlist.append(ymin)
xmaxlist.append(xmax)
@ -4608,36 +4730,35 @@ class TransformEditorTool(FlatCAMTool):
ymaximal = max(ymaxlist)
self.app.progress.emit(20)
for sel_sha in shape_list:
px = 0.5 * (xminimal + xmaximal)
py = 0.5 * (yminimal + ymaximal)
sel_sha.rotate(-num, point=(px, py))
for sel_el in elem_list:
if 'solid' in sel_el:
sel_el['solid'].rotate(-num, point=(px, py))
if 'follow' in sel_el:
sel_el['follow'].rotate(-num, point=(px, py))
if 'clear' in sel_el:
sel_el['clear'].rotate(-num, point=(px, py))
self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sel_sha.geo))
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_("[success] Done. Rotate completed."))
self.app.progress.emit(100)
except Exception as e:
self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e))
return
def on_flip(self, axis):
shape_list = self.draw_app.selected
elem_list = self.draw_app.selected
xminlist = []
yminlist = []
xmaxlist = []
ymaxlist = []
if not shape_list:
if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!"))
return
else:
with self.app.proc_container.new(_("Applying Flip")):
try:
# get mirroring coords from the point entry
@ -4645,9 +4766,11 @@ class TransformEditorTool(FlatCAMTool):
px, py = eval('{}'.format(self.flip_ref_entry.text()))
# get mirroing coords from the center of an all-enclosing bounding box
else:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
# first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
# bounding box
for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin)
yminlist.append(ymin)
xmaxlist.append(xmax)
@ -4665,19 +4788,24 @@ class TransformEditorTool(FlatCAMTool):
self.app.progress.emit(20)
# execute mirroring
for sha in shape_list:
for sel_el in elem_list:
if axis is 'X':
sha.mirror('X', (px, py))
if 'solid' in sel_el:
sel_el['solid'].mirror('X', (px, py))
if 'follow' in sel_el:
sel_el['follow'].mirror('X', (px, py))
if 'clear' in sel_el:
sel_el['clear'].mirror('X', (px, py))
self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
elif axis is 'Y':
sha.mirror('Y', (px, py))
if 'solid' in sel_el:
sel_el['solid'].mirror('Y', (px, py))
if 'follow' in sel_el:
sel_el['follow'].mirror('Y', (px, py))
if 'clear' in sel_el:
sel_el['clear'].mirror('Y', (px, py))
self.app.inform.emit(_('[success] Flip on the X axis done ...'))
self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.progress.emit(100)
except Exception as e:
@ -4685,19 +4813,21 @@ class TransformEditorTool(FlatCAMTool):
return
def on_skew(self, axis, num):
shape_list = self.draw_app.selected
elem_list = self.draw_app.selected
xminlist = []
yminlist = []
if not shape_list:
if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to shear/skew!"))
return
else:
with self.app.proc_container.new(_("Applying Skew")):
try:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
# first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
# bounding box
for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin)
yminlist.append(ymin)
@ -4707,17 +4837,23 @@ class TransformEditorTool(FlatCAMTool):
self.app.progress.emit(20)
for sha in shape_list:
for sel_el in elem_list:
if axis is 'X':
sha.skew(num, 0, point=(xminimal, yminimal))
if 'solid' in sel_el:
sel_el['solid'].skew(num, 0, point=(xminimal, yminimal))
if 'follow' in sel_el:
sel_el['follow'].skew(num, 0, point=(xminimal, yminimal))
if 'clear' in sel_el:
sel_el['clear'].skew(num, 0, point=(xminimal, yminimal))
elif axis is 'Y':
sha.skew(0, num, point=(xminimal, yminimal))
if 'solid' in sel_el:
sel_el['solid'].skew(0, num, point=(xminimal, yminimal))
if 'follow' in sel_el:
sel_el['follow'].skew(0, num, point=(xminimal, yminimal))
if 'clear' in sel_el:
sel_el['clear'].skew(0, num, point=(xminimal, yminimal))
self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
self.app.progress.emit(100)
@ -4726,21 +4862,23 @@ class TransformEditorTool(FlatCAMTool):
return
def on_scale(self, axis, xfactor, yfactor, point=None):
shape_list = self.draw_app.selected
elem_list = self.draw_app.selected
xminlist = []
yminlist = []
xmaxlist = []
ymaxlist = []
if not shape_list:
if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to scale!"))
return
else:
with self.app.proc_container.new(_("Applying Scale")):
try:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
# first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
# bounding box
for el in elem_list:
if 'solid' in el:
xmin, ymin, xmax, ymax = el['solid'].bounds()
xminlist.append(xmin)
yminlist.append(ymin)
xmaxlist.append(xmax)
@ -4761,14 +4899,15 @@ class TransformEditorTool(FlatCAMTool):
px = 0
py = 0
for sha in shape_list:
sha.scale(xfactor, yfactor, point=(px, py))
for sel_el in elem_list:
if 'solid' in sel_el:
sel_el['solid'].scale(xfactor, yfactor, point=(px, py))
if 'follow' in sel_el:
sel_el['follow'].scale(xfactor, yfactor, point=(px, py))
if 'clear' in sel_el:
sel_el['clear'].scale(xfactor, yfactor, point=(px, py))
self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis))
self.app.progress.emit(100)
except Exception as e:
@ -4776,38 +4915,33 @@ class TransformEditorTool(FlatCAMTool):
return
def on_offset(self, axis, num):
shape_list = self.draw_app.selected
xminlist = []
yminlist = []
elem_list = self.draw_app.selected
if not shape_list:
if not elem_list:
self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to offset!"))
return
else:
with self.app.proc_container.new(_("Applying Offset")):
try:
# first get a bounding box to fit all
for sha in shape_list:
xmin, ymin, xmax, ymax = sha.bounds()
xminlist.append(xmin)
yminlist.append(ymin)
# get the minimum x,y and maximum x,y for all objects selected
xminimal = min(xminlist)
yminimal = min(yminlist)
self.app.progress.emit(20)
for sha in shape_list:
for sel_el in elem_list:
if axis is 'X':
sha.offset((num, 0))
if 'solid' in sel_el:
sel_el['solid'].offset((num, 0))
if 'follow' in sel_el:
sel_el['follow'].offset((num, 0))
if 'clear' in sel_el:
sel_el['clear'].offset((num, 0))
elif axis is 'Y':
sha.offset((0, num))
if 'solid' in sel_el:
sel_el['solid'].offset((0, num))
if 'follow' in sel_el:
sel_el['follow'].offset((0, num))
if 'clear' in sel_el:
sel_el['clear'].offset((0, num))
self.draw_app.plot_all()
# self.draw_app.add_shape(DrawToolShape(sha.geo))
#
# self.draw_app.transform_complete.emit()
self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
self.app.progress.emit(100)