diff --git a/hypervehicle/components/component.py b/hypervehicle/components/component.py index ba6b1a5..e9aaec4 100644 --- a/hypervehicle/components/component.py +++ b/hypervehicle/components/component.py @@ -90,7 +90,7 @@ class Component(AbstractComponent): def __init__( self, params: dict = None, - edges: list =[], + edges: list = [], stl_resolution: int = 2, verbosity: int = 1, name: str = None, @@ -273,7 +273,7 @@ def surface(self, resolution: int = None): def wrapper(key: str, patch, res_r: int, res_s: int): flip = True if key.split("_")[-1] == "mirrored" else False - #if "swept" in key: + # if "swept" in key: # # Swept fuselage component # res = ( # int(stl_resolution / 4) @@ -369,9 +369,7 @@ def stl_check(self, projecteArea=True, matchingLines=True): if projecteArea: print(f" Project Area Check") normals = np.asarray(mesh.normals, dtype=np.float64) - allowed_max_errors = ( - np.abs(normals).sum(axis=0) * np.finfo(np.float32).eps - ) + allowed_max_errors = np.abs(normals).sum(axis=0) * np.finfo(np.float32).eps sum_normals = normals.sum(axis=0) print(f" allowed error: {allowed_max_errors}") print(f" normals sum: {sum_normals}") @@ -383,10 +381,11 @@ def stl_check(self, projecteArea=True, matchingLines=True): if matchingLines: print(f" Matching Lines Check") - reversed_triangles = (np.cross(mesh.v1 - mesh.v0, - mesh.v2 - mesh.v0) * mesh.normals - ).sum(axis=1) < 0 + reversed_triangles = ( + np.cross(mesh.v1 - mesh.v0, mesh.v2 - mesh.v0) * mesh.normals + ).sum(axis=1) < 0 import itertools + directed_edges = { tuple(edge.ravel() if not rev else edge[::-1, :].ravel()) for rev, edge in zip( @@ -395,11 +394,10 @@ def stl_check(self, projecteArea=True, matchingLines=True): mesh.vectors[:, (0, 1), :], mesh.vectors[:, (1, 2), :], mesh.vectors[:, (2, 0), :], - ), - ) - } - undirected_edges = {frozenset((edge[:3], edge[3:])) for edge in - directed_edges} + ), + ) + } + undirected_edges = {frozenset((edge[:3], edge[3:])) for edge in directed_edges} edge_check = len(directed_edges) == 3 * mesh.data.size print(f" len(directed_edges) == 3 * mesh.data.size") if edge_check: @@ -428,12 +426,13 @@ def stl_check(self, projecteArea=True, matchingLines=True): edge_list = [] for edge in directed_edges: - edge_list.append( frozenset((edge[:3], edge[3:]))) + edge_list.append(frozenset((edge[:3], edge[3:]))) # print(f"edge_list={edge_list}") from collections import Counter + counter_out = Counter(edge_list) - #print(f"counter_out={counter_out}") + # print(f"counter_out={counter_out}") k_list = [] for k, v in counter_out.items(): @@ -444,7 +443,7 @@ def stl_check(self, projecteArea=True, matchingLines=True): return pass_flag - def stl_repair(self, small_distance: float=1e-6): + def stl_repair(self, small_distance: float = 1e-6): """Attempts to repair stl mesh issues. Parameters @@ -462,27 +461,28 @@ def find_groups(vector, small_distance): groups = [] li = [] count = 0 - for i in range(len(vector)-1): + for i in range(len(vector) - 1): if count == 0: x0 = vector[sort_i[i]] - x1 = vector[sort_i[i+1]] - if abs(x1-x0) < small_distance: + x1 = vector[sort_i[i + 1]] + if abs(x1 - x0) < small_distance: if count == 0: li.append(sort_i[i]) - li.append(sort_i[i+1]) + li.append(sort_i[i + 1]) count = 1 else: - li.append(sort_i[i+1]) + li.append(sort_i[i + 1]) else: groups.append(li) li = [] count = 0 - if i == len(vector)-2 and count > 0: + if i == len(vector) - 2 and count > 0: groups.append(li) return groups - iix = find_groups(vectors[:,0], small_distance) - iiy = find_groups(vectors[:,1], small_distance) - iiz = find_groups(vectors[:,2], small_distance) + + iix = find_groups(vectors[:, 0], small_distance) + iiy = find_groups(vectors[:, 1], small_distance) + iiz = find_groups(vectors[:, 2], small_distance) # find intersecting sets and take average for ix in iix: @@ -494,6 +494,6 @@ def find_groups(vector, small_distance): common = list(common0 & set(iz)) if common: vectors[common] = np.mean(vectors[common], axis=0) - mesh.v0 = vectors[0:N_faces,:] - mesh.v1 = vectors[N_faces:2*N_faces,:] - mesh.v2 = vectors[2*N_faces:3*N_faces,:] + mesh.v0 = vectors[0:N_faces, :] + mesh.v1 = vectors[N_faces : 2 * N_faces, :] + mesh.v2 = vectors[2 * N_faces : 3 * N_faces, :] diff --git a/hypervehicle/components/fin.py b/hypervehicle/components/fin.py index af1e6b2..a94edbd 100644 --- a/hypervehicle/components/fin.py +++ b/hypervehicle/components/fin.py @@ -145,7 +145,9 @@ def __init__( "offset_function": offset_func, } - super().__init__(params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name + ) def generate_patches(self): # Initialise @@ -290,13 +292,17 @@ def generate_patches(self): p0p0_top = Line(p0=p0, p1=p0 - Vector3(0, 0, fin_thickness / 2)) p0_botp0 = Line(p0=p0 + Vector3(0, 0, fin_thickness / 2), p1=p0) - #p3p3_top_reversed = SubRangedPath(p3p3_top, 1, 0) - #p3p3_bot_reversed = SubRangedPath(p3p3_bot, 1, 0) + # p3p3_top_reversed = SubRangedPath(p3p3_top, 1, 0) + # p3p3_bot_reversed = SubRangedPath(p3p3_bot, 1, 0) p3p3_top_reversed = ReversedPath(p3p3_top) p3p3_bot_reversed = ReversedPath(p3p3_bot) - bot_1 = CoonsPatch(north=p3p0_top, south=p3p0, east=p0p0_top, west=p3p3_top_reversed) - bot_2 = CoonsPatch(north=p3p0, south=p3p0_bot, east=p0_botp0, west=p3p3_bot_reversed) + bot_1 = CoonsPatch( + north=p3p0_top, south=p3p0, east=p0p0_top, west=p3p3_top_reversed + ) + bot_2 = CoonsPatch( + north=p3p0, south=p3p0_bot, east=p0_botp0, west=p3p3_bot_reversed + ) temp_fin_patch_dict["bot_ellip"] = bottom_ellipse_patch temp_fin_patch_dict["bot_1"] = bot_1 diff --git a/hypervehicle/components/swept.py b/hypervehicle/components/swept.py index 8f0042d..79672b3 100644 --- a/hypervehicle/components/swept.py +++ b/hypervehicle/components/swept.py @@ -5,13 +5,14 @@ from gdtk.geom.path import ReversedPath from gdtk.geom.surface import CoonsPatch + class SweptComponent_old(Component): componenttype = SWEPT_COMPONENT def __init__( self, cross_sections: List[CoonsPatch], - sweep_axis: str = 'n/a', + sweep_axis: str = "n/a", stl_resolution: int = 2, verbosity: int = 1, name: str = None, @@ -34,7 +35,7 @@ def generate_patches(self): p = SweptPatch( cross_sections=self.cross_sections, sweep_axis=self.sweep_axis, - ) + ) self.patches["swept_patch"] = p self.patches["swept_patch_end_0"] = self.cross_sections[0] self.patches["swept_patch_end_1"] = self.cross_sections[-1] @@ -71,10 +72,7 @@ def __init__( """ self.cross_sections = cross_sections self.close_ends = close_ends - super().__init__( - stl_resolution=stl_resolution, - verbosity=verbosity, - name=name) + super().__init__(stl_resolution=stl_resolution, verbosity=verbosity, name=name) self.n_slices = len(self.cross_sections) self.n_edges = len(self.cross_sections[0]) @@ -85,38 +83,40 @@ def check(self, small_number=1e-12): if len(cs) != self.n_edges: # check that each c/s has correct number of edges raise Exception( - f"Swept Component {self.name}." + - f"Slice {ns} has incorrect number of edges.\n" + - f"N_e={len(cs)} - {self.n_edges} expected." + f"Swept Component {self.name}." + + f"Slice {ns} has incorrect number of edges.\n" + + f"N_e={len(cs)} - {self.n_edges} expected." ) for i in range(len(cs)): # check that edges in each c/s form a closed loop - if i < len(cs)-1: - ip = i+1 + if i < len(cs) - 1: + ip = i + 1 else: ip = 0 p1 = cs[i](1) p0 = cs[ip](0) - if (abs(p0.x-p1.x)>small_number or - abs(p0.y-p1.y)>small_number or - abs(p0.z-p1.z)>small_number): + if ( + abs(p0.x - p1.x) > small_number + or abs(p0.y - p1.y) > small_number + or abs(p0.z - p1.z) > small_number + ): raise Exception( - f"Swept Component {self.name}, Slice {ns}, edges not closed.\n" + - f"edges[{i}](1) != edges[{ip}](0)\n" + - f"{p1} != {p0}" + f"Swept Component {self.name}, Slice {ns}, edges not closed.\n" + + f"edges[{i}](1) != edges[{ip}](0)\n" + + f"{p1} != {p0}" ) if self.close_ends is True and self.n_edges != 4: raise Exception( - f"Swept Component {self.name}. Combination of " + - f"close_ends={self.close_ends} and N_edge={self.n_edges} is " + - f"not supported." + f"Swept Component {self.name}. Combination of " + + 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']: + 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']: + 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: @@ -128,14 +128,14 @@ def generate_patches(self): edges = [] for cs in self.cross_sections: edges.append(cs[ne]) - p = SweptPatchfromEdges(edges=edges) + 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}'] + 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 @@ -143,22 +143,26 @@ def generate_patches(self): east = edges[1] north = ReversedPath(edges[2]) west = ReversedPath(edges[3]) - self.patches["swept_patch_end_0"] = CoonsPatch(south=south, north=north, west=west, east=east) + 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'] + 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) + 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'] \ No newline at end of file + self.patch_res_r["swept_patch_end_1"] = self.stl_resolution["e0"] + self.patch_res_s["swept_patch_end_1"] = self.stl_resolution["e1"] diff --git a/hypervehicle/components/wing.py b/hypervehicle/components/wing.py index 9cdae01..be01c60 100644 --- a/hypervehicle/components/wing.py +++ b/hypervehicle/components/wing.py @@ -151,7 +151,9 @@ def __init__( "CLOSE_WING": close_wing, } - super().__init__(params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name) + super().__init__( + params=params, stl_resolution=stl_resolution, verbosity=verbosity, name=name + ) # Extract construction points for planform # TODO - avoid pre-defined params dict structure for flexibility diff --git a/hypervehicle/geometry.py b/hypervehicle/geometry.py index c5e215b..2df37d6 100644 --- a/hypervehicle/geometry.py +++ b/hypervehicle/geometry.py @@ -681,6 +681,7 @@ def __call__(self, r, s) -> Vector3: return Vector3(**args) + class SweptPatchfromEdges(ParametricSurface): """Creates a swept patch from a series of cross sections. Cross sections do not need to be parallel or aligned.""" @@ -705,16 +706,18 @@ def __call__(self, r, s) -> Vector3: r_slices = np.linspace(0, 1, self.n_edges) if r in r_slices: # can evaluate exactly - i = np.where(r_slices==r)[0][0] + i = np.where(r_slices == r)[0][0] return self.edges[i](s) else: # need to interpolate - index_above = np.where(r_slices>r)[0][0] - index_below = index_above-1 + index_above = np.where(r_slices > r)[0][0] + index_below = index_above - 1 above = self.edges[index_above](s) below = self.edges[index_below](s) - alpha = (r-r_slices[index_below]) / (r_slices[index_above]-r_slices[index_below]) - return (1-alpha)*below + alpha*above + alpha = (r - r_slices[index_below]) / ( + r_slices[index_above] - r_slices[index_below] + ) + return (1 - alpha) * below + alpha * above class RotatedPatch(ParametricSurface): diff --git a/hypervehicle/utilities.py b/hypervehicle/utilities.py index c839269..02a1cad 100644 --- a/hypervehicle/utilities.py +++ b/hypervehicle/utilities.py @@ -21,7 +21,7 @@ def parametricSurfce2stl( flip_faces=False, i_clustering_func: callable = None, j_clustering_func: callable = None, - verbosity = 0 + verbosity=0, ) -> mesh.Mesh: """ Function to convert parametric_surface generated using the Eilmer Geometry @@ -94,7 +94,7 @@ def parametricSurfce2stl( # | / 0 \ | # p00-------p10 - N_triangles = 4*(ni)*(nj) + 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 @@ -104,9 +104,9 @@ def parametricSurfce2stl( for j in range(nj): # 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]) + 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)