import argparse
import os
import numpy as np
import vtk
from ..vtk_methods.helper_methods import add_vectors_to_vtk
from ..vtk_methods.exporting import vtk_polydata_writer
[docs]
def load_pts(pts_file):
"""
Loads a .pts file and converts it to an vtkPoints array
The file is expected to start with the number of points, then lists the point coordinates.
:param pts_file: the .pts file name with extension .pts
:return: vtkPoints array
"""
if not os.path.isfile(pts_file):
raise FileNotFoundError(f"The .pts file {pts_file} does not exist!")
if not pts_file.endswith(".pts"):
raise ValueError(f"The .pts file {pts_file} is not a .pts file!")
vtk_points = vtk.vtkPoints()
pts_data = np.loadtxt(pts_file, skiprows=1, usecols=(0, 1, 2))
num_points = int(np.loadtxt(pts_file, max_rows=1))
if pts_data.shape[0] != num_points:
raise ValueError("The number of points declared is not equal to the number of points in the .pts file!")
for point in pts_data:
vtk_points.InsertNextPoint(point)
return vtk_points
OPENCARP_TO_VTK = {
"Ln": (vtk.VTK_LINE, 2),
"Tr": (vtk.VTK_TRIANGLE, 3),
"Tt": (vtk.VTK_TETRA, 4),
"Hx": (vtk.VTK_HEXAHEDRON, 8),
"Py": (vtk.VTK_PYRAMID, 5),
"Pr": (vtk.VTK_WEDGE, 6),
"Qd": (vtk.VTK_QUAD, 4),
}
[docs]
def load_elem(elem_file_path):
if not os.path.isfile(elem_file_path):
raise FileNotFoundError(f"The .elem file {elem_file_path} does not exist!")
if not elem_file_path.endswith(".elem"):
raise ValueError(f"The .elem file {elem_file_path} is not a .elem file!")
with open(elem_file_path, 'r') as f:
lines = f.readlines()
num_elements = int(lines[0])
vtk_cells = vtk.vtkCellArray()
cell_types = []
element_tags = []
for line in lines[1:]:
parts = line.split()
if not parts:
continue
elem_type_str = parts[0]
if elem_type_str not in OPENCARP_TO_VTK:
raise ValueError(f"Unknown element type: {elem_type_str}")
vtk_type, expected_nodes = OPENCARP_TO_VTK[elem_type_str]
node_ids = [int(parts[i]) for i in range(1, expected_nodes + 1)]
cell = vtk.vtkIdList()
for nid in node_ids:
cell.InsertNextId(nid)
vtk_cells.InsertNextCell(cell)
cell_types.append(vtk_type)
element_tags.append(int(parts[expected_nodes + 1]))
if vtk_cells.GetNumberOfCells() != num_elements:
raise ValueError("Declared element count does not match actual element count!")
return vtk_cells, element_tags, cell_types
[docs]
def load_lon(lon_file_path):
if not os.path.exists(lon_file_path):
raise FileNotFoundError(f"The file {lon_file_path} does not exist")
with open(lon_file_path, "r") as file:
lines = file.readlines()
num_arrays = int(lines[0].strip())
data = np.loadtxt(lines[1:])
if num_arrays == 1:
fibers = data.reshape(-1, 3)
sheets = None
elif num_arrays == 2:
fibers = data[:,:3]
sheets = data[:,3:]
else:
raise ValueError("Invalid .lon file: must have 1 or 2 vector arrays.")
return fibers, sheets
[docs]
def convert_openCARP_to_vtk_single_name(file_name:str):
if '.' in file_name:
VALID_EXTENSIONS = {'pts', 'elem', 'lon',''}
# Split filename and extension
base_name, ext = os.path.splitext(file_name)
ext = ext.lower().lstrip('.') # remove leading dot and lowercase
if ext not in VALID_EXTENSIONS:
raise ValueError(
f"Invalid file extension: .{ext}. Must be one of: {', '.join(VALID_EXTENSIONS)}"
)
else:
base_name = file_name
pts_file = f"{base_name}.pts"
elem_file = f"{base_name}.elem"
lon_file = f"{base_name}.lon"
if os.path.exists(lon_file):
return convert_openCARP_to_vtk(pts_file, elem_file, lon_file)
else:
return convert_openCARP_to_vtk(pts_file, elem_file)
[docs]
def convert_openCARP_to_vtk(pts_file_path, elem_file_path, lon_file_path="",poly_data=True):
"""
Convert a .pts and .elem file to a .vtk file.
:param pts_file_path: Filepath of the .pts file with .pts extension
:param elem_file_path: Filepath of the .elem file with .elem extension
:param lon_file_path: Filepath of the .lon file with .lon extension. Optional.
:return: vtk polydata object
"""
vtk_points = load_pts(pts_file_path)
vtk_cells, element_tags, cell_types = load_elem(elem_file_path)
if poly_data:
mesh = vtk.vtkPolyData()
mesh.SetPoints(vtk_points)
mesh.SetPolys(vtk_cells)
cell_data_array = vtk.vtkIntArray()
cell_data_array.SetName("elemTag")
for cell_data in element_tags:
cell_data_array.InsertNextValue(cell_data)
mesh.GetCellData().SetScalars(cell_data_array)
else:
mesh = vtk.vtkUnstructuredGrid()
mesh.SetPoints(vtk_points)
# SetCells requires a vtkUnsignedCharArray of type codes
cell_type_array = vtk.vtkUnsignedCharArray()
for ct in cell_types:
cell_type_array.InsertNextValue(ct)
mesh.SetCells(cell_type_array, vtk_cells)
tags = vtk.vtkIntArray()
tags.SetName("ElementTag")
for t in element_tags:
tags.InsertNextValue(t)
mesh.GetCellData().AddArray(tags)
if lon_file_path:
fibers, sheet = load_lon(lon_file_path)
add_vectors_to_vtk(mesh, fibers, "fiber")
if sheet is not None:
add_vectors_to_vtk(mesh, sheet, "sheet")
return mesh
[docs]
def parse_args():
parser = argparse.ArgumentParser(description="Convert openCARP mesh files to VTK format.")
# Arguments with -- for flexible naming
parser.add_argument('--pts-file', type=str, required=True, default="data/cube.pte",
help="Path to the .pts file containing node positions")
parser.add_argument('--elem-file', type=str, required=True, default="data/cube.elem",
help="Path to the .elem file containing element connectivity")
parser.add_argument('--lon-file', type=str, required=True, default="data/cube.lon",
help="Path to the .lon file containing fiber (and sheet direction)")
parser.add_argument('--output-file', type=str, required=True, help="Path for the output VTK file")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
vtk_mesh = convert_openCARP_to_vtk(args.pts_file, args.elem_file)
vtk_polydata_writer(args.output_file, vtk_mesh)