squish/scripts/torus_mesh.py

326 lines
10 KiB
Python
Raw Normal View History

import argparse, numpy as np, os
from stl.stl import ASCII
from stl import mesh
from squish import DomainParams, Simulation, ordered
from squish.common import OUTPUT_DIR
from scipy.spatial import Voronoi
SUBDIVISION_AMOUNT = 8
def not_in_order(i, j):
if i < j:
return j - i != 1
else:
return i - j == 1
def centroid(site, verts):
area, c = 0, 0
v = verts - site
# print(v)
for i in range(len(v)):
x, y = v[i], v[(i + 1) % len(v)]
a = np.cross(x, y)
area += a
c += a * (x + y)
return c / (3 * area) + site
def flat_sheet(k):
x = np.linspace(-np.pi, np.pi, 101)
x = np.transpose([np.tile(a, len(a)), np.repeat(a, len(a))])
all_verts = np.hstack((a, np.zeros((len(a), 1))))
xx = np.array([[x for x in range(101 * 100) if (x - 100) % 101 > 0]]).T
b = np.hstack((xx, xx + 1, xx + 101))
c = np.hstack((xx + 1, xx + 102, xx + 101))
all_faces = np.concatenate((b, c))
return all_verts, all_faces
def torus_transform(v, R, r):
new_verts = np.empty(v.shape)
# Rotate by 90 degrees, and then shift to range [0, 2\pi]
v[:, :2] = np.matmul(np.array([[0, -1], [1, 0]]), v[:, :2].T).T
v[:, :2] = v[:, :2] % (2 * np.pi)
# v[:, 0], v[:, 1] = v[:, 1], v[:, 0]
new_verts[:, 0] = (R + r * np.cos(v[:, 1])) * np.cos(v[:, 0])
new_verts[:, 1] = (R + r * np.cos(v[:, 1])) * np.sin(v[:, 0])
new_verts[:, 2] = r * np.sin(v[:, 1])
return new_verts
def flat_region(c, verts, height):
m = len(verts)
centers = np.hstack((np.array([c, c]), np.array([[height], [0]])))
v_top = np.hstack((verts, height * np.ones((m, 1))))
v_bot = np.hstack((verts, np.zeros((m, 1))))
v = np.concatenate((centers, v_top, v_bot))
vi = np.atleast_2d(np.arange(m, dtype=int)).T
cent_top, cent_bot = np.zeros((m, 1), dtype=int), np.ones((m, 1), dtype=int)
vert_top_m, vert_top_p = vi + 2, (vi + 1) % m + 2
vert_bot_m, vert_bot_p = vi + m + 2, (vi + 1) % m + m + 2
top_face = np.hstack((cent_top, vert_top_m, vert_top_p))
bot_face = np.hstack((cent_bot, vert_bot_p, vert_bot_m))
f = np.concatenate((top_face, bot_face))
return v, f, 2
def torus_region(c, verts, height):
sd = SUBDIVISION_AMOUNT
d = verts - c
m = len(verts)
# 1 + 6 + 12 ... verts, 1 + 3 + 5 ... faces.
v, f = (
np.empty((1 + m * sd * (sd - 1) // 2, 2)),
np.empty((m * (sd - 1) ** 2, 3), dtype=int),
)
v[0] *= 0
# Vertices are ordered by rings going out from the center.
v_inds = np.arange(-1, sd)
v_inds = 1 + m * v_inds * (v_inds + 1) // 2
v_inds[0] = 0
for i in range(1, sd):
fj, fk = m * (i - 1) ** 2, m * i ** 2
region_verts = [
np.linspace(i * d[j] / (sd - 1), i * d[(j + 1) % m] / (sd - 1), i + 1)[:-1]
for j in range(m)
]
v[v_inds[i] : v_inds[i + 1]] = np.concatenate(region_verts)
faces = []
for j in range(m):
# v_off_inds = v_inds + j * np.arange(sd + 1)
r1, r2 = (
(j * (i - 1) + np.arange(i + 1)) % (m * (i - 1)) + v_inds[i - 1],
(j * i + np.arange(i + 2)) % (m * i) + v_inds[i],
)
r1, r2 = np.atleast_2d(r1).T, np.atleast_2d(r2).T
faces.append(np.hstack((r1[:-1], r2[:-2], r2[1:-1])))
faces.append(np.hstack((r1[:-2], r2[1:-2], r1[1:-1])))
f[fj:fk] = np.concatenate(faces)
v += c
v = np.hstack((v, height * np.ones((len(v), 1))))
return v, f, v_inds[sd - 1]
def main():
parser = argparse.ArgumentParser(description="Generates 3D model for equilibrium.")
parser.add_argument(
"sims_path", metavar="sim_dir", help="simulation to obtain equilibrium from"
)
parser.add_argument("frame_num", metavar="FRAME_NUM", type=int, help="frame number")
parser.add_argument("size", metavar="SIZE", type=int, help="width in mm")
parser.add_argument(
"--torus", dest="torus", action="store_true", help="generates torus model"
)
args = parser.parse_args()
torus = args.torus
size = args.size * 10
# Get desired frame and load.
sim, frames = Simulation.load(args.sims_path)
frames = list(frames)
# num = 100
# nums = [838. 348. 14. 664, 725, 974]
# frames.sort(key=lambda x: x["energy"])
# frame = frames[-32]
# for i, f in enumerate(frames):
# if i == 858:
frame = frames[args.frame_num]
n, w, h = frame["domain"][0], frame["domain"][1], frame["domain"][2]
frame = sim.energy.mode(*frame["domain"], frame["arr"])
# Set up size and scaling variables.
dim = np.array([w, h])
vor = frame.vor_data
alpha = w / h
if torus:
r = alpha * size / 2
R = size / 2 - r
height_bounds = (0.5 * r, 1.25 * r)
scale = np.array([2 * np.pi, 2 * np.pi])
else:
height_bounds = (size / 10, 0.75 * size)
scale = np.array([alpha * size, size])
# Rescale and translate domain.
points = (vor.points - dim / 2) * scale / dim
vertices = (vor.vertices - dim / 2) * scale / dim
# Obtain height scaling.
heights = frame.stats["site_energies"] - ordered.e_hex(sim.domain)
heights -= np.min(heights)
heights *= (height_bounds[1] - height_bounds[0]) / np.max(heights)
heights += height_bounds[0]
# Prepare oriented regions and site_vert list
sites = points[:n]
regions = []
for i, x in enumerate(vor.point_region[:n]):
region = vor.regions[x]
x = sites[i]
p, q = vertices[region[0]] - x, vertices[region[1]] - x
regions.append(region if np.cross(p, q) >= 0 else region[::-1])
site_verts = []
for region in regions:
site_verts.append(vertices[region])
all_verts, all_faces = [], []
offsets = np.empty((n,), dtype=int)
for i, verts in enumerate(site_verts):
c = centroid(sites[i], verts)
if torus:
face_verts, faces, offset = torus_region(c, verts, heights[i])
else:
face_verts, faces, offset = flat_region(c, verts, heights[i])
old_len = len(all_verts)
if i == 0:
all_verts, all_faces = face_verts, faces
else:
all_verts = np.concatenate((all_verts, face_verts))
all_faces = np.concatenate((all_faces, faces + old_len))
offsets[i] = old_len + offset
# Merge regions
for i, x in enumerate(vor.ridge_points):
x, y = x[0], x[1] # Site indices.
no_x, no_y = x >= sim.domain.n, y >= sim.domain.n
if no_x and no_y:
continue
adj_verts = vor.ridge_vertices[i]
v1, v2 = vertices[adj_verts[0]], vertices[adj_verts[1]]
bot_face, top_face = None, None
if torus:
sd = SUBDIVISION_AMOUNT - 1
if no_x:
x, y = y, x
y = y % n
v1_x_ind = regions[x].index(adj_verts[0])
v2_x_ind = regions[x].index(adj_verts[1])
# Need to find matching vertices
v1, v2 = v1 % (2 * np.pi), v2 % (2 * np.pi)
y_verts = vertices[regions[y]] % (2 * np.pi)
v1_y_ind = np.argmin(np.linalg.norm(y_verts - v1, axis=1))
v2_y_ind = np.argmin(np.linalg.norm(y_verts - v2, axis=1))
# We want x lower than y.
off_x, off_y = offsets[x], offsets[y]
if heights[x] > heights[y]:
v1_x_ind, v1_y_ind = v1_y_ind, v1_x_ind
v2_x_ind, v2_y_ind = v2_y_ind, v2_x_ind
off_x, off_y = off_y, off_x
if not_in_order(v1_x_ind, v2_x_ind):
v1_x_ind, v2_x_ind = v2_x_ind, v1_x_ind
if not_in_order(v1_y_ind, v2_y_ind):
v1_y_ind, v2_y_ind = v2_y_ind, v1_y_ind
sds = np.atleast_2d(np.arange(sd)).T
xm, ym = sd * v1_x_ind + sds, sd * v1_y_ind + sds
xp, yp = xm + 1, ym + 1
xp[-1], yp[-1] = sd * v2_x_ind, sd * v2_y_ind
xm += off_x
xp += off_x
ym += off_y
yp += off_y
bot_face = np.hstack((xm, ym[::-1], xp))
top_face = np.hstack((xm, yp[::-1], ym[::-1]))
else:
if no_x ^ no_y:
if no_x:
x = y
m = len(regions[x])
v1_ind = regions[x].index(adj_verts[0])
v2_ind = regions[x].index(adj_verts[1])
if not_in_order(v1_ind, v2_ind):
v1_ind, v2_ind = v2_ind, v1_ind
v1_ind += offsets[x]
v2_ind += offsets[x]
bot_face = np.array([[v1_ind, m + v1_ind, m + v2_ind]], dtype=int)
top_face = np.array([[v1_ind, m + v2_ind, v2_ind]], dtype=int)
else:
v1_x_ind = regions[x].index(adj_verts[0])
v2_x_ind = regions[x].index(adj_verts[1])
v1_y_ind = regions[y].index(adj_verts[0])
v2_y_ind = regions[y].index(adj_verts[1])
# We want x lower than y.
off_x, off_y = offsets[x], offsets[y]
if heights[x] > heights[y]:
v1_x_ind, v1_y_ind = v1_y_ind, v1_x_ind
v2_x_ind, v2_y_ind = v2_y_ind, v2_x_ind
off_x, off_y = off_y, off_x
if not_in_order(v1_x_ind, v2_x_ind):
v1_x_ind, v2_x_ind = v2_x_ind, v1_x_ind
if not_in_order(v1_y_ind, v2_y_ind):
v1_y_ind, v2_y_ind = v2_y_ind, v1_y_ind
v1_x_ind += off_x
v2_x_ind += off_x
v1_y_ind += off_y
v2_y_ind += off_y
bot_face = np.array([[v1_x_ind, v1_y_ind, v2_x_ind]], dtype=int)
top_face = np.array([[v1_x_ind, v2_y_ind, v1_y_ind]], dtype=int)
all_faces = np.concatenate((all_faces, bot_face, top_face))
if torus:
all_verts = torus_transform(all_verts, R, all_verts[:, 2])
# Output into mesh
surf = mesh.Mesh(np.zeros(all_faces.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(all_faces):
for j in range(3):
surf.vectors[i][j] = all_verts[f[j], :]
m_type = "Torus" if torus else "Flat"
out = OUTPUT_DIR / f"N{n} - {m_type} - {args.frame_num}.stl"
# temp = f"n{n}{m_type.lower()}{num}.stl"
surf.save(out)
# os.rename(temp, out)
print(f"Wrote to {out}.")
# n, alpha = 20, np.sqrt(3) * 2 / 5
# u, v = 5, 2
# domain = DomainParams(n, alpha, 1, 1)
# size = 1
# R, r = size, size * (1 - alpha) / alpha
# sites = ordered.sites(domain, (u, v))
if __name__ == "__main__":
np.seterr(divide="ignore")
main()