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
4 changed files with 715 additions and 752 deletions

View File

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

View File

@@ -11,9 +11,14 @@ CAD program, and create G-Code for Isolation routing.
10.05.2019 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 - 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 - optimized Transform tool
9.05.2019
- rework the Gerber parser
8.05.2019 8.05.2019
- added zoom fit for Set Origin command - added zoom fit for Set Origin command

647
camlib.py
View File

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

View File

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