import pandas as pd
from stl import mesh
from tqdm import tqdm
import multiprocess as mp
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from typing import List, Optional, Union
from pysagas.geometry import Cell, Vector
from pysagas.utilities import add_sens_data
[docs]
class AbstractParser(ABC):
"""Interface for a geometry parser."""
filetype = None
[docs]
@abstractmethod
def __init__(self, **kwargs) -> None:
pass
def __repr__(self) -> str:
return f"PySAGAS {self.filetype} parser"
def __str__(self) -> str:
return f"PySAGAS {self.filetype} parser"
@property
@abstractmethod
def filetype(self):
# This is a placeholder for a class variable defining the parser file type
pass
[docs]
@abstractmethod
def load(self) -> List[Cell]:
"""Load cells from file."""
[docs]
@classmethod
@abstractmethod
def load_from_file(self) -> List[Cell]:
"""Convenience method for loading cells from file."""
[docs]
class Parser(AbstractParser):
[docs]
def __init__(self, filepath: str, verbosity: int = 1) -> None:
self.filepath = filepath
self.verbosity = verbosity
[docs]
@classmethod
def load_from_file(
cls,
filepath: str,
geom_sensitivities: Optional[Union[str, pd.DataFrame]] = None,
verbosity: Optional[int] = 1,
**kwargs,
) -> List[Cell]:
"""Convenience method for loading cells from file.
Parameters
----------
filepath : str
The filepath to the geometry.
geom_sensitivities : str | DataFrame, optional
The geometry sensitivity data, to optionally add to the loaded cells. This can
be provided as a path to the data in csv format, or directly as a Pandas
DataFrame. The default is None.
verbosity : int, optional
The verbosity of the code. The defualt is 1.
**kwargs
Additional keyword arguments can be provided to control the sensitivity matching
algorithm.
See Also
--------
pysagas.utilities.add_sens_data
"""
# Create parser instance
parser = cls(filepath, verbosity)
# Load file
cells = parser.load()
if geom_sensitivities:
# Check input type
if isinstance(geom_sensitivities, str):
# File path provided, load into dataframe
geom_sensitivities = pd.read_csv(geom_sensitivities)
elif not isinstance(geom_sensitivities, pd.DataFrame):
raise TypeError("Invalid data provided for 'geom_sensitivities'.")
# Add sensitivity data to cells
add_sens_data(
cells=cells,
data=geom_sensitivities,
verbosity=verbosity,
**kwargs,
)
return cells
[docs]
class STL(Parser):
filetype = "STL"
[docs]
def load(self) -> List[Cell]:
# Load the STL
mesh_obj = mesh.Mesh.from_file(self.filepath)
cells = []
# TODO - can face ids be inferred?
if self.verbosity > 0:
print("\nTranscribing cells:")
pbar = tqdm(
total=len(mesh_obj.vectors),
position=0,
leave=True,
desc=" Cell transcription progress",
)
for vector_triple in mesh_obj.vectors:
vertices = [Vector.from_coordinates(v) for v in vector_triple]
try:
cell = Cell.from_points(vertices)
cells.append(cell)
except:
pass
# Update progress bar
if self.verbosity > 0:
pbar.update(1)
if self.verbosity > 0:
pbar.close()
print("Done.")
return cells
[docs]
class MeshIO(Parser):
"""Meshio parser"""
filetype = "meshio mesh"
[docs]
def __init__(self, filepath: str, verbosity: int = 1) -> None:
# Import meshio
try:
import meshio
except ModuleNotFoundError:
raise Exception("Could not find meshio. Please install it and try again.")
self._meshio = meshio
super().__init__(filepath, verbosity)
[docs]
def load(self) -> List[Cell]:
def mp_wrapper(face):
vertices = [Vector.from_coordinates(mesh_vertices[i]) for i in face]
try:
cell = Cell.from_points(vertices, face_ids=face)
except:
cell = None
return cell
# Load the STL
mesh_obj = self._meshio.read(self.filepath)
if self.verbosity > 0:
print("\nTranscribing cells.")
# Create multiprocessing pool to construct cells
cells = []
pool = mp.Pool()
mesh_vertices = mesh_obj.points
for result in pool.map(mp_wrapper, mesh_obj.cells[0].data):
if result is not None:
cells.append(result)
if self.verbosity > 0:
print("Done.")
return cells
[docs]
class PyMesh(Parser):
filetype = "PyMesh STL"
[docs]
def __init__(self, filepath: str, verbosity: int = 1) -> None:
# Import PyMesh
try:
import pymesh
except ModuleNotFoundError:
raise Exception(
"Could not find pymesh. Please follow the "
+ "installation instructions at "
+ "https://pymesh.readthedocs.io/en/latest/installation.html"
)
self._pymesh = pymesh
super().__init__(filepath, verbosity)
[docs]
def load(self) -> List[Cell]:
def mp_wrapper(face):
vertices = [Vector.from_coordinates(mesh_vertices[i]) for i in face]
try:
cell = Cell.from_points(vertices, face_ids=face)
except:
cell = None
return cell
# Load the STL
mesh_obj = self._pymesh.load_mesh(self.filepath)
if self.verbosity > 0:
print("\nTranscribing cells.")
# Create multiprocessing pool to construct cells
cells = []
pool = mp.Pool()
mesh_vertices = mesh_obj.vertices
for result in pool.map(mp_wrapper, mesh_obj.faces):
if result is not None:
cells.append(result)
if self.verbosity > 0:
print("Done.")
return cells
[docs]
class TRI(Parser):
filetype = ".tri"
[docs]
def load(self) -> List[Cell]:
# Parse .tri file
tree = ET.parse(self.filepath)
root = tree.getroot()
grid = root[0]
piece = grid[0]
points = piece[0]
cells = piece[1]
points_data = points[0].text
cells_data = cells[0].text
points_data_list = [el.split() for el in points_data.splitlines()[1:]]
points_data_list = [[float(j) for j in i] for i in points_data_list]
cells_data_list = [el.split() for el in cells_data.splitlines()[1:]]
cells_data_list = [[int(j) for j in i] for i in cells_data_list]
cells = []
if self.verbosity > 0:
print("\nTranscribing cells:")
pbar = tqdm(
total=len(cells_data_list),
position=0,
leave=True,
desc=" Cell transcription progress",
)
for vertex_idxs in cells_data_list:
vertices = [
Vector.from_coordinates(points_data_list[i]) for i in vertex_idxs
]
cell = Cell.from_points(vertices, face_ids=vertex_idxs)
cells.append(cell)
# Update progress bar
if self.verbosity > 0:
pbar.update(1)
if self.verbosity > 0:
pbar.close()
print("Done.")
return cells
[docs]
@classmethod
def load_from_file(cls, filepath: str, verbosity: int = 1, **kwargs) -> List[Cell]:
"""Convenience method for loading cells from file."""
# Create parser instance
parser = cls(filepath, verbosity)
# Load file
cells = parser.load()
return cells