Created
June 16, 2025 08:54
-
-
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³.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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