Skip to content

Instantly share code, notes, and snippets.

@JonathanMaes
Created June 16, 2025 08:54
Show Gist options
  • Select an option

  • Save JonathanMaes/7bb7564cae08b05bd0364ee696f7c30d to your computer and use it in GitHub Desktop.

Select an option

Save JonathanMaes/7bb7564cae08b05bd0364ee696f7c30d to your computer and use it in GitHub Desktop.
Converts a 3D .obj file to a scalar OVF file at a given resolution. OVF files are used by micromagnetic simulators like OOMMF and mumax³.
import numpy as np
import trimesh # https://github.com/mikedh/trimesh
def write_ovf(voxel_grid, filename, resolution):
""" Writes voxel grid into OVF file format. """
nx, ny, nz = voxel_grid.shape
with open(filename, 'w') as f:
f.write("# OOMMF OVF 2.0\n")
f.write("# Segment count: 1\n")
f.write("# Begin: Segment\n")
f.write("# Begin: Header\n")
f.write(f"# Title: Voxelized geometry\n")
f.write("# meshunit: m\n")
f.write("# valuedim: 1\n")
f.write("# valueunits: None\n")
f.write(f"# xnodes: {nx}\n")
f.write(f"# ynodes: {ny}\n")
f.write(f"# znodes: {nz}\n")
f.write(f"# xstepsize: {resolution}\n")
f.write(f"# ystepsize: {resolution}\n")
f.write(f"# zstepsize: {resolution}\n")
f.write("# End: Header\n")
f.write("# Begin: Data Text\n")
for z in range(nz):
for y in range(ny):
for x in range(nx):
f.write("1\n" if voxel_grid[x, y, z] else "0\n") # 1: inside geometry. 0: outside geometry.
f.write("# End: Data Text\n")
f.write("# End: Segment\n")
convert_to_lf(filename)
def obj_to_voxels(obj_filename, resolution, scale=1, rotate_to_z_up=False):
""" Converts a .obj file to a voxel grid at a certain resolution and scaling.
Scaling with a very small factor is often necessary to obtain coordinates in the nanometre range.
"""
mesh = trimesh.load(obj_filename)
# Center the geometry at the origin and scale it
mesh.apply_translation(-(mesh.bounds[0] + mesh.bounds[1])/2)
mesh.apply_scale(scale)
# Rotate Y-up to Z-up (swap Y- and Z-axes)
if rotate_to_z_up:
rotation_matrix = trimesh.transformations.rotation_matrix(np.pi/2, [1, 0, 0]) # 90° rotation around X-axis
mesh.apply_transform(rotation_matrix)
# Compute the voxelized representation of the mesh
voxelized = mesh.voxelized(pitch=resolution)
voxelized.fill()
voxel_grid = voxelized.matrix
# Find the next 7-smooth size for each dimension
nx, ny, nz = voxel_grid.shape
nx_7smooth = next_7_smooth(nx)
ny_7smooth = next_7_smooth(ny)
nz_7smooth = next_7_smooth(nz)
# Center the geometry by padding the voxel grid
pad_x = (nx_7smooth - nx) // 2
pad_y = (ny_7smooth - ny) // 2
pad_z = (nz_7smooth - nz) // 2
voxel_grid = np.pad(voxel_grid,
((pad_x, nx_7smooth - nx - pad_x),
(pad_y, ny_7smooth - ny - pad_y),
(pad_z, nz_7smooth - nz - pad_z)),
mode='constant', constant_values=0)
print(f"Grid size: {nx_7smooth} x {ny_7smooth} x {nz_7smooth}")
return voxel_grid
## Utility functions
def is_7_smooth(n):
for prime in [2, 3, 5, 7]:
while n % prime == 0:
n //= prime
return n == 1
def next_7_smooth(n):
while not is_7_smooth(n):
n += 1
return n
def convert_to_lf(filepath):
""" Convert CRLF (\r\n) to LF (\n). """
with open(filepath, 'rb') as f:
content = f.read()
content = content.replace(b'\r\n', b'\n')
with open(filepath, 'wb') as f:
f.write(content)
if __name__ == "__main__":
obj_filename = 'teapot.obj' # Path to the .obj file
ovf_filename = 'teapot.ovf' # Output OVF file
resolution = 5e-9 # Set your desired voxel resolution here (in meters)
scale = 4e-8 # Scale the geometry down to nanometers (e.g., 1e-6 for hundreds of nm)
# Convert OBJ to voxel grid with scaling, then convert voxel grid to OVF
voxel_grid = obj_to_voxels(obj_filename, resolution, scale, rotate_to_z_up=True)
write_ovf(voxel_grid, ovf_filename, resolution)
print(f"Converted {obj_filename} to {ovf_filename} with resolution {resolution} and scale {scale}, centered and resized to the nearest 7-smooth grid size.")
# For testing: get some random sample points
for _ in range(10):
rx = np.random.randint(0, voxel_grid.shape[0])
ry = np.random.randint(0, voxel_grid.shape[1])
rz = np.random.randint(0, voxel_grid.shape[2])
point = voxel_grid[rx, ry, rz]
print(f'expectv("m", m.getcell({rx},{ry},{rz}), Vector({point:.0f}, 0, 0), 0)')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment