2024-07-04 22:58:52 +02:00

504 lines
16 KiB
Python

import sys
import numpy as np
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PySide6.QtOpenGLWidgets import QOpenGLWidget
from PySide6.QtCore import Qt, QPoint
from OpenGL.GL import *
from OpenGL.GLU import *
##testing
def create_cube(scale=1):
vertices = np.array([
[0, 0, 0],
[2, 0, 0],
[2, 2, 0],
[0, 2, 0],
[0, 0, 2],
[2, 0, 2],
[2, 2, 2],
[0, 2, 2]
]) * scale
faces = np.array([
[0, 1, 2],
[2, 3, 0],
[4, 5, 6],
[6, 7, 4],
[0, 1, 5],
[5, 4, 0],
[2, 3, 7],
[7, 6, 2],
[0, 3, 7],
[7, 4, 0],
[1, 2, 6],
[6, 5, 1]
])
return vertices, faces
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("OpenGL Cube Viewer")
self.setGeometry(100, 100, 800, 600)
self.opengl_widget = OpenGLWidget()
central_widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(self.opengl_widget)
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
# Load cube data
vertices, faces = create_cube()
self.opengl_widget.load_interactor_mesh((vertices, faces))
class OpenGLWidget(QOpenGLWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.vertices = None
self.faces = None
self.selected_face = -1
self.scale_factor = 1
self.mesh_loaded = None
self.interactor_loaded = None
self.centroid = None
self.stl_file = "out.stl" # Replace with your STL file path
self.lastPos = QPoint()
self.startPos = None
self.endPos = None
self.xRot = 180
self.yRot = 0
self.zoom = -2
self.sketch = []
self.gl_width = self.width()
self.gl_height = self.height()
def map_value_to_range(self, value, value_min=0, value_max=1920, range_min=-1, range_max=1):
value = max(value_min, min(value_max, value))
mapped_value = ((value - value_min) / (value_max - value_min)) * (range_max - range_min) + range_min
return mapped_value
def load_stl(self, filename: str) -> object:
try:
stl_mesh = mesh.Mesh.from_file(filename)
# Extract vertices
vertices = np.concatenate([stl_mesh.v0, stl_mesh.v1, stl_mesh.v2])
# Calculate bounding box
min_x, min_y, min_z = vertices.min(axis=0)
max_x, max_y, max_z = vertices.max(axis=0)
# Calculate centroid
centroid_x = (min_x + max_x) / 2.0
centroid_y = (min_y + max_y) / 2.0
centroid_z = (min_z + max_z) / 2.0
self.mesh_loaded = stl_mesh.vectors
self.centroid = (centroid_x, centroid_y, centroid_z)
except FileNotFoundError:
print(f"Error: File {filename} not found.")
except Exception as e:
print(f"Error loading {filename}: {e}")
return None, (0, 0, 0)
def load_interactor_mesh(self, simp_mesh):
self.interactor_loaded = simp_mesh
# Calculate centroid based on the average position of vertices
centroid = np.mean(simp_mesh[0], axis=0)
self.centroid = tuple(centroid)
print(f"Centroid: {self.centroid}")
self.update()
def load_mesh_direct(self, mesh):
try:
stl_mesh = mesh
# Extract vertices
vertices = np.array(stl_mesh)
# Calculate centroid based on the average position of vertices
centroid = np.mean(vertices, axis=0)
self.mesh_loaded = vertices
self.centroid = tuple(centroid)
print(f"Centroid: {self.centroid}")
self.update()
except Exception as e:
print(e)
def clear_mesh(self):
self.mesh_loaded = None
def initializeGL(self):
glClearColor(0, 0, 0, 1)
glEnable(GL_DEPTH_TEST)
def resizeGL(self, width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
aspect = width / float(height)
self.gl_width = self.width()
self.gl_height = self.height()
gluPerspective(45.0, aspect, 0.01, 1000.0)
glMatrixMode(GL_MODELVIEW)
def unproject(self, x, y, z, modelview, projection, viewport):
mvp = np.dot(projection, modelview)
mvp_inv = np.linalg.inv(mvp)
ndc = np.array([(x - viewport[0]) / viewport[2] * 2 - 1,
(y - viewport[1]) / viewport[3] * 2 - 1,
2 * z - 1,
1])
world = np.dot(mvp_inv, ndc)
print("world undproj", world)
return world[:3] / world[3]
def draw_ray(self, ray_start, ray_end):
glColor3f(1.0, 0.0, 0.0) # Set the color of the ray (red)
glBegin(GL_LINES)
glVertex3f(*ray_start)
glVertex3f(*ray_end)
glEnd()
def mousePressEvent(self, event):
if event.buttons() & Qt.RightButton:
self.select_face(event)
def select_face(self, event):
x = event.position().x()
y = event.position().y()
modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
projection = glGetDoublev(GL_PROJECTION_MATRIX)
viewport = glGetIntegerv(GL_VIEWPORT)
# Unproject near and far points in world space
ray_start = gluUnProject(x, y, 0.0, modelview, projection, viewport)
ray_end = gluUnProject(x, y, 1.0, modelview, projection, viewport)
ray_start = np.array(ray_start)
ray_end = np.array(ray_end)
ray_direction = ray_end - ray_start
ray_direction /= np.linalg.norm(ray_direction)
print(f"Ray start: {ray_start}")
print(f"Ray end: {ray_end}")
print(f"Ray direction: {ray_direction}")
self.selected_face = self.check_intersection(ray_start, ray_end)
print(f"Selected face: {self.selected_face}")
self.update()
def ray_box_intersection(self, ray_origin, ray_direction, box_min, box_max):
inv_direction = 1 / (ray_direction + 1e-7) # Add small value to avoid division by zero
t1 = (box_min - ray_origin) * inv_direction
t2 = (box_max - ray_origin) * inv_direction
t_min = np.max(np.minimum(t1, t2))
t_max = np.min(np.maximum(t1, t2))
print(f"min: {t_min}, max: {t_max}" )
return t_max >= t_min and t_max > 0
def check_intersection(self, ray_start, ray_end):
# Get the current modelview matrix
modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
# Transform vertices to camera space
vertices_cam = [np.dot(modelview, np.append(v, 1))[:3] for v in self.interactor_loaded[0]]
ray_direction = ray_end - ray_start
ray_direction /= np.linalg.norm(ray_direction)
print(f"Checking intersection with {len(self.interactor_loaded[1])} faces")
for face_idx, face in enumerate(self.interactor_loaded[1]):
v0, v1, v2 = [vertices_cam[i] for i in face]
intersection = self.moller_trumbore(ray_start, ray_direction, v0, v1, v2)
if intersection is not None:
print(f"Intersection found with face {face_idx}")
return face_idx
print("No intersection found")
return None
def moller_trumbore(self, ray_origin, ray_direction, v0, v1, v2):
epsilon = 1e-6
# Find vectors for two edges sharing v0
edge1 = v1 - v0
edge2 = v2 - v0
pvec = np.cross(ray_direction, edge2)
det = np.dot(edge1, pvec)
print(det)
"""if det < epsilon:
return None"""
inv_det = 1.0 / det
tvec = ray_origin - v0
u = np.dot(tvec, pvec) * inv_det
print("u", u )
if u < 0.0 or u > 1.0:
return None
qvec = np.cross(tvec, edge1)
# Calculate v parameter and test bounds
v = np.dot(ray_direction, qvec) * inv_det
print("v", v)
if v < 0.0 or u + v > 1.0:
return None
# Calculate t, ray intersects triangle
t = np.dot(edge2, qvec) * inv_det
print("t",t)
if t > epsilon:
return ray_origin + t * ray_direction
return None
def ray_triangle_intersection(self, ray_origin, ray_direction, v0, v1, v2):
epsilon = 1e-5
edge1 = v1 - v0
edge2 = v2 - v0
h = np.cross(ray_direction, edge2)
a = np.dot(edge1, h)
print(f"Triangle vertices: {v0}, {v1}, {v2}")
print(f"a: {a}")
if abs(a) < epsilon:
print("Ray is parallel to the triangle")
return None # Ray is parallel to the triangle
f = 1.0 / a
s = ray_origin - v0
u = f * np.dot(s, h)
print(f"u: {u}")
if u < 0.0 or u > 1.0:
print("u is out of range")
return None
q = np.cross(s, edge1)
v = f * np.dot(ray_direction, q)
print(f"v: {v}")
if v < 0.0 or u + v > 1.0:
print("v is out of range")
return None
t = f * np.dot(edge2, q)
print(f"t: {t}")
if t > epsilon:
intersection_point = ray_origin + t * ray_direction
print(f"Intersection point: {intersection_point}")
return intersection_point
print("t is too small")
return None
def paintGL(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# Apply camera transformation
glTranslatef(0, 0, self.zoom)
glRotatef(self.xRot, 1.0, 0.0, 0.0)
glRotatef(self.yRot, 0.0, 1.0, 0.0)
"""# Apply model transformation
glTranslatef(self.tx, self.ty, self.tz)
glScalef(self.scale, self.scale, self.scale)
glRotatef(self.model_xRot, 1.0, 0.0, 0.0)
glRotatef(self.model_yRot, 0.0, 1.0, 0.0)
glRotatef(self.model_zRot, 0.0, 0.0, 1.0)"""
glColor3f(0.9, 0.8, 0.8)
self.draw_area()
if self.mesh_loaded is not None:
# Adjust the camera for the STL mesh
if self.centroid:
glPushMatrix() # Save current transformation matrix
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
cx, cy, cz = self.centroid
gluLookAt(cx, cy, cz + 100, cx, cy, cz, 0, 1, 0)
self.draw_mesh_direct(self.mesh_loaded)
glPopMatrix() # Restore transformation matrix
if self.interactor_loaded is not None:
# Draw interactor mesh
glPushMatrix() # Save current transformation matrix
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
self.draw_interactor(self.interactor_loaded)
glPopMatrix() # Restore transformation matrix
if self.selected_face is not None:
glColor3f(0.0, 1.0, 0.0) # Red color for selected face
glBegin(GL_TRIANGLES)
for vertex_idx in self.interactor_loaded[1][self.selected_face]:
glVertex3fv(self.interactor_loaded[0][vertex_idx])
glEnd()
# Flush the OpenGL pipeline and swap buffers
if hasattr(self, 'ray_start') and hasattr(self, 'ray_end'):
self.draw_ray(self.ray_start, self.ray_end)
glFlush()
def draw_stl(self, vertices):
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glLightfv(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.6, 0.6, 0.6, 1.0))
glBegin(GL_TRIANGLES)
for triangle in vertices:
for vertex in triangle:
glVertex3fv(vertex)
glEnd()
self.update()
def draw_interactor(self, simp_mesh: tuple):
vertices, faces = simp_mesh
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glLightfv(GL_LIGHT0, GL_POSITION, (0, 0.6, 0.6, 0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.4, 0.4, 0.4, 0.6))
# Draw the faces
glDisable(GL_LIGHTING)
glColor3f(0.2, 0.0, 0.0) # Set face color to red (or any color you prefer)
glBegin(GL_TRIANGLES)
for face in faces:
for vertex_index in face:
glVertex3fv(vertices[vertex_index])
glEnd()
# Draw the lines (edges of the triangles)
glColor3f(0.0, 1.0, 0.0) # Set line color to green (or any color you prefer)
glBegin(GL_LINES)
for face in faces:
for i in range(len(face)):
glVertex3fv(vertices[face[i]])
glVertex3fv(vertices[face[(i + 1) % len(face)]])
glEnd()
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
def draw_mesh_direct(self, points):
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_DEPTH_TEST)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
glLightfv(GL_LIGHT0, GL_POSITION, (0, 0.6, 0.6, 0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.4, 0.4, 0.4, 0.6))
glDisable(GL_LIGHTING)
glBegin(GL_TRIANGLES)
for vertex in points:
glVertex3fv(vertex)
glEnd()
# Draw the lines (edges of the triangles)
#glDisable(GL_LIGHTING) # Disable lighting to avoid affecting the line color
glColor3f(0.0, 0.0, 0.0) # Set line color to black (or any color you prefer)
glBegin(GL_LINES)
for i in range(0, len(points), 3):
glVertex3fv(points[i])
glVertex3fv(points[i + 1])
glVertex3fv(points[i + 1])
glVertex3fv(points[i + 2])
glVertex3fv(points[i + 2])
glVertex3fv(points[i])
glEnd()
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
def draw_area(self):
glColor3f(0.5, 0.5, 0.5) # Gray color
glBegin(GL_LINES)
for x in range(0, self.width(), 1):
x_ndc = self.map_value_to_range(x, 0, value_max=self.width(), range_min=-self.gl_width, range_max=self.gl_width)
glVertex2f(x_ndc, -self.gl_height) # Start from y = -1
glVertex2f(x_ndc, self.gl_height) # End at y = 1
for y in range(0, self.height(), 1):
y_ndc = self.map_value_to_range(y, 0, value_max=self.height(), range_min=-self.gl_height, range_max=self.gl_height)
glVertex2f(-self.gl_width, y_ndc) # Start from x = -1
glVertex2f(self.gl_width, y_ndc) # End at x = 1
glEnd()
def mouseMoveEvent(self, event):
dx = event.x() - self.lastPos.x()
dy = event.y() - self.lastPos.y()
if event.buttons() & Qt.MouseButton.LeftButton :
self.xRot += 0.5 * dy
self.yRot += 0.5 * dx
self.lastPos = event.pos()
self.update()
def wheelEvent(self, event):
delta = event.angleDelta().y()
self.zoom += delta / 200
self.update()
def aspect_ratio(self):
return self.width() / self.height() * (1.0 / abs(self.zoom))
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())