Refactored paint_connect() and path_connect() to receive and return FlatCAMRTreeStorage objects. Updated unittests acordingly.

This commit is contained in:
jpcaram 2015-01-29 15:52:03 -05:00
parent a1345f0a58
commit 6733ebbfa8
4 changed files with 131 additions and 84 deletions

View File

@ -1565,6 +1565,12 @@ class App(QtCore.QObject):
app_obj.inform.emit("[error] Failed to open file: " + filename)
app_obj.progress.emit(0)
raise IOError('Failed to open file: ' + filename)
except ParseError, e:
app_obj.inform.emit("[error] Failed to parse file: " + filename)
app_obj.progress.emit(0)
self.log.error(str(e))
raise
return
# Further parsing
self.progress.emit(70) # TODO: Note the mixture of self and app_obj used here

148
camlib.py
View File

@ -6,6 +6,7 @@
# MIT Licence #
############################################################
#from __future__ import division
from scipy import optimize
import traceback
from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, dot, float32, \
@ -337,7 +338,8 @@ class Geometry(object):
break
return poly_cuts
def clear_polygon2(self, polygon, tooldia, seedpoint=None, overlap=0.15):
@staticmethod
def clear_polygon2(polygon, tooldia, seedpoint=None, overlap=0.15):
"""
Creates geometry inside a polygon for a tool to cover
the whole area.
@ -357,8 +359,12 @@ class Geometry(object):
# Current buffer radius
radius = tooldia / 2 * (1 - overlap)
# The toolpaths
geoms = []
## The toolpaths
# Index first and last points in paths
def get_pts(o):
return [o.coords[0], o.coords[-1]]
geoms = FlatCAMRTreeStorage()
geoms.get_points = get_pts
# Path margin
path_margin = polygon.buffer(-tooldia / 2)
@ -367,7 +373,8 @@ class Geometry(object):
if seedpoint is None:
seedpoint = path_margin.representative_point()
# Grow from seed until outside the box.
# Grow from seed until outside the box. The polygons will
# never have an interior, so take the exterior LinearRing.
while 1:
path = Point(seedpoint).buffer(radius).exterior
path = path.intersection(path_margin)
@ -376,36 +383,30 @@ class Geometry(object):
if path.is_empty:
break
else:
geoms.append(path)
#geoms.append(path)
geoms.insert(path)
radius += tooldia * (1 - overlap)
# Clean edges
# Clean inside edges of the original polygon
outer_edges = [x.exterior for x in autolist(polygon.buffer(-tooldia / 2))]
inner_edges = []
for x in autolist(polygon.buffer(-tooldia / 2)): # Over resulting polygons
for y in x.interiors: # Over interiors of each polygon
inner_edges.append(y)
geoms += outer_edges + inner_edges
#geoms += outer_edges + inner_edges
for g in outer_edges + inner_edges:
geoms.insert(g)
# Optimization: Join paths
# TODO: Re-architecture?
# log.debug("Simplifying paths...")
g = Geometry()
g.solid_geometry = geoms
# g.path_connect()
#return g.flat_geometry
# Optimization connect touching paths
g.flatten(pathonly=True)
# Optimization: Reduce lifts
log.debug("Reducing tool lifts...")
p = self.paint_connect(g.flat_geometry, polygon, tooldia)
p = Geometry.paint_connect(g.flat_geometry, polygon, tooldia)
return p
#return geoms
def scale(self, factor):
"""
Scales all of the object's geometry by a given factor. Override
@ -428,7 +429,7 @@ class Geometry(object):
return
@staticmethod
def paint_connect(geolist, boundary, tooldia):
def paint_connect(storage, boundary, tooldia):
"""
Connects paths that results in a connection segment that is
within the paint area. This avoids unnecessary tool lifting.
@ -442,34 +443,39 @@ class Geometry(object):
def get_pts(o):
return [o.coords[0], o.coords[-1]]
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
for shape in geolist:
if shape is not None: # TODO: This shouldn't have happened.
# Make LlinearRings into linestrings otherwise
# When chaining the coordinates path is messed up.
storage.insert(LineString(shape))
#storage.insert(shape)
# storage = FlatCAMRTreeStorage()
# storage.get_points = get_pts
#
# for shape in geolist:
# if shape is not None: # TODO: This shouldn't have happened.
# # Make LlinearRings into linestrings otherwise
# # When chaining the coordinates path is messed up.
# storage.insert(LineString(shape))
# #storage.insert(shape)
## Iterate over geometry paths getting the nearest each time.
optimized_paths = []
#optimized_paths = []
optimized_paths = FlatCAMRTreeStorage()
optimized_paths.get_points = get_pts
path_count = 0
current_pt = (0, 0)
pt, geo = storage.nearest(current_pt)
storage.remove(geo)
geo = LineString(geo)
current_pt = geo.coords[-1]
try:
while True:
path_count += 1
log.debug("Path %d" % path_count)
# Remove before modifying, otherwise
# deletion will fail.
storage.remove(geo)
pt, candidate = storage.nearest(current_pt)
storage.remove(candidate)
candidate = LineString(candidate)
# If last point in geometry is the nearest
# then reverse coordinates.
if list(pt) == list(geo.coords[-1]):
geo.coords = list(geo.coords)[::-1]
if list(pt) == list(candidate.coords[-1]):
candidate.coords = list(candidate.coords)[::-1]
# Straight line from current_pt to pt.
# Is the toolpath inside the geometry?
@ -479,35 +485,35 @@ class Geometry(object):
log.debug("Jump to path #%d is inside. Joining." % path_count)
# Completely inside. Append...
try:
last = optimized_paths[-1]
last.coords = list(last.coords) + list(geo.coords)
except IndexError:
optimized_paths.append(geo)
geo.coords = list(geo.coords) + list(candidate.coords)
# try:
# last = optimized_paths[-1]
# last.coords = list(last.coords) + list(geo.coords)
# except IndexError:
# optimized_paths.append(geo)
else:
# Have to lift tool. End path.
log.debug("Path #%d not within boundary. Next." % path_count)
optimized_paths.append(geo)
#optimized_paths.append(geo)
optimized_paths.insert(geo)
geo = candidate
current_pt = geo.coords[-1]
# Next
pt, geo = storage.nearest(current_pt)
#pt, geo = storage.nearest(current_pt)
except StopIteration: # Nothing left in storage.
pass
#pass
optimized_paths.insert(geo)
return optimized_paths
@staticmethod
def path_connect(pathlist, origin=(0, 0)):
def path_connect(storage, origin=(0, 0)):
"""
Simplifies a list of paths by joining those whose ends touch.
The list of paths of generated from the geometry.flatten()
method which writes to geometry.flat_geometry. This list
if overwritten by this method with the optimized result.
:return: None
"""
@ -515,18 +521,21 @@ class Geometry(object):
## Index first and last points in paths
def get_pts(o):
return [o.coords[0], o.coords[-1]]
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
for shape in pathlist:
if shape is not None: # TODO: This shouldn't have happened.
storage.insert(shape)
#
# storage = FlatCAMRTreeStorage()
# storage.get_points = get_pts
#
# for shape in pathlist:
# if shape is not None: # TODO: This shouldn't have happened.
# storage.insert(shape)
path_count = 0
pt, geo = storage.nearest(origin)
storage.remove(geo)
optimized_geometry = [geo]
#optimized_geometry = [geo]
optimized_geometry = FlatCAMRTreeStorage()
optimized_geometry.get_points = get_pts
#optimized_geometry.insert(geo)
try:
while True:
path_count += 1
@ -536,6 +545,8 @@ class Geometry(object):
_, left = storage.nearest(geo.coords[0])
print "left is", left
# If left touches geo, remove left from original
# storage and append to geo.
if type(left) == LineString:
if left.coords[0] == geo.coords[0]:
storage.remove(left)
@ -560,6 +571,8 @@ class Geometry(object):
_, right = storage.nearest(geo.coords[-1])
print "right is", right
# If right touches geo, remove left from original
# storage and append to geo.
if type(right) == LineString:
if right.coords[0] == geo.coords[-1]:
storage.remove(right)
@ -581,23 +594,22 @@ class Geometry(object):
geo.coords = list(left.coords) + list(geo.coords)
continue
# No matches on either end
#optimized_geometry.append(geo)
# right is either a LinearRing or it does not connect
# to geo (nothing left to connect to geo), so we continue
# with right as geo.
storage.remove(right)
if type(right) == LinearRing:
# Put the linearring at the beginning so it does
# not iterfere.
optimized_geometry = [right] + optimized_geometry
geo = optimized_geometry[-1]
print "right was LinearRing, getting previous"
optimized_geometry.insert(right)
else:
optimized_geometry.append(right)
# Cannot exteng geo any further. Put it away.
optimized_geometry.insert(geo)
# Continue with right.
geo = right
print "stored right, now geo<-right"
except StopIteration: # Nothing found in storage.
pass
optimized_geometry.insert(geo)
print path_count
@ -1838,7 +1850,7 @@ class Gerber (Geometry):
## --- Buffered ---
width = self.apertures[last_path_aperture]["size"]
geo = LineString(path).buffer(width/2)
geo = LineString(path).buffer(width / 2)
poly_buffer.append(geo)
# --- Apply buffer ---
@ -1850,7 +1862,7 @@ class Gerber (Geometry):
except Exception, err:
#print traceback.format_exc()
log.error("PARSING FAILED. Line %d: %s" % (line_num, gline))
raise
raise ParseError("%s\nLine %d: %s" % (repr(err), line_num, gline))
@staticmethod
def create_flash_geometry(location, aperture):

View File

@ -4,6 +4,16 @@ from shapely.geometry import LineString, Polygon
from shapely.ops import cascaded_union, unary_union
from matplotlib.pyplot import plot, subplot, show, cla, clf, xlim, ylim, title
from camlib import *
from copy import deepcopy
def mkstorage(paths):
def get_pts(o):
return [o.coords[0], o.coords[-1]]
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
for p in paths:
storage.insert(p)
return storage
def plotg2(geo, solid_poly=False, color="black", linestyle='solid'):
@ -75,19 +85,14 @@ class PaintConnectTest(PaintTestCase):
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia)
result = list(result.get_objects())
for r in result:
print r
self.assertEqual(len(result), 1)
# plotg(self.boundary, solid_poly=True)
# plotg(paths, color="red")
# plotg([r.buffer(tooldia / 2) for r in result], solid_poly=True)
# show()
# #cla()
# clf()
self.plot_summary_A(paths, tooldia, result, "WALK expected.")
def test_no_jump1(self):
@ -102,7 +107,9 @@ class PaintConnectTest(PaintTestCase):
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia)
result = list(result.get_objects())
for r in result:
print r
@ -122,7 +129,9 @@ class PaintConnectTest(PaintTestCase):
tooldia = 1.1
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia)
result = list(result.get_objects())
for r in result:
print r
@ -154,7 +163,9 @@ class PaintConnectTest2(PaintTestCase):
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia)
result = list(result.get_objects())
for r in result:
print r
@ -170,6 +181,7 @@ class PaintConnectTest3(PaintTestCase):
def setUp(self):
self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]])
print "TEST w/ LinearRings"
def test_jump2(self):
print "Test: WALK Expected"
@ -184,7 +196,9 @@ class PaintConnectTest3(PaintTestCase):
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia)
result = list(result.get_objects())
for r in result:
print r

View File

@ -7,9 +7,20 @@ from camlib import *
from random import random
def mkstorage(paths):
def get_pts(o):
return [o.coords[0], o.coords[-1]]
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
for p in paths:
storage.insert(p)
return storage
class PathConnectTest1(unittest.TestCase):
def setUp(self):
print "PathConnectTest1.setUp()"
pass
def test_simple_connect(self):
@ -18,8 +29,9 @@ class PathConnectTest1(unittest.TestCase):
LineString([[1, 1], [2, 1]])
]
result = Geometry.path_connect(paths)
result = Geometry.path_connect(mkstorage(paths))
result = list(result.get_objects())
self.assertEqual(len(result), 1)
self.assertTrue(result[0].equals(LineString([[0, 0], [1, 1], [2, 1]])))
@ -30,8 +42,9 @@ class PathConnectTest1(unittest.TestCase):
LineString([[-0.5, 0.5], [0.5, 0]])
]
result = Geometry.path_connect(paths)
result = Geometry.path_connect(mkstorage(paths))
result = list(result.get_objects())
self.assertEqual(len(result), 2)
matches = [p for p in result if p.equals(LineString([[0, 0], [1, 1], [2, 1]]))]
self.assertEqual(len(matches), 1)
@ -46,8 +59,9 @@ class PathConnectTest1(unittest.TestCase):
LineString([[1 + offset_x, 1 + offset_y], [2 + offset_x, 1 + offset_y]])
]
result = Geometry.path_connect(paths)
result = Geometry.path_connect(mkstorage(paths))
result = list(result.get_objects())
self.assertEqual(len(result), 1)
self.assertTrue(result[0].equals(LineString([[0 + offset_x, 0 + offset_y],
[1 + offset_x, 1 + offset_y],
@ -63,8 +77,9 @@ class PathConnectTest1(unittest.TestCase):
LinearRing([[1, 1], [2, 2], [1, 3], [0, 2]])
]
result = Geometry.path_connect(paths)
result = Geometry.path_connect(mkstorage(paths))
result = list(result.get_objects())
self.assertEqual(len(result), 2)
matches = [p for p in result if p.equals(LineString([[0, 0], [1, 1], [2, 1]]))]
self.assertEqual(len(matches), 1)