Skip to main content

用于读取、写入和管理 Khronos Group gltf 和 gltf2 格式的 3D 对象的 Python 库。

项目描述

pygltflib

这是一个用于读取、写入和处理 GLTF 文件的库。它适用于 Python3.6 及更高版本。

它支持整个规范,包括材质和动画。主要特点是:

  • GLB 和 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 ] 属性位置]