- Implemented vtk base for viewing
This commit is contained in:
parent
fcabe449f8
commit
9daf263aad
@ -635,8 +635,10 @@ class SketchWidget(QWidget):
|
|||||||
return self.width() / self.height() * (1.0 / abs(self.zoom))
|
return self.width() / self.height() * (1.0 / abs(self.zoom))
|
||||||
|
|
||||||
def clear_sketch(self):
|
def clear_sketch(self):
|
||||||
self.points = []
|
self.slv_points_main = []
|
||||||
self.update()
|
self.slv_lines_main = []
|
||||||
|
self.reset_buffers()
|
||||||
|
self.solv = SolverSystem()
|
||||||
|
|
||||||
|
|
||||||
# Example usage
|
# Example usage
|
||||||
|
@ -1,15 +1,71 @@
|
|||||||
|
import sys
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
||||||
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
from PySide6.QtOpenGLWidgets import QOpenGLWidget
|
||||||
from PySide6.QtCore import Qt, QPoint
|
from PySide6.QtCore import Qt, QPoint
|
||||||
from OpenGL.GL import *
|
from OpenGL.GL import *
|
||||||
from OpenGL.GLU 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):
|
class OpenGLWidget(QOpenGLWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
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.mesh_loaded = None
|
||||||
|
self.interactor_loaded = None
|
||||||
self.centroid = None
|
self.centroid = None
|
||||||
self.stl_file = "out.stl" # Replace with your STL file path
|
self.stl_file = "out.stl" # Replace with your STL file path
|
||||||
self.lastPos = QPoint()
|
self.lastPos = QPoint()
|
||||||
@ -19,13 +75,13 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
self.yRot = 0
|
self.yRot = 0
|
||||||
self.zoom = -2
|
self.zoom = -2
|
||||||
self.sketch = []
|
self.sketch = []
|
||||||
self.gl_width = self.width() / 100
|
self.gl_width = self.width()
|
||||||
self.gl_height = self.height() / 100
|
self.gl_height = self.height()
|
||||||
|
|
||||||
def map_value_to_range(self, value, value_min=0, value_max=1920, range_min=-1, range_max=1):
|
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))
|
value = max(value_min, min(value_max, value))
|
||||||
mapped_value = ((value - value_min) / (value_max - value_min)) * (range_max - range_min) + range_min
|
mapped_value = ((value - value_min) / (value_max - value_min)) * (range_max - range_min) + range_min
|
||||||
|
|
||||||
return mapped_value
|
return mapped_value
|
||||||
|
|
||||||
def load_stl(self, filename: str) -> object:
|
def load_stl(self, filename: str) -> object:
|
||||||
@ -46,14 +102,23 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
|
|
||||||
self.mesh_loaded = stl_mesh.vectors
|
self.mesh_loaded = stl_mesh.vectors
|
||||||
self.centroid = (centroid_x, centroid_y, centroid_z)
|
self.centroid = (centroid_x, centroid_y, centroid_z)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"Error: File {filename} not found.")
|
print(f"Error: File {filename} not found.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading {filename}: {e}")
|
print(f"Error loading {filename}: {e}")
|
||||||
|
|
||||||
return None, (0, 0, 0)
|
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):
|
def load_mesh_direct(self, mesh):
|
||||||
try:
|
try:
|
||||||
@ -83,40 +148,236 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
glViewport(0, 0, width, height)
|
glViewport(0, 0, width, height)
|
||||||
glMatrixMode(GL_PROJECTION)
|
glMatrixMode(GL_PROJECTION)
|
||||||
glLoadIdentity()
|
glLoadIdentity()
|
||||||
|
|
||||||
aspect = width / float(height)
|
aspect = width / float(height)
|
||||||
|
|
||||||
self.gl_width = self.width() / 1000
|
self.gl_width = self.width()
|
||||||
self.gl_height = self.height() / 1000
|
self.gl_height = self.height()
|
||||||
|
|
||||||
gluPerspective(45.0, aspect, 0.01, 10.0)
|
gluPerspective(45.0, aspect, 0.01, 1000.0)
|
||||||
glMatrixMode(GL_MODELVIEW)
|
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):
|
def paintGL(self):
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
glLoadIdentity()
|
glLoadIdentity()
|
||||||
|
|
||||||
|
# Apply camera transformation
|
||||||
glTranslatef(0, 0, self.zoom)
|
glTranslatef(0, 0, self.zoom)
|
||||||
glRotatef(self.xRot, 1.0, 0.0, 0.0)
|
glRotatef(self.xRot, 1.0, 0.0, 0.0)
|
||||||
glRotatef(self.yRot, 0.0, 1.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()
|
self.draw_area()
|
||||||
|
|
||||||
if self.mesh_loaded is not None:
|
if self.mesh_loaded is not None:
|
||||||
# Adjust the camera
|
# Adjust the camera for the STL mesh
|
||||||
if self.centroid:
|
if self.centroid:
|
||||||
|
glPushMatrix() # Save current transformation matrix
|
||||||
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
|
glScalef(self.scale_factor, self.scale_factor, self.scale_factor) # Apply scaling
|
||||||
|
|
||||||
cx, cy, cz = self.centroid
|
cx, cy, cz = self.centroid
|
||||||
gluLookAt(cx, cy, cz + 100, cx, cy, cz, 0, 1, 0)
|
gluLookAt(cx, cy, cz + 100, cx, cy, cz, 0, 1, 0)
|
||||||
|
|
||||||
self.draw_mesh_direct(self.mesh_loaded)
|
self.draw_mesh_direct(self.mesh_loaded)
|
||||||
else:
|
glPopMatrix() # Restore transformation matrix
|
||||||
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
|
|
||||||
|
|
||||||
|
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):
|
def draw_stl(self, vertices):
|
||||||
glEnable(GL_LIGHTING)
|
glEnable(GL_LIGHTING)
|
||||||
@ -135,6 +396,40 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
glEnd()
|
glEnd()
|
||||||
self.update()
|
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):
|
def draw_mesh_direct(self, points):
|
||||||
glEnable(GL_LIGHTING)
|
glEnable(GL_LIGHTING)
|
||||||
glEnable(GL_LIGHT0)
|
glEnable(GL_LIGHT0)
|
||||||
@ -169,23 +464,21 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
|
|
||||||
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
|
glEnable(GL_LIGHTING) # Re-enable lighting if further drawing requires it
|
||||||
|
|
||||||
|
|
||||||
def draw_area(self):
|
def draw_area(self):
|
||||||
glColor3f(0.5, 0.5, 0.5) # Gray color
|
glColor3f(0.5, 0.5, 0.5) # Gray color
|
||||||
|
|
||||||
glBegin(GL_LINES)
|
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)
|
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) # Start from y = -1
|
||||||
glVertex2f(x_ndc, self.gl_height) # End at 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)
|
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) # Start from x = -1
|
||||||
glVertex2f(self.gl_width, y_ndc) # End at x = 1
|
glVertex2f(self.gl_width, y_ndc) # End at x = 1
|
||||||
glEnd()
|
glEnd()
|
||||||
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, event):
|
def mouseMoveEvent(self, event):
|
||||||
dx = event.x() - self.lastPos.x()
|
dx = event.x() - self.lastPos.x()
|
||||||
dy = event.y() - self.lastPos.y()
|
dy = event.y() - self.lastPos.y()
|
||||||
@ -204,3 +497,8 @@ class OpenGLWidget(QOpenGLWidget):
|
|||||||
def aspect_ratio(self):
|
def aspect_ratio(self):
|
||||||
return self.width() / self.height() * (1.0 / abs(self.zoom))
|
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())
|
447
drawing_modules/vtk_widget.py
Normal file
447
drawing_modules/vtk_widget.py
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import vtk
|
||||||
|
from PySide6 import QtCore, QtWidgets
|
||||||
|
from PySide6.QtCore import Signal
|
||||||
|
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
|
||||||
|
|
||||||
|
|
||||||
|
class VTKWidget(QtWidgets.QWidget):
|
||||||
|
face_data = Signal(dict)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.vtk_widget = QVTKRenderWindowInteractor(self)
|
||||||
|
|
||||||
|
# Create layout and add VTK widget
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(self.vtk_widget)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Create VTK pipeline
|
||||||
|
self.renderer = vtk.vtkRenderer()
|
||||||
|
self.vtk_widget.GetRenderWindow().AddRenderer(self.renderer)
|
||||||
|
self.interactor = self.vtk_widget.GetRenderWindow().GetInteractor()
|
||||||
|
|
||||||
|
# Set up the camera
|
||||||
|
self.camera = self.renderer.GetActiveCamera()
|
||||||
|
self.camera.SetPosition(5, 5, 5)
|
||||||
|
self.camera.SetFocalPoint(0, 0, 0)
|
||||||
|
|
||||||
|
# Set up picking
|
||||||
|
self.picker = vtk.vtkCellPicker()
|
||||||
|
self.picker.SetTolerance(0.0005)
|
||||||
|
|
||||||
|
# Create a mapper and actor for picked cells
|
||||||
|
self.picked_mapper = vtk.vtkDataSetMapper()
|
||||||
|
self.picked_actor = vtk.vtkActor()
|
||||||
|
self.picked_actor.SetMapper(self.picked_mapper)
|
||||||
|
self.picked_actor.GetProperty().SetColor(1.0, 0.0, 0.0) # Red color for picked faces
|
||||||
|
self.renderer.AddActor(self.picked_actor)
|
||||||
|
|
||||||
|
# Set up interactor style
|
||||||
|
self.style = vtk.vtkInteractorStyleTrackballCamera()
|
||||||
|
self.interactor.SetInteractorStyle(self.style)
|
||||||
|
|
||||||
|
# Add observer for mouse clicks
|
||||||
|
self.interactor.AddObserver("RightButtonPressEvent", self.on_click)
|
||||||
|
|
||||||
|
def create_cube_mesh(self):
|
||||||
|
cube_source = vtk.vtkCubeSource()
|
||||||
|
print(cube_source)
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputConnection(cube_source.GetOutputPort())
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
def simplify_mesh(self, input_mesh, target_reduction):
|
||||||
|
# Create the quadric decimation filter
|
||||||
|
decimate = vtk.vtkDecimatePro()
|
||||||
|
decimate.SetInputData(input_mesh)
|
||||||
|
|
||||||
|
# Set the reduction factor (0 to 1, where 1 means maximum reduction)
|
||||||
|
decimate.SetTargetReduction(target_reduction)
|
||||||
|
|
||||||
|
# Optional: Preserve topology (if needed)
|
||||||
|
decimate.PreserveTopologyOn()
|
||||||
|
|
||||||
|
# Perform the decimation
|
||||||
|
decimate.Update()
|
||||||
|
|
||||||
|
return decimate.GetOutput()
|
||||||
|
|
||||||
|
def combine_coplanar_faces(self, input_polydata, tolerance=0.001):
|
||||||
|
# Clean the polydata to merge duplicate points
|
||||||
|
clean = vtk.vtkCleanPolyData()
|
||||||
|
clean.SetInputData(input_polydata)
|
||||||
|
clean.SetTolerance(tolerance)
|
||||||
|
clean.Update()
|
||||||
|
|
||||||
|
# Generate normals and merge coplanar polygons
|
||||||
|
normals = vtk.vtkPolyDataNormals()
|
||||||
|
normals.SetInputConnection(clean.GetOutputPort())
|
||||||
|
normals.SplittingOff() # Disable splitting of sharp edges
|
||||||
|
normals.ConsistencyOn() # Ensure consistent polygon ordering
|
||||||
|
normals.AutoOrientNormalsOn() # Automatically orient normals
|
||||||
|
normals.ComputePointNormalsOff() # We only need face normals
|
||||||
|
normals.ComputeCellNormalsOn() # Compute cell normals
|
||||||
|
normals.Update()
|
||||||
|
|
||||||
|
return normals.GetOutput()
|
||||||
|
|
||||||
|
def poisson_reconstruction(self, points):
|
||||||
|
# Create a polydata object from points
|
||||||
|
point_polydata = vtk.vtkPolyData()
|
||||||
|
point_polydata.SetPoints(points)
|
||||||
|
|
||||||
|
# Create a surface reconstruction filter
|
||||||
|
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||||
|
surf.SetInputData(point_polydata)
|
||||||
|
surf.Update()
|
||||||
|
|
||||||
|
# Create a contour filter to extract the surface
|
||||||
|
cf = vtk.vtkContourFilter()
|
||||||
|
cf.SetInputConnection(surf.GetOutputPort())
|
||||||
|
cf.SetValue(0, 0.0)
|
||||||
|
cf.Update()
|
||||||
|
|
||||||
|
# Reverse normals
|
||||||
|
reverse = vtk.vtkReverseSense()
|
||||||
|
reverse.SetInputConnection(cf.GetOutputPort())
|
||||||
|
reverse.ReverseCellsOn()
|
||||||
|
reverse.ReverseNormalsOn()
|
||||||
|
reverse.Update()
|
||||||
|
|
||||||
|
return reverse.GetOutput()
|
||||||
|
|
||||||
|
def load_interactor_mesh(self, simp_mesh):
|
||||||
|
vertices, faces = simp_mesh
|
||||||
|
self.load_custom_mesh(vertices, faces)
|
||||||
|
|
||||||
|
def load_custom_mesh(self, vertices, faces):
|
||||||
|
### Load meshes by own module
|
||||||
|
# Create a vtkPoints object and store the points in it
|
||||||
|
points = vtk.vtkPoints()
|
||||||
|
for vertex in vertices:
|
||||||
|
points.InsertNextPoint(vertex)
|
||||||
|
|
||||||
|
# Create a vtkCellArray to store the faces
|
||||||
|
cells = vtk.vtkCellArray()
|
||||||
|
for face in faces:
|
||||||
|
triangle = vtk.vtkTriangle()
|
||||||
|
triangle.GetPointIds().SetId(0, face[0])
|
||||||
|
triangle.GetPointIds().SetId(1, face[1])
|
||||||
|
triangle.GetPointIds().SetId(2, face[2])
|
||||||
|
cells.InsertNextCell(triangle)
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(points)
|
||||||
|
polydata.SetPolys(cells)
|
||||||
|
|
||||||
|
# Create mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(polydata) # Make sure this line is present
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
|
||||||
|
# Add to renderer
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
# Force an update of the pipeline
|
||||||
|
mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
def create_simplified_outline(self, polydata):
|
||||||
|
# 1. Extract the outer surface
|
||||||
|
surface_filter = vtk.vtkDataSetSurfaceFilter()
|
||||||
|
surface_filter.SetInputData(polydata)
|
||||||
|
surface_filter.Update()
|
||||||
|
|
||||||
|
# 2. Extract feature edges (only boundary edges)
|
||||||
|
feature_edges = vtk.vtkFeatureEdges()
|
||||||
|
feature_edges.SetInputConnection(surface_filter.GetOutputPort())
|
||||||
|
feature_edges.BoundaryEdgesOn()
|
||||||
|
feature_edges.FeatureEdgesOff()
|
||||||
|
feature_edges.NonManifoldEdgesOff()
|
||||||
|
feature_edges.ManifoldEdgesOff()
|
||||||
|
feature_edges.Update()
|
||||||
|
|
||||||
|
# 3. Clean the edges to merge duplicate points
|
||||||
|
cleaner = vtk.vtkCleanPolyData()
|
||||||
|
cleaner.SetInputConnection(feature_edges.GetOutputPort())
|
||||||
|
cleaner.Update()
|
||||||
|
|
||||||
|
# 4. Optional: Smooth the outline
|
||||||
|
smooth = vtk.vtkSmoothPolyDataFilter()
|
||||||
|
smooth.SetInputConnection(cleaner.GetOutputPort())
|
||||||
|
smooth.SetNumberOfIterations(15)
|
||||||
|
smooth.SetRelaxationFactor(0.1)
|
||||||
|
smooth.FeatureEdgeSmoothingOff()
|
||||||
|
smooth.BoundarySmoothingOn()
|
||||||
|
smooth.Update()
|
||||||
|
|
||||||
|
return smooth
|
||||||
|
|
||||||
|
def render_from_points_direct_with_faces(self, points):
|
||||||
|
# Create a vtkPoints object and store the points in it
|
||||||
|
vtk_points = vtk.vtkPoints()
|
||||||
|
for point in points:
|
||||||
|
vtk_points.InsertNextPoint(point)
|
||||||
|
|
||||||
|
# Create a vtkCellArray to store the triangles
|
||||||
|
triangles = vtk.vtkCellArray()
|
||||||
|
|
||||||
|
# Assuming points are organized as triplets forming triangles
|
||||||
|
for i in range(0, len(points), 3):
|
||||||
|
triangle = vtk.vtkTriangle()
|
||||||
|
triangle.GetPointIds().SetId(0, i)
|
||||||
|
triangle.GetPointIds().SetId(1, i + 1)
|
||||||
|
triangle.GetPointIds().SetId(2, i + 2)
|
||||||
|
triangles.InsertNextCell(triangle)
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
polydata = vtk.vtkPolyData()
|
||||||
|
polydata.SetPoints(vtk_points)
|
||||||
|
polydata.SetPolys(triangles)
|
||||||
|
|
||||||
|
# Optional: Merge duplicate points
|
||||||
|
cleaner = vtk.vtkCleanPolyData()
|
||||||
|
cleaner.SetInputData(polydata)
|
||||||
|
cleaner.Update()
|
||||||
|
|
||||||
|
# Optional: Combine coplanar faces
|
||||||
|
normals = vtk.vtkPolyDataNormals()
|
||||||
|
normals.SetInputConnection(cleaner.GetOutputPort())
|
||||||
|
normals.SplittingOff()
|
||||||
|
normals.ConsistencyOn()
|
||||||
|
normals.AutoOrientNormalsOn()
|
||||||
|
normals.ComputePointNormalsOff()
|
||||||
|
normals.ComputeCellNormalsOn()
|
||||||
|
normals.Update()
|
||||||
|
|
||||||
|
# Create a mapper and actor
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputConnection(normals.GetOutputPort())
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case)
|
||||||
|
actor.GetProperty().EdgeVisibilityOn() # Show edges
|
||||||
|
actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
feature_edges = self.create_simplified_outline(polydata)
|
||||||
|
|
||||||
|
# Create a mapper for the feature edges
|
||||||
|
edge_mapper = vtk.vtkPolyDataMapper()
|
||||||
|
# Already wiht output
|
||||||
|
edge_mapper.SetInputConnection(feature_edges.GetOutputPort())
|
||||||
|
|
||||||
|
# Create an actor for the feature edges
|
||||||
|
edge_actor = vtk.vtkActor()
|
||||||
|
edge_actor.SetMapper(edge_mapper)
|
||||||
|
|
||||||
|
# Set the properties of the edge actor
|
||||||
|
edge_actor.GetProperty().SetColor(1, 0, 0) # Set color (red in this case)
|
||||||
|
edge_actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
# Optionally, if you want to keep the original mesh visible:
|
||||||
|
# (assuming you have the original mesh mapper and actor set up)
|
||||||
|
self.renderer.AddActor(actor) # Add the original mesh actor
|
||||||
|
# Add the edge actor to the renderer
|
||||||
|
self.renderer.AddActor(edge_actor)
|
||||||
|
|
||||||
|
# Force an update of the pipeline
|
||||||
|
mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(f"Number of triangles: {triangles.GetNumberOfCells()}")
|
||||||
|
print(f"Final number of points: {normals.GetOutput().GetNumberOfPoints()}")
|
||||||
|
print(f"Final number of cells: {normals.GetOutput().GetNumberOfCells()}")
|
||||||
|
|
||||||
|
def render_from_points_direct(self, points):
|
||||||
|
### Rendermethod for SDF mesh (output)
|
||||||
|
# Create a vtkPoints object and store the points in it
|
||||||
|
vtk_points = vtk.vtkPoints()
|
||||||
|
for point in points:
|
||||||
|
vtk_points.InsertNextPoint(point)
|
||||||
|
|
||||||
|
# Create a polydata object
|
||||||
|
point_polydata = vtk.vtkPolyData()
|
||||||
|
point_polydata.SetPoints(vtk_points)
|
||||||
|
|
||||||
|
# Surface reconstruction
|
||||||
|
surf = vtk.vtkSurfaceReconstructionFilter()
|
||||||
|
surf.SetInputData(point_polydata)
|
||||||
|
surf.Update()
|
||||||
|
|
||||||
|
# Create a contour filter to extract the surface
|
||||||
|
cf = vtk.vtkContourFilter()
|
||||||
|
cf.SetInputConnection(surf.GetOutputPort())
|
||||||
|
cf.SetValue(0, 0.0)
|
||||||
|
cf.Update()
|
||||||
|
|
||||||
|
# Reverse the normals
|
||||||
|
reverse = vtk.vtkReverseSense()
|
||||||
|
reverse.SetInputConnection(cf.GetOutputPort())
|
||||||
|
reverse.ReverseCellsOn()
|
||||||
|
reverse.ReverseNormalsOn()
|
||||||
|
reverse.Update()
|
||||||
|
|
||||||
|
# Get the reconstructed mesh
|
||||||
|
reconstructed_mesh = reverse.GetOutput()
|
||||||
|
|
||||||
|
"""# Simplify the mesh
|
||||||
|
target_reduction = 1 # Adjust this value as needed
|
||||||
|
simplified_mesh = self.simplify_mesh(reconstructed_mesh, target_reduction)
|
||||||
|
|
||||||
|
combinded_faces = self.combine_coplanar_faces(simplified_mesh, 0.001)"""
|
||||||
|
|
||||||
|
# Create a mapper and actor for the simplified mesh
|
||||||
|
mapper = vtk.vtkPolyDataMapper()
|
||||||
|
mapper.SetInputData(reconstructed_mesh)
|
||||||
|
|
||||||
|
actor = vtk.vtkActor()
|
||||||
|
actor.SetMapper(mapper)
|
||||||
|
actor.GetProperty().SetColor(1, 1, 1) # Set color (white in this case)
|
||||||
|
actor.GetProperty().EdgeVisibilityOn() # Show edges
|
||||||
|
actor.GetProperty().SetLineWidth(2) # Set line width
|
||||||
|
|
||||||
|
# Add the actor to the renderer
|
||||||
|
self.renderer.AddActor(actor)
|
||||||
|
|
||||||
|
# Force an update of the pipeline
|
||||||
|
#mapper.Update()
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(
|
||||||
|
f"Reconstructed mesh: {reconstructed_mesh.GetNumberOfPoints()} points, {reconstructed_mesh.GetNumberOfCells()} cells")
|
||||||
|
"""print(
|
||||||
|
f"Simplified mesh: {simplified_mesh.GetNumberOfPoints()} points, {simplified_mesh.GetNumberOfCells()} cells")"""
|
||||||
|
|
||||||
|
def on_click(self, obj, event):
|
||||||
|
click_pos = self.interactor.GetEventPosition()
|
||||||
|
|
||||||
|
# Perform pick
|
||||||
|
self.picker.Pick(click_pos[0], click_pos[1], 0, self.renderer)
|
||||||
|
|
||||||
|
# Get picked cell
|
||||||
|
cell_id = self.picker.GetCellId()
|
||||||
|
|
||||||
|
if cell_id != -1:
|
||||||
|
print(f"Picked face ID: {cell_id}")
|
||||||
|
|
||||||
|
# Get the polydata and the picked cell
|
||||||
|
polydata = self.picker.GetActor().GetMapper().GetInput()
|
||||||
|
cell = polydata.GetCell(cell_id)
|
||||||
|
|
||||||
|
# Project2D
|
||||||
|
renderer = self.vtk_widget.GetRenderWindow().GetRenderers().GetFirstRenderer()
|
||||||
|
camera = renderer.GetActiveCamera()
|
||||||
|
|
||||||
|
# Get cell type
|
||||||
|
cell_type = cell.GetCellType()
|
||||||
|
print(f"Cell type: {cell_type}")
|
||||||
|
|
||||||
|
# Get points of the cell
|
||||||
|
points = cell.GetPoints()
|
||||||
|
num_points = points.GetNumberOfPoints()
|
||||||
|
print(f"Number of points in the cell: {num_points}")
|
||||||
|
|
||||||
|
vec_points = []
|
||||||
|
|
||||||
|
# Get coordinates of each point
|
||||||
|
for i in range(num_points):
|
||||||
|
point = points.GetPoint(i)
|
||||||
|
print(f"Point {i}: {point}")
|
||||||
|
vec_points.append(point)
|
||||||
|
|
||||||
|
# Get normal of the cell (if it's a polygon)
|
||||||
|
if cell_type == vtk.VTK_TRIANGLE:
|
||||||
|
normal = [0, 0, 0]
|
||||||
|
vtk.vtkPolygon.ComputeNormal(points, normal)
|
||||||
|
print(f"Face normal: {normal}")
|
||||||
|
|
||||||
|
# Get cell data
|
||||||
|
cell_data = polydata.GetCellData()
|
||||||
|
if cell_data:
|
||||||
|
num_arrays = cell_data.GetNumberOfArrays()
|
||||||
|
print(f"Number of cell data arrays: {num_arrays}")
|
||||||
|
for i in range(num_arrays):
|
||||||
|
array = cell_data.GetArray(i)
|
||||||
|
array_name = array.GetName()
|
||||||
|
num_components = array.GetNumberOfComponents()
|
||||||
|
value = [0] * num_components
|
||||||
|
array.GetTuple(cell_id, value)
|
||||||
|
print(f"Cell data '{array_name}': {value}")
|
||||||
|
|
||||||
|
# Get point data (average of all points in the cell)
|
||||||
|
point_data = polydata.GetPointData()
|
||||||
|
if point_data:
|
||||||
|
num_arrays = point_data.GetNumberOfArrays()
|
||||||
|
print(f"Number of point data arrays: {num_arrays}")
|
||||||
|
for i in range(num_arrays):
|
||||||
|
array = point_data.GetArray(i)
|
||||||
|
array_name = array.GetName()
|
||||||
|
num_components = array.GetNumberOfComponents()
|
||||||
|
avg_value = np.zeros(num_components)
|
||||||
|
for j in range(num_points):
|
||||||
|
point_id = cell.GetPointId(j)
|
||||||
|
value = [0] * num_components
|
||||||
|
array.GetTuple(point_id, value)
|
||||||
|
avg_value += np.array(value)
|
||||||
|
avg_value /= num_points
|
||||||
|
print(f"Average point data '{array_name}': {avg_value}")
|
||||||
|
|
||||||
|
if num_points and cell_data:
|
||||||
|
face_orient = {'cell_data': cell_data, 'points': vec_points }
|
||||||
|
print(face_orient)
|
||||||
|
self.face_data.emit(face_orient)
|
||||||
|
|
||||||
|
# Highlight picked face (your existing code)
|
||||||
|
ids = vtk.vtkIdTypeArray()
|
||||||
|
ids.SetNumberOfComponents(1)
|
||||||
|
ids.InsertNextValue(cell_id)
|
||||||
|
|
||||||
|
selection_node = vtk.vtkSelectionNode()
|
||||||
|
selection_node.SetFieldType(vtk.vtkSelectionNode.CELL)
|
||||||
|
selection_node.SetContentType(vtk.vtkSelectionNode.INDICES)
|
||||||
|
selection_node.SetSelectionList(ids)
|
||||||
|
|
||||||
|
selection = vtk.vtkSelection()
|
||||||
|
selection.AddNode(selection_node)
|
||||||
|
|
||||||
|
extract_selection = vtk.vtkExtractSelection()
|
||||||
|
extract_selection.SetInputData(0, polydata)
|
||||||
|
extract_selection.SetInputData(1, selection)
|
||||||
|
extract_selection.Update()
|
||||||
|
|
||||||
|
self.picked_mapper.SetInputData(extract_selection.GetOutput())
|
||||||
|
self.vtk_widget.GetRenderWindow().Render()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.interactor.Initialize()
|
||||||
|
self.interactor.Start()
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.vtk_widget = VTKWidget()
|
||||||
|
self.setCentralWidget(self.vtk_widget)
|
||||||
|
self.setWindowTitle("VTK Mesh Viewer")
|
||||||
|
self.vtk_widget.create_cube_mesh()
|
||||||
|
self.show()
|
||||||
|
self.vtk_widget.start()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
window = MainWindow()
|
||||||
|
sys.exit(app.exec())
|
111
drawing_modules/vysta_widget.py
Normal file
111
drawing_modules/vysta_widget.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pyvista as pv
|
||||||
|
from pyvista.plotting.opts import ElementType
|
||||||
|
from pyvistaqt import QtInteractor
|
||||||
|
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class PyVistaWidget(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# Create the PyVista plotter
|
||||||
|
self.plotter = QtInteractor(self)
|
||||||
|
self.plotter.background_color = "darkgray"
|
||||||
|
|
||||||
|
# Create a layout and add the PyVista widget
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.plotter.interactor)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Set up the picker
|
||||||
|
#self.plotter.enable_cell_picking(callback=self.on_cell_pick, show=True)
|
||||||
|
self.plotter.enable_element_picking(callback=self.on_cell_pick, show=True, mode="face", left_clicking=True)
|
||||||
|
|
||||||
|
def on_cell_pick(self, element):
|
||||||
|
if element is not None:
|
||||||
|
mesh = self.plotter.mesh # Get the current mesh
|
||||||
|
print(mesh)
|
||||||
|
print(element)
|
||||||
|
|
||||||
|
"""# Get the face data
|
||||||
|
face = mesh.extract_cells(element)
|
||||||
|
|
||||||
|
# Compute face normal
|
||||||
|
face.compute_normals(cell_normals=True, inplace=True)
|
||||||
|
normal = face.cell_data['Normals'][0]
|
||||||
|
|
||||||
|
# Get the points of the face
|
||||||
|
points = face.points
|
||||||
|
|
||||||
|
print(f"Picked face ID: {face_id}")
|
||||||
|
print(f"Face normal: {normal}")
|
||||||
|
print("Face points:")
|
||||||
|
for point in points:
|
||||||
|
print(point)"""
|
||||||
|
else:
|
||||||
|
print("No face was picked or the picked element is not a face.")
|
||||||
|
def create_simplified_outline(self, mesh, camera):
|
||||||
|
# Project 3D to 2D
|
||||||
|
points_2d = self.plotter.map_to_2d(mesh.points)
|
||||||
|
|
||||||
|
# Detect silhouette edges (simplified approach)
|
||||||
|
edges = mesh.extract_feature_edges(feature_angle=90, boundary_edges=False, non_manifold_edges=False)
|
||||||
|
|
||||||
|
# Project edges to 2D
|
||||||
|
edge_points_2d = self.plotter.map_to_2d(edges.points)
|
||||||
|
|
||||||
|
# Create 2D outline
|
||||||
|
self.plotter.add_lines(edge_points_2d, color='black', width=2)
|
||||||
|
self.plotter.render()
|
||||||
|
|
||||||
|
def mesh_from_points(self, points):
|
||||||
|
# Convert points to numpy array if not already
|
||||||
|
points = np.array(points)
|
||||||
|
|
||||||
|
# Create faces array
|
||||||
|
num_triangles = len(points) // 3
|
||||||
|
faces = np.arange(len(points)).reshape(num_triangles, 3)
|
||||||
|
faces = np.column_stack((np.full(num_triangles, 3), faces)) # Add 3 as first column
|
||||||
|
|
||||||
|
# Create PyVista PolyData
|
||||||
|
mesh = pv.PolyData(points, faces)
|
||||||
|
|
||||||
|
# Optional: Merge duplicate points
|
||||||
|
mesh = mesh.clean()
|
||||||
|
|
||||||
|
# Optional: Compute normals
|
||||||
|
mesh = mesh.compute_normals(point_normals=False, cell_normals=True, consistent_normals=True)
|
||||||
|
edges = mesh.extract_feature_edges(30, non_manifold_edges=False)
|
||||||
|
|
||||||
|
# Clear any existing meshes
|
||||||
|
self.plotter.clear()
|
||||||
|
|
||||||
|
# Add the mesh to the plotter
|
||||||
|
self.plotter.add_mesh(mesh, pickable=True, color='white', show_edges=True, line_width=2, pbr=True, metallic=0.8, roughness=0.1, diffuse=1)
|
||||||
|
self.plotter.add_mesh(edges, color="red", line_width=10)
|
||||||
|
|
||||||
|
# Reset the camera to fit the new mesh
|
||||||
|
self.plotter.reset_camera()
|
||||||
|
|
||||||
|
# Update the render window
|
||||||
|
self.plotter.update()
|
||||||
|
|
||||||
|
# Print statistics
|
||||||
|
print(f"Original points: {len(points)}")
|
||||||
|
print(f"Number of triangles: {num_triangles}")
|
||||||
|
print(f"Final number of points: {mesh.n_points}")
|
||||||
|
print(f"Final number of cells: {mesh.n_cells}")
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("PyVista in PySide6")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
88
main.py
88
main.py
@ -1,13 +1,14 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import names
|
import names
|
||||||
from PySide6.QtCore import Qt, QPoint
|
from PySide6.QtCore import Qt, QPoint
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog
|
from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog
|
||||||
from Gui import Ui_fluencyCAD # Import the generated GUI module
|
from Gui import Ui_fluencyCAD # Import the generated GUI module
|
||||||
from drawing_modules.gl_widget import OpenGLWidget
|
from drawing_modules.vtk_widget import VTKWidget
|
||||||
|
from drawing_modules.vysta_widget import PyVistaWidget
|
||||||
from drawing_modules.draw_widget2d import SketchWidget
|
from drawing_modules.draw_widget2d import SketchWidget
|
||||||
from sdf import *
|
from sdf import *
|
||||||
from python_solvespace import SolverSystem, ResultFlag
|
from python_solvespace import SolverSystem, ResultFlag
|
||||||
|
from mesh_modules import simple_mesh
|
||||||
|
|
||||||
# main, draw_widget, gl_widget
|
# main, draw_widget, gl_widget
|
||||||
|
|
||||||
@ -19,11 +20,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui = Ui_fluencyCAD()
|
self.ui = Ui_fluencyCAD()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
self.openGLWidget = OpenGLWidget()
|
self.custom_3D_Widget = VTKWidget()
|
||||||
layout = self.ui.gl_box.layout()
|
layout = self.ui.gl_box.layout()
|
||||||
layout.addWidget(self.openGLWidget)
|
layout.addWidget(self.custom_3D_Widget)
|
||||||
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||||
#self.openGLWidget.setSizePolicy(size_policy)
|
#self.custom_3D_Widget.setSizePolicy(size_policy)
|
||||||
|
|
||||||
self.sketchWidget = SketchWidget()
|
self.sketchWidget = SketchWidget()
|
||||||
layout2 = self.ui.sketch_tab.layout() # Get the layout of self.ui.gl_canvas
|
layout2 = self.ui.sketch_tab.layout() # Get the layout of self.ui.gl_canvas
|
||||||
@ -40,10 +41,10 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
#self.ui.pb_apply_code.pressed.connect(self.check_current_tab)
|
#self.ui.pb_apply_code.pressed.connect(self.check_current_tab)
|
||||||
self.ui.sketch_list.currentItemChanged.connect(self.on_item_changed)
|
self.ui.sketch_list.currentItemChanged.connect(self.on_item_changed)
|
||||||
self.ui.sketch_list.itemChanged.connect(self.view_update)
|
self.ui.sketch_list.itemChanged.connect(self.draw_mesh)
|
||||||
|
|
||||||
### Sketches
|
### Sketches
|
||||||
self.ui.pb_origin_wp.pressed.connect(self.add_wp_origin)
|
self.ui.pb_origin_wp.pressed.connect(self.add_new_sketch)
|
||||||
|
|
||||||
self.ui.pb_nw_sktch.pressed.connect(self.add_sketch)
|
self.ui.pb_nw_sktch.pressed.connect(self.add_sketch)
|
||||||
self.ui.pb_del_sketch.pressed.connect(self.del_sketch)
|
self.ui.pb_del_sketch.pressed.connect(self.del_sketch)
|
||||||
@ -65,7 +66,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self.sketchWidget.constrain_done.connect(self.draw_op_complete)
|
self.sketchWidget.constrain_done.connect(self.draw_op_complete)
|
||||||
|
|
||||||
def add_wp_origin(self):
|
def add_new_sketch(self):
|
||||||
|
self.sketchWidget.clear_sketch()
|
||||||
self.sketchWidget.create_workplane()
|
self.sketchWidget.create_workplane()
|
||||||
|
|
||||||
def act_line_mode(self):
|
def act_line_mode(self):
|
||||||
@ -124,13 +126,24 @@ class MainWindow(QMainWindow):
|
|||||||
self.sketchWidget.mouse_mode = None
|
self.sketchWidget.mouse_mode = None
|
||||||
self.sketchWidget.reset_buffers()
|
self.sketchWidget.reset_buffers()
|
||||||
|
|
||||||
def view_update(self):
|
def calc_sketch_projection_3d(self, depth):
|
||||||
print("Update")
|
name = self.ui.sketch_list.currentItem().text()
|
||||||
|
#print("selected_for disp", name)
|
||||||
|
model = self.model['sketch'][name]['sketch_points']
|
||||||
|
#print("sketch points from model", model)
|
||||||
|
simp_mesh = simple_mesh.generate_mesh(model, depth)
|
||||||
|
print("Generated model", simp_mesh)
|
||||||
|
self.custom_3D_Widget. load_interactor_mesh(simp_mesh) #draw_interactor(simp_mesh)
|
||||||
|
|
||||||
|
def draw_mesh(self):
|
||||||
|
#print("Update")
|
||||||
name = self.ui.body_list.currentItem().text()
|
name = self.ui.body_list.currentItem().text()
|
||||||
print("selected_for disp", name)
|
#print("selected_for disp", name)
|
||||||
model = self.model['operation'][name]['sdf_object']
|
model = self.model['operation'][name]['sdf_object']
|
||||||
mesh = model.generate(samples=2**12)
|
mesh = model.generate()
|
||||||
self.openGLWidget.load_mesh_direct(mesh)
|
#print("Mesh sdf", mesh)
|
||||||
|
#self.custom_3D_Widget.render_from_points_direct_with_faces(mesh)
|
||||||
|
self.custom_3D_Widget.render_from_points_direct_with_faces(mesh)
|
||||||
|
|
||||||
def on_item_changed(self, current_item, previous_item):
|
def on_item_changed(self, current_item, previous_item):
|
||||||
if current_item:
|
if current_item:
|
||||||
@ -138,16 +151,25 @@ class MainWindow(QMainWindow):
|
|||||||
#self.view_update()
|
#self.view_update()
|
||||||
print(f"Selected item: {name}")
|
print(f"Selected item: {name}")
|
||||||
|
|
||||||
def add_sketch(self):
|
def convert_points_for_sdf(self):
|
||||||
points_for_poly = []
|
points_for_sdf = []
|
||||||
name = f"sketch-{str(names.get_first_name())}"
|
|
||||||
for point_to_poly in self.sketchWidget.slv_points_main:
|
for point_to_poly in self.sketchWidget.slv_points_main:
|
||||||
points_for_poly.append(self.translate_points_tup(point_to_poly['ui_point']))
|
points_for_sdf.append(self.translate_points_tup(point_to_poly['ui_point']))
|
||||||
|
|
||||||
|
return points_for_sdf
|
||||||
|
|
||||||
|
def add_sketch(self):
|
||||||
|
|
||||||
|
name = f"sketch-{str(names.get_first_name())}"
|
||||||
|
points_for_sdf = self.convert_points_for_sdf()
|
||||||
|
|
||||||
element = {
|
element = {
|
||||||
'id': name,
|
'id': name,
|
||||||
'type': 'polygon',
|
'type': 'sketch',
|
||||||
'sketch_points': points_for_poly,
|
'point_list': self.sketchWidget.slv_points_main,
|
||||||
|
'line_list': self.sketchWidget.slv_lines_main,
|
||||||
|
'sketch_points': points_for_sdf,
|
||||||
|
'solver': self.sketchWidget.solv
|
||||||
}
|
}
|
||||||
|
|
||||||
self.model['sketch'][element['id']] = element
|
self.model['sketch'][element['id']] = element
|
||||||
@ -162,10 +184,15 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def edit_sketch(self):
|
def edit_sketch(self):
|
||||||
name = self.ui.sketch_list.currentItem().text()
|
name = self.ui.sketch_list.currentItem().text()
|
||||||
self.sketchWidget.clear_sketch()
|
#self.sketchWidget.clear_sketch()
|
||||||
points = self.model['sketch'][name]['sketch_points']
|
|
||||||
print("points", points)
|
self.sketchWidget.slv_points_main = self.model['sketch'][name]['point_list']
|
||||||
self.sketchWidget.set_points(points)
|
self.sketchWidget.slv_lines_main = self.model['sketch'][name]['line_list']
|
||||||
|
self.sketchWidget.solv = self.model['sketch'][name]['solver']
|
||||||
|
|
||||||
|
self.sketchWidget.update()
|
||||||
|
print("model",self.model)
|
||||||
|
print("widget", self.sketchWidget.slv_points_main)
|
||||||
|
|
||||||
def del_sketch(self):
|
def del_sketch(self):
|
||||||
print("Deleting")
|
print("Deleting")
|
||||||
@ -217,7 +244,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.body_list.takeItem(row) # Remove the item from the list widget
|
self.ui.body_list.takeItem(row) # Remove the item from the list widget
|
||||||
self.model['operation'].pop(item_name) # Remove the item from the operation dictionary
|
self.model['operation'].pop(item_name) # Remove the item from the operation dictionary
|
||||||
print(f"Removed operation: {item_name}")
|
print(f"Removed operation: {item_name}")
|
||||||
self.openGLWidget.clear_mesh()
|
self.custom_3D_Widget.clear_mesh()
|
||||||
|
|
||||||
def translate_points_tup(self, point: QPoint):
|
def translate_points_tup(self, point: QPoint):
|
||||||
"""QPoints from Display to mesh data
|
"""QPoints from Display to mesh data
|
||||||
@ -240,8 +267,11 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2)
|
length, ok = QInputDialog.getDouble(self, 'Extrude Length', 'Enter a mm value:', decimals=2)
|
||||||
#TODO : Implement cancel
|
#TODO : Implement cancel
|
||||||
|
|
||||||
|
#Create and draw Interactor
|
||||||
geo = Geometry()
|
geo = Geometry()
|
||||||
f = geo.extrude_shape(points, length)
|
f = geo.extrude_shape(points, length)
|
||||||
|
|
||||||
name_op = f"extrd-{name}"
|
name_op = f"extrd-{name}"
|
||||||
element = {
|
element = {
|
||||||
'id': name_op,
|
'id': name_op,
|
||||||
@ -251,11 +281,11 @@ class MainWindow(QMainWindow):
|
|||||||
print(element)
|
print(element)
|
||||||
|
|
||||||
self.model['operation'][name_op] = element
|
self.model['operation'][name_op] = element
|
||||||
|
|
||||||
self.ui.body_list.addItem(name_op)
|
self.ui.body_list.addItem(name_op)
|
||||||
items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0]
|
items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0]
|
||||||
self.ui.body_list.setCurrentItem(items)
|
self.ui.body_list.setCurrentItem(items)
|
||||||
self.view_update()
|
self.draw_mesh()
|
||||||
|
#self.calc_sketch_projection_3d(length)
|
||||||
|
|
||||||
def send_cut(self):
|
def send_cut(self):
|
||||||
name = self.ui.body_list.currentItem().text()
|
name = self.ui.body_list.currentItem().text()
|
||||||
@ -278,13 +308,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.ui.body_list.addItem(name_op)
|
self.ui.body_list.addItem(name_op)
|
||||||
items = self.ui.sketch_list.findItems(name_op, Qt.MatchExactly)
|
items = self.ui.sketch_list.findItems(name_op, Qt.MatchExactly)
|
||||||
self.ui.body_list.setCurrentItem(items[-1])
|
self.ui.body_list.setCurrentItem(items[-1])
|
||||||
self.view_update()
|
self.draw_mesh()
|
||||||
else:
|
else:
|
||||||
print("mindestens 2!")
|
print("mindestens 2!")
|
||||||
|
|
||||||
def load_and_render(self, file):
|
def load_and_render(self, file):
|
||||||
self.openGLWidget.load_stl(file)
|
self.custom_3D_Widget.load_stl(file)
|
||||||
self.openGLWidget.update()
|
self.custom_3D_Widget.update()
|
||||||
|
|
||||||
class Geometry:
|
class Geometry:
|
||||||
def distance(self, p1, p2):
|
def distance(self, p1, p2):
|
||||||
|
50
mesh_modules/simple_mesh.py
Normal file
50
mesh_modules/simple_mesh.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import ConvexHull
|
||||||
|
from stl import mesh
|
||||||
|
|
||||||
|
|
||||||
|
def generate_mesh(points, depth):
|
||||||
|
"""
|
||||||
|
Generate a mesh by extruding a 2D shape along the Z-axis.
|
||||||
|
|
||||||
|
:param points: List of (x, y) tuples representing the 2D shape.
|
||||||
|
:param depth: Extrusion depth along the Z-axis.
|
||||||
|
:return: Tuple of vertices and faces.
|
||||||
|
"""
|
||||||
|
# Convert points to a numpy array
|
||||||
|
points_2d = np.array(points)
|
||||||
|
|
||||||
|
# Get the convex hull of the points to ensure they form a proper polygon
|
||||||
|
hull = ConvexHull(points_2d)
|
||||||
|
hull_points = points_2d[hull.vertices]
|
||||||
|
|
||||||
|
# Generate the top and bottom faces
|
||||||
|
bottom_face = np.hstack((hull_points, np.zeros((hull_points.shape[0], 1))))
|
||||||
|
top_face = np.hstack((hull_points, np.ones((hull_points.shape[0], 1)) * depth))
|
||||||
|
|
||||||
|
# Combine top and bottom vertices
|
||||||
|
vertices_array = np.vstack((bottom_face, top_face))
|
||||||
|
|
||||||
|
# Create faces
|
||||||
|
faces = []
|
||||||
|
|
||||||
|
# Bottom face triangulation (counter-clockwise)
|
||||||
|
for i in range(len(hull_points) - 2):
|
||||||
|
faces.append([0, i + 2, i + 1])
|
||||||
|
|
||||||
|
# Top face triangulation (counter-clockwise, with an offset)
|
||||||
|
top_offset = len(hull_points)
|
||||||
|
for i in range(len(hull_points) - 2):
|
||||||
|
faces.append([top_offset, top_offset + i + 1, top_offset + i + 2])
|
||||||
|
|
||||||
|
# Side faces (ensure counter-clockwise order)
|
||||||
|
for i in range(len(hull_points)):
|
||||||
|
next_i = (i + 1) % len(hull_points)
|
||||||
|
faces.append([i, top_offset + i, top_offset + next_i])
|
||||||
|
faces.append([i, top_offset + next_i, next_i])
|
||||||
|
|
||||||
|
# Convert vertices to the desired format: list of tuples
|
||||||
|
vertices = [tuple(vertex) for vertex in vertices_array]
|
||||||
|
|
||||||
|
return vertices, faces
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user