The glGenVertexArrays function throws this excpetion in one script (loader.py): "OpenGL.error.NullFunctionError: Attempt to call an undefined function glGenVertexArrays", but not in the other script (display_manager.py)
I guess it has something to do with initialization.
loader.py (the 'create_vao' function is relevant)
from raw_model import RawModel
from OpenGL.GL import *
from OpenGL.GLUT import *
class Loader:
__vaos = []
__vbos = []
def load_to_vao(self, positions: list):
vao_id = self.create_vao()
self.store_data_in_attribute_list(0, positions)
self.unbind_vao()
return RawModel(vao_id, len(positions)//3)
#classmethod
def clean_up(cls):
for vao in cls.__vaos:
glDeleteVertexArrays(vao)
for vbo in cls.__vbos:
glDeleteBuffers(vbo)
#classmethod
def create_vao(cls):
vao_id = glGenVertexArrays(1)
cls.__vaos.append(vao_id)
glBindVertexArray(vao_id)
return vao_id
#classmethod
def store_data_in_attribute_list(cls, attribute_number: int, data: list):
vbo_id = glGenBuffers()
cls.__vbos.append(vbo_id)
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW)
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#staticmethod
def unbind_vao():
glBindVertexArray(0)
display_manager.py (the 'create_display' function is relevant):
from OpenGL.GL import *
from OpenGL.GLUT import *
class DisplayManager:
def __init__(self, x: int = 1920, y: int = 1080):
if x is not None: self.__width = x
if y is not None: self.__height = y
def create_display(self, window_name):
glutInit()
glutInitDisplayMode(GLUT_RGBA) # initialize colors
glutInitWindowSize(self.get_width(), self.get_height()) # set windows size
glutInitWindowPosition(0, 0) # set window position
glutCreateWindow(f"{window_name}") # create window (with a name) and set window attribute
print(glGenVertexArrays(1))
input("This ran")
glutDisplayFunc(self.update_display)
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS) # prevent program from stopping
#staticmethod
def update_display():
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white)
glLoadIdentity() # Reset all graphic/shape's position
glutSwapBuffers() # Important for double buffering
def get_width(self):
return self.__width
def get_height(self):
return self.__height
#classmethod
def set_window(cls, window):
cls.__WIND = window
I already tried to include the glutInit function into the loader.py script, with no success. I also tried to figure out in which scripts the glGenVertexArrays function works, with no real clue.
In my main.py function I called the create_vao method before I even created the window. Swapping the order did the trick.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 18 days ago.
The community is reviewing whether to reopen this question as of 18 days ago.
Improve this question
I try to setup a simple 3D Engine in pyOpenGL. The current goal was to achieve a rectangle, which isn't displaying.
The render method I use is following:
#staticmethod
def render(model: RawModel):
glBindVertexArray(model.get_vao_id())
glEnableVertexAttribArray(0)
glDrawArrays(GL_TRIANGLES, 1, model.get_vertex_count())
glDisableVertexAttribArray(0)
glBindVertexArray(0)
I suppose something goes wrong with the glDrawArrays() method, because of how I bound my Buffer data:
#classmethod
def bind_indices_buffer(cls, attribute_number: int, data: list):
data = numpy.array(data, dtype='float32')
vbo_id = glGenBuffers(1)
cls.__vbos.append(vbo_id)
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW)
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
The tutorial I followed was in Java and before the data was put in the glBufferData() method it was converted into a 'FloatBuffer' object. I don't know how to achieve that in Python so I just put it in as an float32 array.
Anyone knows what I exactly did wrong?
All of my files:
main.py:
from display_manager import DisplayManager
from renderer import Renderer
from loader import Loader
from raw_model import RawModel
from OpenGL.GLUT import *
def main():
vertices = [
# left bottom triangle
-0.5, 0.5, 0,
-0.5, -0.5, 0,
0.5, -0.5, 0,
# right top triangle
0.5, -0.5, 0,
0.5, 0.5, 0,
-0.5, 0.5, 0
]
display = DisplayManager()
display.create_display("test") # creates display
renderer = Renderer()
loader = Loader()
model = loader.load_to_vao(vertices)
while glutGetWindow() != 0:
renderer.prepare()
renderer.render(model)
glutMainLoopEvent()
if __name__ == '__main__':
main()
raw_model.py:
class RawModel:
def __init__(self, vao_id: int, vertex_count: int):
self.__vao_id = vao_id
self.__vertex_count = vertex_count
def get_vao_id(self):
return self.__vao_id
def get_vertex_count(self):
return self.__vertex_count
renderer.py:
from OpenGL.GL import *
from raw_model import RawModel
class Renderer:
#staticmethod
def prepare():
glClearColor(1, 0, 0, 1)
glClear(GL_COLOR_BUFFER_BIT)
#staticmethod
def render(model: RawModel):
glBindVertexArray(model.get_vao_id())
glEnableVertexAttribArray(0)
glDrawArrays(GL_TRIANGLES, 1, model.get_vertex_count())
glDisableVertexAttribArray(0)
glBindVertexArray(0)
loader.py:
from OpenGL.GL import *
from OpenGL.GLUT import *
from raw_model import RawModel
class Loader:
__vaos = []
__vbos = []
def load_to_vao(self, positions: list):
vao_id = self.create_vao()
self.bind_indices_buffer(0, positions)
self.unbind_vao()
return RawModel(vao_id, len(positions))
#classmethod
def clean_up(cls):
for vao in cls.__vaos:
glDeleteVertexArrays(1, [vao])
for vbo in cls.__vbos:
glDeleteBuffers(1, [vbo])
#classmethod
def create_vao(cls):
vao_id = glGenVertexArrays(1)
cls.__vaos.append(vao_id)
glBindVertexArray(vao_id)
return vao_id
#classmethod
def bind_indices_buffer(cls, attribute_number: int, data: list):
vbo_id = glGenBuffers(1)
cls.__vbos.append(vbo_id)
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
glBufferData(GL_ARRAY_BUFFER, str(data), GL_STATIC_DRAW)
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
#staticmethod
def unbind_vao():
glBindVertexArray(0)
display_manager.py:
from OpenGL.GL import *
from OpenGL.GLUT import *
class DisplayManager:
__window_id = 0
def __init__(self, x: int = 1920, y: int = 1080):
if x is not None: self.__width = x
if y is not None: self.__height = y
self.next_window_id()
def create_display(self, window_name):
glutInit()
glutInitDisplayMode(GLUT_RGBA) # initialize colors
glutInitWindowSize(self.get_width(), self.get_height()) # set windows size
glutInitWindowPosition(0, 0) # set window position
glutCreateWindow(f"{window_name}") # create window (with a name) and set window attribute
glutSetWindow(self.get_window_id())
glutDisplayFunc(self.update_display)
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS) # prevent program from stopping
#staticmethod
def update_display():
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white)
glLoadIdentity() # Reset all graphic/shape's position
glutSwapBuffers() # Important for double buffering
def destroy_window(self):
glutDestroyWindow(self.get_window_id())
def get_width(self):
return self.__width
def get_height(self):
return self.__height
#classmethod
def next_window_id(cls):
cls.__window_id += 1
def get_window_id(self):
return self.__window_id
The problem is here:
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, 0)
glVertexAttribPointer(attribute_number, 3, GL_FLOAT, False, 0, None)
The type of the lase argument of glVertexAttribIPointer is const GLvoid *. So the argument must be None or ctypes.c_void_p(0), but not 0.
I am trying to draw colliders of Box2D. Now I have only physics in this example without graphics for simplicity. The DrawSegment() method must be called to print hello. I inherited the DebugDrawer class from the b2Draw class:
debug_drawer.py
from Box2D import b2Draw
class DebugDrawer(b2Draw):
def DrawSegment(self, p1, p2, color):
print("hello")
def DrawSolidPolygon(self, vertices, color):
pass
def DrawPoint(self, p, size, color):
pass
def DrawPolygon(self, vertices, color):
pass
def DrawCircle(self, center, radius, color, drawwidth=1):
pass
def DrawSolidCircle(self, center, radius, axis, color):
pass
def DrawTransform(self, xf):
pass
I created one object with the box shape. I have the animationLoop() method that I call with timer. Inside of the animationLoop() method I the self.world.Step() method and I call the paintGL() method by calling the self.update() method. Inside of the paintGL() method I call the self.world.DrawDebugData() method. I expect that the DrawSegment() will be called but it does not happen.
widget.py
from Box2D import (b2_staticBody, b2Body, b2BodyDef, b2FixtureDef,
b2PolygonShape, b2Vec2, b2World)
from OpenGL import GL as gl
from PyQt6.QtCore import QElapsedTimer, QSize, QTimer
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from debug_drawer import DebugDrawer
class Widget(QOpenGLWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Box2D, OpenGL3, PyQt6")
self.setFixedSize(QSize(500, 500))
self.deltaTime = 0
self.WORLD_SCALE = 30.0
self.world = b2World(gravity=b2Vec2(0.0, 9.8))
def initializeGL(self):
gl.glClearColor(0.2, 0.2, 0.2, 1.0)
gl.glEnable(gl.GL_DEPTH_TEST)
self.debugDrawer = DebugDrawer()
self.world.renderer = self.debugDrawer
self.debugDrawer.flags = { 'drawShapes': True,
'drawJoints': True, 'drawAABBs': True, 'drawPairs': True }
# print(self.debugDrawer.flags)
shape = b2PolygonShape()
shape.SetAsBox(50.0 / self.WORLD_SCALE, 50.0 / self.WORLD_SCALE)
bodyDef = b2BodyDef()
bodyDef.type = b2_staticBody
self.body: b2Body = self.world.CreateBody(bodyDef)
fixtureDef = b2FixtureDef()
fixtureDef.shape = shape
fixtureDef.density = 2
self.body.CreateFixture(fixtureDef)
self.timer = QTimer()
self.timer.timeout.connect(self.animationLoop)
self.elapsedTimer = QElapsedTimer()
self.elapsedTimer.start()
self.timer.start(1000//60)
def paintGL(self):
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
self.world.DrawDebugData()
def resizeGL(self, w: int, h: int):
gl.glViewport(0, 0, w, h)
def animationLoop(self):
self.deltaTime = self.elapsedTimer.elapsed() / 1000.0
self.elapsedTimer.restart()
self.world.Step(self.deltaTime, 8, 3)
self.update()
main.py
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QSurfaceFormat
from PyQt6.QtWidgets import QApplication
from widget import Widget
def main():
QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL)
app = QApplication(sys.argv)
format = QSurfaceFormat()
format.setSamples(8)
w = Widget()
w.setFormat(format)
w.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
I should use DrawPolygon to draw segments of colliders when I use boxes to draw borders around game objects. DrawSegment() will be called when an instance of b2EdgeShape is created:
edgeShape = b2EdgeShape()
edgeShape.vertices = [(0.0, 0.0), (1.0, 0.0)]
self.edgeBody: b2Body = self.world.CreateBody(bodyDef)
edgeFixtureDef = b2FixtureDef()
edgeFixtureDef.shape = edgeShape
edgeFixtureDef.density = 2
self.edgeBody.CreateFixture(edgeFixtureDef)
imports
from pyglet.gl import *
import pyglet
from pyglet.window import key
the code
self.batch = pyglet.graphics.Batch
x,y,z = 0,0,0
X,Y,Z = x+1,y+1,z+1
color = ('c3f', (1,1,1)*4)
self.batch.add(4, pyglet.gl.GL_QUADS, None, ('v3f', (x,y,z, X,y,z, X,Y,z, x,Y,z)), color) # problem here
My ide says that function got "4" aka int and expected batch, but i looked at some documentation and it was the same as in the code above.
i am using python 3.8.
Oh and if needed here is all of my code (i do not think you need it):
from pyglet.gl import *
import pyglet
from pyglet.window import key
import math
WIDTH = 1000
HEIGHT = math.floor(WIDTH / 12 * 9)
class Model:
def __init__(self):
self.batch = pyglet.graphics.Batch
x,y,z = 0,0,0
X,Y,Z = x+1,y+1,z+1
color = ('c3f', (1,1,1)*4)
self.batch.add(4, pyglet.gl.GL_QUADS, None, ('v3f', (x,y,z, X,y,z, X,Y,z, x,Y,z)), color)
def draw(self):
self.batch.draw()
class Window(pyglet.window.Window):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(math.floor(WIDTH*0.5), math.floor(HEIGHT*0.5))
self.model = Model()
def on_draw(self):
self.clear()
self.model.draw()
if __name__ == '__main__':
window = Window(width=WIDTH, height=HEIGHT, caption="pyglet.gl test", resizable=True)
glClearColor(0, 160, 160, 1)
pyglet.app.run()
Oh, I sloved it my self. Im just stupid.
I said:
self.batch = pyglet.graphics.Batch
But i had to add () at the end:
self.batch = pyglet.graphics.Batch()
I'm using python's arcade library and I keep getting the "NameError: name 'window' is not defined" error. I tried deleting the main function and just running the
window = MyGame()
for button in window.buttonList:
button.draw()
arcade.run() without the function around it, but now I need to run this from another file without using os.exec or subprocess while still being able to go back and run the first one. I need to use a main function, but I don't know how to do it without raising an error. Here is my code
from subprocess import call
from tkinter import *
from tkinter import filedialog
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Menu"
class MenuItem():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def draw(self):
pass
def func(self):
pass
class FreeDraw(MenuItem):
def func(self):
window.close()
window.set_visible(False)
#run other file
def draw(self):
window.buttonShapes.append(arcade.create_rectangle_outline(self.x, self.y, self.width, self.height, arcade.color.BLACK))
class MyGame(arcade.Window):
""" Our custom Window Class"""
def __init__(self):
""" Initializer """
# Call the parent class initializer
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.buttonList = [FreeDraw(SCREEN_WIDTH/2, 100, 400, 100)]
self.buttonShapes = arcade.ShapeElementList()
arcade.set_background_color(arcade.color.ASH_GREY)
def setup(self):
pass
def on_draw(self):
""" Draw everything """
arcade.start_render()
arcade.draw_text("Free Draw", (self.buttonList[0].x - self.buttonList[0].width / 2) + 115,
self.buttonList[0].y - 25,
arcade.color.BLACK, 30)
self.buttonShapes.draw()
def on_key_press(self, key, modifiers):
pass
def on_key_release(self, key, modifiers):
pass
def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
pass
def on_mouse_release(self, x: float, y: float, button: int,
modifiers: int):
for button in self.buttonList:
if x <= button.x + (button.width / 2) and x >= button.x - (button.width / 2):
if y <= button.y + (button.height / 2) and y >= button.y - (button.height / 2):
button.func()
self.buttonList[0].func()
def main():
window = MyGame()
for button in window.buttonList:
button.draw()
arcade.run()
main()
You can use arcade.View instead of arcade.Window in your separate classes. Example:
import arcade
class View1(arcade.View):
def on_draw(self):
arcade.start_render()
arcade.draw_text('View 1', 300, 200, arcade.color.RED, font_size=30, anchor_x='center')
def on_mouse_press(self, _x, _y, _button, _modifiers):
self.window.show_view(View2())
class View2(arcade.View):
def on_draw(self):
arcade.start_render()
arcade.draw_text('View 2', 300, 200, arcade.color.RED, font_size=30, anchor_x='center')
def on_mouse_press(self, _x, _y, _button, _modifiers):
self.window.show_view(View1())
window = arcade.Window(600, 400)
window.show_view(View1())
arcade.run()
Output:
I have created a RelativeLayout subclass that positions its children in a grid by supplying them positions in the code (Python, not kv file). It works, but items are placed some 25 pixels to the upper-right from the position of layout itself, as shown by the canvas block. Python code for Layout subclass:
class RLMapWidget(RelativeLayout):
def __init__(self, map=None, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
# Connecting to map, factories and other objects this class should know about
self.tile_factory = TileWidgetFactory()
self.map = map
# Initializing tile widgets for BG layer and adding them as children
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
tile_widget = self.tile_factory.create_tile_widget(self.map.get_item(layer='bg',
location=(x, y)))
# tile_widget.pos = (50*x, 50*y)
tile_widget.pos = self._get_screen_pos((x, y))
self.add_widget(tile_widget)
# Initializing widgets for actor layers
for x in range(self.map.size[0]):
for y in range(self.map.size[1]):
if self.map.has_item(layer='actors', location=(x, y)):
actor_widget = self.tile_factory.create_actor_widget(self.map.get_item(layer='actors',
displayed location=(x, y)))
actor_widget.pos=(50*x, 50*y)
self.add_widget(actor_widget)
# Map background canvas. Used solely to test positioning
with self.canvas.before:
Color(0, 0, 1, 1)
self.rect = Rectangle(size = self.size, pos=self.pos)
self.bind(pos=self.update_rect, size=self.update_rect)
# Initializing keyboard bindings and key lists
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_key_down)
# The list of keys that will not be ignored by on_key_down
self.used_keys=['w', 'a', 's', 'd']
def redraw_actors(self):
for actor in self.map.actors:
actor.widget.pos = self._get_screen_pos(actor.location)
def _get_screen_pos(self, location):
"""
Return screen coordinates (in pixels) for a given location
:param location: int tuple
:return: int tuple
"""
return (location[0]*50, location[1]*50)
# Keyboard-related methods
def _on_key_down(self, keyboard, keycode, text, modifiers):
"""
Process keyboard event and make a turn, if necessary
:param keyboard:
:param keycode:
:param text:
:param modifiers:
:return:
"""
if keycode[1] in self.used_keys:
self.map.process_turn(keycode)
self.redraw_actors()
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_key_down)
self._keyboard = None
def update_rect(self, pos, size):
self.rect.pos = self.pos
self.rect.size = self.size
class CampApp(App):
def build(self):
root = FloatLayout()
map_factory = MapFactory()
map = map_factory.create_test_map()
map_widget = RLMapWidget(map=map,
size=(map.size[0]*50, map.size[1]*50),
size_hint=(None, None))
root.add_widget(map_widget)
return root
if __name__ == '__main__':
CampApp().run()
Factory class that makes tiles:
class TileWidgetFactory(object):
def __init__(self):
pass
def create_tile_widget(self, tile):
tile.widget = Image(source=tile.image_source,
size_hint=(None, None))
return tile.widget
def create_actor_widget(self, actor):
actor.widget = Image(source='Tmp_frame_black.png',
size_hint=(None, None))
return actor.widget
Okay, solved it myself. It turns out that if I supply the size to child widgets in factory, they are positioned properly. Although it solves the problem I have, I'd still be grateful if someone can explain where this quirk does come from.