PyPrimeMesh imports geometry through its CAD reader, which does not natively read OBJ files. When an OBJ file uses g group records to divide geometry into named zones — such as inlet, outlet, and wall surfaces — those groups correspond to Named Selections in simulation pre-processing tools. That zone identity, along with the Named Selection it represents, would normally be lost during any intermediate format conversion.
This article describes a pre-processing workflow that bridges that gap. A Python converter reads a single grouped OBJ file and splits it into one binary STL file per group, naming each file after its group. PyPrimeMesh then imports those STL files through its standard CAD route, one part per file. Part names are applied as labels on the imported face zonelets, and zones are created from those labels, so the original OBJ group names — and the Named Selections they represent — are fully traceable through to the final mesh.
The workflow requires no manual geometry editing and no CAD software. The only inputs are the grouped OBJ file and a Python environment with numpy, pyvista, and ansys-meshing-prime installed.
The workflow preserves grouped OBJ geometry for PyPrimeMesh by splitting one OBJ file into one STL file per OBJ group. This is useful when an OBJ contains multiple named zones that need to remain identifiable after import, labeling, wrapping, and meshing.
The key idea is to keep the group name attached to the geometry as long as possible: first as an OBJ g record, then as an STL file name and header, then as a Prime part name, and finally as a label-derived zone.
Group-Preservation Logic
The conversion logic reads OBJ vertices and group records, stores faces under each group name, triangulates polygon faces, and writes one binary STL per group. The parser keeps one shared vertex list for the full OBJ file, while each group gets its own face list. This keeps geometry compact during parsing and allows each group to be exported independently afterward.
The important parsing rules are:
- Lines beginning with
vare read as XYZ vertex coordinates and stored in input order. - Lines beginning with
gdefine the active OBJ group. That group name becomes the zone identity used downstream. - Lines beginning with
fare stored under the currently active group. - Face tokens such as
12,12/4, or12/4/9are supported by reading only the vertex index before the first/. - OBJ indices are converted from one-based indexing to Python zero-based indexing.
- Negative OBJ indices are resolved relative to the current vertex list, which matches the OBJ convention for referencing recently defined vertices.
- Polygonal faces are triangulated so the STL output contains only triangles.
def convert_obj_to_stl(input_path: Path) -> list[Path]:
vertices, faces_by_zone = parse_obj_zones(input_path)
output_files: list[Path] = []
for zone_name, faces in faces_by_zone.items():
if not faces:
continue
output_files.append(write_zone_stl(input_path, zone_name, faces, vertices))
return output_files
OBJ g lines are treated as zone names. When a group is encountered, subsequent faces are collected under that group until another group is found.
elif tag == 'g':
zone_name = ' '.join(parts[1:]).strip()
if zone_name:
current_zone = zone_name
faces_by_zone.setdefault(current_zone, [])
Face records can include vertex, texture, and normal references. Only the vertex reference is needed for the STL surface, so each face token is split at / and the first field is used.
for token in parts[1:]:
vertex_token = token.split('/')[0]
if not vertex_token:
continue
vertex_index = int(vertex_token)
if vertex_index < 0:
vertex_index = len(vertices) + vertex_index + 1
vertex_indices.append(vertex_index - 1)
first_vertex = vertex_indices[0]
for index in range(1, len(vertex_indices) - 1):
zone_faces.append((first_vertex, vertex_indices[index], vertex_indices[index + 1]))
Faces with more than three vertices are converted to triangles using a simple fan triangulation.
Each group is then converted to a PyVista PolyData surface and exported as STL.
Important: The output STL file name is derived from the OBJ group name after removing characters that are unsafe for file systems. This is what allows the original group identity to become visible again during downstream PyPrimeMesh import.
def write_zone_stl(input_path: Path, zone_name: str, faces: list[tuple[int, int, int]], vertices: list[list[float]]) -> Path:
output_name = sanitize_name(zone_name)
output_path = input_path.with_name(f'{output_name}.stl')
polydata = build_polydata(vertices, faces)
polydata.save(str(output_path))
rename_binary_stl_header(output_path, output_name)
return output_path
Downstream PyPrimeMesh Usage
Install or enable the required Python packages in the same environment used by PyPrimeMesh:
pip install numpy pyvista ansys-meshing-prime
Run the OBJ-to-STL split on the grouped OBJ file before calling Prime CAD import.
from pathlib import Path
import ansys.meshing.prime as prime
from convertor import convert_obj_to_stl
prime_client = prime.launch_prime()
model = prime_client.model
mesh_util = prime.lucid.Mesh(model)
template_geom_file = Path('mechanical_sphere_w_groups.obj')
stl_files = convert_obj_to_stl(template_geom_file)
if not stl_files:
raise RuntimeError(f'No STL files were generated from {template_geom_file}')
Then import each STL through the CAD route. The append=True setting allows all generated STL files to be brought into the same model.
file_io = prime.FileIO(model=model)
cad_params = prime.ImportCadParams(
model,
part_creation_type=prime.PartCreationType.PART,
cad_reader_route=prime.CadReaderRoute.PROGRAMCONTROLLED,
append=True,
)
for stl_file in stl_files:
file_io.import_cad(file_name=str(stl_file), params=cad_params)
print(f'imported {stl_file}')
After import, each Prime part name is applied as a label on its face zonelets, and zones are created from those labels. This carries the original OBJ group identity into the Prime model for later wrapping and meshing operations.
for part in model.parts:
part.add_labels_on_zonelets(labels=[part.name], zonelets=part.get_face_zonelets())
mesh_util.create_zones_from_labels()
End-to-End Workflow
- Prepare an OBJ file with meaningful
ggroup records. - Run
convert_obj_to_stl(Path('your_grouped_file.obj')). - Import each generated STL with
prime.FileIO.import_cad(..., append=True). - Add labels from part names and create zones from labels.
- Continue with sizing controls, surface wrapping, volume meshing, and mesh export.