Refactored paint_connect() and path_connect() to receive and return FlatCAMRTreeStorage objects. Updated unittests acordingly.
This commit is contained in:
parent
a1345f0a58
commit
6733ebbfa8
|
@ -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
148
camlib.py
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue