"Paint connect" functional. Added to seed-based painting and unit-tested. "Path connect" still pending. Disabled. "Path connect" unit test added.

This commit is contained in:
jpcaram 2015-01-25 16:55:22 -05:00
parent 9632d9a98f
commit 6b51f03db2
5 changed files with 381 additions and 81 deletions

View File

@ -963,6 +963,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# To be called after clicking on the plot.
def doit(event):
self.app.info("Painting polygon...")
self.app.plotcanvas.mpl_disconnect(subscription)
point = [event.xdata, event.ydata]
self.paint_poly(point, tooldia, overlap)
@ -991,6 +992,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
cp = self.clear_polygon2(poly.buffer(-self.options["paintmargin"]), tooldia, overlap=overlap)
geo_obj.solid_geometry = cp
geo_obj.options["cnctooldia"] = tooldia
self.app.inform.emit("Done.")
name = self.options["name"] + "_paint"
self.app.new_object("geometry", name, gen_paintarea)

190
camlib.py
View File

@ -37,7 +37,7 @@ from descartes.patch import PolygonPatch
import simplejson as json
# TODO: Commented for FlatCAM packaging with cx_freeze
#from matplotlib.pyplot import plot
from matplotlib.pyplot import plot, subplot
import logging
@ -388,12 +388,21 @@ class Geometry(object):
inner_edges.append(y)
geoms += outer_edges + inner_edges
# Optimization
# Optimization: Join paths
# TODO: Re-architecture?
# log.debug("Simplifying paths...")
g = Geometry()
g.solid_geometry = geoms
g.path_connect()
return g.flat_geometry
# g.path_connect()
#return g.flat_geometry
g.flatten(pathonly=True)
# Optimization: Reduce lifts
log.debug("Reducing tool lifts...")
p = self.paint_connect(g.flat_geometry, polygon, tooldia)
return p
#return geoms
@ -418,7 +427,8 @@ class Geometry(object):
"""
return
def paint_connect(self, geolist, boundary, tooldia):
@staticmethod
def paint_connect(geolist, boundary, tooldia):
"""
Connects paths that results in a connection segment that is
within the paint area. This avoids unnecessary tool lifting.
@ -437,17 +447,20 @@ class Geometry(object):
for shape in geolist:
if shape is not None: # TODO: This shouldn't have happened.
storage.insert(shape)
# 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 = []
temp_path = None
path_count = 0
current_pt = (0, 0)
pt, geo = storage.nearest(current_pt)
try:
while True:
path_count += 1
log.debug("Path %d" % path_count)
# Remove before modifying, otherwise
# deletion will fail.
@ -461,27 +474,35 @@ class Geometry(object):
# Straight line from current_pt to pt.
# Is the toolpath inside the geometry?
jump = LineString([current_pt, pt]).buffer(tooldia / 2)
if jump.within(boundary):
log.debug("Jump to path #%d is inside. Joining." % path_count)
# Completely inside. Append...
if temp_path is None:
temp_path = geo
else:
temp_path.coords = list(temp_path.coords) + list(geo.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.
optimized_paths.append(temp_path)
temp_path = geo
log.debug("Path #%d not within boundary. Next." % path_count)
optimized_paths.append(geo)
current_pt = geo.coords[-1]
# Next
pt, geo = storage.nearest(current_pt)
except StopIteration: # Nothing found in storage.
if not temp_path.equals(optimized_paths[-1]):
optimized_paths.append(temp_path)
except StopIteration: # Nothing left in storage.
pass
def path_connect(self):
return optimized_paths
@staticmethod
def path_connect(pathlist):
"""
Simplifies a list of paths by joining those whose ends touch.
The list of paths of generated from the geometry.flatten()
@ -491,7 +512,7 @@ class Geometry(object):
:return: None
"""
flat_geometry = self.flatten(pathonly=True)
# flat_geometry = self.flatten(pathonly=True)
## Index first and last points in paths
def get_pts(o):
@ -500,7 +521,7 @@ class Geometry(object):
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
for shape in flat_geometry:
for shape in pathlist:
if shape is not None: # TODO: This shouldn't have happened.
storage.insert(shape)
@ -558,7 +579,8 @@ class Geometry(object):
except StopIteration: # Nothing found in storage.
pass
self.flat_geometry = optimized_geometry
#self.flat_geometry = optimized_geometry
return optimized_geometry
def convert_units(self, units):
"""
@ -2435,6 +2457,7 @@ class CNCjob(Geometry):
:return: None
"""
assert isinstance(geometry, Geometry)
log.debug("generate_from_geometry_2()")
## Flatten the geometry
# Only linear elements (no polygons) remain.
@ -2442,12 +2465,16 @@ class CNCjob(Geometry):
log.debug("%d paths" % len(flat_geometry))
## Index first and last points in paths
# What points to index.
def get_pts(o):
return [o.coords[0], o.coords[-1]]
# Create the indexed storage.
storage = FlatCAMRTreeStorage()
storage.get_points = get_pts
# Store the geometry
log.debug("Indexing geometry before generating G-Code...")
for shape in flat_geometry:
if shape is not None: # TODO: This shouldn't have happened.
storage.insert(shape)
@ -2455,7 +2482,7 @@ class CNCjob(Geometry):
if tooldia is not None:
self.tooldia = tooldia
self.input_geometry_bounds = geometry.bounds()
# self.input_geometry_bounds = geometry.bounds()
if not append:
self.gcode = ""
@ -2470,6 +2497,7 @@ class CNCjob(Geometry):
self.gcode += self.pausecode + "\n"
## Iterate over geometry paths getting the nearest each time.
log.debug("Starting G-Code...")
path_count = 0
current_pt = (0, 0)
pt, geo = storage.nearest(current_pt)
@ -2997,7 +3025,7 @@ def dict2obj(d):
return d
def plotg(geo, solid_poly=False):
def plotg(geo, solid_poly=False, color="black"):
try:
_ = iter(geo)
except:
@ -3015,15 +3043,15 @@ def plotg(geo, solid_poly=False):
ax.add_patch(patch)
else:
x, y = g.exterior.coords.xy
plot(x, y)
plot(x, y, color=color)
for ints in g.interiors:
x, y = ints.coords.xy
plot(x, y)
plot(x, y, color=color)
continue
if type(g) == LineString or type(g) == LinearRing:
x, y = g.coords.xy
plot(x, y)
plot(x, y, color=color)
continue
if type(g) == Point:
@ -3033,7 +3061,7 @@ def plotg(geo, solid_poly=False):
try:
_ = iter(g)
plotg(g)
plotg(g, color=color)
except:
log.error("Cannot plot: " + str(type(g)))
continue
@ -3380,58 +3408,58 @@ class FlatCAMRTreeStorage(FlatCAMRTree):
return (tidx.bbox[0], tidx.bbox[1]), self.objects[tidx.object]
class myO:
def __init__(self, coords):
self.coords = coords
def test_rti():
o1 = myO([(0, 0), (0, 1), (1, 1)])
o2 = myO([(2, 0), (2, 1), (2, 1)])
o3 = myO([(2, 0), (2, 1), (3, 1)])
os = [o1, o2]
idx = FlatCAMRTree()
for o in range(len(os)):
idx.insert(o, os[o])
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
idx.remove_obj(0, o1)
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
idx.remove_obj(1, o2)
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
def test_rtis():
o1 = myO([(0, 0), (0, 1), (1, 1)])
o2 = myO([(2, 0), (2, 1), (2, 1)])
o3 = myO([(2, 0), (2, 1), (3, 1)])
os = [o1, o2]
idx = FlatCAMRTreeStorage()
for o in range(len(os)):
idx.insert(os[o])
#os = None
#o1 = None
#o2 = None
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
idx.remove(idx.nearest((2,0))[1])
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
idx.remove(idx.nearest((0,0))[1])
print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
# class myO:
# def __init__(self, coords):
# self.coords = coords
#
#
# def test_rti():
#
# o1 = myO([(0, 0), (0, 1), (1, 1)])
# o2 = myO([(2, 0), (2, 1), (2, 1)])
# o3 = myO([(2, 0), (2, 1), (3, 1)])
#
# os = [o1, o2]
#
# idx = FlatCAMRTree()
#
# for o in range(len(os)):
# idx.insert(o, os[o])
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
#
# idx.remove_obj(0, o1)
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
#
# idx.remove_obj(1, o2)
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
#
#
# def test_rtis():
#
# o1 = myO([(0, 0), (0, 1), (1, 1)])
# o2 = myO([(2, 0), (2, 1), (2, 1)])
# o3 = myO([(2, 0), (2, 1), (3, 1)])
#
# os = [o1, o2]
#
# idx = FlatCAMRTreeStorage()
#
# for o in range(len(os)):
# idx.insert(os[o])
#
# #os = None
# #o1 = None
# #o2 = None
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
#
# idx.remove(idx.nearest((2,0))[1])
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]
#
# idx.remove(idx.nearest((0,0))[1])
#
# print [x.bbox for x in idx.rti.nearest((0, 0), num_results=20, objects=True)]

197
tests/test_paint.py Normal file
View File

@ -0,0 +1,197 @@
import unittest
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 *
def plotg2(geo, solid_poly=False, color="black", linestyle='solid'):
try:
for sub_geo in geo:
plotg2(sub_geo, solid_poly=solid_poly, color=color, linestyle=linestyle)
except TypeError:
if type(geo) == Polygon:
if solid_poly:
patch = PolygonPatch(geo,
#facecolor="#BBF268",
facecolor=color,
edgecolor="#006E20",
alpha=0.5,
zorder=2)
ax = subplot(111)
ax.add_patch(patch)
else:
x, y = geo.exterior.coords.xy
plot(x, y, color=color, linestyle=linestyle)
for ints in geo.interiors:
x, y = ints.coords.xy
plot(x, y, color=color, linestyle=linestyle)
if type(geo) == LineString or type(geo) == LinearRing:
x, y = geo.coords.xy
plot(x, y, color=color, linestyle=linestyle)
if type(geo) == Point:
x, y = geo.coords.xy
plot(x, y, 'o')
class PaintTestCase(unittest.TestCase):
# def __init__(self):
# super(PaintTestCase, self).__init__()
# self.boundary = None
# self.descr = None
def plot_summary_A(self, paths, tooldia, result, msg):
plotg2(self.boundary, solid_poly=True, color="green")
plotg2(paths, color="red")
plotg2([r.buffer(tooldia / 2) for r in result], solid_poly=True, color="blue")
plotg2(result, color="black", linestyle='dashed')
title(msg)
xlim(0, 5)
ylim(0, 5)
show()
class PaintConnectTest(PaintTestCase):
"""
Simple rectangular boundary and paths inside.
"""
def setUp(self):
self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]])
def test_jump(self):
print "Test: WALK Expected"
paths = [
LineString([[0.5, 2], [2, 4.5]]),
LineString([[2, 0.5], [4.5, 2]])
]
for p in paths:
print p
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
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):
print "Test: FLY Expected"
paths = [
LineString([[0, 2], [2, 5]]),
LineString([[2, 0], [5, 2]])
]
for p in paths:
print p
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
for r in result:
print r
self.assertEqual(len(result), len(paths))
self.plot_summary_A(paths, tooldia, result, "FLY Expected")
def test_no_jump2(self):
print "Test: FLY Expected"
paths = [
LineString([[0.5, 2], [2, 4.5]]),
LineString([[2, 0.5], [4.5, 2]])
]
for p in paths:
print p
tooldia = 1.1
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
for r in result:
print r
self.assertEqual(len(result), len(paths))
self.plot_summary_A(paths, tooldia, result, "FLY Expected")
class PaintConnectTest2(PaintTestCase):
"""
Boundary with an internal cutout.
"""
def setUp(self):
self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]])
self.boundary = self.boundary.difference(
Polygon([[2, 1], [3, 1], [3, 4], [2, 4]])
)
def test_no_jump3(self):
print "TEST: No jump expected"
paths = [
LineString([[0.5, 1], [1.5, 3]]),
LineString([[4, 1], [4, 4]])
]
for p in paths:
print p
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
for r in result:
print r
self.assertEqual(len(result), len(paths))
self.plot_summary_A(paths, tooldia, result, "FLY Expected")
class PaintConnectTest3(PaintTestCase):
"""
Tests with linerings among elements.
"""
def setUp(self):
self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]])
def test_jump2(self):
print "Test: WALK Expected"
paths = [
LineString([[0.5, 2], [2, 4.5]]),
LineString([[2, 0.5], [4.5, 2]]),
self.boundary.buffer(-0.5).exterior
]
for p in paths:
print p
tooldia = 1.0
print "--"
result = Geometry.paint_connect(paths, self.boundary, tooldia)
for r in result:
print r
self.assertEqual(len(result), 1)
self.plot_summary_A(paths, tooldia, result, "WALK Expected")
if __name__ == '__main__':
unittest.main()

27
tests/test_pathconnect.py Normal file
View File

@ -0,0 +1,27 @@
import unittest
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 *
class PathConnectTest1(unittest.TestCase):
def setUp(self):
pass
def test_simple_connect(self):
paths = [
LineString([[0, 0], [0, 1]]),
LineString([[0, 1], [0, 2]])
]
result = Geometry.path_connect(paths)
self.assertEqual(len(result), 1)
self.assertTrue(result[0].equals(LineString([[0, 0], [0, 2]])))
if __name__ == "__main__":
unittest.main()

46
tests/test_plotg.py Normal file
View File

@ -0,0 +1,46 @@
from shapely.geometry import LineString, Polygon
from shapely.ops import cascaded_union, unary_union
from matplotlib.pyplot import plot, subplot, show
from camlib import *
def plotg2(geo, solid_poly=False, color="black", linestyle='solid'):
try:
for sub_geo in geo:
plotg2(sub_geo, solid_poly=solid_poly, color=color, linestyle=linestyle)
except TypeError:
if type(geo) == Polygon:
if solid_poly:
patch = PolygonPatch(geo,
#facecolor="#BBF268",
facecolor=color,
edgecolor="#006E20",
alpha=0.5,
zorder=2)
ax = subplot(111)
ax.add_patch(patch)
else:
x, y = geo.exterior.coords.xy
plot(x, y, color=color, linestyle=linestyle)
for ints in geo.interiors:
x, y = ints.coords.xy
plot(x, y, color=color, linestyle=linestyle)
if type(geo) == LineString or type(geo) == LinearRing:
x, y = geo.coords.xy
plot(x, y, color=color, linestyle=linestyle)
if type(geo) == Point:
x, y = geo.coords.xy
plot(x, y, 'o')
if __name__ == "__main__":
p = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]])
paths = [
LineString([[0.5, 2], [2, 4.5]]),
LineString([[2, 0.5], [4.5, 2]])
]
plotg2(p, solid_poly=True)
plotg2(paths, linestyle="dashed")
show()