使用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.事务 事务是用户定义的一个数据库操作序列,是一个具有原子性的操作,不可再分,一个事务内的操作要么全做、要么都不做。一般来说,一…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
