Generating positioned roughness elements

Generating positioned roughness elements#

Read parameters from file

[2]:
from cfdmod.use_cases.roughness_gen.parameters import PositionParams
import pathlib

import pprint

pp = pprint.PrettyPrinter()

cfg_file = pathlib.Path("./fixtures/tests/roughness_gen/position_params.yaml")
cfg = PositionParams.from_file(cfg_file)

output_path = pathlib.Path("./output/roughness_gen")

pp.pprint(cfg.model_dump())
{'bounding_box': {'end': (2400.0, 1000.0, 0.0),
                  'start': (-500.0, -1000.0, 0.0)},
 'element_params': {'height': 5.0, 'width': 5.0},
 'spacing_params': {'line_offset': 10.0,
                    'offset_direction': 'x',
                    'spacing': (20.0, 20.0)},
 'surfaces': {'disk': './fixtures/tests/roughness_gen/disk/disk.lnas',
              'loft': './fixtures/tests/roughness_gen/loft/loft.lnas'}}

Read mesh files and get surfaces bounding box

[3]:
from lnas import LnasFormat
import trimesh

import numpy as np

surfaces_read: dict[str, LnasFormat] = {}
surfaces_mesh: dict[str, trimesh.Trimesh] = {}

mesh_bbox = [
    (float("inf"), float("inf"), float("inf")),
    (float("-inf"), float("-inf"), float("-inf")),
]

for sfc, sfc_path in cfg.surfaces.items():
    lnas = LnasFormat.from_file(pathlib.Path(sfc_path))
    surfaces_read[sfc] = lnas
    surfaces_mesh[sfc] = trimesh.Trimesh(
        vertices=lnas.geometry.vertices, faces=lnas.geometry.triangles
    )

    min_point = surfaces_read[sfc].geometry.vertices.min(axis=0)
    max_point = surfaces_read[sfc].geometry.vertices.max(axis=0)

    if any(min_point < mesh_bbox[0]):
        mesh_bbox[0] = np.array([min_point, mesh_bbox[0]]).min(axis=0)
    if any(max_point > mesh_bbox[1]):
        mesh_bbox[1] = np.array([max_point, mesh_bbox[1]]).max(axis=0)

s = np.array([cfg.bounding_box.start, mesh_bbox[0]]).max(axis=0)
e = np.array([cfg.bounding_box.end, mesh_bbox[1]]).min(axis=0)
bbox_to_use = np.array([s, e])

bbox_to_use
[3]:
array([[ -500.        , -1000.        ,   751.74133301],
       [ 2400.        ,  1000.        ,     0.        ]])

Calculate generation parameters

[4]:
lx = bbox_to_use[1][0] - bbox_to_use[0][0]
ly = bbox_to_use[1][1] - bbox_to_use[0][1]

if cfg.spacing_params.offset_direction == "x":
    Nx = int((lx - cfg.spacing_params.line_offset) // (cfg.spacing_params.spacing[0]) + 1)
    Ny = int(
        (ly + cfg.spacing_params.spacing[1])
        // (cfg.element_params.width + cfg.spacing_params.spacing[1])
    )
else:
    Nx = int(lx // (cfg.spacing_params.spacing[0]) + 1)
    Ny = int(
        (ly + cfg.spacing_params.spacing[1] - cfg.spacing_params.line_offset)
        // (cfg.element_params.width + cfg.spacing_params.spacing[1])
    )

Nx, Ny, lx, ly
[4]:
(145, 80, 2900.0, 2000.0)

Setup generation parameters

[5]:
from cfdmod.use_cases.roughness_gen.parameters import GenerationParams

generation_params = GenerationParams(
    N_elements_x=Nx,
    N_elements_y=Ny,
    element_params=cfg.element_params,
    spacing_params=cfg.spacing_params,
)

Build elements and apply linear patterns

[6]:
from cfdmod.use_cases.roughness_gen import build_single_element, linear_pattern

triangles, normals = build_single_element(generation_params.element_params)

single_line_triangles, single_line_normals = linear_pattern(
    triangles,
    normals,
    direction=generation_params.spacing_params.offset_direction,
    n_repeats=generation_params.single_line_elements,
    spacing_value=generation_params.single_line_spacing,
)

full_triangles, full_normals = linear_pattern(
    single_line_triangles,
    single_line_normals,
    direction=generation_params.perpendicular_direction,
    n_repeats=generation_params.multi_line_elements,
    spacing_value=generation_params.multi_line_spacing,
    offset_value=generation_params.spacing_params.line_offset,
)

# Offset to match bounding box limits
full_triangles[:, :, 0] += bbox_to_use[0][0]
full_triangles[:, :, 1] += bbox_to_use[0][1]

print(
    "Single element:",
    f"Vertices count: {triangles.shape[0] * triangles.shape[1]}",
    f"Triangles count: {len(triangles)}",
)
print(
    "Single line",
    f"Vertices count: {single_line_triangles.shape[0] * single_line_triangles.shape[1]}",
    f"Triangles count: {len(single_line_triangles)}",
)
print(
    "Replicated lines",
    f"Vertices count: {full_triangles.shape[0] * full_triangles.shape[1]}",
    f"Triangles count: {len(full_triangles)}",
)
Single element: Vertices count: 6 Triangles count: 2
Single line Vertices count: 870 Triangles count: 290
Replicated lines Vertices count: 69600 Triangles count: 23200

Create profiles with x normal planes

[7]:
x_vals = np.unique(full_triangles[:, :, 0].reshape(-1))

profiles: dict[float, np.ndarray] = {}

for sfc_label, mesh in surfaces_mesh.items():
    x_vals_to_use = x_vals[
        (x_vals >= mesh.vertices[:, 0].min()) & (x_vals <= mesh.vertices[:, 0].max())
    ]

    for x_val in x_vals_to_use:
        section_slice = mesh.section(
            plane_origin=[x_val, 0, 0],
            plane_normal=[-1, 0, 0],
        )
        vertices = np.array(section_slice.to_dict()["vertices"])
        if x_val in profiles.keys():
            profiles[x_val] = np.concatenate((profiles[x_val], vertices))
        else:
            profiles[x_val] = vertices

for k, profile in profiles.items():
    profile = np.array(sorted(profile, key=lambda x: x[1]))

Offset geometry with surface information

[8]:
for tri in full_triangles:
    left_bound = tri[:, 1].min()
    right_bound = tri[:, 1].max()
    prof = profiles[tri[0][0]][
        (profiles[tri[0][0]][:, 1] >= left_bound) & (profiles[tri[0][0]][:, 1] <= right_bound)
    ]
    while len(prof) == 0:
        value_offset = generation_params.element_params.width
        left_bound -= value_offset
        right_bound += value_offset
        prof = profiles[tri[0][0]][
            (profiles[tri[0][0]][:, 1] >= left_bound) & (profiles[tri[0][0]][:, 1] <= right_bound)
        ]

    offset_val = prof[:, 2].min()
    tri[:, 2] += offset_val

Export generated geometry

[9]:
from cfdmod.api.geometry.STL import export_stl

export_stl(output_path / "positioned_elements.stl", full_triangles, full_normals)