--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.16.1 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 orphan: true --- (examples-vis-animations)= # Animations Magpylib can display the motion of objects along paths in the form of animations. ```{hint} 1. Animations work best with the [plotly backend](guide-graphic-backends). 2. If your browser window opens, but your animation does not load, reload the page. 3. Avoid rendering too many frames. ``` Detailed information about how to tune animations can be found in the [graphics documentation](guide-graphic-animations). ---------------------------- ## Simple Animations Animations are created with `show()` by setting `animation=True`. It is also possible to hand over the animation time as a float with this parameter. ```{code-cell} ipython3 import magpylib as magpy import numpy as np # Define magnet with path magnet = magpy.magnet.Cylinder( polarization=(1, 0, 0), dimension=(2, 1), position=(4, 0, 0), style_label="magnet", ) magnet.rotate_from_angax(angle=np.linspace(0, 300, 40), start=0, axis="z", anchor=0) # Define sensor with path sensor = magpy.Sensor( pixel=[(-0.2, 0, 0), (0.2, 0, 0)], position=np.linspace((0, 0, -3), (0, 0, 3), 40), style_label="sensor", ) # Display as animation - prefers plotly backend magpy.show(sensor, magnet, animation=True, backend="plotly") ``` ---------------------------- (examples-vis-animated-subplots)= ## Animated Subplots [Subplots](examples-vis-subplots) are a powerful tool to see the field along a path while viewing the 3D models at the same time. This is specifically illustrative as an animation where the field at the respective path position is indicated by a marker. ```{code-cell} ipython3 # Continuation from above - ensure previous code is executed magpy.show( dict(objects=[magnet, sensor], output=["Bx", "By", "Bz"], col=1), dict(objects=[magnet, sensor], output="model3d", col=2), backend="plotly", animation=True, ) ``` It is also possible to use the [show_context](guide-graphics-show_context) context manager. ```{code-cell} ipython3 # Continuation from above - ensure previous code is executed with magpy.show_context([magnet, sensor], backend="plotly", animation=True) as sc: sc.show(output="Bx", col=1, row=1) sc.show(output="By", col=1, row=2) sc.show(output="Bz", col=2, row=1) sc.show(output="model3d", col=2, row=2) ``` ---------------------------- (examples-vis-exporting-animations)= ## Exporting Animations Animations are wonderful but can be quite difficult to export when they are needed, for example, in a presentation. Here we show how to creat and export animations using the *.gif format. ### Built-In Export The easiest way to export an animation is via the Magpylib built-in command `animation_output` in the `show()` function. It works only with the Pyvista backend. The following code will create a file "test4.gif". ```python import magpylib as magpy import numpy as np # Create magnets with Paths path = [(np.sin(t) + 1.5, 0, 0) for t in np.linspace(0, 2 * np.pi, 30)] cube1 = magpy.magnet.Cuboid(dimension=(1, 1, 1), polarization=(1, 0, 0), position=path) cube2 = cube1.copy(position=-np.array(path), polarization=(-1, 0, 0)) # Store gif with animation_output using Pyvista magpy.show( cube1, cube2, style_legend_show=False, animation=3, animation_output="my.gif", backend="pyvista", style_path_show=False, ) ``` ### Custom Export Pyvista For customizing videos it is best to work directly in the respective graphic backends. Here we show how to transfer the Magpylib graphic objects to a Pyvista plotter, customize the plotting scene, export screen shots, and combine them in a *.gif. The following example also shows how to achieve transparency. ```python import magpylib as magpy import pyvista as pv from PIL import Image def create_gif(images, frame_time, output_file): """Create a GIF from images""" frames = [Image.fromarray(img) for img in images] if frames: frames[0].save( output_file, format="GIF", append_images=frames[1:], save_all=True, duration=frame_time, loop=0, # Infinite loop disposal=2, # Remove previous frames for transparency ) def init_plotter(): """Init Pyvista plotter with custom scene layout""" pl = pv.Plotter(notebook=False, off_screen=True, window_size=[300, 300]) pl.camera_position = [ (5, 5, 5), # Position of the camera (0, 0, 0), # Focal point (what the camera is looking at) (0, 0, 1), # View up direction ] pl.camera.zoom(0.5) pl.set_background("k") # For better transparency return pl def create_frames(frames): """Create frames with Pyvista.""" # Create Magpylib objects mag1 = magpy.magnet.CylinderSegment( dimension=(3, 4, 1, 0, 45), polarization=(0, 0, 1) ) mag2 = magpy.magnet.CylinderSegment( dimension=(2, 3, 1, 0, 45), polarization=(0, 0, -1) ) images = [] pl = init_plotter() for i in range(frames): # Modify object positions mag1.rotate_from_angax(360 / frames, axis="z") mag2.rotate_from_angax(-360 / frames, axis="z") # Transfer Magpylib objects to Pyvista plotter pl.clear() magpy.show(mag1, mag2, canvas=pl, style_legend_show=False) # Edit figure in Pyvista pl.add_mesh(pv.Line(mag1.barycenter, mag2.barycenter), color="cyan") # Screenshot print(f"Writing frame {i+1:3d}/{frames}") ss = pl.screenshot(transparent_background=True, return_img=True) images.append(ss) pl.close() return images def main(): frames = 100 frame_time = 40 output_file = "my.gif" images = create_frames(frames) create_gif(images, frame_time, output_file) if __name__ == "__main__": main() ``` Notice that when providing a canvas, no update to its layout is performed by Magpylib, unless explicitly specified by setting `canvas_update=True` in `show()`. By default `canvas_update='auto'` only updates the canvas if is not provided by the user. Details can be found in the [graphics documentation](guide-graphics-canvas). ### Custom Export Plotly The following examples shows how to work in the Plotly backend. ```python import magpylib as magpy from PIL import Image import io def create_gif(images, frame_time, output_file): """Create GIF from frames in the temporary directory.""" frames = [Image.open(io.BytesIO(data)) for data in images] if frames: frames[0].save( output_file, format="GIF", append_images=frames[1:], save_all=True, duration=frame_time, loop=0, # Infinite loop ) def create_frames(frames): """Create frames with Pyvista.""" # Create Magpylib objects mag1 = magpy.magnet.CylinderSegment( dimension=(3, 4, 1, 0, 45), polarization=(0, 0, 1) ) mag2 = magpy.magnet.CylinderSegment( dimension=(2, 3, 1, 0, 45), polarization=(0, 0, -1) ) images = [] for i in range(frames): # Set object position mag1.rotate_from_angax(360 / frames, axis="z") mag2.rotate_from_angax(-360 / frames, axis="z") fig = magpy.show( mag1, mag2, return_fig=True, backend="plotly", style_legend_show=False ) # Edit figure in Plotly fig.add_scatter3d( x=(0, 0, 4, 4, 0), y=(0, 0, 0, 0, 0), z=(-2, 2, 2, -2, -2), mode="lines", line_color="black", ) # Customize layout fig.update_layout( scene=dict( camera_eye={"x": 1.5, "y": 1.5, "z": 1.5}, camera_up={"x": 0, "y": 0, "z": 1}, xaxis_range=(-5, 5), yaxis_range=(-5, 5), zaxis_range=(-5, 5), ), showlegend=False, margin=dict(l=0, r=0, t=0, b=0), ) # Screenshot (requires kaleido package) print(f"Writing frame {i+1:3d}/{frames}") img = fig.to_image(format="png", width=500, height=500) images.append(img) return images def main(): frames = 50 frame_time = 50 output_file = "my.gif" images = create_frames(frames) create_gif(images, frame_time, output_file) if __name__ == "__main__": main() ```