Pygame,shooting bullets using vectors - vector

Ok so all night me and 5 other guys have been working on a project for our pygame module.Our lecturer hasn't given us any programs to reference from he has just throw a bunch of code at us (without commenting) and expecting us to understand it.We only started python since the start of september.We really need some help.
So we have one human player which is controlled by W A S & D and 15 random dots moving around the screen.We need to use vectors(which we have never used) to shoot at the random dots.
Please someone help.
edit code added~
import pygame
class Terminator(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((20, 20))
self.image.fill((0, 0, 0))
pygame.draw.circle(self.image, (255, 0, 0), (10, 10), 10, 0)
self.rect = self.image.get_rect()
self.dx = screen.get_width()/2
self.dy = screen.get_height()/2
self.rect.center = (self.dx, self.dy)
self.speed = 5
self.screen = screen
def update(self):
self.rect.center = (self.dx, self.dy)
def MoveLeft(self):
if self.rect.left < 0:
self.dx += 0
else:
self.dx -= self.speed
def MoveRight(self):
if self.rect.right > self.screen.get_width():
self.dx += 0
else:
self.dx += self.speed
def MoveUp(self):
if self.rect.top <0:
self.dy += 0
else:
self.dy -= self.speed
def MoveDown(self):
if self.rect.bottom > self.screen.get_height():
self.dy += 0
else:
self.dy += self.speed
<code>
humansprite.py
<pre>
import pygame
import random
import math
class Humans(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((16, 16))
self.image.fill((0, 0, 0))
pygame.draw.circle(self.image, (0, 0, 255), (8, 8), 8, 0)
self.rect = self.image.get_rect()
self.dx = random.randrange(0, screen.get_height())
self.dy = random.randrange(0, screen.get_width())
self.screen = screen
self.speed = 1
self.alive = True
def update(self):
self.rect.centerx -= self.dx
self.rect.centery -= self.dy
if self.rect.right < 0:
self.reset()
def reset(self):
self.rect.left = self.screen.get_width()
self.rect.centery = random.randrange(0, self.screen.get_height())
self.dy = random.randrange(-2, 2)
self.dx = random.randrange(1, 4)
<code>
seekandDestory.py
<pre>
import pygame
from TerminatorSprite import Terminator
from humansSprite import Humans
pygame.init()
def checkKeys(myData):
(event, ship) = myData
if event.key == pygame.K_LEFT:
print 'LEFT'
ship.MoveLeft()
if event.key == pygame.K_RIGHT:
print 'RIGHT'
ship.MoveRight()
if event.key == pygame.K_UP:
print 'UP'
ship.MoveUp()
if event.key == pygame.K_DOWN:
print 'DOWN'
ship.MoveDown()
def main():
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Seek and Destroy")
background = pygame.Surface(screen.get_size())
clock = pygame.time.Clock()
screen.blit(background, (0, 0))
terminator = Terminator(screen)
humans = []
for people in range(15):
people = Humans(screen)
humans.append(people)
terminatorGroup = pygame.sprite.Group(terminator)
humanGroup = pygame.sprite.Group(humans)
clock = pygame.time.Clock()
keepGoing = True
pygame.key.set_repeat(10, 10)
while keepGoing:
clock.tick(60)
pygame.mouse.set_visible(False)
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
if event.type == pygame.KEYDOWN:
myData = (event, terminator)
checkKeys(myData)
#if pygame.sprite.spritecollide(terminator, humanGroup, False):
terminatorGroup.clear(screen, background)
terminatorGroup.update()
terminatorGroup.draw(screen)
humanGroup.clear(screen, background)
humanGroup.update()
humanGroup.draw(screen)
pygame.display.flip()
pygame.mouse.set_visible(True)
if __name__ == "__main__":
main()
<code>

I just finished up this set of examples last night! Check out the Lunar Lander example, especially the way I'm handling vectors in lunarlander.py - it provides a class, V, for vector management.
What you'll probably need is a sprite for your guy with a vector representing his speed (moment-to-moment, probably 0) and angle, then a sprite for each random dot with a similar vector and angle. When you shoot, create a new sprite with a vector whose angle matches the current angle your guy is facing and magnitude matches the speed of the bullet. Then, in the update() method, move each sprite (bullet, random-dot). Very similar to what I'm doing in lunarlander.py.
Good luck! If you've just started Python, that's a tough assignment. Happy coding!

Related

Qt: hide widget with ToolTip flag when window is not visible

I'm creating a video player, below is an MRE implementation of the VideoWidget and a widget to display the video controls, called ControlBar:
import os
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtWidgets as qtw
from PySide6 import QtCore as qtc
from PySide6 import QtGui as qtg
class VideoWidget(qtmw.QVideoWidget):
load_finished_signal = qtc.Signal()
_playback_state_changed_signal = qtc.Signal()
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.setFocusPolicy(qtc.Qt.FocusPolicy.NoFocus)
self.setMouseTracking(True)
# In Qt6, a private internal class is used for rendering the video, so
# add `vw.children()[0].setMouseTracking(True)`
self.children()[0].setMouseTracking(True)
self._seeking = False
self._audio_output = qtm.QAudioOutput(qtm.QMediaDevices.defaultAudioOutput())
self._media_player = qtm.QMediaPlayer(self)
self._control_bar = ControlBar(self)
self._hide_control_bar_timer = qtc.QTimer()
self._media_player.setVideoOutput(self)
self._media_player.setAudioOutput(self._audio_output)
self._media_player.mediaStatusChanged.connect(self._video_loaded)
self._media_player.durationChanged.connect(
self._control_bar.update_total_duration
)
self._media_player.positionChanged.connect(self._control_bar.update_progress)
self.videoSink().videoFrameChanged.connect(self._seeked)
self._control_bar.seek_requested_signal.connect(self._seek)
self._control_bar.pause_play_requested_signal.connect(self._change_playback_state)
self._playback_state_changed_signal.connect(self._control_bar.playback_state_changed)
self._hide_control_bar_timer.setSingleShot(True)
self._hide_control_bar_timer.setInterval(1000)
self._hide_control_bar_timer.timeout.connect(self._hide_control_bar)
def _hide_control_bar(self) -> None:
if not self._control_bar.underMouse():
self._control_bar.setVisible(False)
cursor = self.cursor()
cursor.setShape(qtc.Qt.CursorShape.BlankCursor)
self.setCursor(cursor)
def load(self, location: str) -> None:
self._media_player.setSource(qtc.QUrl.fromLocalFile(location))
def _video_loaded(self, location: str) -> None:
self._control_bar.set_filename(
os.path.basename(self._media_player.source().toString())
)
if self._media_player.source():
self._media_player.play()
self.load_finished_signal.emit()
def close_item(self) -> None:
self._media_player.stop()
self._media_player.setSource(qtc.QUrl())
def _screenshot(self, video_frame: qtm.QVideoFrame):
image = video_frame.toImage()
image.save(filename)
def _update_conrol_bar_position(self) -> None:
geometry = self.geometry()
top_left_global = geometry.topLeft()
self._control_bar.update_geometry_to_width(self.width())
self._control_bar.move(
top_left_global.x(),
top_left_global.y() + geometry.height() - self._control_bar.height(),
)
def _change_playback_state(self) -> None:
if (
self._media_player.playbackState()
== qtm.QMediaPlayer.PlaybackState.PlayingState
):
self._media_player.pause()
elif (
self._media_player.playbackState()
== qtm.QMediaPlayer.PlaybackState.PausedState
or self._media_player.playbackState()
== qtm.QMediaPlayer.PlaybackState.StoppedState
):
self._media_player.play()
self._playback_state_changed_signal.emit()
def mouseMoveEvent(self, event: qtg.QMouseEvent) -> None:
if not self.cursor().shape() & qtc.Qt.CursorShape.ArrowCursor:
cursor = self.cursor()
cursor.setShape(qtc.Qt.CursorShape.ArrowCursor)
self.setCursor(cursor)
if (
not self.width() == self._control_bar.width()
or not self.geometry().contains(self._control_bar.geometry())
):
self._update_conrol_bar_position()
if not self._control_bar.isVisible():
self._control_bar.setVisible(True)
self._hide_control_bar_timer.start()
def mousePressEvent(self, event: qtg.QMouseEvent) -> None:
self._change_playback_state()
event.accept()
return
def mouseDoubleClickEvent(self, event: qtg.QMouseEvent) -> None:
F_event = qtg.QKeyEvent(
qtc.QEvent.KeyPress,
qtc.Qt.Key.Key_F,
qtc.Qt.KeyboardModifier.NoModifier,
"F",
)
qtc.QCoreApplication.sendEvent(self, F_event)
super().mouseDoubleClickEvent(event)
def resizeEvent(self, event: qtg.QResizeEvent) -> None:
super().resizeEvent(event)
self._update_conrol_bar_position()
def contextMenuEvent(self, event: qtg.QContextMenuEvent):
""""""
def keyPressEvent(self, event: qtg.QKeyEvent) -> None:
key = event.key()
if (
key == qtc.Qt.Key.Key_Left
or key == qtc.Qt.Key.Key_Right
or key == qtc.Qt.Key.Key_Less
or key == qtc.Qt.Key.Key_Greater
):
factor = (
1
if event.modifiers() == qtc.Qt.KeyboardModifier.ShiftModifier
else 60
if event.modifiers() == qtc.Qt.KeyboardModifier.ControlModifier
else 5
)
if key == qtc.Qt.Key.Key_Left:
self._seek(max(self._media_player.position() - (1000 * factor), 0))
elif key == qtc.Qt.Key.Key_Right:
self._seek(
min(
self._media_player.position() + (1000 * factor),
self._media_player.duration(),
)
)
elif key == qtc.Qt.Key.Key_F5:
self._screenshot(self.videoSink().videoFrame())
elif key == qtc.Qt.Key.Key_Space:
self._change_playback_state()
elif key == qtc.Qt.Key.Key_Down:
self._media_player.audioOutput().setVolume(
max(self._media_player.audioOutput().volume() - 0.1, 0)
)
elif key == qtc.Qt.Key.Key_Up:
self._media_player.audioOutput().setVolume(
min(self._media_player.audioOutput().volume() + 0.1, 1)
)
elif key == qtc.Qt.Key.Key_M:
self._media_player.audioOutput().setMuted(
not self._media_player.audioOutput().isMuted()
)
else:
super().keyPressEvent(event)
return None
event.accept()
return None
"""
- next in directory
- previous in directory
"""
def _seek(self, position: int) -> None:
if self._seeking:
return
self._seeking = True
if self._media_player.isSeekable():
self._media_player.setPosition(position)
else:
self._seeked()
def _seeked(self):
self._seeking = False
def wheelEvent(self, event: qtg.QWheelEvent) -> None:
"volume"
class ControlBar(qtw.QWidget):
seek_requested_signal = qtc.Signal(int)
pause_play_requested_signal = qtc.Signal()
_WRT_HEIGHT = 100
_MAX_FONT_POINT_F = 20
_WRT_WIDTH = 1920 / 2
# Height should be _WRT_HEIGHT if width is _WRT_WIDTH.
_CONTROL_BAR_WIDTH_TO_HEIGHT_RATIO = _WRT_HEIGHT / _WRT_WIDTH
_FONT_POINT_F_RATIO = _MAX_FONT_POINT_F / _WRT_HEIGHT
_WIDGETS_SIZE_RATIOS = {
"filename": (_WRT_WIDTH / _WRT_WIDTH, 40 / _WRT_HEIGHT),
"pause_play": (30 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
"pause": (10 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
"play": (25 / _WRT_WIDTH, 35 / _WRT_HEIGHT),
"left_duration": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
"right_duration": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
"total_progress_bar": (578 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
"completed_progress_bar": (0 / _WRT_WIDTH, 40 / _WRT_HEIGHT),
}
_PADDINGS_RATIOS = {"v_pad": 5 / _WRT_HEIGHT, "h_pad": 10 / _WRT_WIDTH}
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.setWindowFlag(qtc.Qt.WindowType.ToolTip)
self._total_duration_ms = 0
self._completed_duration_ms = 0
self._widget_shapes = {
"filename_rect": qtc.QRect(),
"pause_play_button_rect": qtc.QRect(),
"pause_path": qtg.QPainterPath(),
"play_triangle": qtg.QPolygon(),
"left_duration_rect": qtc.QRect(),
"progress_bar_rect": qtc.QRect(),
"right_duration_rect": qtc.QRect(),
}
self._show_milliseconds = False
self._filename = ""
self._paused = False
def playback_state_changed(self) -> None:
self._paused = not self._paused
def paintEvent(self, event: qtg.QPaintEvent) -> None:
painter = qtg.QPainter(self)
painter.setRenderHint(qtg.QPainter.RenderHint.Antialiasing)
painter.fillRect(0, 0, self.width(), self.height(), qtc.Qt.GlobalColor.black)
pen = qtg.QPen()
pen.setColor(qtc.Qt.GlobalColor.white)
painter.setPen(pen)
painter.setFont(self._get_font())
painter.drawText(
self._widget_shapes["filename_rect"],
qtc.Qt.AlignmentFlag.AlignLeft,
self._filename,
)
if self._paused:
path = qtg.QPainterPath()
path.addPolygon(self._widget_shapes["play_triangle"])
painter.fillPath(path, qtc.Qt.GlobalColor.white)
else:
painter.fillPath(self._widget_shapes["pause_path"], qtc.Qt.GlobalColor.white)
painter.drawText(
self._widget_shapes["left_duration_rect"],
qtc.Qt.AlignmentFlag.AlignCenter,
_format_milliseconds(self._completed_duration_ms, self._show_milliseconds),
)
painter.fillRect(
self._widget_shapes["progress_bar_rect"], qtc.Qt.GlobalColor.gray
)
painter.fillRect(
self._get_completed_progress_bar_rect(), qtc.Qt.GlobalColor.white
)
painter.drawText(
self._widget_shapes["right_duration_rect"],
qtc.Qt.AlignmentFlag.AlignCenter,
_format_milliseconds(self._total_duration_ms, self._show_milliseconds),
)
def set_filename(self, filename: str) -> None:
self._filename = filename
def update_total_duration(self, duration: int) -> None:
self._total_duration_ms = duration
self.update()
def update_progress(self, progress: int) -> None:
self._completed_duration_ms = progress
self.update()
def update_geometry_to_width(self, width: int) -> None:
new_control_bar_height = int(self._CONTROL_BAR_WIDTH_TO_HEIGHT_RATIO * width)
self.setFixedSize(width, min(new_control_bar_height, self._WRT_HEIGHT))
self._update_widgets_geometry()
def _update_widgets_geometry(self) -> None:
total_height = 0
row_1_total_width = 0
row_2_total_width = 0
total_height += self.height() * self._PADDINGS_RATIOS["v_pad"]
row_1_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
font_metric = qtg.QFontMetrics(self._get_font())
horizontal_advance = font_metric.horizontalAdvance("A")
self._widget_shapes["filename_rect"].setX(row_1_total_width)
self._widget_shapes["filename_rect"].setY(total_height)
self._widget_shapes["filename_rect"].setWidth(
horizontal_advance * len(self._filename)
)
self._widget_shapes["filename_rect"].setHeight(
self.height() * self._WIDGETS_SIZE_RATIOS["filename"][1]
)
total_height += self._widget_shapes["filename_rect"].height()
row_1_total_width += self._widget_shapes["filename_rect"].width()
total_height += self.height() * self._PADDINGS_RATIOS["v_pad"]
row_1_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
self._widget_shapes["pause_play_button_rect"].setX(row_2_total_width)
self._widget_shapes["pause_play_button_rect"].setY(total_height)
self._widget_shapes["pause_play_button_rect"].setWidth(
min(30, self.width() * self._WIDGETS_SIZE_RATIOS["pause_play"][0])
)
self._widget_shapes["pause_play_button_rect"].setHeight(
self.height() * self._WIDGETS_SIZE_RATIOS["pause_play"][1]
)
self._widget_shapes["pause_path"] = qtg.QPainterPath()
pause_1 = qtc.QRect(
self._widget_shapes["pause_play_button_rect"].x(),
self._widget_shapes["pause_play_button_rect"].y(),
min(10, self.width() * self._WIDGETS_SIZE_RATIOS["pause"][0]),
self.height() * self._WIDGETS_SIZE_RATIOS["pause"][1],
)
self._widget_shapes["pause_path"].addRect(pause_1)
pause_2 = qtc.QRect(
self._widget_shapes["pause_play_button_rect"].topRight().x(),
self._widget_shapes["pause_play_button_rect"].y(),
-min(10, self.width() * self._WIDGETS_SIZE_RATIOS["pause"][0]),
self.height() * self._WIDGETS_SIZE_RATIOS["pause"][1],
)
self._widget_shapes["pause_path"].addRect(pause_2)
self._widget_shapes["play_triangle"] = qtg.QPolygon()
right = self._widget_shapes["pause_play_button_rect"].center()
right.setX(self._widget_shapes["pause_play_button_rect"].right())
self._widget_shapes["play_triangle"].append(
[
self._widget_shapes["pause_play_button_rect"].topLeft(),
self._widget_shapes["pause_play_button_rect"].bottomLeft(),
right,
]
)
row_2_total_width += self._widget_shapes["pause_play_button_rect"].width()
row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
self._widget_shapes["left_duration_rect"].setX(row_2_total_width)
self._widget_shapes["left_duration_rect"].setY(total_height)
self._widget_shapes["left_duration_rect"].setWidth(
horizontal_advance
* len(
_format_milliseconds(
self._completed_duration_ms, self._show_milliseconds
)
)
)
self._widget_shapes["left_duration_rect"].setHeight(
self.height() * self._WIDGETS_SIZE_RATIOS["left_duration"][1]
)
row_2_total_width += self._widget_shapes["left_duration_rect"].width()
row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
self._widget_shapes["progress_bar_rect"].setX(row_2_total_width)
self._widget_shapes["progress_bar_rect"].setY(total_height)
self._widget_shapes["progress_bar_rect"].setWidth(
self.width() * self._WIDGETS_SIZE_RATIOS["total_progress_bar"][0]
)
self._widget_shapes["progress_bar_rect"].setHeight(
self.height() * self._WIDGETS_SIZE_RATIOS["total_progress_bar"][1]
)
row_2_total_width += self._widget_shapes["progress_bar_rect"].width()
row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
# To adjust the size of the progress bar according to the remaining width
# left after adding all remaining widget's widths.
theoritical_width = row_2_total_width
theoritical_width += horizontal_advance * len(
_format_milliseconds(self._total_duration_ms, self._show_milliseconds)
)
theoritical_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
remaining_width = self.width() - theoritical_width
self._widget_shapes["progress_bar_rect"].setWidth(
self._widget_shapes["progress_bar_rect"].width() + remaining_width
)
row_2_total_width += remaining_width
self._widget_shapes["right_duration_rect"].setX(row_2_total_width)
self._widget_shapes["right_duration_rect"].setY(total_height)
self._widget_shapes["right_duration_rect"].setWidth(
horizontal_advance
* len(
_format_milliseconds(self._total_duration_ms, self._show_milliseconds)
)
)
self._widget_shapes["right_duration_rect"].setHeight(
self.height() * self._WIDGETS_SIZE_RATIOS["right_duration"][1]
)
row_2_total_width += self._widget_shapes["right_duration_rect"].width()
row_2_total_width += self.width() * self._PADDINGS_RATIOS["h_pad"]
def _get_font(self) -> qtg.QFont:
font = qtg.QFont()
font.setPointSizeF(self.height() * self._FONT_POINT_F_RATIO)
return font
def _get_completed_progress_bar_rect(self) -> qtc.QRect:
completed_width = int(
(self._completed_duration_ms / (self._total_duration_ms or 1))
* self._widget_shapes["progress_bar_rect"].width()
)
completed_rect = qtc.QRect(self._widget_shapes["progress_bar_rect"])
completed_rect.setWidth(completed_width)
return completed_rect
def _get_time_from_mouse_press(self, point: qtc.QPoint) -> None:
return int(
(point.x() / self._widget_shapes["progress_bar_rect"].width())
* self._total_duration_ms
)
def mousePressEvent(self, event: qtg.QMouseEvent) -> None:
point = event.pos()
if self._widget_shapes["left_duration_rect"].contains(point):
self._show_milliseconds = not self._show_milliseconds
self._update_widgets_geometry()
elif self._widget_shapes["progress_bar_rect"].contains(point):
self.seek_requested_signal.emit(
self._get_time_from_mouse_press(
point - self._widget_shapes["progress_bar_rect"].topLeft()
)
)
elif self._widget_shapes["pause_play_button_rect"].contains(point):
self.pause_play_requested_signal.emit()
self.update()
event.accept()
return
def mouseMoveEvent(self, event: qtg.QMouseEvent) -> None:
if self._widget_shapes["progress_bar_rect"].contains(event.pos()):
self.seek_requested_signal.emit(
self._get_time_from_mouse_press(
event.pos() - self._widget_shapes["progress_bar_rect"].topLeft()
)
)
event.accept()
return
def _format_milliseconds(milliseconds: int, show_milliseconds: bool = False) -> str:
seconds, milliseconds = divmod(milliseconds, 1000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return f"{hours:02}:{minutes:02}:{seconds:02}" + (
f".{milliseconds:03}" if show_milliseconds else ""
)
app = qtw.QApplication()
vw = VideoWidget()
vw.show()
vw.load("video_file")
app.exec()
While this works as expected for the most part, if I alt-tab into another window (effectively hiding the program) with the ControlBar displayed, the bar is visible on top of the other window as well. I'm guessing such is the nature of the Qt.ToolTip window flag. How do I have it "show" or "hide" with the main program?
Also, the _update_widgets_geometry() function's implementation is rather tedious, is there a better way to do this? "This" being the way I layout (set the QRects for) the shapes in the ControlBar.
EDIT:
The code should work "out of the box", just pass a valid video file path to the vw.load() at the end of the code. A screenshot.

Godot: 2D Raycast is going opposite directions

Just started learning Godot and am having trouble with 2D Raycasts going left instead of right or right instead of left.
So I've got a basic top down 2D game with a raycast on the player. Facing up and down works perfectly fine, it's just left and right that are causing headaches. The code looks fine, so that's why I'm here
onready var rayCast = get_node("RayCast2D")
func _physics_process(delta):
vel = Vector2()
# inputs
if Input.is_action_pressed("move_up"):
vel.y -= 1
facingDir = Vector2(0, -1)
if Input.is_action_pressed("move_down"):
vel.y += 1
facingDir = Vector2(0, 1)
if Input.is_action_pressed("move_left"):
vel.x -= 1
facingDir = Vector2(1, 0)
if Input.is_action_pressed("move_right"):
vel.x += 1
facingDir = Vector2(-1, 0)
vel = vel.normalized()
move_and_slide(vel * moveSpeed)
manage_animations()
func manage_animations ():
if vel.x > 0:
play_animation("MoveRight")
elif vel.x < 0:
play_animation("MoveLeft")
elif vel.y < 0:
play_animation("MoveUp")
elif vel.y > 0:
play_animation("MoveDown")
elif facingDir.x == -1:
play_animation("IdleRight")
elif facingDir.x == 1:
play_animation("IdleLeft")
elif facingDir.y == -1:
play_animation("IdleUp")
elif facingDir.y == 1:
play_animation("IdleDown")
func _process(delta):
if Input.is_action_just_pressed("interract"):
try_interact()
func try_interact():
rayCast.cast_to = facingDir * interactDist
if rayCast.is_colliding():
if rayCast.get_collider() is KinematicBody2D:
rayCast.get_collider().take_damage(damage)
elif rayCast.get_collider().has_method("on_interact"):
rayCast.get_collider().on_interact(self)
I've tried changing the Vector2 x and y variables in the move left and right and while that works, the walking is all messed up then.
Any ideas?

draw cursor on a QChartView object

I need to draw a cursor on the QChartView object. something like this:
Cursor on the Chart
Whenever a user clicks on the chart the cursor should be moved there.
I have no idea how it is possible. As I searched It seems that this is not a built-in feature of QChartView. So How can I do it?
BTW, I'm newbie to the QT.
I am facing the same problem. Drawing a cursor at the mouse position seems to be trivial by referring to this anwser. Note that I modify the codes from this anwser in x.setter from self.update() to self.scene().update(). This is important for updating the cursor. To be honest, I don't know why. You can leave a comment if you know the difference.
# refer to https://stackoverflow.com/a/67596291/9758790
import sys
import time
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtChart import (
QChart,
QChartView,
QLineSeries,
)
from PyQt5.Qt import *
class ChartView(QChartView):
_x = None
#property
def x(self):
return self._x
#x.setter
def x(self, x):
self._x = x
# self.update()
self.scene().update()
def drawForeground(self, painter, rect):
if self.x is None:
return
painter.save()
pen = QPen(QColor("indigo"))
pen.setWidth(3)
painter.setPen(pen)
# p = self.chart().mapToPosition(QPointF(self.x, 0))
p = QPointF(self.x, 0)
r = self.chart().plotArea()
p1 = QPointF(p.x(), r.top())
p2 = QPointF(p.x(), r.bottom())
painter.drawLine(p1, p2)
painter.restore()
def mousePressEvent(self, env):
# refer to https://stackoverflow.com/a/44078533/9758790
scene_position = self.mapToScene(env.pos())
chart_position = self.chart().mapFromScene(scene_position)
value_at_position = self.chart().mapToValue(chart_position)
if self.chart().axisX().min() < value_at_position.x() < self.chart().axisX().max():
self.x = scene_position.x()
def main():
app = QApplication(sys.argv)
series1 = QLineSeries() << QPointF(0, 3) << QPointF(1, 1) << QPointF(3, 9) << QPointF(4, 1)
series2 = QLineSeries() << QPointF(0, 9) << QPointF(1, 8) << QPointF(3, 6) << QPointF(4, 6)
series3 = QLineSeries() << QPointF(0, 4) << QPointF(1, 2) << QPointF(3, 2) << QPointF(4, 3)
chart = QChart()
chart.addSeries(series1)
chart.addSeries(series2)
chart.addSeries(series3)
chart.createDefaultAxes()
chart.legend().setVisible(True)
chart.legend().setAlignment(Qt.AlignBottom)
chartView = ChartView(chart)
chartView.setRenderHint(QPainter.Antialiasing)
chartView.x = None
window = QMainWindow()
window.setCentralWidget(chartView)
window.resize(420, 300)
window.show()
app.exec()
if __name__ == "__main__":
main()
Which gives:
However, I find it difficult to draw the intersection point of the cursor and the line chart. As mentioned in this forum and this forum, if the cursor points at the interpolated part between two data point, it is difficult to decide the y coordinates of the intersection points, because Qt doesn't provide a build-in function to get the interpolated function value at value_at_position.x(). This question is also related to find the interpolated value. So I tried to do interpolation by my self.
The following version of codes will also draw the intersection point.
# refer to https://stackoverflow.com/a/67596291/9758790
import sys
import time
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtChart import (
QChart,
QChartView,
QLineSeries,
)
from PyQt5.Qt import *
import math
class ChartView(QChartView):
_cursor = None
_y = []
#property
def cursor(self):
return self._cursor
#cursor.setter
def cursor(self, point):
# refer to https://stackoverflow.com/a/44078533/9758790
scene_position = self.mapToScene(point)
chart_position = self.chart().mapFromScene(scene_position)
value_at_position = self.chart().mapToValue(chart_position)
if self.chart().axisX().min() < value_at_position.x() < self.chart().axisX().max():
self._cursor = scene_position
# self.update()
self.scene().update()
def drawForeground(self, painter, rect):
if self.cursor is None:
return
painter.save()
pen = QPen(QColor("indigo"))
pen.setWidth(1)
painter.setPen(pen)
p = self.cursor
r = self.chart().plotArea()
p1 = QPointF(p.x(), r.top())
p2 = QPointF(p.x(), r.bottom())
painter.drawLine(p1, p2)
chart_position = self.chart().mapFromScene(self.cursor)
value_at_position = self.chart().mapToValue(chart_position)
for series_i in self.chart().series():
pen2 = QPen(series_i.color())
pen2.setWidth(10)
painter.setPen(pen2)
# find the nearest points
min_distance_left = math.inf
min_distance_right = math.inf
nearest_point_left = None
nearest_point_right = None
exact_point = None
for p_i in series_i.pointsVector():
if p_i.x() > value_at_position.x():
if p_i.x() - value_at_position.x() < min_distance_right:
min_distance_right = p_i.x() - value_at_position.x()
nearest_point_right = p_i
elif p_i.x() < value_at_position.x():
if value_at_position.x() - p_i.x() < min_distance_right:
min_distance_left = value_at_position.x() - p_i.x()
nearest_point_left = p_i
else:
exact_point = p_i
nearest_point_left = None
nearest_point_right = None
break
if nearest_point_right is not None and nearest_point_left is not None:
# do linear interpolated by my self
k = ((nearest_point_right.y() - nearest_point_left.y()) / (nearest_point_right.x() - nearest_point_left.x()))
point_interpolated_y = nearest_point_left.y() + k * (value_at_position.x() - nearest_point_left.x())
point_interpolated_x = value_at_position.x()
point_interpolated = QPointF(point_interpolated_x, point_interpolated_y)
painter.drawPoint(self.chart().mapToScene(self.chart().mapToPosition(point_interpolated)))
if exact_point is not None:
painter.drawPoint(self.chart().mapToScene(self.chart().mapToPosition(exact_point)))
painter.restore()
def mousePressEvent(self, env):
self.cursor = env.pos()
def main():
app = QApplication(sys.argv)
series1 = QLineSeries() << QPointF(0, 3) << QPointF(1, 1) << QPointF(3, 9) << QPointF(4, 1)
series2 = QLineSeries() << QPointF(0, 9) << QPointF(1, 8) << QPointF(3, 6) << QPointF(4, 6)
series3 = QLineSeries() << QPointF(0, 4) << QPointF(1, 2) << QPointF(3, 2) << QPointF(4, 3)
chart = QChart()
chart.addSeries(series1)
chart.addSeries(series2)
chart.addSeries(series3)
chart.createDefaultAxes()
chart.legend().setVisible(True)
chart.legend().setAlignment(Qt.AlignBottom)
chartView = ChartView(chart)
chartView.setRenderHint(QPainter.Antialiasing)
chartView.x = None
window = QMainWindow()
window.setCentralWidget(chartView)
window.resize(420, 300)
window.show()
app.exec()
if __name__ == "__main__":
main()
Which gives:
This link Track Line with Data Labels provide a solution to find a nearest data point to the mouse position and draw a line there, which avoids dealing with interpolation. My codes can also be modified to draw in this way.

Seems impossible to rotate custom QGraphicsItem using QPainter (or any other method.)

Here is the code. It runs. To exhibit this bug. Right-click the ellipse, scale it by click-dragging on the ellipse. Right-click it > "Done editing". Then do the same thing with "Rotate."
I've tried over 10 different permutations of using self.setRotation, self.setTransform, painter.rotate, and so on... The only time it did rotate was when I did self.setTransform(self.transform().rotate(r)) but the result was wrong: scale & rotate in the wrong order or something.
from PyQt5.QtWidgets import QGraphicsItem, QMenu
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt
def scaleRect(rect, scale_x, scale_y):
T = QTransform.fromScale(scale_x, scale_y)
return T.mapRect(rect)
def debugPrintTransformMatrix(T):
print(str(T.m11()) + ' ' + str(T.m12()) + ' ' + str(T.m13()))
print(str(T.m21()) + ' ' + str(T.m22()) + ' ' + str(T.m23()))
print(str(T.m31()) + ' ' + str(T.m32()) + ' ' + str(T.m33()))
# Assumes no shearing or stretching.
# Only Rotation, Translation, and Scaling.
def extractTransformScale(T):
# This is math matrix notation transposed (debug print self.sceneTransform() to see)
Sx = sqrt(T.m11()**2 + T.m12()**2)
Sy = sqrt(T.m21()**2 + T.m22()**2)
return Sx, Sy
def extractTransformTranslate(T):
return T.m31(), T.m32()
class Object(QGraphicsItem):
def sceneEvent(self, event):
if event.type() == QEvent.GraphicsSceneMouseMove:
# move, scale, or rotate
if self._mode in ['scale', 'rotate']:
mouse_pos = event.scenePos()
last_pos = event.lastScenePos()
if self._mode == 'scale':
s = self.mouseScalingFactors(mouse_pos, last_pos)
self.setTransform(self.transform().scale(*s))
if self._mode == 'rotate':
r = self.mouseRotationAngle(mouse_pos, last_pos)
self.setRotation(self.rotation() + r)
return True
return super().sceneEvent(event)
def __init__(self):
super().__init__()
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
self._lastPos = QPointF(0, 0)
self.setPos(self._lastPos)
self._mode = 'neutral'
def setRenderHints(self, painter):
painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)
def boundingRectExtraScale(self):
return (1.2 , 1.2)
def mouseScalingFactors(self, pos, last_pos):
delta = pos - last_pos
return (1.01 ** delta.x(), 1.01 ** delta.y())
def mouseRotationAngle(self, pos, last_pos):
return 1 #TODO
def createDefaultContextMenu(self):
menu = QMenu()
if self._mode == 'neutral':
menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
else:
menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
return menu
def contextMenuEvent(self, event):
menu = self.createDefaultContextMenu()
menu.exec(event.screenPos())
def setMode(self, mode):
self._mode = mode
def setSelected(self, selected):
super().setSelected(selected)
self.update()
class ShapedObject(Object):
def __init__(self):
super().__init__()
self._shape = {
'name' : 'ellipse',
'radius': 35
}
self._brush = QBrush(Qt.darkGreen)
self._pen = QPen(Qt.yellow, 3)
def shapeDef(self):
return self._shape
def boundingRect(self):
rect = self.shapeRect()
s = self.boundingRectExtraScale()
return scaleRect(rect, *s)
def shape(self): #TODO QPainterPath shape for collision detection
# Should call self.boundingRectExtraScale()
return super().shape()
def paint(self, painter, option, widget):
self.setRenderHints(painter)
#super().paint(painter, option, widget)
shape = self.shapeDef()
name = shape['name']
painter.setBrush(self._brush)
painter.setPen(self._pen)
painter.save()
# ********** HERE IS THE PROBLEM *************
debugPrintTransformMatrix(painter.transform())
painter.rotate(5)
debugPrintTransformMatrix(painter.transform())
rect = self.shapeRect()
if name == 'ellipse':
painter.drawEllipse(rect)
painter.restore()
def shapeRect(self):
shape = self.shapeDef()
name = shape['name']
if name == 'ellipse':
r = shape['radius']
rect = QRectF(-r, -r, 2*r, 2*r)
return rect
####
import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
view = QGraphicsView()
scene = QGraphicsScene()
view.setScene(scene)
window.setCentralWidget(view)
ellipse = ShapedObject()
scene.addItem(ellipse)
window.show()
sys.exit(app.exec_())
Got it to work. It was the order of the operations rotate / scale. Never use an circle to test your rotation code, lol!
from PyQt5.QtWidgets import QGraphicsItem, QMenu, QGraphicsRotation, QGraphicsScale
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush, QPainterPath
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt
def scaleRect(rect, scale_x, scale_y):
T = QTransform.fromScale(scale_x, scale_y)
return T.mapRect(rect)
def debugPrintTransformMatrix(T):
print(str(T.m11()) + ' ' + str(T.m12()) + ' ' + str(T.m13()))
print(str(T.m21()) + ' ' + str(T.m22()) + ' ' + str(T.m23()))
print(str(T.m31()) + ' ' + str(T.m32()) + ' ' + str(T.m33()))
# Assumes no shearing or stretching.
# Only Rotation, Translation, and Scaling.
def extractTransformScale(T):
# This is math matrix notation transposed (debug print self.sceneTransform() to see)
Sx = sqrt(T.m11()**2 + T.m12()**2)
Sy = sqrt(T.m21()**2 + T.m22()**2)
return Sx, Sy
def extractTransformTranslate(T):
return T.m31(), T.m32()
class Object(QGraphicsItem):
def sceneEvent(self, event):
if event.type() == QEvent.GraphicsSceneMouseMove:
# move, scale, or rotate
if self._mode in ['scale', 'rotate']:
mouse_pos = event.scenePos()
last_pos = event.lastScenePos()
if self._mode == 'scale':
s = self.mouseScalingFactors(mouse_pos, last_pos)
self.applyScaleTransform(*s)
if self._mode == 'rotate':
r = self.mouseRotationAngle(mouse_pos, last_pos)
self.applyRotateTransform(r)
return True
return super().sceneEvent(event)
def __init__(self):
super().__init__()
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
self._lastPos = QPointF(0, 0)
self.setPos(self._lastPos)
self._mode = 'neutral'
self._scale = QGraphicsScale()
self._rotate = QGraphicsRotation()
self.setTransformations([self._rotate, self._scale])
def applyRotateTransform(self, angle):
self._rotate.setAngle(self._rotate.angle() + 15)
def applyScaleTransform(self, sx, sy):
self._scale.setXScale(sx * self._scale.xScale())
self._scale.setYScale(sy * self._scale.yScale())
def setRenderHints(self, painter):
painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)
def boundingRectExtraScale(self):
return (1.2 , 1.2)
def mouseScalingFactors(self, pos, last_pos):
delta = pos - last_pos
return (1.01 ** delta.x(), 1.01 ** delta.y())
def mouseRotationAngle(self, pos, last_pos):
return 1 #TODO
def createDefaultContextMenu(self):
menu = QMenu()
if self._mode == 'neutral':
menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
else:
menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
return menu
def contextMenuEvent(self, event):
menu = self.createDefaultContextMenu()
menu.exec(event.screenPos())
def setMode(self, mode):
self._mode = mode
def setSelected(self, selected):
super().setSelected(selected)
self.update()
class ShapedObject(Object):
def __init__(self):
super().__init__()
self._shape = {
'name' : 'ellipse',
'radius': 35
}
self._brush = QBrush(Qt.darkGreen)
self._pen = QPen(Qt.yellow, 3)
def shapeDef(self):
return self._shape
def boundingRect(self):
rect = self.shapeRect()
s = self.boundingRectExtraScale()
return scaleRect(rect, *s)
def shape(self): #TODO QPainterPath shape for collision detection
# Should call self.boundingRectExtraScale()
return super().shape()
def paint(self, painter, option, widget):
self.setRenderHints(painter)
#super().paint(painter, option, widget)
shape = self.shapeDef()
name = shape['name']
painter.setBrush(self._brush)
painter.setPen(self._pen)
painter.save()
path = QPainterPath()
if name == 'ellipse':
r = shape['radius']
path.addEllipse(QPointF(0, 0), r, r)
painter.drawPath(path)
painter.restore()
def shapeRect(self):
shape = self.shapeDef()
name = shape['name']
if name == 'ellipse':
r = shape['radius']
rect = QRectF(-r, -r, 2*r, 2*r)
return rect
####
import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
view = QGraphicsView()
scene = QGraphicsScene()
view.setScene(scene)
window.setCentralWidget(view)
ellipse = ShapedObject()
scene.addItem(ellipse)
window.show()
sys.exit(app.exec_())

Converting 2D screen coordinates to 3D Coordinates in PyQT

I am using PyQT for one of the first times, and I'm having trouble figuring out where a mouse click is in 3D space. Obviously it is not a perfect 1-to-1 mapping, but let's say that I click on a location (x, y, 0) on my QtGui. How can I transform that mouse click to its 3D location using the camera?
Context: I am trying to have users draw splines in 3-D, and in order to do this, I need to know where the user is clicking for when I render the spline. I'm building an application using PythonOCC. I've attached my code below.
import random
import sys
import IPython
from OCC.Display.qtDisplay import qtViewer3d, get_qt_modules
from OCC.gp import gp_Pnt2d, gp_Pnt
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge,
BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeWire)
from OCC.BRepFill import BRepFill_Filling
from OCC.GeomAbs import GeomAbs_C0
from OCC.GeomAPI import GeomAPI_PointsToBSpline
from OCC.TColgp import TColgp_Array1OfPnt
QtCore, QtGui, QtOpenGL = get_qt_modules()
try:
from OpenGL.GL import (glViewport, glMatrixMode, glOrtho, glLoadIdentity,
GL_PROJECTION, GL_MODELVIEW)
except ImportError:
msg = "for this example, the OpenGL module is required" \
"why not run \"pip install PyOpenGL\"\?"
sys.exit(status=1)
class GLWidget(qtViewer3d):
def __init__(self, parent=None):
super(GLWidget, self).__init__(parent)
self._initialized = False
midnight = QtCore.QTime(0, 0, 0)
random.seed(midnight.secsTo(QtCore.QTime.currentTime()))
self.object = 0
self.xRot = 0
self.yRot = 0
self.zRot = 0
self.image = QtGui.QImage()
self.bubbles = []
self.lastPos = QtCore.QPoint()
self.lines = []
self.current_point = None
self.pts = []
self.shiftHeld = True
self.trolltechGreen = QtGui.QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
self.trolltechPurple = QtGui.QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)
self.animationTimer = QtCore.QTimer()
self.animationTimer.setSingleShot(False)
self.animationTimer.timeout.connect(self.animate)
self.animationTimer.start(25)
self.setAutoFillBackground(False)
self.setMinimumSize(200, 200)
self.setWindowTitle("Overpainting a Scene")
# parameters for overpainting
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, 0)
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
def setXRotation(self, angle):
if angle != self.xRot:
self.xRot = angle
def setYRotation(self, angle):
if angle != self.yRot:
self.yRot = angle
def setZRotation(self, angle):
if angle != self.zRot:
self.zRot = angle
def mousePressEvent(self, event):
self.lastPos = event.pos()
super(GLWidget, self).mousePressEvent(event)
worldCoords = super(GLWidget, self).mapToGlobal( self.lastPos )
print self.lastPos
if event.buttons() & QtCore.Qt.RightButton and not (event.modifiers() & QtCore.Qt.ShiftModifier):
print 'first'
self.pts.append(gp_Pnt(self.lastPos.x(), self.lastPos.y(), 0.0))
elif event.buttons() & QtCore.Qt.RightButton and (event.modifiers() & QtCore.Qt.ShiftModifier):
print 'second'
curve = self.points_to_bspline(self.pts)
self._display.DisplayShape(curve, update=True)
self.pts = [] #clear it
def mouseMoveEvent(self, event):
dx = event.x() - self.lastPos.x()
dy = event.y() - self.lastPos.y()
"""
if (event.buttons() & QtCore.Qt.LeftButton):
self.setXRotation(self.xRot + 8 * dy)
self.setYRotation(self.yRot + 8 * dx)
elif (event.buttons() & QtCore.Qt.RightButton):
self.setXRotation(self.xRot + 8 * dy)
self.setZRotation(self.zRot + 8 * dx)
"""
self.lastPos = event.pos()
super(GLWidget, self).mouseMoveEvent(event)
def paintGL(self):
if self._inited:
self._display.Context.UpdateCurrentViewer()
def paintEvent(self, event):
if self._inited:
self._display.Context.UpdateCurrentViewer()
self.makeCurrent()
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
if self.context().isValid():
self.swapBuffers()
if self._drawbox:
painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1))
rect = QtCore.QRect(*self._drawbox)
painter.drawRect(rect)
"""
for bubble in self.bubbles:
if bubble.rect().intersects(QtCore.QRectF(event.rect())):
bubble.drawBubble(painter)
"""
painter.end()
self.doneCurrent()
else:
print('invalid OpenGL context: Qt cannot overpaint viewer')
def showEvent(self, event):
pass
#self.createBubbles(20 - len(self.bubbles))
def sizeHint(self):
return QtCore.QSize(400, 400)
def animate(self):
pass
"""
for bubble in self.bubbles:
bubble.move(self.rect())
self.update()
"""
def setupViewport(self, width, height):
side = min(width, height)
glViewport((width - side) // 2, (height - side) // 2, side, side)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0)
glMatrixMode(GL_MODELVIEW)
def points_to_bspline(self, pnts):
pts = TColgp_Array1OfPnt(0, len(pnts)-1)
for n, i in enumerate(pnts):
pts.SetValue(n, i)
crv = GeomAPI_PointsToBSpline(pts)
return crv.Curve()
if __name__ == '__main__':
def TestOverPainting():
class AppFrame(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle(self.tr("qtDisplay3d overpainting example"))
self.resize(640, 480)
self.canva = GLWidget(self)
mainLayout = QtGui.QHBoxLayout()
mainLayout.addWidget(self.canva)
mainLayout.setContentsMargins(0, 0, 0, 0)
self.setLayout(mainLayout)
def runTests(self):
self.canva._display.Test()
app = QtGui.QApplication(sys.argv)
frame = AppFrame()
frame.show()
frame.canva.InitDriver()
frame.runTests()
app.exec_()
TestOverPainting()
To answer my own question:
(x, y, z, vx, vy, vz) = self._display.View.ConvertWithProj(self.lastPos.x(), self.lastPos.y())
Gives the entire line of points that x and y can map to, with x, y, and z being one point, and vx, vy, and vz giving the parameterization of the line.

Resources