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] centers = np.array([[c[0], c[1], heights[i]]]) v_top = np.hstack((verts, heights[i] * np.ones((len(verts), 1)))) face_verts = np.concatenate((centers, v_top)) 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 # 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()