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())