当前位置:首页>python>Python实现:TPMS晶格结构生成器,一键导出STL模型

Python实现:TPMS晶格结构生成器,一键导出STL模型

  • 2026-10-11 08:41:56
Python实现:TPMS晶格结构生成器,一键导出STL模型
点击上方蓝字订阅!

三周期极小曲面(TPMS)是一类具有零平均曲率的周期性曲面,在自然界中广泛存在,如蝴蝶翅膀的鳞片、骨骼的微结构等。基于TPMS设计的晶格结构因其优异的力学性能、高比表面积和可控的孔隙率,成为增材制造(3D打印)领域的研究热点。本文提供一个完全自包含的Python脚本,能够根据用户指定的TPMS类型、结构形式(杆状网络型或片状壳体型)和目标体积分数,自动生成封闭的STL网格文件,可直接用于3D打印或有限元分析。

代码的核心思想是将TPMS的隐函数方程离散化到规则网格上,通过Marching Cubes算法提取等值面,并对网格边界添加盖面以保证模型的封闭性。为了精确控制体积分数,程序采用先粗调后二分法的方式自动校准水平集常数r。整个过程无需任何外部几何建模软件,仅依赖NumPy、SciPy和scikit-image三个科学计算库,即可在数秒内生成高精度网格。

import numpy as np
from skimage.measure import marching_cubes, find_contours
from scipy.spatial import Delaunay

# ==================== TPMS 隐函数定义 ====================
deftpms_function(x, y, z, tpms_type):
"""
    计算指定类型TPMS的隐函数值 F(x,y,z)
    支持类型: 'P', 'G', 'D', 'I', 'S', 'F', 'N'
    """

    pi2 = 2 * np.pi
    pi4 = 4 * np.pi

if tpms_type == 'P':
return - (np.cos(pi2 * x) + np.cos(pi2 * y) + np.cos(pi2 * z))
elif tpms_type == 'G':
return (np.cos(pi2 * x) * np.sin(pi2 * y) +
                np.cos(pi2 * y) * np.sin(pi2 * z) +
                np.cos(pi2 * z) * np.sin(pi2 * x))
elif tpms_type == 'D':
return (np.sin(pi2 * x) * np.sin(pi2 * y) * np.sin(pi2 * z) +
                np.sin(pi2 * x) * np.cos(pi2 * y) * np.cos(pi2 * z) +
                np.cos(pi2 * x) * np.sin(pi2 * y) * np.cos(pi2 * z) +
                np.cos(pi2 * x) * np.cos(pi2 * y) * np.sin(pi2 * z))
elif tpms_type == 'I':
return (2 * (np.cos(pi2 * x) * np.cos(pi2 * y) +
                     np.cos(pi2 * y) * np.cos(pi2 * z) +
                     np.cos(pi2 * z) * np.cos(pi2 * x)) -
                (np.cos(pi4 * x) + np.cos(pi4 * y) + np.cos(pi4 * z)))
elif tpms_type == 'S':
return (np.cos(pi4 * x) * np.sin(pi2 * y) * np.cos(pi2 * z) +
                np.cos(pi2 * x) * np.cos(pi4 * y) * np.sin(pi2 * z) +
                np.sin(pi2 * x) * np.cos(pi2 * y) * np.cos(pi4 * z))
elif tpms_type == 'F':
return - (4 * (np.cos(pi2 * x) * np.cos(pi2 * y) * np.cos(pi2 * z)) -
                  (np.cos(pi4 * x) * np.cos(pi4 * y) +
                   np.cos(pi4 * y) * np.cos(pi4 * z) +
                   np.cos(pi4 * z) * np.cos(pi4 * x)))
elif tpms_type == 'N':
return3 * (np.cos(pi2 * x) + np.cos(pi2 * y) + np.cos(pi2 * z)) + \
4 * np.cos(pi2 * x) * np.cos(pi2 * y) * np.cos(pi2 * z)
else:
raise ValueError(f"不支持的TPMS类型: {tpms_type}")

# ==================== 网格生成辅助函数 ====================
defgenerate_grid(x_range, y_range, z_range, n):
"""
    在指定长方体区域内生成规则网格
    返回: 网格点数组, 各轴坐标序列, 网格间距
    """

    x = np.linspace(x_range[0], x_range[1], n + 1)
    y = np.linspace(y_range[0], y_range[1], n + 1)
    z = np.linspace(z_range[0], z_range[1], n + 1)
    xx, yy, zz = np.meshgrid(x, y, z, indexing='ij')
    spacing = ((x_range[1] - x_range[0]) / n,
               (y_range[1] - y_range[0]) / n,
               (z_range[1] - z_range[0]) / n)
return xx, yy, zz, x, y, z, spacing

# ==================== 边界盖面生成 ====================
defadd_isocaps(x_coords, y_coords, z_coords, phi, level=0):
"""
    为三维标量场phi的边界添加等值面盖面(三角剖分)
    返回盖面的顶点和三角形索引(局部坐标)
    """

    nx, ny, nz = phi.shape
    all_verts = []
    all_faces = []
    offset = 0

# 六个边界平面的定义
    planes = [
        (0, 0, x_coords[0], lambda u, v: (x_coords[0], u, v)),
        (0, -1, x_coords[-1], lambda u, v: (x_coords[-1], u, v)),
        (1, 0, y_coords[0], lambda u, v: (u, y_coords[0], v)),
        (1, -1, y_coords[-1], lambda u, v: (u, y_coords[-1], v)),
        (2, 0, z_coords[0], lambda u, v: (u, v, z_coords[0])),
        (2, -1, z_coords[-1], lambda u, v: (u, v, z_coords[-1]))
    ]

for axis, idx, fixed_val, to3d in planes:
# 提取二维切片
if axis == 0:
            slice_phi = phi[idx, :, :]          # shape (ny, nz)
            u_coords = y_coords
            v_coords = z_coords
elif axis == 1:
            slice_phi = phi[:, idx, :]          # shape (nx, nz)
            u_coords = x_coords
            v_coords = z_coords
else:
            slice_phi = phi[:, :, idx]          # shape (nx, ny)
            u_coords = x_coords
            v_coords = y_coords

# 提取等值线轮廓
        contours = find_contours(slice_phi, level)
for contour in contours:
# 将轮廓点从像素坐标映射到实际坐标
            u = u_coords[0] + contour[:, 1] * (u_coords[-1] - u_coords[0]) / (len(u_coords)-1)
            v = v_coords[0] + contour[:, 0] * (v_coords[-1] - v_coords[0]) / (len(v_coords)-1)
            points_2d = np.column_stack((u, v))

if len(points_2d) >= 3:
                tri = Delaunay(points_2d)                     # 三角剖分
                verts_3d = np.array([to3d(ui, vi) for ui, vi in points_2d])
                faces_3d = tri.simplices
                all_verts.append(verts_3d)
                all_faces.append(faces_3d + offset)
                offset += len(verts_3d)

if all_verts:
return np.vstack(all_verts), np.vstack(all_faces)
return np.empty((0, 3)), np.empty((0, 3), dtype=int)

# ==================== 体积与表面积计算 ====================
defstl_volume(vertices, faces):
"""
    计算封闭三角网格的体积(有向)和表面积
    """

    v0 = vertices[faces[:, 0]]
    v1 = vertices[faces[:, 1]]
    v2 = vertices[faces[:, 2]]
# 体积:散度定理
    vol = np.sum((v0[:, 0] * (v1[:, 1] * v2[:, 2] - v2[:, 1] * v1[:, 2]) -
                  v1[:, 0] * (v0[:, 1] * v2[:, 2] - v2[:, 1] * v0[:, 2]) +
                  v2[:, 0] * (v0[:, 1] * v1[:, 2] - v1[:, 1] * v0[:, 2])) / 6.0)
# 表面积
    normals = np.cross(v1 - v0, v2 - v0, axis=1)
    area = 0.5 * np.linalg.norm(normals, axis=1)
return vol, np.sum(area)

# ==================== 二进制STL写入 ====================
defwrite_binary_stl(filename, vertices, faces, title="TPMS Lattice"):
"""
    将三角网格写入二进制STL文件
    """

    vertices = vertices.astype(np.float32)
    v0 = vertices[faces[:, 0]]
    v1 = vertices[faces[:, 1]]
    v2 = vertices[faces[:, 2]]
    normals = np.cross(v1 - v0, v2 - v0, axis=1)
    norms = np.linalg.norm(normals, axis=1, keepdims=True)
    norms[norms == 0] = 1
    normals /= norms
    facets = np.hstack([normals, v0, v1, v2]).astype(np.float32)

with open(filename, 'wb') as f:
        f.write(title.encode('ascii')[:80].ljust(80, b'\x00'))
        f.write(np.array(len(faces), dtype=np.uint32).tobytes())
for facet in facets:
            f.write(facet.tobytes())
            f.write(b'\x00\x00')

# ==================== 体积分数校准 ====================
defcalibrate_r(xx, yy, zz, x_coords, y_coords, z_coords, spacing, tpms_type, struct_type,
                vol_frac_target, r_start=-0.07, r_step=0.1, tol=1e-4, max_iter=50)
:

"""
    通过粗调+二分法自动寻找合适的水平集常数r,使体积分数接近目标值
    struct_type: 'net' (杆状) 或 'sheet' (片状)
    """

    F_val = tpms_function(xx, yy, zz, tpms_type)
# 粗调
    r = r_start
    vol_frac = 0
for _ in range(max_iter):
if struct_type == 'net':
            phi = F_val + r
else:
            phi = -(F_val + r) * (F_val - r)

        verts, faces, _, _ = marching_cubes(phi, level=0, spacing=spacing)
        verts[:, 0] += xx[0,0,0]
        verts[:, 1] += yy[0,0,0]
        verts[:, 2] += zz[0,0,0]
if len(verts) > 0and len(faces) > 0:
            cap_verts, cap_faces = add_isocaps(x_coords, y_coords, z_coords, phi, level=0)
if len(cap_verts) > 0:
                all_verts = np.vstack([verts, cap_verts])
                all_faces = np.vstack([faces, cap_faces + len(verts)])
else:
                all_verts, all_faces = verts, faces
            vol, _ = stl_volume(all_verts, all_faces)
            vol_frac = abs(vol)
else:
            vol_frac = 0

if abs(vol_frac - vol_frac_target) < tol:
break
if vol_frac < vol_frac_target:
            r += r_step
else:
            r -= r_step
if r > 1or r < -1:
break

# 二分法精调
if vol_frac > vol_frac_target:
        r1, r2 = r - r_step, r
else:
        r1, r2 = r, r + r_step
for _ in range(max_iter):
        r = (r1 + r2) / 2
if struct_type == 'net':
            phi = F_val + r
else:
            phi = -(F_val + r) * (F_val - r)

        verts, faces, _, _ = marching_cubes(phi, level=0, spacing=spacing)
        verts[:, 0] += xx[0,0,0]
        verts[:, 1] += yy[0,0,0]
        verts[:, 2] += zz[0,0,0]
if len(verts) > 0and len(faces) > 0:
            cap_verts, cap_faces = add_isocaps(x_coords, y_coords, z_coords, phi, level=0)
if len(cap_verts) > 0:
                all_verts = np.vstack([verts, cap_verts])
                all_faces = np.vstack([faces, cap_faces + len(verts)])
else:
                all_verts, all_faces = verts, faces
            vol, _ = stl_volume(all_verts, all_faces)
            vol_frac = abs(vol)
else:
            vol_frac = 0

if abs(vol_frac - vol_frac_target) < tol:
break
if vol_frac > vol_frac_target:
            r2 = r
else:
            r1 = r
return r, vol_frac

# ==================== 主程序 ====================
defmain():
# ---------- 用户可调参数 ----------
    n_final = 80# 最终网格分辨率(越高越精细)
    n_calib = 60# 校准用分辨率
    tpms = 'P'# TPMS类型: P, G, D, I, S, F, N
    struct_type = "net"# "net" 或 "sheet"
    vol_frac_target = 0.27# 目标体积分数 (0~1)
    num_cell = 1# 每个方向上的晶胞个数
    target_size = 1.0# 最终模型的边长
    center = np.array([0, 0, 0])  # 模型中心坐标
# -------------------------------

    print("=" * 50)
    print("TPMS 晶格结构生成器")
    print(f"类型: {tpms}, 结构: {struct_type}, 目标体积分数: {vol_frac_target}")
    print("=" * 50)

# 1. 校准水平集常数 r
    print("正在校准水平集常数 r ...")
    x_range = (0, 1)
    y_range = (0, 1)
    z_range = (0, 1)
    xx, yy, zz, xc, yc, zc, sp = generate_grid(x_range, y_range, z_range, n_calib)
    r, vol_frac = calibrate_r(xx, yy, zz, xc, yc, zc, sp, tpms, struct_type, vol_frac_target)
    print(f"校准完成: r = {r:.6f}, 实际体积分数 = {vol_frac:.6f}")

# 2. 生成最终网格
    print("正在生成最终网格 ...")
    x_range_f = (0, num_cell)
    y_range_f = (0, num_cell)
    z_range_f = (0, num_cell)
    xx_f, yy_f, zz_f, xc_f, yc_f, zc_f, sp_f = generate_grid(x_range_f, y_range_f, z_range_f, n_final)

    F_final = tpms_function(xx_f, yy_f, zz_f, tpms)
if struct_type == 'net':
        phi_f = F_final + r
else:
        phi_f = -(F_final + r) * (F_final - r)

    verts, faces, _, _ = marching_cubes(phi_f, level=0, spacing=sp_f)
    verts[:, 0] += x_range_f[0]
    verts[:, 1] += y_range_f[0]
    verts[:, 2] += z_range_f[0]

    cap_verts, cap_faces = add_isocaps(xc_f, yc_f, zc_f, phi_f, level=0)
if len(cap_verts) > 0:
        all_verts = np.vstack([verts, cap_verts])
        all_faces = np.vstack([faces, cap_faces + len(verts)])
else:
        all_verts, all_faces = verts, faces

# 平移和缩放
    all_verts -= center
    all_verts *= target_size

    vol, _ = stl_volume(all_verts, all_faces)
    vol_frac_final = abs(vol) / (target_size ** 3)
    print(f"最终模型体积分数: {vol_frac_final:.6f}")

    write_binary_stl('lattice.stl', all_verts, all_faces)
    print("STL文件已保存为 lattice.stl")

if __name__ == "__main__":
    main()

代码使用说明

上述代码完全独立,只需安装依赖库即可运行:

pip install numpy scipy scikit-image

所有核心算法均内嵌于脚本中,无需任何外部几何处理软件。用户只需修改主函数开头的参数(如TPMS类型、结构形式、目标体积分数、晶胞数量和模型尺寸),运行后即可在当前目录下生成lattice.stl文件。该文件可直接导入3D建模软件(如Blender、MeshLab)或切片软件,用于增材制造。

技术要点深度解读

TPMS的数学本质是隐函数F(x,y,z)=c,其中c为水平集常数。当c=0时,曲面通过原点;改变c可连续调整曲面的体积分数。对于杆状网络结构(net),曲面定义为F(x,y,z)=r;对于片状壳结构(sheet),曲面定义为(F+r)(F-r)=0,相当于两个等值面的夹层区域。通过二分法自动寻找合适的r,使得模型体积分数逼近目标值,这是整个算法的关键所在。

在网格生成环节,Marching Cubes算法将隐函数离散化后提取三角形面片,但原始输出是开放的(模型边界处有孔洞)。为了获得可用于实体打印的封闭模型,代码额外为六个边界平面添加了盖面:对每个边界平面上的隐函数值进行二维等值线提取,再使用Delaunay三角剖分生成填充面。这种方法保证了最终模型的水密性,可直接用于体积计算和3D打印。

体积分数的精确控制依赖于封闭网格的体积计算。代码通过散度定理计算有向体积,避免了繁琐的四面体剖分,仅需一次遍历所有三角形即可获得高精度结果。校准过程中,粗调阶段采用变步长搜索快速逼近目标区间,精调阶段使用二分法进一步收敛,兼顾了效率与准确性。

该实现将数学定义、离散化、网格生成、边界处理、体积校准和文件输出整合为一条流畅的流水线,为TPMS晶格结构的快速设计提供了一套开箱即用的解决方案。无论是用于科研中的力学性能分析,还是工程中的轻量化设计,都能从中获得高效支持。

 • end • 

陪伴是最长情的告白

 为你推送最实用的资讯 

识别二维码 关注我们 

最新文章

随机文章