Flush or close Plotter

Hello. In order to produce an animation frame by frame, I’m looking for a way to flush or close the Plotter() object at the end of each loop.

from compas_plotters import Plotter
from compas.geometry import Point
from random import randint
l = [(randint(0, 10),randint(0, 10)) for _ in range(30)]
for p in l:
    plotter = Plotter()
    plotter.add(Point(*p)) # minimal example
    plotter.save("./frames/{:02d}_{:02d}.png".format(*p))
    # close/flush plotter

The above example code produces the warning : RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (matplotlib.pyplot.figure) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam figure.max_open_warning). figure = plt.figure(
Thanks !

Maybe the solution could be here : matplotlib.figure — Matplotlib 3.5.3 documentation

I extend the example for a use case with layers.

from compas_plotters import Plotter
from compas.geometry import Point
from random import randint
l = [(randint(0, 10),randint(0, 10)) for _ in range(30)]
l2 = [(randint(0, 10),randint(0, 10)) for _ in range(30)]
for p, p2 in zip(l, l2):
    plotter = Plotter()
    plotter.add(Point(*p)) # minimal example
    plotter.zoom_extents()
    plotter.save("./frames/{:02d}_{:02d}_Layer_1.svg".format(*p)) # --> first layer
    # --> clear plotter
    plotter.add(Point(*p2)) # minimal example
    plotter.save("./frames/{:02d}_{:02d}_Layer_2.svg".format(*p)) # --> second layer

In this example it is guaranteed that layer 1 and layer 2 are of the same size. Matplotlib seems to optimize image size and to make new plotter objects could therefore end up with two layers of different sizes.

UPDATE:

Gettng closer to the goal. When adding geometry, the Plotter instance creates an internal Artist object. It could normally be accessed and cleared. Though it seems not implemented yet.

for arts in plotter.artists:
    arts.clear()

Having some trouble understanding the error message.
raise PluginNotInstalledError('Plugin not found for extension point URL: {}'.format(extension_point_url)) compas.plugins.PluginNotInstalledError: Plugin not found for extension point URL: https:/plugins.compas.dev/drawing-utils/clear
I guess a starting point is here: compas/artist.py at main · compas-dev/compas · GitHub

the plotter provides a decorator method for making animated gifs. it is however, not very well documented. therefore please refer to the following example (which generates one of the images of the plotter tutorial in the docs: dynamic plots)

from compas.geometry import Pointcloud, Translation
from compas.utilities import i_to_red, pairwise
from compas_plotters import Plotter

plotter = Plotter(figsize=(8, 5))

pointcloud = Pointcloud.from_bounds(8, 5, 0, 10)

for index, (a, b) in enumerate(pairwise(pointcloud)):
    color = i_to_red(max(index / 10, 0.1), normalize=True)
    artist = plotter.add(a, edgecolor=color)

plotter.add(b, size=10, edgecolor=(1, 0, 0))
plotter.zoom_extents()
plotter.pause(1.0)


@plotter.on(
    interval=0.1,
    frames=50,
    record=True,
    recording="docs/_images/tutorial/plotters_dynamic.gif",
    dpi=150,
)
def move(frame):
    print(frame)
    for a, b in pairwise(pointcloud):
        vector = b - a
        a.transform(Translation.from_vector(vector * 0.1))


plotter.show()

1 Like

Another example coming up. This pattern adds objects to the animation (instead transforming an existing one). The important thing to note is that the number of frames has to be defined beforehand:

from random import randint
from compas.geometry import Point
from compas_plotters import Plotter

plot = Plotter(view=((-8.0, 16.0), (-5.0, 10.0)),
               figsize=(8.0, 5.0),
               dpi=100, bgcolor=(1.0, 1.0, 1.0),
               show_axes=False,
               zstack='zorder')

random_points = [Point(randint(0, 10), randint(0, 10))
                 for _ in range(randint(0, 30))]


def animate(plotter, objects, file):
    @plotter.on(interval=0.1,
                frames=len(objects),
                record=True,
                recording=file)
    def add_objects(frame):
        plotter.add(objects[frame], size=10)


animate(plot, random_points, "./demo.gif")

print("animation completed")
plot.show()

But it’s not possible to invoke a second function for a same gif file. The file is overwritten.