From 2f89c9a22b2a581b4662e9928826542a56cc0339 Mon Sep 17 00:00:00 2001 From: SteveDoyle2 Date: Tue, 15 Oct 2024 21:40:48 -0700 Subject: [PATCH] reverting fluent gui; more fluent checks --- pyNastran/converters/fluent/fluent.py | 19 +++ pyNastran/converters/fluent/fluent_io.py | 117 ++++++++++++------ pyNastran/converters/fluent/test_fluent.py | 8 ++ .../converters/fluent/test_fluent_gui.py | 15 +++ .../converters/fluent/ugrid_to_fluent.py | 55 ++++++++ 5 files changed, 174 insertions(+), 40 deletions(-) create mode 100644 pyNastran/converters/fluent/ugrid_to_fluent.py diff --git a/pyNastran/converters/fluent/fluent.py b/pyNastran/converters/fluent/fluent.py index bfcc12188..cc37f67bb 100644 --- a/pyNastran/converters/fluent/fluent.py +++ b/pyNastran/converters/fluent/fluent.py @@ -23,6 +23,14 @@ def __init__(self, auto_read_write_h5: bool=True, self.auto_read_write_h5 = auto_read_write_h5 self.log = get_logger2(log=log, debug=debug) + self.node_id = np.array([], dtype='int32') + self.xyz = np.zeros((0, 3), dtype='int32') + self.element_id = np.array([], dtype='int32') + self.quads = np.zeros((0, 6), dtype='int32') + self.tris = np.zeros((0, 5), dtype='int32') + self.titles = [] + self.results = np.zeros((0, 0), dtype='float64') + def _get_h5_filename(self, h5_filename: PathLike) -> str: if h5_filename == '': base, ext = os.path.splitext(self.fluent_filename) @@ -121,6 +129,12 @@ def read_fluent(self, fluent_filename: PathLike) -> None: #self.tri_pressure = tri_ def write_fluent(self, fluent_filename: str) -> None: + assert len(self.node_id) > 0, self.node_id + assert len(self.node_id) == len(self.xyz) + assert self.tris.shape[1] == 5, self.tris.shape + assert self.quads.shape[1] == 6, self.quads.shape + assert len(self.element_id) > 0, self.element_id + base, ext = os.path.splitext(fluent_filename) vrt_filename = base + '.vrt' daten_filename = base + '.daten' @@ -229,6 +243,11 @@ def write_cell(vrt_filename: PathLike, quads: np.ndarray, tris: np.ndarray) -> N dim_fmt = '%-8d %8s %8s %8d %8s\n' tri_fmt = '%-8d %8s %8s %8s\n' quad_fmt = '%-8d %8s %8s %8s %8s\n' + + ntri, ntri_col = tris.shape + nquad, nquad_col = quads.shape + assert ntri_col == 5, tris.shape + assert nquad_col == 6, quads.shape with open(vrt_filename, 'w') as cel_file: # row1 = [eid, dim, pid, 3, 4] # row2 = [eid, n1, n2, n3, n4] diff --git a/pyNastran/converters/fluent/fluent_io.py b/pyNastran/converters/fluent/fluent_io.py index 2c3836dbc..2599fb17d 100644 --- a/pyNastran/converters/fluent/fluent_io.py +++ b/pyNastran/converters/fluent/fluent_io.py @@ -34,68 +34,77 @@ def load_fluent_geometry(self, fld_filename: str, return log = self.gui.log - model = read_fluent(fld_filename, log=log, debug=False) + model = read_fluent( + fld_filename, auto_read_write_h5=False, + log=log, debug=False) #self.model_type = model.model_type - #self.node = node + node_id = model.node_id nodes = model.xyz - #self.element_id = element_id - tris = model.tris - quads = model.quads + nnodes = len(nodes) # support multiple results titles = model.titles results = model.results - - element_id = model.element_id #np.arange(1, nelement+1) - #assert np.array_equal(element_id, np.unique(element_id)) - - # we reordered the tris/quads to be continuous to make them easier to add - iquad = np.searchsorted(element_id, quads[:, 0]) - itri = np.searchsorted(element_id, tris[:, 0]) - - quad_results = results[iquad, :] - tri_results = results[itri, :] - - region = np.hstack([quads[:, 1], tris[:, 1]]) - results = np.vstack([quad_results, tri_results]) - nquad = len(quads) - ntri = len(tris) - nelement = nquad + ntri - log.debug(f'results.shape = {results.shape}') - - node_id = model.node_id - nnodes = len(nodes) - assert len(element_id) == len(region) + if 1: + element_id = model.element_ids + region = model.region + elements_list = model.elements_list + nelement = len(element_id) + is_list = True + else: # pragma: no cover + is_list = False + tris = model.tris + quads = model.quads + + element_id = model.element_id #np.arange(1, nelement+1) + assert np.array_equal(element_id, np.unique(element_id)) + assert len(element_id) == len(region) + + # we reordered the tris/quads to be continuous to make them easier to add + iquad = np.searchsorted(element_id, quads[:, 0]) + itri = np.searchsorted(element_id, tris[:, 0]) + + quad_results = results[iquad, :] + tri_results = results[itri, :] + + region = np.hstack([quads[:, 1], tris[:, 1]]) + results = np.vstack([quad_results, tri_results]) + nquad = len(quads) + ntri = len(tris) + nelement = nquad + ntri + log.debug(f'results.shape = {results.shape}') self.gui.nnodes = nnodes self.gui.nelements = nelement self.gui.log.info('nnodes=%s nelements=%s' % (self.gui.nnodes, self.gui.nelements)) - grid = self.gui.grid - grid.Allocate(self.gui.nelements, 1000) + ugrid = self.gui.grid + ugrid.Allocate(self.gui.nelements, 1000) + assert nodes is not None points = numpy_to_vtk_points(nodes) - _create_elements(grid, node_id, model.quads, model.tris) - - assert len(element_id) == len(region) + ugrid.SetPoints(points) log.info(f'created vtk points') - self.gui.nid_map = {} - #elem.SetNumberOfPoints(nnodes) - assert nodes is not None xmax, ymax, zmax = nodes.max(axis=0) xmin, ymin, zmin = nodes.min(axis=0) - self.gui.log.info('xmax=%s xmin=%s' % (xmax, xmin)) - self.gui.log.info('ymax=%s ymin=%s' % (ymax, ymin)) - self.gui.log.info('zmax=%s zmin=%s' % (zmax, zmin)) + log.info('xmax=%s xmin=%s' % (xmax, xmin)) + log.info('ymax=%s ymin=%s' % (ymax, ymin)) + log.info('zmax=%s zmin=%s' % (zmax, zmin)) dim_max = max(xmax-xmin, ymax-ymin, zmax-zmin) - self.gui.create_global_axes(dim_max) + assert len(node_id) == len(np.unique(node_id)) + if is_list: + _create_elements_list(ugrid, node_id, elements_list) + else: + _create_elements(ugrid, node_id, model.quads, model.tris) log.info(f'created vtk elements') - grid.SetPoints(points) - grid.Modified() + self.gui.nid_map = {} + self.gui.create_global_axes(dim_max) + + ugrid.Modified() # loadSTLResults - regions/loads self.gui.scalar_bar_actor.VisibilityOff() @@ -116,6 +125,34 @@ def load_fluent_geometry(self, fld_filename: str, self.gui._finish_results_io2(model_name, form, cases) log.info(f'finished') +def _create_elements_list(ugrid: vtkUnstructuredGrid, + node_id: np.ndarray, + elements_list: list[list[int]]): + assert np.array_equal(node_id, np.unique(node_id)) + assert node_id.min() >= 0, node_id.min() + nid_to_index = {nid : i for i, nid in enumerate(node_id)} + #print(min(node_id), max(node_id)) + + for facei in elements_list: + face = np.array(facei, dtype='int32') # cast str to int + if len(face) == 3: + elem = vtkTriangle() + epoints = elem.GetPointIds() + epoints.SetId(0, nid_to_index[face[0]]) + epoints.SetId(1, nid_to_index[face[1]]) + epoints.SetId(2, nid_to_index[face[2]]) + ugrid.InsertNextCell(5, epoints) + elif len(face) == 4: + elem = vtkQuad() + epoints = elem.GetPointIds() + epoints.SetId(0, nid_to_index[face[0]]) + epoints.SetId(1, nid_to_index[face[1]]) + epoints.SetId(2, nid_to_index[face[2]]) + epoints.SetId(3, nid_to_index[face[3]]) + ugrid.InsertNextCell(9, epoints) + else: # pragma: no cover + raise RuntimeError(face) + def _create_elements(ugrid: vtkUnstructuredGrid, node_ids: np.ndarray, tris: np.ndarray, diff --git a/pyNastran/converters/fluent/test_fluent.py b/pyNastran/converters/fluent/test_fluent.py index f0e28011f..e3573404e 100644 --- a/pyNastran/converters/fluent/test_fluent.py +++ b/pyNastran/converters/fluent/test_fluent.py @@ -13,6 +13,7 @@ from pyNastran.converters.fluent.nastran_to_fluent import nastran_to_fluent from pyNastran.converters.fluent.fluent_to_tecplot import fluent_to_tecplot from pyNastran.converters.tecplot.tecplot import read_tecplot +from pyNastran.converters.fluent.ugrid_to_fluent import ugrid_to_fluent_filename warnings.simplefilter('always') np.seterr(all='raise') @@ -20,6 +21,7 @@ PKG_PATH = pyNastran.__path__[0] BWB_PATH = Path(os.path.join(PKG_PATH, '..', 'models', 'bwb')) TEST_PATH = Path(os.path.join(PKG_PATH, 'converters', 'fluent')) +UGRID_PATH = os.path.join(PKG_PATH, 'converters', 'aflr', 'ugrid', 'models') class TestFluent(unittest.TestCase): @@ -98,6 +100,12 @@ def test_nastran_to_fluent(self): tecplot = fluent_to_tecplot(vrt_filename, tecplot_filename) #read_tecplot(tecplot_filename) # TODO: fix tecplot parsing; the file is correct... + def test_ugrid3d_gui_box(self): + """simple UGRID3D box model""" + ugrid_filename = os.path.join(UGRID_PATH, 'box.b8.ugrid') + fluent_filename = os.path.join(UGRID_PATH, 'box.vrt') + fluent_model = ugrid_to_fluent_filename(ugrid_filename, fluent_filename) + def main(): # pragma: no cover import time time0 = time.time() diff --git a/pyNastran/converters/fluent/test_fluent_gui.py b/pyNastran/converters/fluent/test_fluent_gui.py index c0b7e4784..9d52b57eb 100644 --- a/pyNastran/converters/fluent/test_fluent_gui.py +++ b/pyNastran/converters/fluent/test_fluent_gui.py @@ -9,12 +9,14 @@ #from pyNastran.bdf.bdf import BDF from pyNastran.converters.fluent.fluent_io import FluentIO from pyNastran.converters.fluent.nastran_to_fluent import nastran_to_fluent +from pyNastran.converters.fluent.ugrid_to_fluent import ugrid_to_fluent_filename PKG_PATH = Path(pyNastran.__path__[0]) MODEL_PATH = PKG_PATH / '..' / 'models' BWB_PATH = MODEL_PATH / 'bwb' TEST_PATH = PKG_PATH / 'converters' / 'fluent' +UGRID_PATH = os.path.join(PKG_PATH, 'converters', 'aflr', 'ugrid', 'models') class FluentGui(FakeGUIMethods): @@ -39,10 +41,23 @@ def test_fluent_geometry_01(self): tecplot_filename = BWB_PATH / 'bwb_saero.plt' nastran_to_fluent(nastran_filename, vrt_filename, log=log) + log = get_logger(level='warning', encoding='utf-8') test = FluentGui() test.log = log test.on_load_geometry(vrt_filename, geometry_format='fluent', stop_on_failure=True) + def test_fluent_gui_ugrid3d_gui_box(self): + """simple UGRID3D box model""" + ugrid_filename = os.path.join(UGRID_PATH, 'box.b8.ugrid') + fluent_filename = os.path.join(UGRID_PATH, 'box.vrt') + fluent_model = ugrid_to_fluent_filename(ugrid_filename, fluent_filename) + + log = get_logger(level='warning', encoding='utf-8') + test = FluentGui() + test.log = log + test.on_load_geometry( + fluent_filename, geometry_format='fluent', stop_on_failure=True) + if __name__ == '__main__': # pragma: no cover unittest.main() diff --git a/pyNastran/converters/fluent/ugrid_to_fluent.py b/pyNastran/converters/fluent/ugrid_to_fluent.py new file mode 100644 index 000000000..38bc7ee0c --- /dev/null +++ b/pyNastran/converters/fluent/ugrid_to_fluent.py @@ -0,0 +1,55 @@ +import numpy as np +from pyNastran.utils import PathLike +from pyNastran.converters.aflr.ugrid.ugrid_reader import read_ugrid +from pyNastran.converters.fluent.fluent import Fluent, read_fluent + +def ugrid_to_fluent_filename(ugrid_filename: PathLike, + fluent_filename: PathLike) -> Fluent: + model = ugrid_to_fluent(ugrid_filename) + model.write_fluent(fluent_filename) + return model + +def ugrid_to_fluent(ugrid_filename: PathLike) -> Fluent: + ugrid_model = read_ugrid(ugrid_filename) + fluent_model = Fluent(auto_read_write_h5=False) + + # self.nodes = np.array([], dtype='float32') + # self.tris = np.array([], dtype='int32') + # self.quads = np.array([], dtype='int32') + # self.pids = np.array([], dtype='int32') + + # self.tets = np.array([], dtype='int32') + # self.penta5s = np.array([], dtype='int32') + # self.penta6s = np.array([], dtype='int32') + # self.hexas = np.array([], dtype='int32') + nnodes = len(ugrid_model.nodes) + ntri = len(ugrid_model.tris) + nquad = len(ugrid_model.quads) + neids = ntri + nquad + assert neids > 0, (ntri, nquad) + element_id = np.arange(1, neids+1) + property_id = ugrid_model.pids + + eid_tri = element_id[:ntri] + eid_quad = element_id[ntri:] + pid_tri = property_id[:ntri] + pid_quad = property_id[ntri:] + assert len(eid_quad) == nquad + if ntri: + assert len(eid_tri) == ntri + tris = np.column_stack([eid_tri, pid_tri, ugrid_model.tris]) + print(ugrid_model.tris.shape, tris.shape) + assert tris.shape[1] == 6, tris.shape + fluent_model.tris = tris + + if nquad: + quads = np.column_stack([eid_quad, pid_quad, ugrid_model.quads]) + assert quads.shape[1] == 6, quads.shape + fluent_model.quads = quads + + fluent_model.node_id = np.arange(1, nnodes+1) + fluent_model.xyz = ugrid_model.nodes + fluent_model.element_id = element_id + fluent_model.titles = ['ShellID', 'PropertyID'] + fluent_model.results = property_id.reshape((neids, 1)) + return fluent_model