用于读取、写入和管理 Khronos Group gltf 和 gltf2 格式的 3D 对象的 Python 库。
项目描述
pygltflib
这是一个用于读取、写入和处理 GLTF 文件的库。它适用于 Python3.6 及更高版本。
它支持整个规范,包括材质和动画。主要特点是:
- GLB 和 GLTF 支持
- 缓冲区数据转换
- 扩展
- 所有属性都有类型提示
目录
- 快速开始
- 安装
- 我如何能...
- 创建一个空的 GLTF2 对象?
- 添加场景?
- 加载文件?
- 加载二进制 GLB 文件?
- 加载具有不寻常扩展名的二进制文件?
- 访问场景的第一个节点(构成场景的对象)?
- 创建网格?
- 将缓冲区转换为 GLB 二进制缓冲区?
- 将缓冲区转换为数据 uri(嵌入式)缓冲区?
- 将缓冲区转换为二进制文件(外部)缓冲区?
- 将glb转换为gltf文件?
- 访问扩展程序?
- 向属性添加自定义属性?
- 删除一个bufferView?
- 验证 gltf 对象?
- 将 GLTF 文件中的纹理图像转换为它们自己的 PNG 文件?
- 使用自定义文件名将纹理图像从 GLTF 文件转换为自己的 PNG 文件?
- 转换为文件时指定我的图像的路径?
- 将图像从 GLTF 文件导出到任何位置(即 GLTF 文件之外)?
- 将PNG文件作为纹理导入GLTF?
- 关于
- 更详细的用法
- 运行测试
快速开始
安装
pip install pygltflib
我如何能...
创建一个空的 GLTF2 对象?
from pygltflib import GLTF2
gltf = GLTF2()
添加场景?
from pygltflib import GLTF2, Scene
gltf = GLTF2()
scene = Scene()
gltf.scenes.append(scene) # scene available at gltf.scenes[0]
加载文件?
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
加载二进制 GLB 文件?
glb_filename = "glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb"
glb = GLTF2().load(glb_filename) # load method auto detects based on extension
加载具有不寻常扩展名的二进制文件?
glb = GLTF2().load_binary("BinaryGLTF.glk") # load_json and load_binary helper methods
访问场景的第一个节点(构成场景的对象)?
gltf = GLTF2().load("glTF-Sample-Models/2.0/Box/glTF/Box.gltf")
current_scene = gltf.scenes[gltf.scene]
node_index = current_scene.nodes[0] # scene.nodes is the indices, not the objects
box = gltf.nodes[node_index]
box.matrix # will output vertices for the box object
创建网格?
请参阅本文档后半部分中较长的示例
将缓冲区转换为 glb 二进制缓冲区?
from pygltflib import GLTF2, BufferFormat
gltf = GLTF2().load("glTF-Sample-Models/2.0/Box/glTF/Box.gltf")
gltf.convert_buffers(BufferFormat.BINARYBLOB) # convert buffers to GLB blob
将缓冲区转换为数据 uri(嵌入式)缓冲区?
gltf.convert_buffers(BufferFormat.DATAURI) # convert buffer URIs to data.
将缓冲区转换为二进制文件(外部)缓冲区?
gltf.convert_buffers(BufferFormat.BINFILE) # convert buffers to files
gltf.save("test.gltf") # all the buffers are saved in 0.bin, 1.bin, 2.bin.
将glb转换为gltf文件?
from pygltflib.utils import glb2gltf, gltf2glb
# convert glb to gltf
glb2gltf("glTF-Sample-Models/2.0/Box/glTF-Binary/Box.glb")
访问扩展程序?
# on a primitve
gltf.meshes[0].primitives[0].extensions['KHR_draco_mesh_compression']
# on a material
gltf.materials[0].extensions['ADOBE_materials_thin_transparency']
向属性添加自定义属性?
# Application-specific semantics must start with an underscore, e.g., _TEMPERATURE.
a = Attributes()
a._MYCUSTOMATTRIBUTE = 123
gltf.meshes[0].primitives[0].attributes._MYOTHERATTRIBUTE = 456
删除一个bufferView?
gltf.remove_bufferView(0) # this will update all accessors, images and sparse accessors to remove the first bufferView
验证 gltf 对象?
from pygltflib import GLTF2
from pygltflib.validator import validate, summary
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
validate(gltf) # will throw an error depending on the problem
summary(gltf) # will pretty print human readable summary of errors
# NOTE: Currently this experimental validator only validates a few rules about GLTF2 objects
将 GLTF 文件中的纹理图像转换为它们自己的 PNG 文件?
从 pygltflib 导入 GLTF2
从 pygltflib.utils 导入 ImageFormat
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
gltf 。convert_images ( ImageFormat.FILE ) gltf . _
_ 图像[ 0 ] 。uri # 现在将是 0.png 并且纹理图像将保存在 0.png
使用自定义文件名将纹理图像从 GLTF 文件转换为自己的 PNG 文件?
从 pygltflib 导入 GLTF2
从 pygltflib.utils 导入 ImageFormat
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
gltf 。图像[ 0 ] 。name = "cube.png" # 将数据 uri 保存到此文件(无论数据格式如何)
gltf 。convert_images ( ImageFormat.FILE ) gltf . _
_ 图像[0 ] 。uri # 现在将是 cube.png 并且纹理图像将保存在 cube.png
转换为文件时指定我的图像的路径?
默认情况下,pygltflib 将从与 GLTF 文件相同的位置加载图像。
它还会在转换图像缓冲区或数据 uri 时尝试将图像文件保存到该位置。
您可以使用“路径”参数覆盖加载/保存位置convert_images
从 pygltflib 导入 GLTF2
从 pygltflib.utils 导入 ImageFormat
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
gltf 。图像[ 0 ] 。name = "cube.png" # 将数据 uri 保存到此文件(无论数据格式如何)
gltf 。convert_images ( ImageFormat .文件, path = '/destination/')
gltf 。图像[ 0 ] 。uri # 现在将是 cube.png 并且纹理图像将保存在 /destination/cube.png
将图像从 GLTF 文件导出到任何位置(即 GLTF 文件之外)?
从 pygltflib 导入 GLTF2
从 pygltflib.utils 导入 ImageFormat
filename = "glTF-Sample-Models/2.0/AnimatedCube/glTF/AnimatedCube.gltf"
gltf = GLTF2().load(filename)
gltf 。export_image ( 0 , "output/cube.png" , override = True ) # 现在在 output/cube.png 处有一个图像文件
将PNG文件作为纹理导入GLTF?
from pygltflib import GLTF2
from pygltflib.utils import ImageFormat, Image
gltf = GLTF2()
image = Image()
image.uri = "myfile.png"
gltf.images.append(image)
gltf.convert_images(ImageFormat.DATAURI)
gltf.images[0].uri # will now be something like "data:image/png;base64,iVBORw0KGg..."
gltf.images[0].name # will be myfile.png
更详细的用法如下
关于
这是一个非官方的库,用于跟踪 GLTF2 的官方文件格式。
该库最初是为加载和保存简单的网格而构建的,但对整个规范(包括材质和动画)的支持非常好。支持 json (.gltf) 和二进制 (.glb) 文件格式,虽然 .glb 支持目前缺少一些功能。
它需要 python 3.6 及更高版本,因为它使用数据类并且所有属性都有类型提示。还有 f 弦,很多 f 弦。
查看下表,了解哪些示例模型可以验证。
问题?贡献?错误报告?在项目的 gitlab 页面上打开一个问题。我们很想听听您的用例,pygltflib以帮助推动路线图。
路线图
- 添加用于创建网格的辅助函数
- 测试覆盖率
- 在自定义 Attribute 属性上强制使用单个下划线
- 研究从扩展创建类
- 自动验证和目视检查
贡献者
- 卢克·米勒
- 塞巴斯蒂安·霍夫纳
- 阿瑟·范霍夫
- 阿里富拉·扬
- 丹尼尔·海恩
- 乔恩时间
- 劳里奥
- 彼得·苏特
- 弗雷德里克·德韦内
- 朱利安·斯特林
- 约翰内斯·德姆
- 玛格丽达席尔瓦
- 帕蒂潘·旺克劳
- 亚历山大·德鲁兹
- 阿德里亚诺·马丁斯
- 兹米特里·斯塔布罗斯基
- 伊蒂米尔
- 弗洛里安·布鲁吉瑟
- 凯文·克雷泽
谢谢
pyltflib为维多利亚电影公司支持的“The Beat: A Glam Noir Game”制作。
变更日志
-
1.15.3:
- 默认情况下使用 sort_keys 进行确定性输出 (Kevin Kreise)
-
1.15.2:
- buffer.uri 默认为 None (Kevin Kreise)
-
1.15.1:
- 数据类仅在 python 3.6.x 上需要安装(来自 Saeid Akbari 分支的樱桃挑选)
AlphaMode两年后不推荐使用(直接使用pygltflib.BLEND,pygltflib.MASK,pygltflib.OPAQUE常量)SparseAccessor两年后已弃用(使用AccessorSparseIndicesandAccessorSparseValues代替)MaterialTexture两年后已弃用(TextureInfo改为使用)deprecated从项目中删除需求
-
1.15.0:
- 显着提高
save_to_bytes性能(快 20 倍)(Florian Bruggisser)- 注意:底层二进制 blob 现在是可变的,而不是不可变的。
- 显着提高
-
1.14.7
- 添加
GLTF.get_data_from_buffer_uri辅助方法以简化对缓冲区数据的访问(请参阅 README.md 中的边界框示例)(el_flamenco)
- 添加
-
1.14.6
- 保存二进制 glb 文件时使用紧凑的 json (Laubeee)
-
1.14.5
- 取消引用符合标准的文件路径 (irtimir)
-
1.14.4
- 添加
GLTF.export_image将图像从 GLTF2 文件导出到任何位置的方法 (Khac Hoa Le) - 加载扩展时删除无关的打印消息 (Michael Daw)
- 添加
有关旧版本,请参阅 [CHANGELOG.md] ( https://gitlab.com/dodgyville/pygltflib/-/blob/master/CHANGELOG.md )
安装
pip install pygltflib
或者
py -m pip install pygltflib
资源
git clone https://gitlab.com/dodgyville/pygltflib
更详细的用法
注意:这些示例使用了Khronos 在以下位置提供的官方示例模型:
https://github.com/KhronosGroup/glTF-Sample-Models
一个简单的网格
from pygltflib import *
# create gltf objects for a scene with a primitive triangle with indexed geometry
gltf = GLTF2()
scene = Scene()
mesh = Mesh()
primitive = Primitive()
node = Node()
buffer = Buffer()
bufferView1 = BufferView()
bufferView2 = BufferView()
accessor1 = Accessor()
accessor2 = Accessor()
# add data
buffer.uri = "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA="
buffer.byteLength = 44
bufferView1.buffer = 0
bufferView1.byteOffset = 0
bufferView1.byteLength = 6
bufferView1.target = ELEMENT_ARRAY_BUFFER
bufferView2.buffer = 0
bufferView2.byteOffset = 8
bufferView2.byteLength = 36
bufferView2.target = ARRAY_BUFFER
accessor1.bufferView = 0
accessor1.byteOffset = 0
accessor1.componentType = UNSIGNED_SHORT
accessor1.count = 3
accessor1.type = SCALAR
accessor1.max = [2]
accessor1.min = [0]
accessor2.bufferView = 1
accessor2.byteOffset = 0
accessor2.componentType = FLOAT
accessor2.count = 3
accessor2.type = VEC3
accessor2.max = [1.0, 1.0, 0.0]
accessor2.min = [0.0, 0.0, 0.0]
primitive.attributes.POSITION = 1
node.mesh = 0
scene.nodes = [0]
# assemble into a gltf structure
gltf.scenes.append(scene)
gltf.meshes.append(mesh)
gltf.meshes[0].primitives.append(primitive)
gltf.nodes.append(node)
gltf.buffers.append(buffer)
gltf.bufferViews.append(bufferView1)
gltf.bufferViews.append(bufferView2)
gltf.accessors.append(accessor1)
gltf.accessors.append(accessor2)
# save to file
gltf.save("triangle.gltf")
从图元读取顶点数据和/或获取边界球体
import pathlib
import struct
import miniball
import numpy
from pygltflib import GLTF2
# load an example gltf file from the khronos collection
fname = pathlib.Path("glTF-Sample-Models/2.0/Box/glTF-Embedded/Box.gltf")
gltf = GLTF2().load(fname)
# get the first mesh in the current scene (in this example there is only one scene and one mesh)
mesh = gltf.meshes[gltf.scenes[gltf.scene].nodes[0]]
# get the vertices for each primitive in the mesh (in this example there is only one)
for primitive in mesh.primitives:
# get the binary data for this mesh primitive from the buffer
accessor = gltf.accessors[primitive.attributes.POSITION]
bufferView = gltf.bufferViews[accessor.bufferView]
buffer = gltf.buffers[bufferView.buffer]
data = gltf.get_data_from_buffer_uri(buffer.uri)
# pull each vertex from the binary buffer and convert it into a tuple of python floats
vertices = []
for i in range(accessor.count):
index = bufferView.byteOffset + accessor.byteOffset + i*12 # the location in the buffer of this vertex
d = data[index:index+12] # the vertex data
v = struct.unpack("<fff", d) # convert from base64 to three floats
vertices.append(v)
print(i, v)
# convert a numpy array for some manipulation
S = numpy.array(vertices)
# use a third party library to perform Ritter's algorithm for finding smallest bounding sphere
C, radius_squared = miniball.get_bounding_ball(S)
# output the results
print(f"center of bounding sphere: {C}\nradius squared of bounding sphere: {radius_squared}")
创建网格,转换为字节,再转换回网格
几何图形源自glTF 2.0 Box Sample,但点法线被删除,点在可能的地方被重用,以减小示例的大小。请注意,某些部分是硬编码的(用于数组编码和解码的类型和形状,没有字节填充)。
import numpy as np
import pygltflib
使用定义网格numpy:
points = np.array(
[
[-0.5, -0.5, 0.5],
[0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, 0.5],
[0.5, -0.5, -0.5],
[-0.5, -0.5, -0.5],
[0.5, 0.5, -0.5],
[-0.5, 0.5, -0.5],
],
dtype="float32",
)
triangles = np.array(
[
[0, 1, 2],
[3, 2, 1],
[1, 0, 4],
[5, 4, 0],
[3, 1, 6],
[4, 6, 1],
[2, 3, 7],
[6, 7, 3],
[0, 2, 5],
[7, 5, 2],
[5, 7, 4],
[6, 4, 7],
],
dtype="uint8",
)
GLTF2从点和三角形数组创建具有单个场景、单个节点和单个网格的glb 样式:
triangles_binary_blob = triangles.flatten().tobytes()
points_binary_blob = points.tobytes()
gltf = pygltflib.GLTF2(
scene=0,
scenes=[pygltflib.Scene(nodes=[0])],
nodes=[pygltflib.Node(mesh=0)],
meshes=[
pygltflib.Mesh(
primitives=[
pygltflib.Primitive(
attributes=pygltflib.Attributes(POSITION=1), indices=0
)
]
)
],
accessors=[
pygltflib.Accessor(
bufferView=0,
componentType=pygltflib.UNSIGNED_BYTE,
count=triangles.size,
type=pygltflib.SCALAR,
max=[int(triangles.max())],
min=[int(triangles.min())],
),
pygltflib.Accessor(
bufferView=1,
componentType=pygltflib.FLOAT,
count=len(points),
type=pygltflib.VEC3,
max=points.max(axis=0).tolist(),
min=points.min(axis=0).tolist(),
),
],
bufferViews=[
pygltflib.BufferView(
buffer=0,
byteLength=len(triangles_binary_blob),
target=pygltflib.ELEMENT_ARRAY_BUFFER,
),
pygltflib.BufferView(
buffer=0,
byteOffset=len(triangles_binary_blob),
byteLength=len(points_binary_blob),
target=pygltflib.ARRAY_BUFFER,
),
],
buffers=[
pygltflib.Buffer(
byteLength=len(triangles_binary_blob) + len(points_binary_blob)
)
],
)
gltf.set_binary_blob(triangles_binary_blob + points_binary_blob)
写入GLTF2字节:
glb = b"".join(gltf.save_to_bytes()) # save_to_bytes returns an array of the components of a glb
GLTF2从字节加载:
gltf = pygltflib.GLTF2.load_from_bytes(glb)
解码numpy数组GLTF2:
binary_blob = gltf 。binary_blob ()
triangles_accessor = gltf 。访问器[ gltf . 网格[ 0 ] 。原语[ 0 ] 。指数]
triangles_buffer_view = gltf 。bufferViews [ triangles_accessor 。bufferView ]
三角形 = np 。frombuffer (
binary_blob [
triangles_buffer_view . byteOffset
+ triangles_accessor . byteOffset : 三角形缓冲区视图。byteOffset
+ triangles_buffer_view 。byteLength
],
dtype = "uint8" ,
count = triangles_accessor 。数,
) 。重塑(( - 1 , 3 ))
points_accessor = gltf 。访问器[ gltf . 网格[ 0 ] 。原语[ 0 ] 。属性。位置]