import os import sys import cv2 import signal import vtkmodules.all as vtk from PyQt6.QtCore import QTimer, QSocketNotifier, QObject, pyqtSignal from PyQt6.QtWidgets import ( QApplication, QMainWindow, QFrame, QVBoxLayout, QWidget, QLabel, QHBoxLayout ) from PyQt6.QtGui import QImage, QPixmap from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor class SigintHandler(QObject): sigint_received = pyqtSignal() def __init__(self): super().__init__() self._fd_r, self._fd_w = os.pipe() signal.signal(signal.SIGINT, self._sigint_handler) self._notifier = QSocketNotifier(self._fd_r, QSocketNotifier.Type.Read) self._notifier.activated.connect(self._emit_signal) def _sigint_handler(self, signum, frame): os.write(self._fd_w, b'\x00') def _emit_signal(self): os.read(self._fd_r, 1) self.sigint_received.emit() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("PLY Viewer with Live OpenCV Feed") # === Layout Setup === central_widget = QWidget() h_layout = QHBoxLayout(central_widget) # === VTK PLY Viewer === self.vtk_frame = QFrame() self.vtk_layout = QVBoxLayout() self.vtkWidget = QVTKRenderWindowInteractor(self.vtk_frame) self.vtk_layout.addWidget(self.vtkWidget) self.vtk_frame.setLayout(self.vtk_layout) self.renderer = vtk.vtkRenderer() reader = vtk.vtkPLYReader() reader.SetFileName("your_model.ply") # 🔁 Replace with your PLY file path reader.Update() mapper = vtk.vtkPolyDataMapper() mapper.SetInputConnection(reader.GetOutputPort()) actor = vtk.vtkActor() actor.SetMapper(mapper) self.renderer.AddActor(actor) self.renderer.SetBackground(0.1, 0.2, 0.4) self.vtkWidget.GetRenderWindow().AddRenderer(self.renderer) self.iren = self.vtkWidget.GetRenderWindow().GetInteractor() self.iren.Initialize() # === OpenCV Camera Panel === self.image_label = QLabel("Starting camera...") self.image_label.setFixedWidth(320) self.image_label.setScaledContents(True) # === Layout Assembly === h_layout.addWidget(self.vtk_frame, stretch=3) h_layout.addWidget(self.image_label, stretch=1) self.setCentralWidget(central_widget) # === OpenCV Capture and Timer === self.cap = cv2.VideoCapture(0) self.timer = QTimer() self.timer.timeout.connect(self.update_image) self.timer.start(30) def update_image(self): ret, frame = self.cap.read() if ret: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = frame.shape bytes_per_line = ch * w qimg = QImage(frame.data, w, h, bytes_per_line, QImage.Format.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qimg)) else: self.image_label.setText("Camera error") def closeEvent(self, event): if self.cap.isOpened(): self.cap.release() event.accept() class MainApp(QApplication): def __init__(self, argv): super().__init__(argv) self.sigint_handler = SigintHandler() self.sigint_handler.sigint_received.connect(self.quit_on_sigint) self.window = MainWindow() self.window.resize(1200, 700) self.window.show() def quit_on_sigint(self): print("SIGINT received — exiting cleanly.") raise KeyboardInterrupt if __name__ == "__main__": try: app = MainApp(sys.argv) sys.exit(app.exec()) except KeyboardInterrupt: print("KeyboardInterrupt caught — shutting down.") sys.exit(0)