"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:
parent
9632d9a98f
commit
6b51f03db2
|
@ -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
190
camlib.py
|
@ -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)]
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
Loading…
Reference in New Issue