Model loading and texturing

This commit is contained in:
Dane Johnson
2021-01-18 18:25:47 -06:00
parent 66bf7776c7
commit 155b572aca
1283 changed files with 533814 additions and 42 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
pyassimp examples
=================
- `sample.py`: shows how to load a model with pyassimp, and display some statistics.
- `3d_viewer.py`: an OpenGL 3D viewer that requires shaders
- `fixed_pipeline_3d_viewer`: an OpenGL 3D viewer using the old fixed-pipeline.
Only for illustration example. Base new projects on `3d_viewer.py`.
Requirements for the 3D viewers:
- `pyopengl` (on Ubuntu/Debian, `sudo apt-get install python-opengl`)
- `pygame` (on Ubuntu/Debian, `sudo apt-get install python-pygame`)

View File

@@ -0,0 +1,372 @@
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
""" This program demonstrates the use of pyassimp to load and
render objects with OpenGL.
'c' cycles between cameras (if any available)
'q' to quit
This example mixes 'old' OpenGL fixed-function pipeline with
Vertex Buffer Objects.
Materials are supported but textures are currently ignored.
For a more advanced example (with shaders + keyboard/mouse
controls), check scripts/sdl_viewer.py
Author: Séverin Lemaignan, 2012
This sample is based on several sources, including:
- http://www.lighthouse3d.com/tutorials
- http://www.songho.ca/opengl/gl_transform.html
- http://code.activestate.com/recipes/325391/
- ASSIMP's C++ SimpleOpenGL viewer
"""
import sys
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
import logging
logger = logging.getLogger("pyassimp_opengl")
logging.basicConfig(level=logging.INFO)
import math
import numpy
import pyassimp
from pyassimp.postprocess import *
from pyassimp.helper import *
name = 'pyassimp OpenGL viewer'
height = 600
width = 900
class GLRenderer():
def __init__(self):
self.scene = None
self.using_fixed_cam = False
self.current_cam_index = 0
# store the global scene rotation
self.angle = 0.
# for FPS calculation
self.prev_time = 0
self.prev_fps_time = 0
self.frames = 0
def prepare_gl_buffers(self, mesh):
""" Creates 3 buffer objets for each mesh,
to store the vertices, the normals, and the faces
indices.
"""
mesh.gl = {}
# Fill the buffer for vertex positions
mesh.gl["vertices"] = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
glBufferData(GL_ARRAY_BUFFER,
mesh.vertices,
GL_STATIC_DRAW)
# Fill the buffer for normals
mesh.gl["normals"] = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
glBufferData(GL_ARRAY_BUFFER,
mesh.normals,
GL_STATIC_DRAW)
# Fill the buffer for vertex positions
mesh.gl["triangles"] = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
mesh.faces,
GL_STATIC_DRAW)
# Unbind buffers
glBindBuffer(GL_ARRAY_BUFFER,0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
def load_model(self, path, postprocess = None):
logger.info("Loading model:" + path + "...")
if postprocess:
self.scene = pyassimp.load(path, processing=postprocess)
else:
self.scene = pyassimp.load(path)
logger.info("Done.")
scene = self.scene
#log some statistics
logger.info(" meshes: %d" % len(scene.meshes))
logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
logger.info(" materials: %d" % len(scene.materials))
self.bb_min, self.bb_max = get_bounding_box(self.scene)
logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
for index, mesh in enumerate(scene.meshes):
self.prepare_gl_buffers(mesh)
# Finally release the model
pyassimp.release(scene)
def cycle_cameras(self):
self.current_cam_index
if not self.scene.cameras:
return None
self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
cam = self.scene.cameras[self.current_cam_index]
logger.info("Switched to camera " + str(cam))
return cam
def set_default_camera(self):
if not self.using_fixed_cam:
glLoadIdentity()
gluLookAt(0.,0.,3.,
0.,0.,-5.,
0.,1.,0.)
def set_camera(self, camera):
if not camera:
return
self.using_fixed_cam = True
znear = camera.clipplanenear
zfar = camera.clipplanefar
aspect = camera.aspect
fov = camera.horizontalfov
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
# Compute gl frustrum
tangent = math.tan(fov/2.)
h = znear * tangent
w = h * aspect
# params: left, right, bottom, top, near, far
glFrustum(-w, w, -h, h, znear, zfar)
# equivalent to:
#gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
cam = transform(camera.position, camera.transformation)
at = transform(camera.lookat, camera.transformation)
gluLookAt(cam[0], cam[2], -cam[1],
at[0], at[2], -at[1],
0, 1, 0)
def fit_scene(self, restore = False):
""" Compute a scale factor and a translation to fit and center
the whole geometry on the screen.
"""
x_max = self.bb_max[0] - self.bb_min[0]
y_max = self.bb_max[1] - self.bb_min[1]
tmp = max(x_max, y_max)
z_max = self.bb_max[2] - self.bb_min[2]
tmp = max(z_max, tmp)
if not restore:
tmp = 1. / tmp
logger.info("Scaling the scene by %.03f" % tmp)
glScalef(tmp, tmp, tmp)
# center the model
direction = -1 if not restore else 1
glTranslatef( direction * self.scene_center[0],
direction * self.scene_center[1],
direction * self.scene_center[2] )
return x_max, y_max, z_max
def apply_material(self, mat):
""" Apply an OpenGL, using one OpenGL display list per material to cache
the operation.
"""
if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
diffuse = numpy.array(mat.properties.get("diffuse", [0.8, 0.8, 0.8, 1.0]))
specular = numpy.array(mat.properties.get("specular", [0., 0., 0., 1.0]))
ambient = numpy.array(mat.properties.get("ambient", [0.2, 0.2, 0.2, 1.0]))
emissive = numpy.array(mat.properties.get("emissive", [0., 0., 0., 1.0]))
shininess = min(mat.properties.get("shininess", 1.0), 128)
wireframe = mat.properties.get("wireframe", 0)
twosided = mat.properties.get("twosided", 1)
setattr(mat, "gl_mat", glGenLists(1))
glNewList(mat.gl_mat, GL_COMPILE)
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
glEndList()
glCallList(mat.gl_mat)
def do_motion(self):
gl_time = glutGet(GLUT_ELAPSED_TIME)
self.angle = (gl_time - self.prev_time) * 0.1
self.prev_time = gl_time
# Compute FPS
self.frames += 1
if gl_time - self.prev_fps_time >= 1000:
current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
logger.info('%.0f fps' % current_fps)
self.frames = 0
self.prev_fps_time = gl_time
glutPostRedisplay()
def recursive_render(self, node):
""" Main recursive rendering method.
"""
# save model matrix and apply node transformation
glPushMatrix()
m = node.transformation.transpose() # OpenGL row major
glMultMatrixf(m)
for mesh in node.meshes:
self.apply_material(mesh.material)
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointer(3, GL_FLOAT, 0, None)
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
glEnableClientState(GL_NORMAL_ARRAY)
glNormalPointer(GL_FLOAT, 0, None)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_NORMAL_ARRAY)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
for child in node.children:
self.recursive_render(child)
glPopMatrix()
def display(self):
""" GLUT callback to redraw OpenGL surface
"""
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
glRotatef(self.angle,0.,1.,0.)
self.recursive_render(self.scene.rootnode)
glutSwapBuffers()
self.do_motion()
return
####################################################################
## GLUT keyboard and mouse callbacks ##
####################################################################
def onkeypress(self, key, x, y):
if key == 'c':
self.fit_scene(restore = True)
self.set_camera(self.cycle_cameras())
if key == 'q':
sys.exit(0)
def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
"""
:param autofit: if true, scale the scene to fit the whole geometry
in the viewport.
"""
# First initialize the openGL context
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
if not fullscreen:
glutInitWindowSize(width, height)
glutCreateWindow(name)
else:
glutGameModeString("1024x768")
if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
glutEnterGameMode()
else:
print("Fullscreen mode not available!")
sys.exit(1)
self.load_model(filename, postprocess = postprocess)
glClearColor(0.1,0.1,0.1,1.)
#glShadeModel(GL_SMOOTH)
glEnable(GL_LIGHTING)
glEnable(GL_CULL_FACE)
glEnable(GL_DEPTH_TEST)
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
glEnable(GL_NORMALIZE)
glEnable(GL_LIGHT0)
glutDisplayFunc(self.display)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(35.0, width/float(height) , 0.10, 100.0)
glMatrixMode(GL_MODELVIEW)
self.set_default_camera()
if autofit:
# scale the whole asset to fit into our view frustum·
self.fit_scene()
glPushMatrix()
glutKeyboardFunc(self.onkeypress)
glutIgnoreKeyRepeat(1)
glutMainLoop()
if __name__ == '__main__':
if not len(sys.argv) > 1:
print("Usage: " + __file__ + " <model>")
sys.exit(0)
glrender = GLRenderer()
glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
"""
This module uses the sample.py script to load all test models it finds.
Note: this is not an exhaustive test suite, it does not check the
data structures in detail. It just verifies whether basic
loading and querying of 3d models using pyassimp works.
"""
import os
import sys
# Make the development (ie. GIT repo) version of PyAssimp available for import.
sys.path.insert(0, '..')
import sample
from pyassimp import errors
# Paths to model files.
basepaths = [os.path.join('..', '..', '..', 'test', 'models'),
os.path.join('..', '..', '..', 'test', 'models-nonbsd')]
# Valid extensions for 3D model files.
extensions = ['.3ds', '.x', '.lwo', '.obj', '.md5mesh', '.dxf', '.ply', '.stl',
'.dae', '.md5anim', '.lws', '.irrmesh', '.nff', '.off', '.blend']
def run_tests():
ok, err = 0, 0
for path in basepaths:
print("Looking for models in %s..." % path)
for root, dirs, files in os.walk(path):
for afile in files:
base, ext = os.path.splitext(afile)
if ext in extensions:
try:
sample.main(os.path.join(root, afile))
ok += 1
except errors.AssimpError as error:
# Assimp error is fine; this is a controlled case.
print(error)
err += 1
except Exception:
print("Error encountered while loading <%s>"
% os.path.join(root, afile))
print('** Loaded %s models, got controlled errors for %s files'
% (ok, err))
if __name__ == '__main__':
run_tests()

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python
#-*- coding: UTF-8 -*-
"""
This module demonstrates the functionality of PyAssimp.
"""
import sys
import logging
logging.basicConfig(level=logging.INFO)
import pyassimp
import pyassimp.postprocess
def recur_node(node,level = 0):
print(" " + "\t" * level + "- " + str(node))
for child in node.children:
recur_node(child, level + 1)
def main(filename=None):
scene = pyassimp.load(filename, processing=pyassimp.postprocess.aiProcess_Triangulate)
#the model we load
print("MODEL:" + filename)
print
#write some statistics
print("SCENE:")
print(" meshes:" + str(len(scene.meshes)))
print(" materials:" + str(len(scene.materials)))
print(" textures:" + str(len(scene.textures)))
print
print("NODES:")
recur_node(scene.rootnode)
print
print("MESHES:")
for index, mesh in enumerate(scene.meshes):
print(" MESH" + str(index+1))
print(" material id:" + str(mesh.materialindex+1))
print(" vertices:" + str(len(mesh.vertices)))
print(" first 3 verts:\n" + str(mesh.vertices[:3]))
if mesh.normals.any():
print(" first 3 normals:\n" + str(mesh.normals[:3]))
else:
print(" no normals")
print(" colors:" + str(len(mesh.colors)))
tcs = mesh.texturecoords
if tcs.any():
for tc_index, tc in enumerate(tcs):
print(" texture-coords "+ str(tc_index) + ":" + str(len(tcs[tc_index])) + "first3:" + str(tcs[tc_index][:3]))
else:
print(" no texture coordinates")
print(" uv-component-count:" + str(len(mesh.numuvcomponents)))
print(" faces:" + str(len(mesh.faces)) + " -> first:\n" + str(mesh.faces[:3]))
print(" bones:" + str(len(mesh.bones)) + " -> first:" + str([str(b) for b in mesh.bones[:3]]))
print
print("MATERIALS:")
for index, material in enumerate(scene.materials):
print(" MATERIAL (id:" + str(index+1) + ")")
for key, value in material.properties.items():
print(" %s: %s" % (key, value))
print
print("TEXTURES:")
for index, texture in enumerate(scene.textures):
print(" TEXTURE" + str(index+1))
print(" width:" + str(texture.width))
print(" height:" + str(texture.height))
print(" hint:" + str(texture.achformathint))
print(" data (size):" + str(len(texture.data)))
# Finally release the model
pyassimp.release(scene)
def usage():
print("Usage: sample.py <3d model>")
if __name__ == "__main__":
if len(sys.argv) != 2:
usage()
else:
main(sys.argv[1])

File diff suppressed because it is too large Load Diff