From 9daf263aad984d34623eccb402fd2a65a00810fd Mon Sep 17 00:00:00 2001
From: bklronin <therrmann23@gmail.com>
Date: Thu, 4 Jul 2024 22:58:52 +0200
Subject: [PATCH] - Implemented vtk base for viewing

---
 drawing_modules/draw_widget2d.py |   6 +-
 drawing_modules/gl_widget.py     | 338 +++++++++++++++++++++--
 drawing_modules/vtk_widget.py    | 447 +++++++++++++++++++++++++++++++
 drawing_modules/vysta_widget.py  | 111 ++++++++
 main.py                          |  88 ++++--
 mesh_modules/simple_mesh.py      |  50 ++++
 6 files changed, 989 insertions(+), 51 deletions(-)
 create mode 100644 drawing_modules/vtk_widget.py
 create mode 100644 drawing_modules/vysta_widget.py
 create mode 100644 mesh_modules/simple_mesh.py

diff --git a/drawing_modules/draw_widget2d.py b/drawing_modules/draw_widget2d.py
index f7435ed..b7dc005 100644
--- a/drawing_modules/draw_widget2d.py
+++ b/drawing_modules/draw_widget2d.py
@@ -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
diff --git a/drawing_modules/gl_widget.py b/drawing_modules/gl_widget.py
index 2a0ea4c..6bfe56e 100644
--- a/drawing_modules/gl_widget.py
+++ b/drawing_modules/gl_widget.py
@@ -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())
\ No newline at end of file
diff --git a/drawing_modules/vtk_widget.py b/drawing_modules/vtk_widget.py
new file mode 100644
index 0000000..46656dc
--- /dev/null
+++ b/drawing_modules/vtk_widget.py
@@ -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())
diff --git a/drawing_modules/vysta_widget.py b/drawing_modules/vysta_widget.py
new file mode 100644
index 0000000..c3f8d51
--- /dev/null
+++ b/drawing_modules/vysta_widget.py
@@ -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)
+
+
+
+
diff --git a/main.py b/main.py
index 4758028..9006f51 100644
--- a/main.py
+++ b/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):
diff --git a/mesh_modules/simple_mesh.py b/mesh_modules/simple_mesh.py
new file mode 100644
index 0000000..e7a181c
--- /dev/null
+++ b/mesh_modules/simple_mesh.py
@@ -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
+