Skip to content

Instantly share code, notes, and snippets.

@kwahoo2
Created August 13, 2023 22:00
Show Gist options
  • Save kwahoo2/145dc9f7019c5d485a824225182d705f to your computer and use it in GitHub Desktop.
Save kwahoo2/145dc9f7019c5d485a824225182d705f to your computer and use it in GitHub Desktop.
# This script creates a simple Qt Widget, renders it to a Coin3D texture (SoTexture2), and creates a cube (SoCube) with that texture.
# It also simulates (def simulate_picking(self)) a 3D ray that intersects with Coin3D render of the QWidget. If the point of intersection is on the "Hello World" button widget, then QMouseEvent (pressed and relesed) is triggered and button activation is animated.
from OpenGL import GL
from PySide2.QtWidgets import QOpenGLWidget, QApplication
from PySide2.QtCore import Qt, QTimer, Signal, Slot
from PySide2.QtGui import QOpenGLContext
from PySide2.QtGui import qGray, qRed, qGreen, qBlue, qAlpha
from pivy.coin import SoFrustumCamera
from pivy.coin import SoSeparator
from pivy.coin import SoSceneManager
from pivy.coin import SbViewportRegion
from pivy.coin import SbColor, SoBaseColor
from pivy.coin import SoTranslation
from pivy.coin import SoCube, SoSphere
from pivy.coin import SoDirectionalLight
from pivy.coin import SoTexture2, SoSFImage
from pivy.coin import SbVec2s, SbVec3f, SbVec4f
from pivy.coin import SoRayPickAction, SoLineSet, SoVertexProperty
import sys
import random
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton
from PySide2.QtGui import QPixmap, QPainter, QMouseEvent
from PySide2.QtCore import QPoint
class myOpenGLWidget(QOpenGLWidget):
simulate_click = Signal(SbVec4f)
gl_w_closed = Signal()
def __init__(self, parent=None):
QOpenGLWidget.__init__(self, parent)
self.pick_timer = QTimer()
self.pick_timer.timeout.connect(self.simulate_picking)
self.pick_timer.start(1000)
w = 600
h = 600
self.context = QOpenGLContext()
camera = SoFrustumCamera()
self.vp_reg = SbViewportRegion(w, h)
self.resize(w, h)
self.m_scenemanager = SoSceneManager()
self.m_scenemanager.setViewportRegion(self.vp_reg)
#self.m_scenemanager.setBackgroundColor(SbColor(0.0, 0.0, 0.8))
self.rootScene = SoSeparator()
self.rootScene.ref()
camera.nearDistance.setValue(0.1)
camera.farDistance.setValue(1000)
camera.position.setValue(0, 0, -0.9)
camera.orientation.setValue(0.045, -0.045, -0.041, 0.997)
self.rootScene.addChild(camera)
light = SoDirectionalLight()
self.rootScene.addChild(light)
self.sep = SoSeparator()
self.pick_sep = SoSeparator()
self.rootScene.addChild(self.pick_sep)
self.rootScene.addChild(self.sep)
trans = SoTranslation()
trans.translation.setValue(0, 0.0, -2.5)
self.sep.addChild(trans)
# Make a cube
self.sep.addChild(SoCube())
# Add a texture
self.textu = SoTexture2()
self.sep.insertChild(self.textu, 1)
self.m_scenemanager.setSceneGraph(self.rootScene)
self.col2 = SoBaseColor() # blue sphere to show intersection point
self.col2.rgb = SbColor(0, 0, 1)
self.pick_sep.addChild(self.col2)
self.line = SoLineSet()
self.pick_sep.addChild(self.line)
self.trans2 = SoTranslation()
self.pick_sep.addChild(self.trans2)
self.sph = SoSphere()
self.sph.radius.setValue(0.05)
self.pick_sep.addChild(self.sph)
def do_picking (self, start, dire):
# setup of the ray vector for picking
m_pick_action = SoRayPickAction(self.vp_reg)
m_pick_action.setRay(start, dire) # starting point and direction vector
print ("Ray: ", start.getValue(), dire.getValue())
m_pick_action.apply(self.sep)
picked_point = m_pick_action.getPickedPoint()
if picked_point:
pnt_coords = picked_point.getPoint() # intersection SbVec3f
print ("Intersection point: ", pnt_coords.getValue())
tex_coords = picked_point.getTextureCoords() # SbVec4f
print ("Texture intersection coords: ", tex_coords.getValue())
self.col2.rgb = SbColor(0, 0, 1)
self.trans2.translation.setValue(pnt_coords)
vprop = SoVertexProperty()
vprop.vertex.set1Value(0, start) # Set first vertex to be 0, 0, 0
vprop.vertex.set1Value(1, pnt_coords) # Set second vertex
self.line.vertexProperty = vprop
self.simulate_click.emit(tex_coords)
else:
self.col2.rgb = SbColor(1, 0, 0) # blue sphere if picked, red if not
self.update()
def simulate_picking(self):
# Creates a random 3D picking ray defined by two vectors: start point and direction
start = SbVec3f(0.0, 0.0, 0.0)
direction = SbVec3f(random.uniform(-0.5, 0.5), random.uniform(-0.2, 0.2) , -1.0)
self.do_picking(start, direction)
def initializeGL(self):
funcs = self.context.functions()
funcs.initializeOpenGLFunctions()
self.context.create()
def resizeGL(self, w, h):
self.vp_reg = SbViewportRegion(w, h)
self.m_scenemanager.setViewportRegion(self.vp_reg)
def paintGL(self):
GL.glEnable(GL.GL_LIGHTING)
GL.glEnable(GL.GL_CULL_FACE)
GL.glEnable(GL.GL_DEPTH_TEST)
self.m_scenemanager.render()
GL.glDisable(GL.GL_CULL_FACE)
GL.glDisable(GL.GL_DEPTH_TEST)
@Slot(SoSFImage)
def update_texture(self, img):
self.textu.image = img
self.update()
def closeEvent(self, event):
self.rootScene.unref()
self.gl_w_closed.emit()
event.accept()
class sourceWidget(QWidget):
widget_rend_soimage = Signal(SoSFImage)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
# Create a QWidget and layout
layout = QVBoxLayout()
self.setLayout(layout)
self.button = QPushButton("Hello, World!")
layout.addWidget(self.button)
self.resize(300,300)
# circle showing where an event happened
self.circle_center = None
self.button.clicked.connect(self.button_click)
self.render_flag = False
def button_click(self):
print ("Button clicked")
self.button.blockSignals(True)
self.button.animateClick() # executes 100ms animated click, othewise event would be invisible for the user
QTimer.singleShot(200, self.unblock_button_signals)
def unblock_button_signals(self):
self.button.blockSignals(False)
def paintEvent(self, event):
if not self.render_flag:
QTimer.singleShot(0, self.render_widget) # avoid recursive repaint
def render_widget(self):
# Render the QWidget to an RGB image
self.render_flag = True
qimg = self.render_qwidget_to_rgb_image()
soimg = qimage_to_sosfimage(qimg)
self.widget_rend_soimage.emit(soimg)
self.render_flag = False
@Slot(SbVec4f)
def simulate_click(self, tex_coords):
s = self.size()
u_widget = int(s.width() * tex_coords[0])
v_widget = int(s.height() * (1 - tex_coords[1]))
pos = QPoint(u_widget, v_widget)
glo_pos = self.mapToGlobal(pos)
target_widget = self.childAt(pos)
if target_widget:
pos_on_target = target_widget.mapFromGlobal(glo_pos)
press_event = QMouseEvent(
QMouseEvent.MouseButtonPress,
pos_on_target,
Qt.LeftButton,
Qt.LeftButton,
Qt.NoModifier,
)
QApplication.sendEvent(target_widget, press_event)
release_event = QMouseEvent(
QMouseEvent.MouseButtonRelease,
pos_on_target,
Qt.LeftButton,
Qt.LeftButton,
Qt.NoModifier,
)
QApplication.sendEvent(target_widget, release_event)
def render_qwidget_to_rgb_image(self):
# Create a QPixmap to render the widget
pixmap = QPixmap(self.size())
# Create a QPainter and render the widget onto the pixmap
painter = QPainter(pixmap)
offset = QPoint(0, 0)
self.render(painter, offset)
painter.end()
# Convert the QPixmap to QImage
image = pixmap.toImage()
return image
def qimage_to_sosfimage(p):
# Convert QImage to SoSFImage
size = SbVec2s(p.width(), p.height())
buffersize = p.sizeInBytes()
# print (buffersize, size[0], size[1])
numcomponents = int(buffersize / (size[0] * size[1]))
img = SoSFImage()
width = size[0]
height = size[1]
byteList = bytearray()
for y in reversed(range(height)): # reversed because 0,0 is in left-top for Qt and left-bottom for GL
for x in range(width):
rgba = p.pixel(x, y)
if numcomponents <= 2:
byteList.append(qGray(rgba))
if numcomponents == 2:
byteList.append(qAlpha(rgba))
elif numcomponents <= 4:
byteList.append(qRed(rgba))
byteList.append(qGreen(rgba))
byteList.append(qBlue(rgba))
if numcomponents == 4:
byteList.append(qAlpha(rgba))
_bytes = bytes(byteList)
img.setValue(size, numcomponents, _bytes)
return img
if __name__ == '__main__':
QApplication.setAttribute(Qt.AA_UseDesktopOpenGL)
app = QApplication([])
s_widget = sourceWidget() # QWidget
s_widget.setAttribute(Qt.WA_DontShowOnScreen) # QWidget is hidden, only the Coin3D window is visible
s_widget.show()
m_glwidget = myOpenGLWidget() # QOpenGLWidget with Coin3D scene
m_glwidget.show()
m_glwidget.simulate_click.connect(s_widget.simulate_click)
s_widget.widget_rend_soimage.connect(m_glwidget.update_texture)
m_glwidget.gl_w_closed.connect(s_widget.close)
sys.exit(app.exec_())
@kwahoo2
Copy link
Author

kwahoo2 commented Aug 14, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment