Giter Site home page Giter Site logo

qpanda3d's Introduction

QPanda3D

A working Panda3D wrapper for PyQt5 The objective is to be able to put on the same screen, panda3D and pyQT widgets.

This package is still a work in progress. What works :

  • Creating a panda3D world inside a seemlessly QWidget object that can be placed alog with other QT stuff
  • Full access to panda3D objects, lights ...
  • Keyboard press and up are supported starting from v 0.5 What doesn't work yet:
  • Mouse and timed keyboard interactions

Installation

pip install QPanda3D

Usage

1 - create your world by inheriting from Panda3DWorld

from QPanda3D.Panda3DWorld import Panda3DWorld
class MyWorld(Panda3DWorld):
        Panda3DWorld.__init__(self)
        # from this point, act as if you are defining a classic panda3D environment
        self.cam.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda')
        self.testModel.reparentTo(render)

2 - In your main, just create an instance of your world, create a Q

from QPanda3D.QPanda3DWidget import QPanda3DWidget
if __name__ == "__main__":
    world = MyWorld()
    
    app = QApplication(sys.argv)
    appw=QMainWindow()
    appw.setGeometry(50, 50, 800, 600)

    pandaWidget = QPanda3DWidget(world)
    appw.setCentralWidget(pandaWidget)
    appw.show()
    
    sys.exit(app.exec_())

Widget resizing policy

Starting from V 0.4, the widget is automatically resized without making any stretching artefacts. Resizing policy parameters (introduced in V 0.2) have been removed since they are no more needed.

from QPanda3D.QPanda3DWidget import QPanda3DWidget
if __name__ == "__main__":
    world = MyWorld()
    
    app = QApplication(sys.argv)
    appw=QMainWindow()
    appw.setGeometry(50, 50, 800, 600)

    pandaWidget = QPanda3DWidget(world)
    appw.setCentralWidget(pandaWidget)
    appw.show()
    
    sys.exit(app.exec_())

you can also tell the Panda3DWorld object what is the default view size that you prefer when creating it.

from QPanda3D.Panda3DWorld import Panda3DWorld
class MyWorld(Panda3DWorld):
        Panda3DWorld.__init__(self, width=1024, height=768)
        # from this point, act as if you are defining a classic panda3D environment
        self.cam.setPos(0, -28, 6)
        self.testModel = loader.loadModel('panda')
        self.testModel.reparentTo(render)

Just make sure that your ratio is adequate with your real widget size.

Mouse events handling

Mouse position is sent from PyQt interface to panda using messages. You can get these information using the following event handlers : mouse1 :Mouse Button 1 Pressed mouse2 :Mouse Button 2 Pressed mouse3 :Mouse Button 3 Pressed mouse1-up :Mouse Button 1 Released mouse2-up :Mouse Button 2 Released mouse3-up :Mouse Button 3 Released wheel_up :Mouse Wheel rolled upwards wheel_down :Mouse Wheel rolled downwards

When you handle those events, starting from version 0.2.9, you can add an event argument to your event handler method to receive relevent information about the actual position of the mouse in the PyQt 2D canvas.

here is an example of how you can use this. You can also find a complete example in the examples list

        #accept few mouse events
        self.accept('mouse1', self.mousePressEventLeft)
        self.accept("mouse2", self.wheelEvent)
        self.accept("mouse1-up", self.mouseReleaseEventLeft)
        self.accept("mouse-move", self.mouseMoveEvent)

    def mousePressEventLeft(self, evt:dict):
        le_key_evt.setText(f"press @ {evt['x']},{evt['y']}")
    def mouseReleaseEventLeft(self, evt:dict):
        le_key_evt.setText(f"release @ {evt['x']},{evt['y']}")
    def wheelEvent(self, evt:dict):
        le_key_evt.setText(f"Wheel with {evt['delta']}")
    def mouseMoveEvent(self, evt:dict):
        le_key_evt.setText(f"Mouse moved to  {evt['x']},{evt['y']}")

Special thanks

I want to thank all the contributers to this little opensource project. In chronological order :

  • Thanks to fraca7 for his commit (Change film size according to widget resize)
  • Many thanks to nmevenkamp for the valuable updates and bugfixes he apported to this project.
  • Also thanks to augasur for bringing to our knowledge problems he faced while using pyinstaller with qpanda3d (preblem solved).
  • Also thanks to arthurpdesimone for bringing to our knowledge problems he faced while using mouse interaction.

If other people want to contribute to this project, the're welcome.

qpanda3d's People

Contributors

evwtrentini avatar fraca7 avatar jakuhor avatar majabojarska avatar marekjansky avatar nmevenkamp avatar parisneo avatar timozen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

qpanda3d's Issues

AttributeError: 'panda3d.core.GraphicsBuffer' object has no attribute 'getPointer'

I understand that your README is stating, that mouse stuff isn't supported right now, but i tried it anyway. I'm a bit confused about the error though, because my application (without qt) was inherited by the ShowBase class like your QPanda3DWorld class. I try to access the currents mouse position via the self.win attribute of the ShowBase class, shouldn't that work anyways?

Traceback (most recent call last): ^ File "C:\Users\****\earth_plotting\venv\lib\site-packages\QPanda3D\QPanda3DWidget.py", line 35, in tick taskMgr.step() File "C:\Users\****\earth_plotting\venv\lib\site-packages\direct\task\Task.py", line 495, in step self.mgr.poll() File "EarthPlot.py", line 313, in OrbitCameraTask md = self.win.getPointer(0) AttributeError: 'panda3d.core.GraphicsBuffer' object has no attribute 'getPointer'

The code of the task producing the problem is:

def OrbitCameraTask(self,task):
        delta_factor = .01 * self.camLens.getFov()[1]
        md = self.win.getPointer(0)
        if base.mouseWatcherNode.isButtonDown(MouseButton.one()):
            self.props.setCursorHidden(True)
            self.win.requestProperties(self.props)
            x = md.getX()
            y = md.getY()
            if self.win.movePointer(0, int(self.x_mouse_position), int(self.y_mouse_position)):
                self.heading = self.heading - (x - self.x_mouse_position) * delta_factor
                self.pitch = self.pitch - (y - self.y_mouse_position) * delta_factor
            if self.pitch > 90:
                self.pitch = 90
            elif self.pitch <-90:
                self.pitch = -90
            self.parentnode.setHpr(self.heading, self.pitch,0)
        else:
            self.props.setCursorHidden(False)
            self.win.requestProperties(self.props)
            self.x_mouse_position = md.getX()
            self.y_mouse_position = md.getY()
        return task.cont

Everything else seems to work fine, sadly the orbit camera is an important part of my application

Objects don't occlude each other as expected

I implemented a small MWE to showcase the issue (see code below).

It creates four cards of random color and places them next to each other (like you would hold them in your hand when playing any kind of card game). Using 8 buttons, you can move the cards forward and backward. I observe the following behaviour: when moving cards from the back to the front, they don't go "through" the adjacent cards. Instead, it seems that the card nearest to the camera on initialization is always rendered on top on the screen, even if it should be behind any of the other cards at some point.

For comparison, you can exchange run_qt() for run_panda3d() at the bottom of the MWE script, which shows that when using plain Panda3D, the cards occlude each other as expected.

Tested on Windows 10, GTX 1070 Ti, Python 3.6.

I'd really appreciate if you could have a look since I was hoping to use this module to migrate the GUI for my card game from Panda's DirectGUI to Qt.

Python source for MWE:
# Standard imports
import sys

# External imports
import numpy as np
from panda3d.core import *


def get_card_node_path(parent) -> NodePath:
    width = 6.35
    height = 8.89
    thickness = 0.529

    dimensions = [width / 2, height / 2, thickness / 2]

    vertex_format = GeomVertexFormat().getV3n3cpt2()
    vertex_data = GeomVertexData("vertex_data", vertex_format, Geom.UHStatic)

    vertexCount = 0

    tris = GeomTriangles(Geom.UHStatic)

    vertex_writer = GeomVertexWriter(vertex_data, "vertex")
    color_writer = GeomVertexWriter(vertex_data, "color")
    normal_writer = GeomVertexWriter(vertex_data, "normal")
    texcoord_writer = GeomVertexWriter(vertex_data, "texcoord")

    for i in range(3):
        for direction in (-1, 1):
            normal = VBase3()
            normal[i] = direction

            if i == 1:
                rgb = [1., 1., 1.]
            else:
                rgb = [np.random.rand(), np.random.rand(), np.random.rand()]

            r, g, b = rgb
            color = (r, g, b, 1.)

            for a, b in ((-1., -1.), (-1., 1.), (1., 1.), (1., -1.)):
                vertex = VBase3()

                vertex[i] = direction * dimensions[i]
                vertex[(i + direction) % 3] = a * dimensions[(i + direction) % 3]
                vertex[(i + direction * 2) % 3] = b * dimensions[(i + direction * 2) % 3]

                vertex_writer.addData3f(vertex)
                color_writer.addData4f(color)
                normal_writer.addData3f(normal)

                if i == 2:
                    if direction == -1:
                        texcoord_writer.addData2f((1 - b) / 4, (a + 1) / 2)
                    else:
                        texcoord_writer.addData2f(0.5 + (a + 1) / 4, (b + 1) / 2)
                else:
                    texcoord_writer.addData2f(0, 0)

            vertexCount += 4

            tris.addVertices(vertexCount - 2, vertexCount - 3, vertexCount - 4)
            tris.addVertices(vertexCount - 4, vertexCount - 1, vertexCount - 2)

    geom = Geom(vertex_data)
    geom.addPrimitive(tris)

    node = GeomNode("geom_node")
    node.addGeom(geom)

    card_np = parent.attachNewNode(node)
    card_np.setPos(0, 0, 0)

    return card_np


def run_qt():
    loadPrcFileData('', 'back-buffers 0')

    from PyQt5 import QtWidgets
    from QPanda3D.Panda3DWorld import Panda3DWorld
    from QPanda3D.QPanda3DWidget import QPanda3DWidget

    app = QtWidgets.QApplication(sys.argv)
    main_window = QtWidgets.QMainWindow()
    main_window.setGeometry(50, 50, 800, 600)

    world = Panda3DWorld()
    panda_widget = QPanda3DWidget(world)

    main_window.setCentralWidget(panda_widget)
    main_window.show()

    # add lights
    ambient_light = AmbientLight('ambient_light')
    ambient_light.setColor(VBase4(1, 1, 1, 1))
    ambient_light_np = world.render.attachNewNode(ambient_light)
    world.render.setLight(ambient_light_np)

    directional_light = DirectionalLight('directional_light')
    directional_light.setColor(VBase4(1, 1, 1, 1))
    directional_light_np = world.camera.attachNewNode(directional_light)
    world.render.setLight(directional_light_np)

    # position camera
    world.camera.setPos(0, -50, 50)
    world.camera.lookAt(0, 0, 0)

    # add cards
    num_cards = 4
    cards = [get_card_node_path(world.render) for _ in range(num_cards)]

    for i, np in enumerate(cards):
        np.setPos(i * 2, 10 - i, -5)
        np.lookAt(np, 0, 0, -1)

    # add GUI
    layout = QtWidgets.QGridLayout()

    widget = QtWidgets.QWidget(parent=panda_widget)

    def move(index, direction):
        np = cards[index]
        np.setPos(np, 0, 0, direction)
        print("Moved card #{}".format(index))

    for index in range(num_cards):
        button = QtWidgets.QPushButton("Move #{} forward".format(index))
        button.clicked.connect(lambda state, x=index: move(x, -1))
        layout.addWidget(button, 1, index)

        button = QtWidgets.QPushButton("Move #{} backwards".format(index))
        button.clicked.connect(lambda state, x=index: move(x, 1))
        layout.addWidget(button, 0, index)

    widget.setLayout(layout)
    widget.show()

    # run
    sys.exit(app.exec_())


def run_panda3d():
    from direct.showbase.ShowBase import ShowBase
    from direct.gui.DirectGui import DirectButton

    base = ShowBase()
    base.disableMouse()

    # add lights
    ambient_light = AmbientLight('ambient_light')
    ambient_light.setColor(VBase4(1, 1, 1, 1))
    ambient_light_np = render.attachNewNode(ambient_light)
    render.setLight(ambient_light_np)

    directional_light = DirectionalLight('directional_light')
    directional_light.setColor(VBase4(1, 1, 1, 1))
    directional_light_np = camera.attachNewNode(directional_light)
    render.setLight(directional_light_np)

    # position camera
    camera.setPos(0, -50, 50)
    camera.lookAt(0, 0, 0)

    # add cards
    num_cards = 4
    cards = [get_card_node_path(render) for _ in range(num_cards)]

    for i, np in enumerate(cards):
        np.setPos(i * 2, 10 - i, -5)
        np.lookAt(np, 0, 0, -1)

    # add GUI
    def move(index, direction):
        np = cards[index]
        np.setPos(np, 0, 0, direction)
        print("Moved card #{}".format(index))

    # Add button
    for index in range(num_cards):
        DirectButton(
            text=("Move #{} backwards".format(index)),
            scale=.05,
            command=lambda x=index: move(x, 1),
            pos=(-0.9 + 0.6 * index, 0, 0.9)
        )
        DirectButton(
            text=("Move #{} forward".format(index)),
            scale=.05,
            command=lambda x=index: move(x, -1),
            pos=(-0.9 + 0.6 * index, 0, 0.8)
        )

    # run
    base.run()


if __name__ == '__main__':
    run_qt()

Keyboard events integration

For now, only few keyboard touches are supported and forwarded from Qt to panda3D.
A complete integration is required.

Create a contributor's/maintainer's guide

I'd like to propose creating a contributor's/maintainer's guide. It would provide information about:

  • preferred formatter & linter,
  • docstring style,
  • test framework,
  • etc.

I can take care of this, however it would be good to discuss the details first ☺️.

My suggestions:

  • formatter: black with default settings,
  • linter: flake8,
  • docstring style: numpy,
  • test framework: pytest + tox,
  • CI environment: GitHub Actions.

Then we could develop some automation scripts that would test against lint errors, run unit tests (if implemented) for different Python versions. This could be handled via tox's isolated test environments, both on a local machine and GitHub Actions.

Aspect ratio wrong when resize on an OrtographicLens

Hi! found that changing the lens to Ortographic, the resize function on QPanda3DWidget does not work, as the self.initial_size is declared as the self.size() value, which is a generic default from QT. Changing that to QSizeF(size.x, size.y) makes the Ortographic and Perspective lens work correctly

Pyinstaller build fails

Hi,
love the library, but I have encountered the problem if I try to build buttons_example.py to EXE with PyInstaller. Here is Traceback which we got when trying to launch built EXE.

Warning: unable to auto-locate config files in directory named by "<auto>etc".
Known pipe types:
(all display modules loaded.)
Traceback (most recent call last):
  File "buttons_example.py", line 74, in <module>
  File "buttons_example.py", line 25, in __init__
  File "QPanda3D\Panda3DWorld.py", line 51, in __init__
  File "direct\showbase\ShowBase.py", line 339, in __init__
  File "direct\showbase\ShowBase.py", line 1021, in openDefaultWindow
  File "direct\showbase\ShowBase.py", line 1056, in openMainWindow
  File "direct\showbase\ShowBase.py", line 766, in openWindow
  File "direct\showbase\ShowBase.py", line 752, in <lambda>
  File "direct\showbase\ShowBase.py", line 818, in _doOpenWindow
  File "direct\showbase\ShowBase.py", line 648, in makeDefaultPipe
  File "direct\directnotify\Notifier.py", line 130, in error
Exception: No graphics pipe is available!
Your Config.prc file must name at least one valid panda display
library via load-display or aux-display.
[4484] Failed to execute script buttons_example

Here is my run.spec file:

block_cipher = None

import importlib
from pathlib import Path

package_imports = [['qtmodern', ['resources/frameless.qss', 'resources/style.qss']]]

added_file = []
for package, files in package_imports:
    proot = Path(importlib.import_module(package).__file__).parent
    added_file.extend((proot / f, package) for f in files)
added_file.append(('D:\\Anaconda\\envs\\python_opengl\\Lib\\site-packages\\PyQt5\\Qt5\\bin\*', 'PyQt5\\Qt\\bin'))


a = Analysis(['buttons_example.py'],
             pathex=['D:\\Projektai\\METOD_robotai\\Source\\ProductData\\panda_test',
			 'D:\\Anaconda\\envs\\python_opengl\\Lib\\site-packages'],
             binaries=[],
             datas=added_file,
             hiddenimports=['panda3d',
                            'PyQt5',
                            'numpy',
                            'QPanda3D',
                            'openal',
                            'pygame'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='run',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=False,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='run')

Stack:

  • conda 4.7.11
  • Windows 10
  • Python 3.6.9
  • pyinstaller==4.3
  • PyQt5==5.15.4
  • QPanda3D==0.2.7

MyWorld in hello world example has no __init__ function

In particular python complains because the base class init expects a self (hidden first param). So I wrapped it in an init() method and it works, except:

adding:
Panda3DWorld.init(self, width=1024, height=768)

The preffered width / height has no effect.

Clear color not working on mac OS

Hello. The following example works fine under Ubuntu 17.10 (panda3d 1.10.2, Qt 5.12.2, glxGraphicsPipe), but under mac OS Mojave (panda3d 1.10.2, Qt 5.11.1, CocoaGraphicsPipe) the clear color seems not to be active; the background stays black and "older" pixels are not cleared.

#!/usr/bin/env python3

import math

from PyQt5 import QtWidgets

from QPanda3D.QPanda3DWidget import QPanda3DWidget
from QPanda3D.Panda3DWorld import Panda3DWorld

from direct.task import Task
from panda3d.core import VBase4


class World(Panda3DWorld):
    def __init__(self):
        super().__init__()

        self.win.setClearColorActive(True)
        self.win.setClearColor(VBase4(0, 0.5, 0, 1))

        self.taskMgr.add(self.spinCameraTask, 'SpinCameraTask')

        self._model = self.loader.loadModel('models/panda-model')
        self._model.reparentTo(self.render)

    def spinCameraTask(self, task):
        angle = task.time * 6 * math.pi / 180
        self.camera.setPos(20 * math.sin(angle), -20 * math.cos(angle), 3)
        self.camera.setHpr(angle * 180 / math.pi, 0, 0)
        return Task.cont


class TestWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        widget = QPanda3DWidget(World(), self, stretch=True, keep_ratio=True)
        self.setCentralWidget(widget)

        self.resize(800, 600)
        self.show()
        self.raise_()


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    win = TestWindow()
    app.exec_()

Any idea ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.