Last active
June 18, 2024 02:04
-
-
Save nicoguaro/560e7805693eb44c7854530d15158438 to your computer and use it in GitHub Desktop.
Visualize origami configurations using Matplotlib.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: ori-py | |
channels: | |
- conda-forge | |
dependencies: | |
- python | |
- numpy | |
- scipy | |
- matplotlib | |
- jupyterlab | |
- pyvista |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize origami panels in Matplotlib | |
@author: Nicolás Guarín-Zapata | |
@date: May 2024 | |
""" | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from mpl_toolkits.mplot3d.art3d import Poly3DCollection | |
from matplotlib.colors import LightSource | |
nodes = np.array([ | |
[0.000, 0, 0], | |
[0.707, 0, 0.707], | |
[1.414, 0, 0], | |
[0.000, 1, 0], | |
[0.707, 1, 0.707], | |
[1.414, 1, 0]]) | |
panels = [ | |
[0, 1, 4, 3], | |
[1, 2, 5, 4]] | |
x, y, z = nodes.T | |
nodes2 = nodes.copy() | |
nodes2[:, 2] += 0.01 | |
poly3d = [nodes[panels[0]], nodes[panels[1]]] | |
poly3d2 = [nodes2[panels[0]], nodes2[panels[1]]] | |
#%% | |
ls = LightSource() | |
poly_collection = Poly3DCollection(poly3d, linewidths=1, | |
facecolors='#d86a96', | |
edgecolors="#3c3c3c", shade=True, | |
lightsource=ls) | |
poly_collection2 = Poly3DCollection(poly3d2, linewidths=1, | |
facecolors='#d86a96', | |
edgecolors="#3c3c3c", shade=True, | |
lightsource=ls) | |
#%% Visualization | |
fig = plt.figure() | |
ax = fig.add_subplot(111, projection='3d') | |
ax.add_collection3d(poly_collection) | |
ax.add_collection3d(poly_collection2) | |
# Fix aspect ratio | |
max_range = np.array([x.max()-x.min(), y.max()-y.min(), | |
z.max()-z.min()]).max() / 2.0 | |
mean_x = x.mean() | |
mean_y = y.mean() | |
mean_z = z.mean() | |
ax.set_xlim(mean_x - max_range, mean_x + max_range) | |
ax.set_ylim(mean_y - max_range, mean_y + max_range) | |
ax.set_zlim(mean_z - max_range, mean_z + max_range) | |
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize origami panels in Matplotlib and | |
PyVista | |
@author: Nicolás Guarín-Zapata | |
@date: June 2024 | |
""" | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from mpl_toolkits.mplot3d.art3d import Poly3DCollection | |
from matplotlib.colors import LightSource | |
import pyvista as pv | |
from PIL import Image | |
import os | |
def save_gif_PIL(outfile, files, fps=5, loop=0): | |
"""Helper function for saving GIFs | |
Parameters | |
---------- | |
outfile : string | |
Path to the output file. | |
files : list | |
List of paths with the PNG files. | |
fps : int (optional) | |
Frames per second. | |
loop : int | |
The number of times the GIF should loop. | |
0 means that it will loop forever. | |
""" | |
imgs = [Image.open(file) for file in files] | |
imgs[0].save(fp=outfile, format='GIF', append_images=imgs[1:], | |
save_all=True, duration=int(1000/fps), loop=loop) | |
def plot_panels(nodes, panels, ax=None, **plot_kwargs): | |
"""Plot origami panels in 3D as a 3D collection of polygons | |
Parameters | |
---------- | |
nodes : ndarray, float | |
Coordinates of the vertices (n_nodes, 3). | |
panels : list | |
List with the vertices number for each panel. | |
The numbers should be integers. | |
ax : Matplotlib.axes (optional) | |
Axes to add the graphic. None by default. If None is | |
passed it creates a new one. | |
fix_aspect_ratio : bool | |
Flag to fix the aspect ratio of the figure according | |
to the location of the nodes. True by default. | |
Returns | |
------- | |
ax : Matplotlib.axesg | |
Axes to add the graphic. If None is passed it creates a | |
new one. | |
""" | |
if ax is None: | |
fig = plt.figure() | |
ax = fig.add_subplot(111, projection='3d') | |
poly3d = list(nodes[panel] for panel in panels) | |
ls = LightSource() | |
poly_collection = Poly3DCollection(poly3d, | |
shade=True, | |
lightsource=ls, | |
**plot_kwargs) | |
ax.add_collection3d(poly_collection) | |
return ax | |
def plot_panels_pv(nodes, panels, plotter=None, **plot_kwargs): | |
"""Plot origami panels in 3D as a 3D collection of polygons | |
Parameters | |
---------- | |
nodes : ndarray, float | |
Coordinates of the vertices (n_nodes, 3). | |
panels : list | |
List with the vertices number for each panel. | |
The numbers should be integers. | |
Returns | |
------- | |
ax : Matplotlib.axesg | |
Axes to add the graphic. If None is passed it creates a | |
new one. | |
""" | |
if plotter is None: | |
plotter = pv.Plotter() | |
faces = list([len(panel), *panel] for panel in panels) | |
faces = np.hstack(faces) | |
surf = pv.PolyData(nodes, faces) | |
plotter.add_mesh(surf, **plot_kwargs) | |
return plotter | |
if __name__ == "__main__": | |
# Example with 3 panels | |
nodes = np.array([ | |
[0.000, 0, 0], | |
[0.707, 0, 0.707], | |
[1.414, 0, 0], | |
[0.000, 1, 0], | |
[0.707, 1, 0.707], | |
[1.414, 1, 0.707]]) | |
# One square and two triangles | |
panels = [ | |
[0, 1, 4, 3], | |
[1, 2, 4], | |
[2, 5, 4]] | |
# PyVista | |
pyvista_kwargs = {"line_width": 1, "show_edges": True, "lighting": True, | |
"color": "#d86a96"} | |
pl = pv.Plotter() | |
plot_panels_pv(nodes, panels, plotter=pl, **pyvista_kwargs) | |
pl.enable_shadows() | |
pl.show() | |
# Matplotlib | |
fig = plt.figure() | |
ax = fig.add_subplot(111, projection='3d') | |
plot_kwargs_wire = {"linewidths": 1, "edgecolors": "#3c3c3c", | |
"facecolors": "#d86a96", "alpha": 0.4} | |
plot_kwargs = {"linewidths": 1, "edgecolors": "#3c3c3c", | |
"facecolors": "#d86a96"} | |
nodes2 = nodes.copy() | |
niter = 10 | |
dz = 1/niter | |
files = [] | |
for cont in range(niter): | |
plt.cla() | |
nodes2[:, 2] += dz | |
plot_panels(nodes, panels, ax=ax, **plot_kwargs_wire) | |
plot_panels(nodes2, panels, ax=ax, **plot_kwargs) | |
ax.auto_scale_xyz([0, 1.5], [0, 1], [0, 2]) | |
plt.axis("image") | |
file = f"ori_{str(cont).zfill(2)}.png" | |
plt.savefig(file) | |
files.append(file) | |
plt.show() | |
save_gif_PIL("ori_anim.gif", files, fps=5, loop=0) | |
[os.remove(file) for file in files] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize Miura Pattern using Matplotlib | |
@author: Nicolás Guarín-Zapata | |
@date: June 2024 | |
""" | |
import os | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from plot_ori_panels import plot_panels, save_gif_PIL | |
data = np.load("miura_folding_data.npy", allow_pickle=True).item() | |
nodes_ini = data["Node"] | |
nodes_his = data["Node_history"] | |
nodes_his = np.stack(nodes_his) | |
panels = data["Panel"] | |
mins = nodes_his.min(axis=0).min(axis=0) | |
maxs = nodes_his.max(axis=0).max(axis=0) | |
plot_kwargs_wire = {"linewidths": 1, "edgecolors": "#3c3c3c22", | |
"alpha": 0.0} | |
plot_kwargs = {"linewidths": 1, "edgecolors": "#192231", | |
"facecolors": "#99C961"} | |
fig = plt.figure() | |
ax = fig.add_subplot(111, projection='3d') | |
files = [] | |
for cont in range(60): | |
plt.cla() | |
plot_panels(nodes_ini - np.array([0, 0, 0.1]), panels, ax=ax, **plot_kwargs_wire) | |
plot_panels(nodes_his[cont,:,:], panels, ax=ax, **plot_kwargs) | |
plt.xticks([0, 5, 10, 15, 20]) | |
plt.yticks([0, 5, 10, 15, 20]) | |
ax.set_zticks([0, 2]) | |
ax.set(xticklabels=[], yticklabels=[], zticklabels=[]) | |
ax.auto_scale_xyz([mins[0], maxs[0]], | |
[mins[1], maxs[1]], | |
[mins[2], maxs[2]]) | |
plt.axis("image") | |
plt.tight_layout() | |
file = f"ori_{str(cont).zfill(2)}.png" | |
plt.savefig(file, dpi=600) | |
files.append(file) | |
save_gif_PIL("ori_anim.gif", files, fps=5, loop=0) | |
[os.remove(file) for file in files] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize Miura Pattern using PyVista | |
@author: Nicolás Guarín-Zapata | |
@date: June 2024 | |
""" | |
#import os | |
import numpy as np | |
import pyvista as pv | |
from plot_ori_panels import plot_panels_pv | |
data = np.load("miura_folding_data.npy", allow_pickle=True).item() | |
nodes_ini = data["Node"] | |
nodes_his = data["Node_history"] | |
nodes_his = np.stack(nodes_his) | |
panels = data["Panel"] | |
plot_kwargs_wire = {"line_width": 0.5, "show_edges": True, "lighting": True, | |
"opacity": 0.5, "color": "#d86a96"} | |
plot_kwargs = {"line_width": 1, "show_edges": True, "lighting": True, | |
"color": "#d86a96"} | |
pl = pv.Plotter() | |
plot_panels_pv(nodes_ini, panels, plotter=pl, **plot_kwargs_wire) | |
plot_panels_pv(nodes_his[-1, :, :], panels, plotter=pl, **plot_kwargs) | |
pl.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize origami panels in Matplotlib | |
@author: Nicolás Guarín-Zapata | |
@date: May 2024 | |
""" | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from mpl_toolkits.mplot3d.art3d import Poly3DCollection | |
from matplotlib.colors import LightSource | |
nodes = np.array([ | |
[0.000, 0, 0], | |
[0.707, 0, 0.707], | |
[1.414, 0, 0], | |
[0.000, 1, 0], | |
[0.707, 1, 0.707], | |
[1.414, 1, 0]]) | |
panels = [ | |
[0, 1, 4, 3], | |
[1, 2, 5, 4]] | |
x, y, z = nodes.T | |
poly3d = [nodes[panels[0]], nodes[panels[1]]] | |
#%% | |
ls = LightSource() | |
poly_collection = Poly3DCollection(poly3d, linewidths=1, | |
facecolors='#d86a96', | |
edgecolors="#3c3c3c", shade=True, | |
lightsource=ls) | |
#%% Visualization | |
fig = plt.figure() | |
ax = fig.add_subplot(111, projection='3d') | |
ax.add_collection3d(poly_collection) | |
# Fix aspect ratio | |
max_range = np.array([x.max()-x.min(), y.max()-y.min(), | |
z.max()-z.min()]).max() / 2.0 | |
mean_x = x.mean() | |
mean_y = y.mean() | |
mean_z = z.mean() | |
ax.set_xlim(mean_x - max_range, mean_x + max_range) | |
ax.set_ylim(mean_y - max_range, mean_y + max_range) | |
ax.set_zlim(mean_z - max_range, mean_z + max_range) | |
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
Visualize origami triangular panels in Matplotlib | |
@author: Nicolás Guarín-Zapata | |
@date: May 2024 | |
""" | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from matplotlib.colors import LightSource | |
nodes = np.array([ | |
[0, 0, 0], | |
[1, 0, 0], | |
[1, 1, -0.5], | |
[0, 1, 0]]) | |
panels = [ | |
[0, 1, 3], | |
[1, 2, 3]] | |
x, y, z = nodes.T | |
fig = plt.figure() | |
ls = LightSource() | |
ax = fig.add_subplot(projection='3d') | |
ax.plot_trisurf(x, y, z, triangles=panels, linewidth=1, | |
facecolor='#d86a96', edgecolor="#3c3c3c", | |
shade=True, lightsource=ls) | |
# Fix aspect ratio | |
max_range = np.array([x.max()-x.min(), y.max()-y.min(), | |
z.max()-z.min()]).max() / 2.0 | |
mean_x = x.mean() | |
mean_y = y.mean() | |
mean_z = z.mean() | |
ax.set_xlim(mean_x - max_range, mean_x + max_range) | |
ax.set_ylim(mean_y - max_range, mean_y + max_range) | |
ax.set_zlim(mean_z - max_range, mean_z + max_range) | |
plt.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment