- Implemented vtk base for viewing
This commit is contained in:
@@ -1,15 +1,71 @@
|
||||
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 *
|
||||
from stl import mesh
|
||||
|
||||
##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.scale_factor = 0.001
|
||||
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()
|
||||
@@ -19,13 +75,13 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
self.yRot = 0
|
||||
self.zoom = -2
|
||||
self.sketch = []
|
||||
self.gl_width = self.width() / 100
|
||||
self.gl_height = self.height() / 100
|
||||
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:
|
||||
@@ -46,14 +102,23 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
|
||||
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:
|
||||
@@ -83,40 +148,236 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
glViewport(0, 0, width, height)
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
|
||||
aspect = width / float(height)
|
||||
|
||||
self.gl_width = self.width() / 1000
|
||||
self.gl_height = self.height() / 1000
|
||||
self.gl_width = self.width()
|
||||
self.gl_height = self.height()
|
||||
|
||||
gluPerspective(45.0, aspect, 0.01, 10.0)
|
||||
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)
|
||||
|
||||
glColor3f(0.9, 0.8, 0.8)
|
||||
"""# 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
|
||||
# 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)
|
||||
else:
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0) # Set the clear color (black with full opacity)
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Clear the color and depth buffers
|
||||
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)
|
||||
@@ -135,6 +396,40 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
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)
|
||||
@@ -169,23 +464,21 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
|
||||
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(), 20):
|
||||
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(), 20):
|
||||
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()
|
||||
@@ -204,3 +497,8 @@ class OpenGLWidget(QOpenGLWidget):
|
||||
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())
|
||||
Reference in New Issue
Block a user