Skip to content

Commit

Permalink
Added support for meshing with different grid resultions in r and s d…
Browse files Browse the repository at this point in the history
…irections.
  • Loading branch information
ingojahn committed Apr 29, 2024
1 parent f75cfa3 commit 0029043
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 93 deletions.
27 changes: 19 additions & 8 deletions hypervehicle/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ def __init__(

# Processed objects
self.patches = {} # Parametric patches (continuous)
self.patch_res_r = {} # corresponding stl resolution in 'r' direction
self.patch_res_s = {} # corresponding stl resolution in 's' direction

# VTK Attributes
self.grids = None # Structured grids
Expand Down Expand Up @@ -256,10 +258,20 @@ def surface(self, resolution: int = None):
"No patches have been generated. " + "Please call .generate_patches()."
)

# Create case list
case_list = []
for k, patch in self.patches.items():
if isinstance(stl_resolution, int):
res_r = stl_resolution
res_s = stl_resolution
else:
res_r = self.patch_res_r[k]
res_s = self.patch_res_s[k]
case_list.append([k, patch, res_r, res_s])

# Prepare multiprocessing arguments iterable
def wrapper(key: str, patch):
def wrapper(key: str, patch, res_r: int, res_s: int):
flip = True if key.split("_")[-1] == "mirrored" else False
res = stl_resolution

#if "swept" in key:
# # Swept fuselage component
Expand All @@ -269,29 +281,28 @@ def wrapper(key: str, patch):
# else int(stl_resolution / 4) * 4
# )
# flip = True if "1" in key else False

surface = parametricSurfce2stl(
patch, res, flip_faces=flip, **self._clustering
patch, res_r, res_s, flip_faces=flip, **self._clustering
)
return (key, surface)

multiprocess = True # flag to disable multiprocessing for debugging
multiprocess = False # flag to disable multiprocessing for debugging
self.surfaces = {}
if multiprocess is True:
# Initialise surfaces and pool
pool = mp.Pool()

# Submit tasks
print(f"START: Creating stl - multiprocessor run.")
for result in pool.starmap(wrapper, self.patches.items()):
for result in pool.starmap(wrapper, case_list):
self.surfaces[result[0]] = result[1]
print(" DONE: Creating stl - multiprocess.")
else:
for case in self.patches.items():
for case in case_list:
k = case[0]
pat = case[1]
print(f"START: Creating stl for '{k}'.")
result = wrapper(k, pat)
result = wrapper(k, pat, case[2], case[3])
self.surfaces[result[0]] = result[1]
print(" DONE: Creating stl.")

Expand Down
40 changes: 34 additions & 6 deletions hypervehicle/components/swept.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Dict
from hypervehicle.components.component import Component
from hypervehicle.geometry import SweptPatch, SweptPatchfromEdges
from hypervehicle.components.constants import SWEPT_COMPONENT
Expand Down Expand Up @@ -47,7 +47,7 @@ def __init__(
self,
cross_sections: List[List],
close_ends: bool = True,
stl_resolution: int = 1,
stl_resolution: int | Dict[str, int] = 1,
verbosity: int = 1,
name: str = None,
) -> None:
Expand All @@ -64,10 +64,10 @@ def __init__(
4 paths.
stl_resolution : int, optional
Defines same stl_resolution to all edges.
If 0 - then switch to stl_step automatic stl resolutions
stl_step : float, optional
stl resolution is calculated automatically to achieve desired step
size along edges
stl_resolution : dict [str, int]
Defines different stl resolution for different edges. Keys are
are: 'e0', 'e1', ... 'eN', 'sweep' for edges in first cross-section
and for swept edges.
"""
self.cross_sections = cross_sections
self.close_ends = close_ends
Expand Down Expand Up @@ -111,6 +111,16 @@ def check(self, small_number=1e-12):
f"close_ends={self.close_ends} and N_edge={self.n_edges} is " +
f"not supported."
)
if self.close_ends and isinstance(self.stl_resolution, Dict):
flag = 0
if self.stl_resolution['e0'] != self.stl_resolution['e2']:
print("edge 'e0' and 'e2' don't have same stl_resolution.")
flag = 1
if self.stl_resolution['e1'] != self.stl_resolution['e3']:
print("edge 'e1' and 'e3' don't have same stl_resolution.")
flag = 1
if flag > 0:
raise Exception(f"stl_resolution not compatible for close_end.")

def generate_patches(self):
for ne in range(self.n_edges):
Expand All @@ -120,6 +130,12 @@ def generate_patches(self):
edges.append(cs[ne])
p = SweptPatchfromEdges(edges=edges)
self.patches[k] = p
if isinstance(self.stl_resolution, int):
self.patch_res_r[k] = self.stl_resolution
self.patch_res_s[k] = self.stl_resolution
else:
self.patch_res_r[k] = self.stl_resolution['sweep']
self.patch_res_s[k] = self.stl_resolution[f'e{ne}']

if self.close_ends == True:
edges = self.cross_sections[0] # front
Expand All @@ -128,9 +144,21 @@ def generate_patches(self):
north = ReversedPath(edges[2])
west = ReversedPath(edges[3])
self.patches["swept_patch_end_0"] = CoonsPatch(south=south, north=north, west=west, east=east)
if isinstance(self.stl_resolution, int):
self.patch_res_r["swept_patch_end_0"] = self.stl_resolution
self.patch_res_s["swept_patch_end_0"] = self.stl_resolution
else:
self.patch_res_r["swept_patch_end_0"] = self.stl_resolution['e0']
self.patch_res_s["swept_patch_end_0"] = self.stl_resolution['e1']
edges = self.cross_sections[-1] # rear
south = ReversedPath(edges[0])
east = ReversedPath(edges[3])
north = edges[2]
west = edges[1]
self.patches["swept_patch_end_1"] = CoonsPatch(south=south, north=north, west=west, east=east)
if isinstance(self.stl_resolution, int):
self.patch_res_r["swept_patch_end_1"] = self.stl_resolution
self.patch_res_s["swept_patch_end_1"] = self.stl_resolution
else:
self.patch_res_r["swept_patch_end_1"] = self.stl_resolution['e0']
self.patch_res_s["swept_patch_end_1"] = self.stl_resolution['e1']
132 changes: 53 additions & 79 deletions hypervehicle/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

def parametricSurfce2stl(
parametric_surface,
triangles_per_edge: int,
triangles_per_edge_r: int,
triangles_per_edge_s: int,
si: float = 1.0,
sj: float = 1.0,
mirror_y=False,
Expand All @@ -37,8 +38,11 @@ def parametricSurfce2stl(
sj : float, optional
The clustering in the j-direction. The default is 1.0.
triangles_per_edge : int
The resolution for the stl object.
triangles_per_edge_r : int
The resolution for the stl object - x/i direction.
triangles_per_edge_s : int
The resolution for the stl object - y/j direction.
mirror_y : bool, optional
Create mirror image about x-z plane. The default is False.
Expand All @@ -58,8 +62,8 @@ def parametricSurfce2stl(
"""
# TODO - allow different ni and nj discretisation

ni = triangles_per_edge
nj = triangles_per_edge
ni = triangles_per_edge_r
nj = triangles_per_edge_s

if verbosity > 1:
print(f"Triangles per edge: (ni, nj) = ({ni}, {nj})")
Expand All @@ -81,83 +85,53 @@ def parametricSurfce2stl(
print(f"r_list = {r_list}")
print(f"s_list = {s_list}")

# Create vertices for corner points of each quad cell
# columns x, y, z for each vertex row
# quad exterior vertices + quad centres
vertices: np.ndarray = np.zeros(((ni + 1) * (nj + 1) + ni * nj, 3))
centre_ix: int = (ni + 1) * (nj + 1)

# For stl generation we split each cell into 4x triangles with indece [0, 3]
# p01-------p11
# | \ 2 / |
# | \ / |
# | 3 pc 1 |
# | / \ |
# | / 0 \ |
# p00-------p10

N_triangles = 4*(ni)*(nj)
stl_mesh = mesh.Mesh(np.zeros(N_triangles, dtype=mesh.Mesh.dtype))
# mesh.vectors contains a list defining three corners of each triangle.
t = 0
# For vertices along the x direction (i)
for i, r in enumerate(r_list):
# For vertices along the y direction (j)
for j, s in enumerate(s_list):
# Evaluate position
pos = parametric_surface(r, s)

# Assign vertex
vertices[j * (ni + 1) + i] = np.array([pos.x, y_mult * pos.y, pos.z])

# Create vertices for centre point of each quad cell
try:
# Try index to bail before calling surface
vertices[centre_ix + (j * ni + i)]

r0 = r_list[i]
r1 = r_list[i + 1]
s0 = s_list[j]
s1 = s_list[j + 1]

# Get corner points
pos00 = parametric_surface(r0, s0)
pos10 = parametric_surface(r1, s0)
pos01 = parametric_surface(r0, s1)
pos11 = parametric_surface(r1, s1)

# Evaluate quad centre coordinate
pos_x = 0.25 * (pos00.x + pos10.x + pos01.x + pos11.x)
pos_y = 0.25 * (pos00.y + pos10.y + pos01.y + pos11.y)
pos_z = 0.25 * (pos00.z + pos10.z + pos01.z + pos11.z)

# Assign quad centre vertices
vc = np.array([pos_x, y_mult * pos_y, pos_z])
vertices[centre_ix + (j * ni + i)] = vc

except IndexError:
# Index out of bounds
pass

# Create list of faces, defining the face vertices
faces = []
for i in range(ni):
# For vertices along the y direction (j)
for j in range(nj):
p00 = j * (nj + 1) + i # bottom left
p10 = j * (nj + 1) + i + 1 # bottom right
p01 = (j + 1) * (ni + 1) + i # top left
p11 = (j + 1) * (ni + 1) + i + 1 # top right

pc = centre_ix + j * min(ni, nj) + i # vertex at centre of cell

if mirror_y or flip_faces:
faces.append([p00, pc, p10])
faces.append([p10, pc, p11])
faces.append([p11, pc, p01])
faces.append([p01, pc, p00])
else:
faces.append([p00, p10, pc])
faces.append([p10, p11, pc])
faces.append([p11, p01, pc])
faces.append([p01, p00, pc])

faces = np.array(faces)

# Create the STL mesh object
stl_mesh = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for ix, face in enumerate(faces):
# For each face
for c in range(3):
# For each coordinate x/y/z
stl_mesh.vectors[ix][c] = vertices[face[c], :]

# get corner points for current cell (quad)
pos00 = parametric_surface(r_list[i], s_list[j])
pos10 = parametric_surface(r_list[i+1], s_list[j])
pos01 = parametric_surface(r_list[i], s_list[j+1])
pos11 = parametric_surface(r_list[i+1], s_list[j+1])
# get centre for current cell (quad)
pos_x = 0.25 * (pos00.x + pos10.x + pos01.x + pos11.x)
pos_y = 0.25 * (pos00.y + pos10.y + pos01.y + pos11.y)
pos_z = 0.25 * (pos00.z + pos10.z + pos01.z + pos11.z)

# add triangle 0 [p00, p10, pc]
stl_mesh.vectors[t][0] = np.array([pos00.x, y_mult * pos00.y, pos00.z])
stl_mesh.vectors[t][1] = np.array([pos10.x, y_mult * pos10.y, pos10.z])
stl_mesh.vectors[t][2] = np.array([pos_x, y_mult * pos_y, pos_z])
t += 1
# add triangle 1 [p10, p11, pc]
stl_mesh.vectors[t][0] = np.array([pos10.x, y_mult * pos10.y, pos10.z])
stl_mesh.vectors[t][1] = np.array([pos11.x, y_mult * pos11.y, pos11.z])
stl_mesh.vectors[t][2] = np.array([pos_x, y_mult * pos_y, pos_z])
t += 1
# add triangle 2 [p11, p01, pc]
stl_mesh.vectors[t][0] = np.array([pos11.x, y_mult * pos11.y, pos11.z])
stl_mesh.vectors[t][1] = np.array([pos01.x, y_mult * pos01.y, pos01.z])
stl_mesh.vectors[t][2] = np.array([pos_x, y_mult * pos_y, pos_z])
t += 1
# add triangle 3 [p01, p00, pc]
stl_mesh.vectors[t][0] = np.array([pos01.x, y_mult * pos01.y, pos01.z])
stl_mesh.vectors[t][1] = np.array([pos00.x, y_mult * pos00.y, pos00.z])
stl_mesh.vectors[t][2] = np.array([pos_x, y_mult * pos_y, pos_z])
t += 1
return stl_mesh


Expand Down

0 comments on commit 0029043

Please sign in to comment.