Skip to content

Commit 978dc56

Browse files
committed
Initial commit. Support basic glTF structure and unrolled view
0 parents  commit 978dc56

23 files changed

+625
-0
lines changed

README.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# pygltftoolkit
2+
3+
## Table of Contents
4+
5+
- [About](#about)
6+
- [Install](#install)
7+
- [Usage](#usage)
8+
- [Limitations and Development](#dev)
9+
10+
## About <a name = "about"></a>
11+
12+
pygltftoolkit aims to provide high-level API for loading and processing glTF 2.0 files.
13+
14+
### Install <a name = "install"></a>
15+
16+
There are just a couple of dependencies to install:
17+
18+
```
19+
python>=3.8
20+
numpy
21+
pygltflib
22+
PIL
23+
```
24+
25+
## Usage <a name = "usage"></a>
26+
27+
Loading a file:
28+
29+
```
30+
import pygltgtoolkit as pygltk
31+
gltf = pygltk.load("/path/to/file.glb")
32+
```
33+
34+
pygltftoolkit provides an "unrolled" view of the scene in addition to basic graph structure
35+
36+
```
37+
vertices = gltf.vertices
38+
faces = gltf.faces
39+
triangles = vertices[faces]
40+
```
41+
42+
In order to preserve dependencies on graph structure when using "unrolled" view, a number of maps are stored. Note that these maps correspond to the nodes that contain meshes and meshes directly (that is parents are not accounted for). Maps exist for nodes, meshes and primitives (however maps are local for primitives as they don't have global id).
43+
44+
```
45+
# Obtaining nodes containing meshes
46+
nodes = np.unique(gltf.node_map)
47+
48+
# Working withs the corresponding triangles
49+
for node_id in nodes:
50+
node_mask = gltf.node_map == node_id
51+
node_triangles = vertices[faces[node_mask]]
52+
# Process node_triangles ...
53+
```
54+
55+
## Limitations and Development <a name = "dev"></a>
56+
57+
There is a number of limitations that may or may not be lifted in the future:
58+
* Only a single scene is supported. Multiple scenes are used rarely and there was even a proposal to remove the support for multiple scenes in a single file. See [discussion](https://github.com/KhronosGroup/glTF/issues/1542).
59+
* Skin, animation and sampler part of the glTF graph are not supported (and are not planned currently).
60+
* Currently only the following attributes are extracted from primitives, if present: ["COLOR_0", "NORMAL", "POSITION", "TEXCOORD_0"].
61+
* Limited number of texture types supported (excluding normalTexture, occlusionTexture, emissiveTexture)
62+
63+
Current TODOs (approximately in order of priority):
64+
* Support STK annotations
65+
* Point cloud sampling (starting with CPU, CUDA implementation may be added in the future) and exporting to hdf5
66+
* Mesh modification and exporting
67+
* KDTree and KNN
68+
* Support transformations (node transformations defined in glTF are already supported)
69+
* Integrate renderer

__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .core import *

__pycache__/__init__.cpython-39.pyc

202 Bytes
Binary file not shown.

__pycache__/core.cpython-39.pyc

1.53 KB
Binary file not shown.

core.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import tempfile
2+
3+
import pygltflib
4+
5+
from .gltfScene import gltfScene
6+
7+
8+
def load(
9+
path: str,
10+
stk_segmentation: str = None,
11+
stk_articulation: str = None,
12+
stk_precomputed_segmentation: str = None
13+
) -> gltfScene:
14+
"""
15+
Load the glTF 2.0 file. Allows to load the segmentation and articulation annotations as produced by the STK.
16+
Args:
17+
path: string, the path to the glTF 2.0 file
18+
stk_segmentation: string, the path to the segmentation annotations produced by the STK. Defaults to None.
19+
stk_articulation: string, the path to the articulation annotations produced by the STK. Defaults to None.
20+
Returns:
21+
scene: pygltftoolkit.gltfScene object, the glTF 2.0 scene.
22+
"""
23+
scene = pygltflib.GLTF2().load(path)
24+
25+
# We support only a single scene in glTF file.
26+
# Multiple scenes are rarely used and it was even proposed to remove them from the glTF 2.0 specification.
27+
# See https://github.com/KhronosGroup/glTF/issues/1542
28+
if len(scene.scenes) > 1:
29+
raise ValueError("Only one scene in the glTF file is supported.")
30+
31+
# Please use .glb, we will handle .gltf with an ugly trick
32+
if path.endswith(".gltf"):
33+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
34+
scene.save_binary(temp_file.name)
35+
temp_file_path = temp_file.name
36+
scene = pygltflib.GLTF2().load(temp_file_path)
37+
38+
gltf = gltfScene(scene)
39+
40+
# Load the segmentation and articulation annotations
41+
if stk_segmentation is not None:
42+
# No support yet
43+
gltf.load_stk_segmentation(stk_segmentation)
44+
if stk_articulation is not None:
45+
if stk_segmentation is None:
46+
raise ValueError("Please provide the segmentation annotations as well.")
47+
# No support yet
48+
gltf.load_stk_articulation(stk_articulation)
49+
if stk_precomputed_segmentation is not None:
50+
# No support yet
51+
gltf.load_stk_precomputed_segmentation(stk_precomputed_segmentation)
52+
53+
return gltf

gltfScene/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .gltfScene import gltfScene
221 Bytes
Binary file not shown.
9.68 KB
Binary file not shown.

gltfScene/components/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .mesh import Mesh
2+
from .node import Node
3+
from .primitive import Primitive
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

gltfScene/components/mesh.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
class Mesh():
3+
def __init__(self, id: int, primitives: list, name: str = None):
4+
"""
5+
Initialize the Mesh object
6+
Args:
7+
id: int, the id of the mesh
8+
name: str, the name of the mesh
9+
primitives: list, the primitives of the mesh
10+
Properties:
11+
id: int, the id of the mesh
12+
name: str, the name of the mesh
13+
primitives: list, the primitives of the mesh
14+
"""
15+
self.id: int = id
16+
17+
self.name: str = None
18+
if name is not None:
19+
self.name: str = name
20+
21+
self.primitives: list = primitives

gltfScene/components/node.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import numpy as np
2+
3+
from .mesh import Mesh
4+
5+
6+
class Node():
7+
def __init__(self, id: int,
8+
children: list,
9+
mesh: Mesh,
10+
matrix: list = [1, 0, 0, 0, 1, 0, 0, 0, 1],
11+
translation: list = [0, 0, 0],
12+
rotation: list = [0, 0, 0, 1],
13+
scale: list = [1, 1, 1],
14+
name: str = None):
15+
"""
16+
Initialize the Node object, initialize next components recursively
17+
Args:
18+
id: int, the id of the node
19+
children: list, the children of the node
20+
mesh: pygltflib.gltfScene.components.Mesh, Mesh object referenced by the current node
21+
matrix: list or np.ndarray(np.float32), the matrix of the node
22+
translation: list or np.ndarray(np.float32), the translation of the node
23+
rotation: list or np.ndarray(np.float32), the rotation of the node
24+
scale: list or np.ndarray(np.float32), the scale of the node
25+
name: str, the name of the node
26+
Properties:
27+
id: int, the id of the node
28+
children: list, the children of the node
29+
mesh: pygltflib.gltfScene.components.Mesh, Mesh object referenced by the current node
30+
matrix: np.ndarray(np.float32), the matrix of the node
31+
translation: np.ndarray(np.float32), the translation of the node
32+
rotation: np.ndarray(np.float32), the rotation of the node
33+
scale: np.ndarray(np.float32), the scale of the node
34+
cameras: list, the cameras of the node
35+
name: str, the name of the node
36+
"""
37+
self.id: int = id
38+
self.children: list = children
39+
self.mesh = None
40+
if mesh is not None:
41+
self.mesh = mesh
42+
43+
if isinstance(matrix, list):
44+
matrix = np.asarray(matrix, dtype=np.float32)
45+
elif isinstance(matrix, np.ndarray) and matrix.dtype != np.float32:
46+
raise ValueError("Matrix must be of type np.float32 or list.")
47+
self.matrix: np.ndarray = matrix
48+
if isinstance(translation, list):
49+
translation = np.asarray(translation, dtype=np.float32)
50+
elif isinstance(translation, np.ndarray) and translation.dtype != np.float32:
51+
raise ValueError("Translation must be of type np.float32 or list.")
52+
self.translation: np.ndarray = translation
53+
if isinstance(rotation, list):
54+
rotation = np.asarray(rotation, dtype=np.float32)
55+
elif isinstance(rotation, np.ndarray) and rotation.dtype != np.float32:
56+
raise ValueError("Rotation must be of type np.float32 or list.")
57+
self.rotation: np.ndarray = rotation
58+
if isinstance(scale, list):
59+
scale = np.asarray(scale, dtype=np.float32)
60+
elif isinstance(scale, np.ndarray) and scale.dtype != np.float32:
61+
raise ValueError("Scale must be of type np.float32 or list.")
62+
self.scale: np.ndarray = scale
63+
64+
self.cameras = [] # TODO initialize and support cameras
65+
66+
self.name: str = None
67+
if name is not None:
68+
self.name = name

gltfScene/components/primitive.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import numpy as np
2+
3+
from .visual.material import Material
4+
5+
6+
class Primitive():
7+
def __init__(self, attributes: dict,
8+
vertex_colors: np.ndarray = None,
9+
material: Material = None):
10+
"""
11+
Initialize the Primitive object
12+
Args:
13+
attributes: dict, the attributes of the primitive
14+
material: pygltftoolkit.gltfScene.components.visual.Material, the material of the primitive
15+
Properties:
16+
attributes: dict, the attributes of the primitive
17+
material: pygltftoolkit.gltfScene.components.visual.Material, the material of the primitive
18+
"""
19+
self.attributes: dict = attributes
20+
self.has_normals: bool = False
21+
if "NORMAL" in self.attributes:
22+
self.has_normals = True
23+
self.has_texture: bool = False
24+
if "TEXCOORD_0" in self.attributes:
25+
self.has_texture = True
26+
self.has_colors: bool = False
27+
if "COLOR_0" in self.attributes:
28+
self.has_colors = True
29+
self.has_baseColorFactor: bool = False
30+
if not self.has_texture and not self.has_colors:
31+
self.has_baseColorFactor = True
32+
if vertex_colors is not None:
33+
self.has_colors = True
34+
self.material: Material = material
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .image import TextureImage
2+
from .material import PBRMaterial, TextureMaterial
Binary file not shown.
Binary file not shown.
Binary file not shown.

gltfScene/components/visual/image.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from PIL import Image
2+
3+
4+
class TextureImage():
5+
def __init__(self, image: Image, mimeType: str, name: str = None) -> None:
6+
"""
7+
Initialize the TextureImage object
8+
Args:
9+
image: PIL.Image, the image
10+
mimeType: str, the mimeType of the image. Either of ["image/jpeg", "image/png"]
11+
name: str, the name of the image
12+
Properties:
13+
image: PIL.Image, the image
14+
name: str, the name of the image
15+
mimeType: str, the mimeType of the image. Either of ["image/jpeg", "image/png"]
16+
width: int, the width of the image
17+
height: int, the height of the image
18+
"""
19+
self.image: Image = image
20+
self.name: str = name
21+
self.mimeType: str = mimeType
22+
self.width: int = image.width
23+
self.height: int = image.height
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import numpy as np
2+
3+
from .image import TextureImage
4+
5+
6+
class Material():
7+
"""
8+
Abstract class for Material objects
9+
"""
10+
def __init__(self) -> None:
11+
pass
12+
13+
14+
class TextureMaterial(Material):
15+
def __init__(self, uv: np.ndarray, image: TextureImage) -> None:
16+
"""
17+
Initialize the TextureMaterial object
18+
Args:
19+
texture: str, the path to the texture
20+
Properties:
21+
texture: str, the path to the texture
22+
"""
23+
self.texture: str = image
24+
self.uv: np.ndarray = uv
25+
26+
27+
class PBRMaterial(Material):
28+
def __init__(self, baseColorFactor: np.ndarray, metallicFactor: float, roughnessFactor: float) -> None:
29+
"""
30+
Initialize the PBRMaterial object
31+
Args:
32+
baseColorFactor: np.ndarray(np.float32), the base color factor
33+
metallicFactor: float, the metallic factor
34+
roughnessFactor: float, the roughness factor
35+
Properties:
36+
baseColorFactor: np.ndarray(np.float32), the base color factor
37+
metallicFactor: float, the metallic factor
38+
roughnessFactor: float, the roughness factor
39+
"""
40+
self.baseColorFactor: np.ndarray = baseColorFactor
41+
self.metallicFactor: float = metallicFactor
42+
self.roughnessFactor: float = roughnessFactor

0 commit comments

Comments
 (0)