- 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))
|
||||
|
||||
def clear_sketch(self):
|
||||
self.points = []
|
||||
self.update()
|
||||
self.slv_points_main = []
|
||||
self.slv_lines_main = []
|
||||
self.reset_buffers()
|
||||
self.solv = SolverSystem()
|
||||
|
||||
|
||||
# Example usage
|
||||
|
@ -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())
|
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 names
|
||||
from PySide6.QtCore import Qt, QPoint
|
||||
from PySide6.QtWidgets import QApplication, QMainWindow, QSizePolicy, QInputDialog
|
||||
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 sdf import *
|
||||
from python_solvespace import SolverSystem, ResultFlag
|
||||
from mesh_modules import simple_mesh
|
||||
|
||||
# main, draw_widget, gl_widget
|
||||
|
||||
@ -19,11 +20,11 @@ class MainWindow(QMainWindow):
|
||||
self.ui = Ui_fluencyCAD()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.openGLWidget = OpenGLWidget()
|
||||
self.custom_3D_Widget = VTKWidget()
|
||||
layout = self.ui.gl_box.layout()
|
||||
layout.addWidget(self.openGLWidget)
|
||||
layout.addWidget(self.custom_3D_Widget)
|
||||
size_policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||
#self.openGLWidget.setSizePolicy(size_policy)
|
||||
#self.custom_3D_Widget.setSizePolicy(size_policy)
|
||||
|
||||
self.sketchWidget = SketchWidget()
|
||||
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.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
|
||||
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_del_sketch.pressed.connect(self.del_sketch)
|
||||
@ -65,7 +66,8 @@ class MainWindow(QMainWindow):
|
||||
|
||||
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()
|
||||
|
||||
def act_line_mode(self):
|
||||
@ -124,13 +126,24 @@ class MainWindow(QMainWindow):
|
||||
self.sketchWidget.mouse_mode = None
|
||||
self.sketchWidget.reset_buffers()
|
||||
|
||||
def view_update(self):
|
||||
print("Update")
|
||||
def calc_sketch_projection_3d(self, depth):
|
||||
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()
|
||||
print("selected_for disp", name)
|
||||
#print("selected_for disp", name)
|
||||
model = self.model['operation'][name]['sdf_object']
|
||||
mesh = model.generate(samples=2**12)
|
||||
self.openGLWidget.load_mesh_direct(mesh)
|
||||
mesh = model.generate()
|
||||
#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):
|
||||
if current_item:
|
||||
@ -138,16 +151,25 @@ class MainWindow(QMainWindow):
|
||||
#self.view_update()
|
||||
print(f"Selected item: {name}")
|
||||
|
||||
def add_sketch(self):
|
||||
points_for_poly = []
|
||||
name = f"sketch-{str(names.get_first_name())}"
|
||||
def convert_points_for_sdf(self):
|
||||
points_for_sdf = []
|
||||
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 = {
|
||||
'id': name,
|
||||
'type': 'polygon',
|
||||
'sketch_points': points_for_poly,
|
||||
'type': 'sketch',
|
||||
'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
|
||||
@ -162,10 +184,15 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def edit_sketch(self):
|
||||
name = self.ui.sketch_list.currentItem().text()
|
||||
self.sketchWidget.clear_sketch()
|
||||
points = self.model['sketch'][name]['sketch_points']
|
||||
print("points", points)
|
||||
self.sketchWidget.set_points(points)
|
||||
#self.sketchWidget.clear_sketch()
|
||||
|
||||
self.sketchWidget.slv_points_main = self.model['sketch'][name]['point_list']
|
||||
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):
|
||||
print("Deleting")
|
||||
@ -217,7 +244,7 @@ class MainWindow(QMainWindow):
|
||||
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
|
||||
print(f"Removed operation: {item_name}")
|
||||
self.openGLWidget.clear_mesh()
|
||||
self.custom_3D_Widget.clear_mesh()
|
||||
|
||||
def translate_points_tup(self, point: QPoint):
|
||||
"""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)
|
||||
#TODO : Implement cancel
|
||||
|
||||
#Create and draw Interactor
|
||||
geo = Geometry()
|
||||
f = geo.extrude_shape(points, length)
|
||||
|
||||
name_op = f"extrd-{name}"
|
||||
element = {
|
||||
'id': name_op,
|
||||
@ -251,11 +281,11 @@ class MainWindow(QMainWindow):
|
||||
print(element)
|
||||
|
||||
self.model['operation'][name_op] = element
|
||||
|
||||
self.ui.body_list.addItem(name_op)
|
||||
items = self.ui.body_list.findItems(name_op, Qt.MatchExactly)[0]
|
||||
self.ui.body_list.setCurrentItem(items)
|
||||
self.view_update()
|
||||
self.draw_mesh()
|
||||
#self.calc_sketch_projection_3d(length)
|
||||
|
||||
def send_cut(self):
|
||||
name = self.ui.body_list.currentItem().text()
|
||||
@ -278,13 +308,13 @@ class MainWindow(QMainWindow):
|
||||
self.ui.body_list.addItem(name_op)
|
||||
items = self.ui.sketch_list.findItems(name_op, Qt.MatchExactly)
|
||||
self.ui.body_list.setCurrentItem(items[-1])
|
||||
self.view_update()
|
||||
self.draw_mesh()
|
||||
else:
|
||||
print("mindestens 2!")
|
||||
|
||||
def load_and_render(self, file):
|
||||
self.openGLWidget.load_stl(file)
|
||||
self.openGLWidget.update()
|
||||
self.custom_3D_Widget.load_stl(file)
|
||||
self.custom_3D_Widget.update()
|
||||
|
||||
class Geometry:
|
||||
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