Examples
Examples for the Python bindings.
The magnum-examples repository contains a few
examples in pure Python in the src/python/
directory. These currently
mirror the C++ examples and show how to achieve the same
in Python.
Your First Triangle
Basic rendering with builtin shaders. Fully equivalent to the C++ version.
import array from magnum import * from magnum import gl, shaders from magnum.platform.sdl2 import Application class TriangleExample(Application): def __init__(self): configuration = self.Configuration() configuration.title = "Magnum Python Triangle Example" Application.__init__(self, configuration) buffer = gl.Buffer() buffer.set_data(array.array('f', [ -0.5, -0.5, 1.0, 0.0, 0.0, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 0.0, 1.0 ])) self._mesh = gl.Mesh() self._mesh.count = 3 self._mesh.add_vertex_buffer(buffer, 0, 5*4, shaders.VertexColorGL2D.POSITION) self._mesh.add_vertex_buffer(buffer, 2*4, 5*4, shaders.VertexColorGL2D.COLOR3) self._shader = shaders.VertexColorGL2D() def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR) self._shader.draw(self._mesh) self.swap_buffers() exit(TriangleExample().exec())
Textured Quad
Importing image data, texturing and custom shaders. Fully equivalent to the C++ version.
import os import array from magnum import * from magnum import gl, shaders, trade from magnum.platform.sdl2 import Application class TexturedQuadShader(gl.AbstractShaderProgram): POSITION = gl.Attribute( gl.Attribute.Kind.GENERIC, 0, gl.Attribute.Components.TWO, gl.Attribute.DataType.FLOAT) TEXTURE_COORDINATES = gl.Attribute( gl.Attribute.Kind.GENERIC, 1, gl.Attribute.Components.TWO, gl.Attribute.DataType.FLOAT) _texture_unit = 0 def __init__(self): super().__init__() vert = gl.Shader(gl.Version.GL330, gl.Shader.Type.VERTEX) vert.add_source(""" layout(location = 0) in vec4 position; layout(location = 1) in vec2 textureCoordinates; out vec2 interpolatedTextureCoordinates; void main() { interpolatedTextureCoordinates = textureCoordinates; gl_Position = position; } """.lstrip()) vert.compile() self.attach_shader(vert) frag = gl.Shader(gl.Version.GL330, gl.Shader.Type.FRAGMENT) frag.add_source(""" uniform vec3 color = vec3(1.0, 1.0, 1.0); uniform sampler2D textureData; in vec2 interpolatedTextureCoordinates; out vec4 fragmentColor; void main() { fragmentColor.rgb = color*texture(textureData, interpolatedTextureCoordinates).rgb; fragmentColor.a = 1.0; } """.lstrip()) frag.compile() self.attach_shader(frag) self.link() self._color_uniform = self.uniform_location('color') self.set_uniform(self.uniform_location('textureData'), self._texture_unit) def color(self, color: Color3): self.set_uniform(self._color_uniform, color) color = property(None, color) def bind_texture(self, texture: gl.Texture2D): texture.bind(self._texture_unit) class TexturedQuadExample(Application): def __init__(self): configuration = self.Configuration() configuration.title = "Magnum Python Textured Quad Example" Application.__init__(self, configuration) vertices = gl.Buffer() vertices.set_data(array.array('f', [ 0.5, -0.5, 1.0, 0.0, 0.5, 0.5, 1.0, 1.0, -0.5, -0.5, 0.0, 0.0, -0.5, 0.5, 0.0, 1.0 ])) indices = gl.Buffer() indices.set_data(array.array('I', [ 0, 1, 2, 2, 1, 3 ])) self._mesh = gl.Mesh() self._mesh.count = 6 self._mesh.add_vertex_buffer(vertices, 0, 4*4, TexturedQuadShader.POSITION) self._mesh.add_vertex_buffer(vertices, 2*4, 4*4, TexturedQuadShader.TEXTURE_COORDINATES) self._mesh.set_index_buffer(indices, 0, gl.MeshIndexType.UNSIGNED_INT) importer = trade.ImporterManager().load_and_instantiate('TgaImporter') importer.open_file(os.path.join(os.path.dirname(__file__), '../texturedquad/stone.tga')) image = importer.image2d(0) self._texture = gl.Texture2D() self._texture.wrapping = gl.SamplerWrapping.CLAMP_TO_EDGE self._texture.minification_filter = gl.SamplerFilter.LINEAR self._texture.magnification_filter = gl.SamplerFilter.LINEAR self._texture.set_storage(1, gl.TextureFormat.RGB8, image.size) self._texture.set_sub_image(0, Vector2i(), image) # or self._shader = shaders.FlatGL2D(shaders.FlatGL2D.Flags.TEXTURED) self._shader = TexturedQuadShader() def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR) self._shader.color = (1.0, 0.7, 0.7) self._shader.bind_texture(self._texture) self._shader.draw(self._mesh) self.swap_buffers() exit(TexturedQuadExample().exec())
Primitives
Importing mesh data, 3D transformations and input handling. Equivalent to the C++ version except that it uses meshtools.compile() instead of interleaving the data by hand — the low-level MeshTools APIs are not exposed yet.
from magnum import * from magnum import gl, meshtools, primitives, shaders from magnum.platform.sdl2 import Application class PrimitivesExample(Application): def __init__(self): configuration = self.Configuration() configuration.title = "Magnum Python Primitives Example" Application.__init__(self, configuration) gl.Renderer.enable(gl.Renderer.Feature.DEPTH_TEST) gl.Renderer.enable(gl.Renderer.Feature.FACE_CULLING) self._mesh = meshtools.compile(primitives.cube_solid()) self._shader = shaders.PhongGL() self._transformation = ( Matrix4.rotation_x(Deg(30.0))@ Matrix4.rotation_y(Deg(40.0))) self._projection = ( Matrix4.perspective_projection( fov=Deg(35.0), aspect_ratio=1.33333, near=0.01, far=100.0)@ Matrix4.translation(Vector3.z_axis(-10.0))) self._color = Color3.from_hsv(Deg(35.0), 1.0, 1.0) def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR| gl.FramebufferClear.DEPTH) self._shader.light_positions = [(7.0, 5.0, 2.5, 0.0)] self._shader.light_colors = [Color3(1.0)] self._shader.diffuse_color = self._color self._shader.ambient_color = Color3.from_hsv(self._color.hue(), 1.0, 0.3) self._shader.transformation_matrix = self._transformation self._shader.normal_matrix = self._transformation.rotation_scaling() self._shader.projection_matrix = self._projection self._shader.draw(self._mesh) self.swap_buffers() def pointer_release_event(self, event: Application.PointerEvent): if not event.is_primary or \ not (event.pointer & (self.Pointer.MOUSE_LEFT|self.Pointer.FINGER)): return self._color = Color3.from_hsv(self._color.hue() + Deg(50.0), 1.0, 1.0) self.redraw() def pointer_move_event(self, event: Application.PointerMoveEvent): if not event.is_primary or \ not (event.pointers & (self.Pointer.MOUSE_LEFT|self.Pointer.FINGER)): return delta = 1.0*event.relative_position/Vector2(self.window_size) self._transformation = ( Matrix4.rotation_x(Rad(delta.y))@ self._transformation@ Matrix4.rotation_y(Rad(delta.x))) self.redraw() exit(PrimitivesExample().exec())
Primitives, using a scene graph
Same behavior as above, but this time handling transformations using the scene graph. Compared to doing the same in C++ there’s less worrying about data ownership, as the reference counting handles most of it.
from magnum import * from magnum import gl, meshtools, primitives, scenegraph, shaders from magnum.platform.sdl2 import Application from magnum.scenegraph.matrix import Scene3D, Object3D class CubeDrawable(scenegraph.Drawable3D): def __init__(self, object: Object3D, drawables: scenegraph.DrawableGroup3D, mesh: gl.Mesh, shader: shaders.PhongGL, color: Color4): scenegraph.Drawable3D.__init__(self, object, drawables) self._mesh = mesh self._shader = shader self.color = color # Settable from outside def draw(self, transformation_matrix: Matrix4, camera: scenegraph.Camera3D): self._shader.light_positions = [ Vector4(camera.camera_matrix.transform_point((-3.0, 5.0, 10.0)), 0.0) ] self._shader.light_colors = [Color3(1.0)] self._shader.diffuse_color = self.color self._shader.ambient_color = Color3.from_hsv(self.color.hue(), 1.0, 0.3) self._shader.transformation_matrix = transformation_matrix self._shader.normal_matrix = transformation_matrix.rotation_scaling() self._shader.projection_matrix = camera.projection_matrix self._shader.draw(self._mesh) class PrimitivesSceneGraphExample(Application): def __init__(self): configuration = self.Configuration() configuration.title = "Magnum Python Primitives + SceneGraph Example" Application.__init__(self, configuration) gl.Renderer.enable(gl.Renderer.Feature.DEPTH_TEST) gl.Renderer.enable(gl.Renderer.Feature.FACE_CULLING) # Scene and drawables self._scene = Scene3D() self._drawables = scenegraph.DrawableGroup3D() # Camera setup camera_object = Object3D(parent=self._scene) camera_object.translate(Vector3.z_axis(10.0)) self._camera = scenegraph.Camera3D(camera_object) self._camera.projection_matrix = Matrix4.perspective_projection( fov=Deg(35.0), aspect_ratio=1.33333, near=0.01, far=100.0) # Cube object and drawable self._cube = Object3D(parent=self._scene) self._cube.rotate_y(Deg(40.0)) self._cube.rotate_x(Deg(30.0)) self._cube_drawable = CubeDrawable(self._cube, self._drawables, meshtools.compile(primitives.cube_solid()), shaders.PhongGL(), Color3.from_hsv(Deg(35.0), 1.0, 1.0)) def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR| gl.FramebufferClear.DEPTH) self._camera.draw(self._drawables) self.swap_buffers() def pointer_release_event(self, event: Application.PointerEvent): if not event.is_primary or \ not (event.pointer & (self.Pointer.MOUSE_LEFT|self.Pointer.FINGER)): return self._cube_drawable.color = Color3.from_hsv( self._cube_drawable.color.hue() + Deg(50.0), 1.0, 1.0) self.redraw() def pointer_move_event(self, event: Application.PointerMoveEvent): if not event.is_primary or \ not (event.pointers & (self.Pointer.MOUSE_LEFT|self.Pointer.FINGER)): return delta = 1.0*event.relative_position/Vector2(self.window_size) self._cube.rotate_y_local(Rad(delta.x)) self._cube.rotate_x(Rad(delta.y)) self.redraw() exit(PrimitivesSceneGraphExample().exec())
Model viewer
Scene graph, resource management and model importing. Goal is to be equivalent to the C++ version except that right now it imports the meshes directly by name as the full scene hierarchy import APIs from Trade::AbstractImporter are not exposed yet.
import os from magnum import * from magnum import gl, meshtools, scenegraph, shaders, trade from magnum.platform.sdl2 import Application from magnum.scenegraph.matrix import Scene3D, Object3D class ColoredDrawable(scenegraph.Drawable3D): def __init__(self, object: Object3D, drawables: scenegraph.DrawableGroup3D, mesh: gl.Mesh, shader: shaders.PhongGL, color: Color4): scenegraph.Drawable3D.__init__(self, object, drawables) self._mesh = mesh self._shader = shader self._color = color def draw(self, transformation_matrix: Matrix4, camera: scenegraph.Camera3D): self._shader.light_positions = [ Vector4(camera.camera_matrix.transform_point((-3.0, 10.0, 10.0)), 0.0) ] self._shader.diffuse_color = self._color self._shader.transformation_matrix = transformation_matrix self._shader.normal_matrix = transformation_matrix.rotation_scaling() self._shader.projection_matrix = camera.projection_matrix self._shader.draw(self._mesh) class ViewerExample(Application): def __init__(self): configuration = self.Configuration() configuration.title = "Magnum Python Viewer Example" Application.__init__(self, configuration) gl.Renderer.enable(gl.Renderer.Feature.DEPTH_TEST) gl.Renderer.enable(gl.Renderer.Feature.FACE_CULLING) # Scene and drawables self._scene = Scene3D() self._drawables = scenegraph.DrawableGroup3D() # Every scene needs a camera camera_object = Object3D(parent=self._scene) camera_object.translate(Vector3.z_axis(5.0)) self._camera = scenegraph.Camera3D(camera_object) self._camera.aspect_ratio_policy = scenegraph.AspectRatioPolicy.EXTEND self._camera.projection_matrix = Matrix4.perspective_projection( fov=Deg(35.0), aspect_ratio=1.0, near=0.01, far=100.0) self._camera.viewport = self.framebuffer_size # Base object, parent of all (for easy manipulation) self._manipulator = Object3D(parent=self._scene) # Setup renderer and shader defaults gl.Renderer.enable(gl.Renderer.Feature.DEPTH_TEST) gl.Renderer.enable(gl.Renderer.Feature.FACE_CULLING) colored_shader = shaders.PhongGL() colored_shader.ambient_color = Color3(0.06667) colored_shader.shininess = 80.0 # Import Suzanne head and eyes (yes, sorry, it's all hardcoded here) importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), '../viewer/scene.glb')) suzanne_object = Object3D(parent=self._manipulator) suzanne_mesh = meshtools.compile( importer.mesh(importer.mesh_for_name('Suzanne'))) suzanne_eyes_mesh = meshtools.compile( importer.mesh(importer.mesh_for_name('Eyes'))) self._suzanne = ColoredDrawable(suzanne_object, self._drawables, suzanne_mesh, colored_shader, Color3(0.15, 0.49, 1.0)) self._suzanne_eyes = ColoredDrawable(suzanne_object, self._drawables, suzanne_eyes_mesh, colored_shader, Color3(0.95)) def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR| gl.FramebufferClear.DEPTH) self._camera.draw(self._drawables) self.swap_buffers() def pointer_move_event(self, event: Application.PointerMoveEvent): if not event.is_primary or \ not (event.pointers & (self.Pointer.MOUSE_LEFT|self.Pointer.FINGER)): return delta = 1.0*event.relative_position/Vector2(self.window_size) self._manipulator.rotate_y_local(Rad(delta.x)) self._manipulator.rotate_x(Rad(delta.y)) self.redraw() def scroll_event(self, event: Application.ScrollEvent): if not event.offset.y: return # Distance to origin distance = self._camera.object.transformation.translation.z # Move 15% of the distance back or forward self._camera.object.translate(Vector3.z_axis( distance*(1.0 - (1.0/0.85 if event.offset.y > 0 else 0.85)))) self.redraw() exit(ViewerExample().exec())
Text
Distance-field text rendering. Fully equivalent to the C++ version.
import os from magnum import * from magnum import gl, shaders, text from magnum.platform.sdl2 import Application class TextExample(Application): def __init__(self): configuration = self.Configuration() configuration.window_flags |= self.Configuration.WindowFlag.RESIZABLE configuration.title = "Magnum Python Text Example" Application.__init__(self, configuration) # Load a TrueTypeFont plugin and open the font self._font = text.FontManager().load_and_instantiate('TrueTypeFont') self._font.open_file(os.path.join(os.path.dirname(__file__), '../text/SourceSansPro-Regular.ttf'), 180.0) # Glyphs we need to render everything self._cache = text.DistanceFieldGlyphCacheGL(Vector2i(2048), Vector2i(512), 22) self._font.fill_glyph_cache(self._cache, "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789:-+,.!°ěäПривітСΓειασουκόμ ") # Text that rotates using mouse wheel. Size relative to the window size # (1/10 of it) -- if you resize the window, it gets bigger self._rotating_text = text.Renderer2D(self._font, self._cache, 0.2, text.Alignment.MIDDLE_CENTER) self._rotating_text.reserve(128) self._rotating_text.render( "Hello, world!\n" "Ahoj, světe!\n" "Привіт Світ!\n" "Γεια σου κόσμε!\n" "Hej Världen!") # Dynamically updated text that shows rotation/zoom of the other. Size # in points that stays the same if you resize the window. Aligned so # top right of the bounding box is at mesh origin, and then transformed # so the origin is at the top right corner of the window. self._dynamic_text = text.Renderer2D(self._font, self._cache, 32.0, text.Alignment.TOP_RIGHT) self._dynamic_text.reserve(40) self._transformation_projection_dynamic_text =\ Matrix3.projection(Vector2(self.window_size))@\ Matrix3.translation(Vector2(self.window_size)*0.5) self._transformation_rotating_text = Matrix3.rotation(Deg(-10.0)) self._projection_rotating_text = Matrix3.projection( Vector2.x_scale(Vector2(self.window_size).aspect_ratio())) self._shader = shaders.DistanceFieldVectorGL2D() gl.Renderer.enable(gl.Renderer.Feature.BLENDING) gl.Renderer.set_blend_function(gl.Renderer.BlendFunction.ONE, gl.Renderer.BlendFunction.ONE_MINUS_SOURCE_ALPHA) gl.Renderer.set_blend_equation(gl.Renderer.BlendEquation.ADD, gl.Renderer.BlendEquation.ADD) self.update_text() def draw_event(self): gl.default_framebuffer.clear(gl.FramebufferClear.COLOR| gl.FramebufferClear.DEPTH) self._shader.bind_vector_texture(self._cache.texture) self._shader.transformation_projection_matrix = \ self._projection_rotating_text @ self._transformation_rotating_text self._shader.color = [0.184, 0.514, 0.8] self._shader.outline_color = [0.863, 0.863, 0.863] self._shader.outline_range = (0.45, 0.35) self._shader.smoothness = 0.025/\ self._transformation_rotating_text.uniform_scaling() self._shader.draw(self._rotating_text.mesh) self._shader.transformation_projection_matrix = \ self._transformation_projection_dynamic_text self._shader.color = [1.0, 1.0, 1.0] self._shader.outline_range = (0.5, 1.0) self._shader.smoothness = 0.075 self._shader.draw(self._dynamic_text.mesh) self.swap_buffers() def viewport_event(self, event: Application.ViewportEvent): gl.default_framebuffer.viewport = ((Vector2i(), event.framebuffer_size)) self._projection_rotating_text = Matrix3.projection( Vector2.x_scale(Vector2(self.window_size).aspect_ratio())) self._transformation_projection_dynamic_text =\ Matrix3.projection(Vector2(self.window_size))@\ Matrix3.translation(Vector2(self.window_size)*0.5) def scroll_event(self, event: Application.ScrollEvent): if not event.offset.y: return if event.offset.y > 0: self._transformation_rotating_text =\ Matrix3.rotation(Deg(1.0)) @\ Matrix3.scaling(Vector2(1.1)) @\ self._transformation_rotating_text else: self._transformation_rotating_text =\ Matrix3.rotation(Deg(-1.0)) @\ Matrix3.scaling(Vector2(1.0/1.1)) @\ self._transformation_rotating_text self.update_text() event.accepted = True self.redraw() def update_text(self): # TODO show rotation once Complex.from_matrix() is a thing self._dynamic_text.render("Scale: {:.2}" .format(self._transformation_rotating_text.uniform_scaling())) exit(TextExample().exec())