使用Houdini输出四面体网格并输出tetgen格式
我们的目标是从houdini输出生成的四面体,希望是tetgen格式的。
众所周知,houdini是不能直接输出四面体的。
有三方案去解决:
- 输出点云ply文件,然后利用tetgen生成网格。
- 输出Hounidi内置的.geo格式文件,然后写个脚本去解析json,因为这个文件就是个json。
- 直接从Houdini中利用Python节点输出tetgen格式。
我探索并试验了以上所有三种方案。优缺点如下:
第一种方案的缺点是四面体是不可控的,因为是tetgen现生成的。
第二种方案的缺点是你要保证geo里面没有多余的数据。例如还存储的颜色或uv等信息,或者houdini的prim上还存了其他信息,就会导致解析失败。当然我们可以使用clean节点清除掉这些多余信息。我会把脚本放到文末。
第三种方案是最好的。可控性最好。因此后面我们会说这种方法。
tetgen的数据格式
我们首先要介绍一下tetgen的数据格式:
我们需要其中三种:
.node结尾代表顶点位置
.ele结尾代表四面体编号
.face结尾代表三角形编号

如图,其中第一个数代表点/面/单元的数量
ele和face中的顶点编号对应的都是node中点前面那个编号。
例如ele中每一行为:
当前单元的编号 四面体第1个顶点的编号 四面体第2个顶点的编号 四面体第3个顶点的编号 四面体第4个顶点的编号
Houdini中使用python节点写出

各个节点的作用如下所示:
- file: 读入obj
- clean: 清理多余信息(非必要)
- polyreduce: 简化模型,缩小顶点数(非必要)
- tetconform: 生成四面体
- split: 分出表面三角形和四面体
- null:无作用的节点,只是为了占位
- python node(write_faces): 写出表面三角形编号
- python node(write_nodes):写出所有顶点位置
- python node(write_eles):写出四面体编号
脚本的内容在附录。

在这里,我们要讲解一下houdini中的数据。
分为四种:
- points 几何点的信息,包括点的位置
- vertices: 拓扑顶点信息。例如可以存顶点的编号
- primitives: 图元信息。可以存储例如该四面体的体积大小等
- detail: 整个几何体的信息。
一个常见的误区是混淆points和vertices。points完全是空间中真实存在的一个点。具有位置速度等信息。但是vertices可以认为是对顶点编号的reference。例如一个正方体的角点,可以被三个面同时共享。他都是同一个几何点,但是却有三个不同的vertices归属于不同的面。这样的好处是保证了唯一性:一个point只对应一个vertex,一个vertex只被一个primitive所包含。
这里要注意的是,vertices中存的是什么完全取决于图元是什么。假如是个四面体,就可以是四个顶点编号。假如是三角形,就是三角形三个点编号。

正是由于三角形与四面体都被存在primitive中,所以我们才用split将其分开。方面后面输出。
注意在tetconform中勾选add surface triangles才会输出三角面。

最后稍微讲解下python节点中的脚本。
请注意,houdini中的python是完全面向对象的,因此万物皆为对象。
我们仅以write_nodes为例。API请查阅Houdini的官方文档。
import hou # houdini包
geo = hou.pwd().geometry() #当前python节点的第一个输入端口所对应的对象,是个SOP对象
print(geo)import os
path = hou.hipFile.path() #hip文件所在的位置
path = os.path.dirname(path) + "/models/bunny2000_try.node"
print(f"path is {path}")pts = geo.points() # 获取SOP对象上的point对象列表(是个list)
numpts = len(pts)
print("numpts:", numpts)f = open(path, 'w')
f.write(str(numpts)+" 3 0 0\n")
for i in range(numpts):pt = pts[i] pos = pt.position() #获取point对象的position属性,就是位置f.write(" "+str(i)+" "+str(pos[0])+" "+str(pos[1])+" "+str(pos[2])+"\n")
f.close()
完毕。
(非必要内容)在taichi中ggui显示
请见
https://github.com/chunleili/learn-meshtaichi
中的tut03
其中主要就是多写了directly_import_surf这个函数而已。
网格文件也请见这里。
结果如图

附录1:Houdini中python节点的脚本内容
write_faces
import hou
geo = hou.pwd().geometry()
print(geo)import os
path = hou.hipFile.path()
path = os.path.dirname(path) + "/models/bunny2000_try.face"
print(f"path is {path}")# write surface triangles
tris = geo.prims()
num_tris = len(tris)
f = open(path[:-4]+"face", 'w')
f.write(str(num_tris)+" 0\n")
for i in range(num_tris):tri = tris[i].points()f.write(" "+str(i)+" "+str(tri[0].number())+" "+str(tri[1].number())+" "+str(tri[2].number())+ " -1" +"\n")
f.close()
write_nodes
import hou
geo = hou.pwd().geometry()
print(geo)import os
path = hou.hipFile.path()
path = os.path.dirname(path) + "/models/bunny2000_try.node"
print(f"path is {path}")pts = geo.points()
numpts = len(pts)
print("numpts:", numpts)f = open(path, 'w')
f.write(str(numpts)+" 3 0 0\n")
for i in range(numpts):pt = pts[i]pos = pt.position()f.write(" "+str(i)+" "+str(pos[0])+" "+str(pos[1])+" "+str(pos[2])+"\n")
f.close()
write_eles
import hou
geo = hou.pwd().geometry()
print(geo)import os
path = hou.hipFile.path()
path = os.path.dirname(path) + "/models/bunny2000_try.ele"
print(f"path is {path}")eles = geo.prims()
num_eles = len(eles)
print("num_eles:", num_eles)
f1 = open(path[:-4]+".ele", 'w')
f1.write(str(num_eles)+" 4 0\n")
for i in range(num_eles):ele = eles[i].points()f1.write(" "+str(i)+" "+str(ele[0].number())+" "+str(ele[1].number())+" "+str(ele[2].number())+" "+str(ele[3].number())+"\n")
f1.close()
附录2: houdni的geo文件解析转换为tetgen格式四面体的脚本
import os
import jsondef read_geo(from_path):with open(from_path,'r') as f:data=json.load(f)# 读取顶点个数等信息pointcount=data[5] # 点个数vertexcount=data[7] primitivecount=data[9] # 四面体个数# 读取四面体的索引topology = data[13]pointref = topology[1]tet_indices = pointref[1]# 四面体的索引,是一个一维数组# 读取顶点的位置attributes = data[15]pointattributes = attributes[1]positions = pointattributes[0][1][7][5]return tet_indices,positions, pointcount,vertexcount,primitivecountdef write_tetgen(tet_indices,positions, pointcount, primitivecount,to_path, gen_face=False):# 写入tetgen的node文件(也就是顶点的位置)node_file = to_path+".node"if(os.path.exists(node_file)):print("remove file: "+node_file)os.remove(node_file)with open(node_file,'w') as f:f.write(str(pointcount)+" 3 0 0\n")for i in range(pointcount):f.write(" "+str(i)+" "+str(positions[i][0])+" "+str(positions[i][1])+" "+str(positions[i][2])+"\n")# 写入tetgen的ele文件(也就是四面体的索引)ele_file = to_path+".ele"if(os.path.exists(ele_file)):print("remove file: "+ele_file)os.remove(ele_file)with open(ele_file,'w') as f:f.write(str(primitivecount)+" 4 0\n")for i in range(primitivecount):f.write(" "+str(i)+" "+str(tet_indices[i*4])+" "+str(tet_indices[i*4+1])+" "+str(tet_indices[i*4+2])+" "+str(tet_indices[i*4+3])+"\n")# 写入tetgen的face文件(也就是三角面的索引)face_file = to_path+".face"if(os.path.exists(face_file)):print("remove file: "+face_file)os.remove(face_file)if(gen_face):# 由于本身没有三角面,所以如果想生成face,就自己遍历一遍facecount = 0for i in range(primitivecount):facecount += 4with open(face_file,'w') as f:f.write(str(facecount)+" 0\n")face_i = 0for i in range(primitivecount):f.write(" "+str(face_i)+" " + str(tet_indices[i*4])+" "+str(tet_indices[i*4+2])+" "+str(tet_indices[i*4+1])+" -1\n")face_i += 1f.write(" "+str(face_i)+" " + str(tet_indices[i*4])+" "+str(tet_indices[i*4+3])+" "+str(tet_indices[i*4+2])+" -1\n")face_i += 1f.write(" "+str(face_i)+" " + str(tet_indices[i*4])+" "+str(tet_indices[i*4+1])+" "+str(tet_indices[i*4+3])+" -1\n")face_i += 1f.write(" "+str(face_i)+" " + str(tet_indices[i*4+1])+" "+str(tet_indices[i*4+2])+" "+str(tet_indices[i*4+3])+" -1\n")face_i += 1print("\n\nwrite tetgen file success! \nnode file: "+node_file+"\nele file: "+ele_file)if __name__ == '__main__':from_path="models/bunny1000_dilate/bunny1000_dilate.geo"to_path=from_path[:-4]tet_indices,positions, pointcount,vertexcount,primitivecount = read_geo(from_path)write_tetgen(tet_indices,positions, pointcount,primitivecount,to_path, gen_face=True)
附录3:在太极ggui中显示
learn-meshtaichi tut03
import taichi as ti
import meshtaichi_patcher as Patcherti.init()# CAUTION: 我们只加了这一个函数, 其他的基本不变。这个就是用来读取face文件的
def directly_import_surf():import numpy as npimport ospwd = os.getcwd().replace("\\", "/")face_file_name = pwd + "/models/bunny_tet/bunny_tet.face"# print("face_file_name: ", face_file_name)with open(face_file_name, 'r') as f:lines = f.readlines()NF = int(lines[0].split()[0])face_indices = np.zeros((NF, 3), dtype=np.int32)for i in range(NF):face_indices[i] = np.array(lines[i + 1].split()[1:-1], dtype=np.int32)return face_indices.flatten()
armadillo_surf_indices = directly_import_surf()# 读入四面体网格
def init_tet_mesh(model_name):#基本与上面一样,只是多了一个CV关系,表示通过一个cell可以找到它的四个顶点theMesh = Patcher.load_mesh(model_name, relations=["CV"])theMesh.verts.place({'x' : ti.math.vec3})theMesh.verts.x.from_numpy(theMesh.get_position_as_numpy())display_indices = ti.field(ti.u32, shape = len(armadillo_surf_indices))display_indices.from_numpy(armadillo_surf_indices) #这里直接读入了face文件return theMesh, display_indicesmodel_name = "models/bunny_tet/bunny_tet.node"
armadillo, armadillo_indices = init_tet_mesh(model_name)
armadillo_indices.to_numpy()window = ti.ui.Window("taichimesh", (1024, 1024))
canvas = window.get_canvas()
scene = ti.ui.Scene()
camera = ti.ui.Camera()
camera.up(0, 1, 0)
camera.fov(75)
camera.position(4.5,4.5,0.6)
camera.lookat(3.8, 3.8, 0.5)
camera.fov(75)frame = 0
paused = ti.field(int, shape=())
paused[None] = 1
while window.running:# 用下面这段代码,通过提前设置一个paused变量,我们就可以在运行的时候按空格暂停和继续了!for e in window.get_events(ti.ui.PRESS):if e.key == ti.ui.SPACE:paused[None] = not paused[None]print("paused:", paused[None])if not paused[None]:# substep()print(f"frame: {frame}")frame += 1# 我们可以通过下面的代码来查看相机的位置和lookat,这样我们就能知道怎么调整相机的位置了# print("camera.curr_position",camera.curr_position)# print("camera.curr_lookat",camera.curr_lookat)# movement_speed=0.05表示移动速度,hold_key=ti.ui.RMB表示按住右键可以移动视角# wasdqe可以移动相机camera.track_user_inputs(window, movement_speed=0.05, hold_key=ti.ui.RMB)scene.set_camera(camera)scene.mesh(armadillo.verts.x, armadillo_indices, color = (0.5,0.5,0.5))scene.point_light(pos=(0.5, 1.5, 0.5), color=(1, 1, 1))scene.ambient_light((0.5,0.5,0.5))canvas.scene(scene)window.show()
相关文章:
使用Houdini输出四面体网格并输出tetgen格式
我们的目标是从houdini输出生成的四面体,希望是tetgen格式的。 众所周知,houdini是不能直接输出四面体的。 有三方案去解决: 输出点云ply文件,然后利用tetgen生成网格。输出Hounidi内置的.geo格式文件,然后写个脚本…...
组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比
组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比 目录 组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现EMD-KP...
【C语言】操作符详解总结(万字)
操作符详解1. 操作符分类2. 算术操作符3. 移位操作符3.1 整数的二进制是怎么形成的3.2 左移操作符3.3 右移操作符4. 位操作符5. 赋值操作符6. 单目操作符6.1 单目操作符介绍6.2 sizeof 和 数组7. 关系操作符8. 逻辑操作符9. 条件操作符9.1 练习19.2 练习210. 逗号表达式11. 下标…...
mac系统手册(帮助/说明)
文章目录1. mac自带的帮助文档2. Mac使用技巧(提示)2.1 聚焦搜索2.2 截图(录制屏幕)2.3 调出右键菜单2.4 快速查看2.5 翻译2.5.1 词典解释2.5.2 翻译(字、词和句)3. macOS使用手册3.1 在聚焦中进行计算和转…...
VLC播放器Demo(录像,截图等功能),Android播放器Demo可二次开发。
VLC播放器Demo(录像,截图等功能),可二次开发。 GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer …...
WeSpeaker支持C++部署链路
WeSpeaker正式更新C部署链路,推理引擎使用OnnxRuntime,支持从语音中提取Speaker Embedding信息,代码详见WeSpeaker/runtime[1]。 Libtorch和onnx的选择? Speaker Embedding提取任务流程简单,并且声纹模型(如ResNet\E…...
window vscode编辑appsmith源码
前言 本来最开始用的idea打开wsl中的appsmith,卡得一批。最后没办法,用自己的电脑装成ubuntu server,然后vscode的远程开发对appsmith源码进行编辑。如果自己电脑内存16个G或者更大可能打开wsl中的估计会还好,我公司电脑只有8g所…...
操作系统面试题
操作系统一、简介篇1.解释一下什么是操作系统2.操作系统的主要功能3.软件访问硬件的几种方式4.操作系统的主要目的是什么5.为什么Linux系统下的应用程序不能直接在Windows下运行6.什么是用户态和内核态7.用户态和内核态如何切换8.什么是内核二、进程和线程篇1.多处理系统的优势…...
Kafka入门(七)
下面聊聊Kafka的配置参数,包括生产者的配置参数、Broker的配置参数、消费者的配置参数。 1、生产者配置参数 acks 该参数控制了生产者的消息发送确认机制,用于指定分区中必须有多少个副本成功接收到消息后生产者才会认为这条消息写入是成功的,…...
微服务介绍
微服务 微服务架构发展 微服务这个概念最早是在2011年5月威尼斯的一个软件架构会议上讨论提出的,用于描述一些作为通用架构风格的设计原则;2012年3月在波兰举行的Degree Conference大会,james lewis做演讲,讨论了微服务一些原则…...
搭建SpringBoot多模块微服务项目脚手架(三)
搭建SpringBoot多模块微服务项目脚手架(三) 文章目录搭建SpringBoot多模块微服务项目脚手架(三)1.概述项目结构2.接口返回统一信息模板2.1.封装返回统一信息思路介绍2.2.封装json数据格式1.导入依赖2.封装code码3.封装json格式模板4.使用统一返回信息3.接口统一请求信息模板3.1…...
对vue3中reactive、toref、torefs、ref的详细理解
reactive:将平常的一个对象转换成响应式对象。所谓的响应式对象就是当页面点击修改此对象时,页面无需刷新而在页面上的其他地方有用到这个对象的地方会自动同步修改过来例如: <template><div class"container"><di…...
C++ Primer Plus 第6版 读书笔记(6) 第 6 章 分支语句和逻辑运算符
第 6 章 分支语句和逻辑运算符 C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言,是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的,通过大量短小精悍的程序详细而全面地阐述了 C的基本概念和技术,…...
Java Class 加密工具 ClassFinal
Jar包加密工具 ClassFinal介绍环境依赖使用说明下载加密命令行示例maven插件方式无密码模式机器绑定启动加密后的jar启动参数给密码不加密码参数直接启动1. 密码文件获取2. 交互输入参考资料介绍 ClassFinal 是一款 java class 文件安全加密工具,支持直接加密jar包…...
【蓝桥杯集训·每日一题】AcWing 3555. 二叉树
文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴最近公共祖先一、题目 1、原题链接 3555. 二叉树 2、题目描述 给定一个 n 个结点(编号 1∼n)构成的二叉树,其根结点为 1 号点。 进行 m…...
【JavaScript运行原理之V8引擎】V8引擎解析JavaScript代码原理
1. 编程语言的执行 高级语言最终都需要编译为低级语言才能被硬件执行,越高级的语言中间的转换时间越长,效率越低,越低级的语言执行素的越快,但是由于缺少高级语言便捷的语法特性所以很难编写代码。 2. 大杂烩JS 它是作者在1995…...
C++11:智能指针
文章目录1. 介绍1.1 动态内存与智能指针2. 使用2.1 创建2.2 使用3. 原理3.1 RAII3.2 像指针一样使用3.3 支持智能指针对象拷贝auto_ptrRAII4. 标准库中的智能指针4.1 unique_ptr模拟实现4.2 shared_ptr引用计数模拟实现定制删除器4.3 weak_ptrshared_ptr造成的循环引用问题与sh…...
ccc-pytorch-RNN(7)
文章目录一、RNN简介二、RNN关键结构三、RNN的训练方式四、时间序列预测五、梯度弥散和梯度爆炸问题一、RNN简介 RNN(Recurrent Neural Network)中文循环神经网络,用于处理序列数据。它与传统人工神经网络和卷积神经网络的输入和输出相互独立…...
docker安装(linux)
安装需要的软件包 yum install -y yum-utils 设置stable镜像仓库(使用阿里云镜像) yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 更新yum软件包索引 yum makecache fast 安装DOCKER 引擎 yum -y…...
【数据库概论】10.1 事务及其作用
事务是一系列的数据库操作,是数据库应用程序的基本逻辑单元 10.1 事务的基本概念 1.事务 事务是用户定义的一个数据库操作序列,是一个具有原子性的操作,不可再分,一个事务内的操作要么全做、要么都不做。一般来说,一…...
3个步骤轻松解决B站缓存视频无法播放问题:m4s格式转换完全指南
3个步骤轻松解决B站缓存视频无法播放问题:m4s格式转换完全指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾遇到这样的情…...
如何在Windows上轻松安装安卓应用?APK-Installer终极指南
如何在Windows上轻松安装安卓应用?APK-Installer终极指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer APK-Installer是一款专为Windows平台设计的安卓应…...
华硕笔记本性能控制终极方案:G-Helper完全指南
华硕笔记本性能控制终极方案:G-Helper完全指南 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Strix, Scar, and…...
2025届必备的六大降重复率工具解析与推荐
Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在人工智能技术以迅猛之势发展的当下,AI辅助毕业论文写作已然成为学术研究范畴里…...
基于R语言的自动数据收集:网络抓取和文本挖掘实用指南【1.2】
第2章 HTML在网络上浏览时,我们阅读和操作的几乎所有内容的背后都隐藏着一个标准:超文本标记语言(Hyper Text Markup Language,HTML)。不管是我们在维基百科上查找信息,在Google搜索站点,查看我…...
从‘听不清’到‘听得准’:深入FunASR的VAD模型,教你调参优化语音识别在嘈杂环境下的表现
从‘听不清’到‘听得准’:深入FunASR的VAD模型,教你调参优化语音识别在嘈杂环境下的表现 在工业巡检的轰鸣声中,工程师的语音指令频繁被机器噪音淹没;车载语音助手总在高速风噪下错误触发;户外采访录音里的对话被风声…...
ESP32-S3项目实战:从Github下载‘小智’代码到成功烧录的完整避坑记录
ESP32-S3项目实战:从Github下载‘小智’代码到成功烧录的完整避坑记录 在物联网开发领域,ESP32-S3凭借其出色的性能和丰富的外设接口,成为众多开发者的首选。本文将带你完整走一遍从Github获取"小智"开源项目到最终烧录运行的整个流…...
杰理之变声接口异常死机问题【篇】
void audio_voice_changer_mode_switch(u16 uuid, char *name, VOICE_CHANGER_MODE mode)...
微信小程序连接大模型:百川2-13B赋能智能客服小程序开发
微信小程序连接大模型:百川2-13B赋能智能客服小程序开发 最近在做一个宠物用品商城的小程序,老板提了个需求,想给用户加个24小时在线的智能客服,能回答一些关于宠物喂养、商品咨询的常见问题。一开始想用传统的规则问答ÿ…...
Realistic Vision V5.1 虚拟摄影棚结合传统软件:生成素材导入PS进行后期合成
Realistic Vision V5.1 虚拟摄影棚结合传统软件:生成素材导入PS进行后期合成 你有没有遇到过这样的场景:脑子里有一个绝佳的创意画面,但要么找不到合适的模特和场景,要么拍摄成本高得吓人,要么就是后期修图修到天昏地…...
