Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 76 additions & 8 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,98 @@ on:
- published

env:
# Build `universal2` and `arm64` wheels on an Intel runner.
# Note that the `arm64` wheel and the `arm64` part of the `universal2`
# wheel cannot be tested in this configuration.
CIBW_ARCHS_MACOS: "x86_64 arm64"
CIBW_ARCHS_WINDOWS: "AMD64"
CIBW_SKIP: "cp38-*"
CIBW_BUILD_VERBOSITY: 1

jobs:

# --------------------------------------------------------------------------
# Build wheels for all OS × Python versions
# --------------------------------------------------------------------------
build_wheels:
name: Build wheels on ${{ matrix.os }}
name: Build wheels (${{ matrix.os }} / Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
include:
# ---------------- Ubuntu ----------------
- os: ubuntu-latest
python-version: "3.9"
tag: cp39
- os: ubuntu-latest
python-version: "3.10"
tag: cp310
- os: ubuntu-latest
python-version: "3.11"
tag: cp311
- os: ubuntu-latest
python-version: "3.12"
tag: cp312
- os: ubuntu-latest
python-version: "3.13"
tag: cp313
- os: ubuntu-latest
python-version: "3.14"
tag: cp314

# ---------------- Windows ----------------
- os: windows-latest
python-version: "3.9"
tag: cp39
- os: windows-latest
python-version: "3.10"
tag: cp310
- os: windows-latest
python-version: "3.11"
tag: cp311
- os: windows-latest
python-version: "3.12"
tag: cp312
- os: windows-latest
python-version: "3.13"
tag: cp313
- os: windows-latest
python-version: "3.14"
tag: cp314

# ---------------- macOS ----------------
- os: macos-latest
python-version: "3.9"
tag: cp39
- os: macos-latest
python-version: "3.10"
tag: cp310
- os: macos-latest
python-version: "3.11"
tag: cp311
- os: macos-latest
python-version: "3.12"
tag: cp312
- os: macos-latest
python-version: "3.13"
tag: cp313
- os: macos-latest
python-version: "3.14"
tag: cp314

env:
CIBW_BUILD: "${{ matrix.tag }}-*"

steps:
- uses: actions/checkout@v4

- name: Build wheels
uses: pypa/cibuildwheel@v3.2.1
uses: pypa/cibuildwheel@v3.3.0

- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}
path: ./wheelhouse/*.whl
name: wheels-${{ matrix.os }}-py${{ matrix.python-version }}
path: wheelhouse/*.whl
if-no-files-found: error

build_sdist:
name: Build source distribution
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ nose_debug
test.ply
.vscode
*.ply
*.stl
128 changes: 81 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@

[![CI](https://github.com/ifilot/pytessel/actions/workflows/build_wheels.yml/badge.svg)](https://github.com/ifilot/pytessel/actions/workflows/build_wheels.yml)
[![PyPI](https://img.shields.io/pypi/v/pytessel?color=green)](https://pypi.org/project/pytessel/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/pypi)](https://pypi.org/project/pytessel/)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

## Purpose

Python package for building isosurfaces from 3D scalar fields
PyTessel is a Python package for constructing isosurfaces from 3D scalar fields
using the marching cubes algorithm. It is designed for scientific visualization,
computational geometry, and mesh generation workflows. While PyTessel was
originally developed for rendering molecular orbitals, it is flexible enough to
tessellate arbitrary scalar fields.

## Installation
![isosurface](img/isosurface.png)

[![PyPI](https://img.shields.io/pypi/v/pytessel?color=green)](https://pypi.org/project/pytessel/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/pypi)](https://pypi.org/project/pytessel/)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytessel)
## Installation

```
pip install pytessel
```

## Getting started

In the script below, the isosurface of a threedimensional Gaussian function is
constructed. The isosurface is written to `test.ply` and can, for example,
be opened using `ctmviewer` (Linux) or `3D viewer` (Windows).
The example below constructs an isosurface of a three-dimensional Gaussian function.
The resulting surface is written to `test.ply`, which can be viewed using tools
such as:

* `ctmviewer` (Linux)
* `3D Viewer` (Windows, free via Microsoft Store)
* Blender, MeshLab, etc.

```python
from pytessel import PyTessel
Expand All @@ -31,31 +38,52 @@ import numpy as np
def main():
pytessel = PyTessel()

# generate some data
# Generate a regular grid
x = np.linspace(0, 10, 50)
# the grid is organized with z the slowest moving index and x the fastest moving index
grid = np.flipud(np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3,-1)).T

R = [5,5,5]
scalarfield = np.reshape(np.array([gaussian(r,R) for r in grid]), (len(x),len(x),len(x)))
# Grid ordering:
# z is the slowest-moving index, x is the fastest-moving index
grid = np.flipud(
np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3, -1)
).T

R = [5, 5, 5]
scalarfield = np.reshape(
np.array([gaussian(r, R) for r in grid]),
(len(x), len(x), len(x))
)

unitcell = np.diag(np.ones(3) * 10.0)

vertices, normals, indices = pytessel.marching_cubes(scalarfield.flatten(), scalarfield.shape, unitcell.flatten(), 0.1)
vertices, normals, indices = pytessel.marching_cubes(
scalarfield.flatten(),
scalarfield.shape,
unitcell.flatten(),
0.1
)

pytessel.write_ply('test.ply', vertices, normals, indices)

def gaussian(r, R):
return np.exp(-(r-R).dot((r-R)))
return np.exp(-(r - R).dot(r - R))

if __name__ == '__main__':
main()
```

## Isosurface quality

In the script below, 6 different images are created of an icosahedral "metaball" using a grid
of `10x10x10`,`20x20x20`,`25x25x25`,`50x50x50`,`100x100x100`, and `200x200x200` points. The
resulting `.ply` files are rendered using [Blender](https://www.blender.org/).
The script below demonstrates how grid resolution affects surface quality. Six
isosurfaces of an icosahedral metaball are generated using grids of:

* `10×10×10`
* `20×20×20`
* `25×25×25`
* `50×50×50`
* `100×100×100`
* `200×200×200`

Each surface is exported as a .ply file and rendered using [Blender](https://www.blender.org/).

```python
from pytessel import PyTessel
Expand All @@ -67,50 +95,52 @@ def main():
"""
pytessel = PyTessel()

for nrpoints in [10,20,25,50,100,200]:
for nrpoints in [10, 20, 25, 50, 100, 200]:
sz = 3

x = np.linspace(-sz, sz, nrpoints)
y = np.linspace(-sz, sz, nrpoints)
z = np.linspace(-sz, sz, nrpoints)

xx, yy, zz, field = icosahedron_field(x,y,z)
xx, yy, zz, field = icosahedron_field(x, y, z)

unitcell = np.diag(np.ones(3) * sz * 2)
pytessel = PyTessel()
isovalue = 3.75
vertices, normals, indices = pytessel.marching_cubes(field.flatten(), field.shape, unitcell.flatten(), isovalue)

pytessel.write_ply('icosahedron_%03i.ply' % nrpoints, vertices, normals, indices)

def icosahedron_field(x,y,z):
vertices, normals, indices = pytessel.marching_cubes(
field.flatten(),
field.shape,
unitcell.flatten(),
isovalue
)

pytessel.write_ply(
f'icosahedron_{nrpoints:03d}.ply',
vertices,
normals,
indices
)

def icosahedron_field(x, y, z):
"""
Produce a scalar field for the icosahedral metaballs
Produce a scalar field for icosahedral metaballs
"""
phi = (1 + np.sqrt(5)) / 2
vertices = [
[0,1,phi],
[0,-1,-phi],
[0,1,-phi],
[0,-1,phi],
[1,phi,0],
[-1,-phi,0],
[1,-phi,0],
[-1,phi,0],
[phi,0,1],
[-phi,0,-1],
[phi,0,-1],
[-phi,0,1]
[0, 1, phi], [0, -1, -phi], [0, 1, -phi], [0, -1, phi],
[1, phi, 0], [-1, -phi, 0], [1, -phi, 0], [-1, phi, 0],
[phi, 0, 1], [-phi, 0, -1], [phi, 0, -1], [-phi, 0, 1]
]

xx,yy,zz = np.meshgrid(x,y,z)
xx, yy, zz = np.meshgrid(x, y, z)
field = np.zeros_like(xx)

for v in vertices:
field += f(xx,yy,zz,v[0], v[1],v[2])
field += metaball(xx, yy, zz, v[0], v[1], v[2])

return xx,yy,zz,field
return xx, yy, zz, field

def f(x,y,z,X0,Y0,Z0):
def metaball(x, y, z, X0, Y0, Z0):
"""
Single metaball function
"""
Expand All @@ -120,10 +150,14 @@ if __name__ == '__main__':
main()
```

![Icosahedral metaballs](https://raw.githubusercontent.com/ifilot/pytessel/master/img/metaballs_3x2.png)
![Icosahedral metaballs](img/metaballs_3x2.png)

## Local compilation (Linux)
## Gallery

```bash
python3 setup.py build_ext --inplace bdist
```
### Stanford Bunny

![bunny](img/bunny.png)

### Gyroid surface

![gyroid](img/gyroid.png)
2 changes: 0 additions & 2 deletions doc.Dockerfile

This file was deleted.

6 changes: 0 additions & 6 deletions environment.yml

This file was deleted.

60 changes: 0 additions & 60 deletions example/build_metaballs_icosahedron_rectangular_grid.py

This file was deleted.

File renamed without changes.
Loading