Skip to main content

Importing Grouped OBJ Files into PyPrimeMesh

alan.varghese@… | 08.20.2026

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 v are read as XYZ vertex coordinates and stored in input order.
  • Lines beginning with g define the active OBJ group. That group name becomes the zone identity used downstream.
  • Lines beginning with f are stored under the currently active group.
  • Face tokens such as 12, 12/4, or 12/4/9 are 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 g group 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.

Connect with Ansys