Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Open Toontown 2023-10-14 18:06:35 -04:00
commit 00e452ede6
74 changed files with 1972 additions and 1371 deletions

View File

@ -355,6 +355,23 @@ jobs:
rmdir panda3d-1.10.14
(cd thirdparty/darwin-libs-a && rm -rf rocket)
- name: Set up Python 3.12
if: matrix.os != 'windows-2019'
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Build Python 3.12
if: matrix.os != 'windows-2019'
shell: bash
run: |
python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10
- name: Test Python 3.12
if: matrix.os != 'windows-2019'
shell: bash
run: |
python -m pip install -r requirements-test.txt
PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:

View File

@ -2650,11 +2650,11 @@ class main(wx.Frame):
for inputFile in inputs:
if (inputFile != ''):
inputFilename = inputFile.split('\\')[-1]
print "Compare: ", inFile, filename, inputFile, inputFilename
print("Compare: ", inFile, filename, inputFile, inputFilename)
if inputFilename == filename:
inputTime = os.path.getmtime(inputFile)
outputTime = os.path.getmtime(inFile)
print "Matched: ", (inputTime > outputTime)
print("Matched: ", (inputTime > outputTime))
inputChanged = (inputTime > outputTime)
break
'''
@ -2848,7 +2848,7 @@ class main(wx.Frame):
except ValueError:
return
#print self.batchList
#print(self.batchList)
def OnBatchItemEdit(self, event):
selectedItemId = self.batchTree.GetSelections()

View File

@ -1,4 +1,4 @@
from distutils.core import setup
from setuptools import setup
import py2exe
setup(console=['Panda3DToolsGUI.py'])

View File

@ -2,6 +2,10 @@
DirectNotify module: this module contains the DirectNotify class
"""
from __future__ import annotations
from panda3d.core import StreamWriter
from . import Notifier
from . import Logger
@ -12,39 +16,39 @@ class DirectNotify:
mulitple notify categories via a dictionary of Notifiers.
"""
def __init__(self):
def __init__(self) -> None:
"""
DirectNotify class keeps a dictionary of Notfiers
"""
self.__categories = {}
self.__categories: dict[str, Notifier.Notifier] = {}
# create a default log file
self.logger = Logger.Logger()
# This will get filled in later by ShowBase.py with a
# C++-level StreamWriter object for writing to standard
# output.
self.streamWriter = None
self.streamWriter: StreamWriter | None = None
def __str__(self):
def __str__(self) -> str:
"""
Print handling routine
"""
return "DirectNotify categories: %s" % (self.__categories)
#getters and setters
def getCategories(self):
def getCategories(self) -> list[str]:
"""
Return list of category dictionary keys
"""
return list(self.__categories.keys())
def getCategory(self, categoryName):
def getCategory(self, categoryName: str) -> Notifier.Notifier | None:
"""getCategory(self, string)
Return the category with given name if present, None otherwise
"""
return self.__categories.get(categoryName, None)
def newCategory(self, categoryName, logger=None):
def newCategory(self, categoryName: str, logger: Logger.Logger | None = None) -> Notifier.Notifier:
"""newCategory(self, string)
Make a new notify category named categoryName. Return new category
if no such category exists, else return existing category
@ -52,9 +56,11 @@ class DirectNotify:
if categoryName not in self.__categories:
self.__categories[categoryName] = Notifier.Notifier(categoryName, logger)
self.setDconfigLevel(categoryName)
return self.getCategory(categoryName)
notifier = self.getCategory(categoryName)
assert notifier is not None
return notifier
def setDconfigLevel(self, categoryName):
def setDconfigLevel(self, categoryName: str) -> None:
"""
Check to see if this category has a dconfig variable
to set the notify severity and then set that level. You cannot
@ -77,40 +83,42 @@ class DirectNotify:
level = 'error'
category = self.getCategory(categoryName)
assert category is not None, f'failed to find category: {categoryName!r}'
# Note - this print statement is making it difficult to
# achieve "no output unless there's an error" operation - Josh
# print ("Setting DirectNotify category: " + categoryName +
# " to severity: " + level)
if level == "error":
category.setWarning(0)
category.setInfo(0)
category.setDebug(0)
category.setWarning(False)
category.setInfo(False)
category.setDebug(False)
elif level == "warning":
category.setWarning(1)
category.setInfo(0)
category.setDebug(0)
category.setWarning(True)
category.setInfo(False)
category.setDebug(False)
elif level == "info":
category.setWarning(1)
category.setInfo(1)
category.setDebug(0)
category.setWarning(True)
category.setInfo(True)
category.setDebug(False)
elif level == "debug":
category.setWarning(1)
category.setInfo(1)
category.setDebug(1)
category.setWarning(True)
category.setInfo(True)
category.setDebug(True)
else:
print("DirectNotify: unknown notify level: " + str(level)
+ " for category: " + str(categoryName))
def setDconfigLevels(self):
def setDconfigLevels(self) -> None:
for categoryName in self.getCategories():
self.setDconfigLevel(categoryName)
def setVerbose(self):
def setVerbose(self) -> None:
for categoryName in self.getCategories():
category = self.getCategory(categoryName)
category.setWarning(1)
category.setInfo(1)
category.setDebug(1)
assert category is not None
category.setWarning(True)
category.setInfo(True)
category.setDebug(True)
def popupControls(self, tl = None):
# Don't use a regular import, to prevent ModuleFinder from picking
@ -119,5 +127,5 @@ class DirectNotify:
NotifyPanel = importlib.import_module('direct.tkpanels.NotifyPanel')
NotifyPanel.NotifyPanel(self, tl)
def giveNotify(self,cls):
def giveNotify(self, cls) -> None:
cls.notify = self.newCategory(cls.__name__)

View File

@ -1,27 +1,30 @@
"""Logger module: contains the logger class which creates and writes
data to log files on disk"""
from __future__ import annotations
import io
import time
import math
class Logger:
def __init__(self, fileName="log"):
def __init__(self, fileName: str = "log") -> None:
"""
Logger constructor
"""
self.__timeStamp = 1
self.__timeStamp = True
self.__startTime = 0.0
self.__logFile = None
self.__logFile: io.TextIOWrapper | None = None
self.__logFileName = fileName
def setTimeStamp(self, enable):
def setTimeStamp(self, enable: bool) -> None:
"""
Toggle time stamp printing with log entries on and off
"""
self.__timeStamp = enable
def getTimeStamp(self):
def getTimeStamp(self) -> bool:
"""
Return whether or not we are printing time stamps with log entries
"""
@ -29,24 +32,25 @@ class Logger:
# logging control
def resetStartTime(self):
def resetStartTime(self) -> None:
"""
Reset the start time of the log file for time stamps
"""
self.__startTime = time.time()
def log(self, entryString):
def log(self, entryString: str) -> None:
"""log(self, string)
Print the given string to the log file"""
if self.__logFile is None:
self.__openLogFile()
assert self.__logFile is not None
if self.__timeStamp:
self.__logFile.write(self.__getTimeStamp())
self.__logFile.write(entryString + '\n')
# logging functions
def __openLogFile(self):
def __openLogFile(self) -> None:
"""
Open a file for logging error/warning messages
"""
@ -56,14 +60,14 @@ class Logger:
logFileName = self.__logFileName + "." + st
self.__logFile = open(logFileName, "w")
def __closeLogFile(self):
def __closeLogFile(self) -> None:
"""
Close the error/warning output file
"""
if self.__logFile is not None:
self.__logFile.close()
def __getTimeStamp(self):
def __getTimeStamp(self) -> str:
"""
Return the offset between current time and log file startTime
"""

View File

@ -2,11 +2,16 @@
Notifier module: contains methods for handling information output
for the programmer/user
"""
from __future__ import annotations
from .Logger import Logger
from .LoggerGlobal import defaultLogger
from direct.showbase import PythonUtil
from panda3d.core import ConfigVariableBool, NotifyCategory, StreamWriter, Notify
import time
import sys
from typing import NoReturn
class NotifierException(Exception):
@ -20,13 +25,13 @@ class Notifier:
# messages instead of writing them to the console. This is
# particularly useful for integrating the Python notify system
# with the C++ notify system.
streamWriter = None
streamWriter: StreamWriter | None = None
if ConfigVariableBool('notify-integrate', True):
streamWriter = StreamWriter(Notify.out(), False)
showTime = ConfigVariableBool('notify-timestamp', False)
def __init__(self, name, logger=None):
def __init__(self, name: str, logger: Logger | None = None) -> None:
"""
Parameters:
name (str): a string name given to this Notifier instance.
@ -42,12 +47,12 @@ class Notifier:
self.__logger = logger
# Global default levels are initialized here
self.__info = 1
self.__warning = 1
self.__debug = 0
self.__logging = 0
self.__info = True
self.__warning = True
self.__debug = False
self.__logging = False
def setServerDelta(self, delta, timezone):
def setServerDelta(self, delta: float, timezone: int) -> None:
"""
Call this method on any Notify object to globally change the
timestamp printed for each line of all Notify objects.
@ -65,7 +70,7 @@ class Notifier:
self.info("Notify clock adjusted by %s (and timezone adjusted by %s hours) to synchronize with server." % (PythonUtil.formatElapsedSeconds(delta), (time.timezone - timezone) / 3600))
def getTime(self):
def getTime(self) -> str:
"""
Return the time as a string suitable for printing at the
head of any notify message
@ -74,14 +79,14 @@ class Notifier:
# the task is out of focus on win32. time.clock doesn't have this problem.
return time.strftime(":%m-%d-%Y %H:%M:%S ", time.localtime(time.time() + self.serverDelta))
def getOnlyTime(self):
def getOnlyTime(self) -> str:
"""
Return the time as a string.
The Only in the name is referring to not showing the date.
"""
return time.strftime("%H:%M:%S", time.localtime(time.time() + self.serverDelta))
def __str__(self):
def __str__(self) -> str:
"""
Print handling routine
"""
@ -89,26 +94,26 @@ class Notifier:
(self.__name, self.__info, self.__warning, self.__debug, self.__logging)
# Severity funcs
def setSeverity(self, severity):
def setSeverity(self, severity: int) -> None:
from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
if severity >= NSError:
self.setWarning(0)
self.setInfo(0)
self.setDebug(0)
self.setWarning(False)
self.setInfo(False)
self.setDebug(False)
elif severity == NSWarning:
self.setWarning(1)
self.setInfo(0)
self.setDebug(0)
self.setWarning(True)
self.setInfo(False)
self.setDebug(False)
elif severity == NSInfo:
self.setWarning(1)
self.setInfo(1)
self.setDebug(0)
self.setWarning(True)
self.setInfo(True)
self.setDebug(False)
elif severity <= NSDebug:
self.setWarning(1)
self.setInfo(1)
self.setDebug(1)
self.setWarning(True)
self.setInfo(True)
self.setDebug(True)
def getSeverity(self):
def getSeverity(self) -> int:
from panda3d.core import NSDebug, NSInfo, NSWarning, NSError
if self.getDebug():
return NSDebug
@ -120,7 +125,7 @@ class Notifier:
return NSError
# error funcs
def error(self, errorString, exception=NotifierException):
def error(self, errorString: object, exception: type[Exception] = NotifierException) -> NoReturn:
"""
Raise an exception with given string and optional type:
Exception: error
@ -134,7 +139,7 @@ class Notifier:
raise exception(errorString)
# warning funcs
def warning(self, warningString):
def warning(self, warningString: object) -> int:
"""
Issue the warning message if warn flag is on
"""
@ -148,20 +153,20 @@ class Notifier:
self.__print(string)
return 1 # to allow assert myNotify.warning("blah")
def setWarning(self, enable):
def setWarning(self, enable: bool) -> None:
"""
Enable/Disable the printing of warning messages
"""
self.__warning = enable
def getWarning(self):
def getWarning(self) -> bool:
"""
Return whether the printing of warning messages is on or off
"""
return self.__warning
# debug funcs
def debug(self, debugString):
def debug(self, debugString: object) -> int:
"""
Issue the debug message if debug flag is on
"""
@ -175,20 +180,20 @@ class Notifier:
self.__print(string)
return 1 # to allow assert myNotify.debug("blah")
def setDebug(self, enable):
def setDebug(self, enable: bool) -> None:
"""
Enable/Disable the printing of debug messages
"""
self.__debug = enable
def getDebug(self):
def getDebug(self) -> bool:
"""
Return whether the printing of debug messages is on or off
"""
return self.__debug
# info funcs
def info(self, infoString):
def info(self, infoString: object) -> int:
"""
Print the given informational string, if info flag is on
"""
@ -202,39 +207,39 @@ class Notifier:
self.__print(string)
return 1 # to allow assert myNotify.info("blah")
def getInfo(self):
def getInfo(self) -> bool:
"""
Return whether the printing of info messages is on or off
"""
return self.__info
def setInfo(self, enable):
def setInfo(self, enable: bool) -> None:
"""
Enable/Disable informational message printing
"""
self.__info = enable
# log funcs
def __log(self, logEntry):
def __log(self, logEntry: str) -> None:
"""
Determine whether to send informational message to the logger
"""
if self.__logging:
self.__logger.log(logEntry)
def getLogging(self):
def getLogging(self) -> bool:
"""
Return 1 if logging enabled, 0 otherwise
"""
return self.__logging
def setLogging(self, enable):
def setLogging(self, enable: bool) -> None:
"""
Set the logging flag to int (1=on, 0=off)
"""
self.__logging = enable
def __print(self, string):
def __print(self, string: str) -> None:
"""
Prints the string to output followed by a newline.
"""
@ -251,7 +256,7 @@ class Notifier:
the function call (with parameters).
"""
#f.f_locals['self'].__init__.im_class.__name__
if self.__debug:
if __debug__ and self.__debug:
state = ''
doId = ''
if obj is not None:
@ -285,13 +290,13 @@ class Notifier:
self.__print(string)
return 1 # to allow assert self.notify.debugStateCall(self)
def debugCall(self, debugString=''):
def debugCall(self, debugString: object = '') -> int:
"""
If this notify is in debug mode, print the time of the
call followed by the notifier category and
the function call (with parameters).
"""
if self.__debug:
if __debug__ and self.__debug:
message = str(debugString)
string = ":%s:%s \"%s\" %s"%(
self.getOnlyTime(),

View File

@ -1,5 +1,8 @@
from __future__ import annotations
import os
import time
from typing import Iterable
class RotatingLog:
@ -8,7 +11,12 @@ class RotatingLog:
to a new file if the prior file is too large or after a time interval.
"""
def __init__(self, path="./log_file", hourInterval=24, megabyteLimit=1024):
def __init__(
self,
path: str = "./log_file",
hourInterval: int | None = 24,
megabyteLimit: int | None = 1024,
) -> None:
"""
Args:
path: a full or partial path with file name.
@ -28,33 +36,33 @@ class RotatingLog:
if megabyteLimit is not None:
self.sizeLimit = megabyteLimit*1024*1024
def __del__(self):
def __del__(self) -> None:
self.close()
def close(self):
def close(self) -> None:
if hasattr(self, "file"):
self.file.flush()
self.file.close()
self.closed = self.file.closed
del self.file
else:
self.closed = 1
self.closed = True
def shouldRotate(self):
def shouldRotate(self) -> bool:
"""
Returns a bool about whether a new log file should
be created and written to (while at the same time
stopping output to the old log file and closing it).
"""
if not hasattr(self, "file"):
return 1
return True
if self.timeLimit is not None and time.time() > self.timeLimit:
return 1
return True
if self.sizeLimit is not None and self.file.tell() > self.sizeLimit:
return 1
return 0
return True
return False
def filePath(self):
def filePath(self) -> str:
dateString = time.strftime("%Y_%m_%d_%H", time.localtime())
for i in range(26):
limit = self.sizeLimit
@ -65,7 +73,7 @@ class RotatingLog:
# Maybe we should clear the self.sizeLimit here... maybe.
return path
def rotate(self):
def rotate(self) -> None:
"""
Rotate the log now. You normally shouldn't need to call this.
See write().
@ -88,12 +96,13 @@ class RotatingLog:
#self.newlines = self.file.newlines # Python 2.3, maybe
if self.timeLimit is not None and time.time() > self.timeLimit:
assert self.timeInterval is not None
self.timeLimit=time.time()+self.timeInterval
else:
# We'll keep writing to the old file, if available.
print("RotatingLog error: Unable to open new log file \"%s\"." % (path,))
def write(self, data):
def write(self, data: str) -> int | None:
"""
Write the data to either the current log or a new one,
depending on the return of shouldRotate() and whether
@ -105,14 +114,15 @@ class RotatingLog:
r = self.file.write(data)
self.file.flush()
return r
return None
def flush(self):
def flush(self) -> None:
return self.file.flush()
def fileno(self):
def fileno(self) -> int:
return self.file.fileno()
def isatty(self):
def isatty(self) -> bool:
return self.file.isatty()
def __next__(self):
@ -131,14 +141,14 @@ class RotatingLog:
def xreadlines(self):
return self.file.xreadlines()
def seek(self, offset, whence=0):
def seek(self, offset: int, whence: int = 0) -> int:
return self.file.seek(offset, whence)
def tell(self):
def tell(self) -> int:
return self.file.tell()
def truncate(self, size):
def truncate(self, size: int | None) -> int:
return self.file.truncate(size)
def writelines(self, sequence):
def writelines(self, sequence: Iterable[str]) -> None:
return self.file.writelines(sequence)

View File

@ -1,6 +1,7 @@
import math
from panda3d.core import BitMask32, Mat4, NodePath, Point3, VBase3, Vec3, Vec4, rad2Deg
from direct.showbase.DirectObject import DirectObject
from direct.showbase import ShowBaseGlobal
from .DirectUtil import CLAMP, useDirectRenderStyle
from .DirectGeometry import getCrankAngle, getScreenXY
from . import DirectGlobals as DG
@ -26,7 +27,7 @@ class DirectCameraControl(DirectObject):
self.orthoViewRoll = 0.0
self.lastView = 0
self.coa = Point3(0, 100, 0)
self.coaMarker = base.loader.loadModel('models/misc/sphere')
self.coaMarker = ShowBaseGlobal.loader.loadModel('models/misc/sphere')
self.coaMarker.setName('DirectCameraCOAMarker')
self.coaMarker.setTransparency(1)
self.coaMarker.setColor(1, 0, 0, 0)
@ -37,8 +38,8 @@ class DirectCameraControl(DirectObject):
self.fLockCOA = 0
self.nullHitPointCount = 0
self.cqEntries = []
self.coaMarkerRef = base.direct.group.attachNewNode('coaMarkerRef')
self.camManipRef = base.direct.group.attachNewNode('camManipRef')
self.coaMarkerRef = ShowBaseGlobal.direct.group.attachNewNode('coaMarkerRef')
self.camManipRef = ShowBaseGlobal.direct.group.attachNewNode('camManipRef')
self.switchDirBelowZero = True
self.manipulateCameraTask = None
self.manipulateCameraInterval = None
@ -112,11 +113,6 @@ class DirectCameraControl(DirectObject):
self.perspCollPlane2 = None # [gjeon] used for new LE
def toggleMarkerVis(self):
## if base.direct.cameraControl.coaMarker.isHidden():
## base.direct.cameraControl.coaMarker.show()
## else:
## base.direct.cameraControl.coaMarker.hide()
if self.coaMarker.isHidden():
self.coaMarker.show()
else:
@ -132,11 +128,14 @@ class DirectCameraControl(DirectObject):
# Hide the marker for this kind of motion
self.coaMarker.hide()
# Record time of start of mouse interaction
base = ShowBaseGlobal.base
self.startT = base.clock.getFrameTime()
self.startF = base.clock.getFrameCount()
# If the cam is orthogonal, spawn differentTask
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
base.direct.camera.getName() != 'persp':
direct = ShowBaseGlobal.direct
if hasattr(direct, "manipulationControl") and \
direct.manipulationControl.fMultiView and \
direct.camera.getName() != 'persp':
self.spawnOrthoZoom()
else:
# Start manipulation
@ -167,7 +166,9 @@ class DirectCameraControl(DirectObject):
def mouseFlyStart(self, modifiers):
# Record undo point
# base.direct.pushUndo([base.direct.camera]) # Wasteful use of undo
base = ShowBaseGlobal.base
direct = ShowBaseGlobal.direct
#direct.pushUndo([direct.camera]) # Wasteful use of undo
if self.useMayaCamControls and modifiers == 4: # alt is down, use maya controls
# Hide the marker for this kind of motion
self.coaMarker.hide()
@ -176,15 +177,16 @@ class DirectCameraControl(DirectObject):
self.startF = base.clock.getFrameCount()
# Start manipulation
# If the cam is orthogonal, spawn differentTask
if hasattr(base.direct, "manipulationControl") and base.direct.manipulationControl.fMultiView and\
base.direct.camera.getName() != 'persp':
if hasattr(direct, "manipulationControl") and \
direct.manipulationControl.fMultiView and \
direct.camera.getName() != 'persp':
self.spawnOrthoTranslate()
else:
self.spawnXZTranslate()
self.altDown = 1
elif not self.useMayaCamControls:
# Where are we in the display region?
if ((abs(base.direct.dr.mouseX) < 0.9) and (abs(base.direct.dr.mouseY) < 0.9)):
if abs(direct.dr.mouseX) < 0.9 and abs(direct.dr.mouseY) < 0.9:
# MOUSE IS IN CENTRAL REGION
# Hide the marker for this kind of motion
self.coaMarker.hide()
@ -194,19 +196,18 @@ class DirectCameraControl(DirectObject):
# Start manipulation
self.spawnXZTranslateOrHPanYZoom()
# END MOUSE IN CENTRAL REGION
elif abs(direct.dr.mouseX) > 0.9 and abs(direct.dr.mouseY) > 0.9:
# Mouse is in corners, spawn roll task
self.spawnMouseRollTask()
else:
if ((abs(base.direct.dr.mouseX) > 0.9) and
(abs(base.direct.dr.mouseY) > 0.9)):
# Mouse is in corners, spawn roll task
self.spawnMouseRollTask()
else:
# Mouse is in outer frame, spawn mouseRotateTask
self.spawnMouseRotateTask()
# Mouse is in outer frame, spawn mouseRotateTask
self.spawnMouseRotateTask()
if not modifiers == 4:
self.altDown = 0
def mouseFlyStop(self):
self.__stopManipulateCamera()
base = ShowBaseGlobal.base
stopT = base.clock.getFrameTime()
deltaT = stopT - self.startT
stopF = base.clock.getFrameCount()
@ -215,7 +216,8 @@ class DirectCameraControl(DirectObject):
# if not self.useMayaCamControls and (deltaT <= 0.25) or (deltaF <= 1):
# Do this when not trying to manipulate camera
if not self.altDown and len(base.direct.selected.getSelectedAsList()) == 0:
direct = ShowBaseGlobal.direct
if not self.altDown and len(direct.selected.getSelectedAsList()) == 0:
# Check for a hit point based on
# current mouse position
# Allow intersection with unpickable objects
@ -224,13 +226,13 @@ class DirectCameraControl(DirectObject):
skipFlags = DG.SKIP_HIDDEN | DG.SKIP_BACKFACE
# Skip camera (and its children), unless control key is pressed
skipFlags |= DG.SKIP_CAMERA * (1 - base.getControl())
self.computeCOA(base.direct.iRay.pickGeom(skipFlags = skipFlags))
self.computeCOA(direct.iRay.pickGeom(skipFlags = skipFlags))
# Record reference point
self.coaMarkerRef.setPosHprScale(base.cam, 0, 0, 0, 0, 0, 0, 1, 1, 1)
# Record entries
self.cqEntries = []
for i in range(base.direct.iRay.getNumEntries()):
self.cqEntries.append(base.direct.iRay.getEntry(i))
for i in range(direct.iRay.getNumEntries()):
self.cqEntries.append(direct.iRay.getEntry(i))
# Show the marker
self.coaMarker.show()
# Resize it
@ -251,7 +253,7 @@ class DirectCameraControl(DirectObject):
# Spawn the new task
t = Task.Task(self.XZTranslateOrHPanYZoomTask)
# For HPanYZoom
t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
self.__startManipulateCamera(task = t)
def spawnXZTranslateOrHPPan(self):
@ -277,7 +279,7 @@ class DirectCameraControl(DirectObject):
self.__stopManipulateCamera()
# Spawn new task
t = Task.Task(self.HPanYZoomTask)
t.zoomSF = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
t.zoomSF = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
self.__startManipulateCamera(task = t)
def spawnOrthoZoom(self):
@ -294,13 +296,13 @@ class DirectCameraControl(DirectObject):
self.__startManipulateCamera(func = self.HPPanTask)
def XZTranslateOrHPanYZoomTask(self, state):
if base.direct.fShift:
if ShowBaseGlobal.direct.fShift:
return self.XZTranslateTask(state)
else:
return self.HPanYZoomTask(state)
def XZTranslateOrHPPanTask(self, state):
if base.direct.fShift:
if ShowBaseGlobal.direct.fShift:
# Panning action
return self.HPPanTask(state)
else:
@ -308,43 +310,46 @@ class DirectCameraControl(DirectObject):
return self.XZTranslateTask(state)
def XZTranslateTask(self, state):
coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
xlateSF = coaDist / base.direct.dr.near
base.direct.camera.setPos(base.direct.camera,
(-0.5 * base.direct.dr.mouseDeltaX *
base.direct.dr.nearWidth *
direct = ShowBaseGlobal.direct
coaDist = Vec3(self.coaMarker.getPos(direct.camera)).length()
xlateSF = coaDist / direct.dr.near
direct.camera.setPos(direct.camera,
(-0.5 * direct.dr.mouseDeltaX *
direct.dr.nearWidth *
xlateSF),
0.0,
(-0.5 * base.direct.dr.mouseDeltaY *
base.direct.dr.nearHeight *
(-0.5 * direct.dr.mouseDeltaY *
direct.dr.nearHeight *
xlateSF))
return Task.cont
def OrthoTranslateTask(self, state):
# create ray from the camera to detect 3d position
iRay = SelectionRay(base.direct.camera)
iRay.collider.setFromLens(base.direct.camNode, base.direct.dr.mouseX, base.direct.dr.mouseY)
direct = ShowBaseGlobal.direct
iRay = SelectionRay(direct.camera)
iRay.collider.setFromLens(direct.camNode, direct.dr.mouseX, direct.dr.mouseY)
#iRay.collideWithBitMask(1)
iRay.collideWithBitMask(BitMask32.bit(21))
iRay.ct.traverse(base.direct.grid)
iRay.ct.traverse(direct.grid)
entry = iRay.getEntry(0)
hitPt = entry.getSurfacePoint(entry.getFromNodePath())
iRay.collisionNodePath.removeNode()
del iRay
if hasattr(state, 'prevPt'):
base.direct.camera.setPos(base.direct.camera, (state.prevPt - hitPt))
direct.camera.setPos(direct.camera, (state.prevPt - hitPt))
state.prevPt = hitPt
return Task.cont
def HPanYZoomTask(self, state):
# If the cam is orthogonal, don't rotate or zoom.
if (hasattr(base.direct.cam.node(), "getLens") and
base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
direct = ShowBaseGlobal.direct
if (hasattr(direct.cam.node(), "getLens") and
direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
return
if base.direct.fControl:
moveDir = Vec3(self.coaMarker.getPos(base.direct.camera))
if direct.fControl:
moveDir = Vec3(self.coaMarker.getPos(direct.camera))
# If marker is behind camera invert vector
if moveDir[1] < 0.0:
moveDir.assign(moveDir * -1)
@ -353,18 +358,18 @@ class DirectCameraControl(DirectObject):
moveDir = Vec3(Y_AXIS)
if self.useMayaCamControls: # use maya controls
moveDir.assign(moveDir * ((base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY)
moveDir.assign(moveDir * ((direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY)
* state.zoomSF))
hVal = 0.0
else:
moveDir.assign(moveDir * (-1.0 * base.direct.dr.mouseDeltaY *
moveDir.assign(moveDir * (-1.0 * direct.dr.mouseDeltaY *
state.zoomSF))
if base.direct.dr.mouseDeltaY > 0.0:
if direct.dr.mouseDeltaY > 0.0:
moveDir.setY(moveDir[1] * 1.0)
hVal = 0.5 * base.direct.dr.mouseDeltaX * base.direct.dr.fovH
hVal = 0.5 * direct.dr.mouseDeltaX * direct.dr.fovH
base.direct.camera.setPosHpr(base.direct.camera,
direct.camera.setPosHpr(direct.camera,
moveDir[0],
moveDir[1],
moveDir[2],
@ -372,39 +377,42 @@ class DirectCameraControl(DirectObject):
0.0, 0.0)
if self.lockRoll:
# flatten roll
base.direct.camera.setR(0)
direct.camera.setR(0)
return Task.cont
def OrthoZoomTask(self, state):
filmSize = base.direct.camNode.getLens().getFilmSize()
factor = (base.direct.dr.mouseDeltaX -1.0 * base.direct.dr.mouseDeltaY) * 0.1
x = base.direct.dr.getWidth()
y = base.direct.dr.getHeight()
base.direct.dr.orthoFactor -= factor
if base.direct.dr.orthoFactor < 0:
base.direct.dr.orthoFactor = 0.0001
base.direct.dr.updateFilmSize(x, y)
direct = ShowBaseGlobal.direct
filmSize = direct.camNode.getLens().getFilmSize()
factor = (direct.dr.mouseDeltaX -1.0 * direct.dr.mouseDeltaY) * 0.1
x = direct.dr.getWidth()
y = direct.dr.getHeight()
direct.dr.orthoFactor -= factor
if direct.dr.orthoFactor < 0:
direct.dr.orthoFactor = 0.0001
direct.dr.updateFilmSize(x, y)
return Task.cont
def HPPanTask(self, state):
base.direct.camera.setHpr(base.direct.camera,
(0.5 * base.direct.dr.mouseDeltaX *
base.direct.dr.fovH),
(-0.5 * base.direct.dr.mouseDeltaY *
base.direct.dr.fovV),
direct = ShowBaseGlobal.direct
direct.camera.setHpr(direct.camera,
(0.5 * direct.dr.mouseDeltaX *
direct.dr.fovH),
(-0.5 * direct.dr.mouseDeltaY *
direct.dr.fovV),
0.0)
return Task.cont
def spawnMouseRotateTask(self):
# Kill any existing tasks
self.__stopManipulateCamera()
direct = ShowBaseGlobal.direct
if self.perspCollPlane:
iRay = SelectionRay(base.direct.camera)
iRay.collider.setFromLens(base.direct.camNode, 0.0, 0.0)
iRay = SelectionRay(direct.camera)
iRay.collider.setFromLens(direct.camNode, 0.0, 0.0)
iRay.collideWithBitMask(1)
if base.direct.camera.getPos().getZ() >=0:
if direct.camera.getPos().getZ() >=0:
iRay.ct.traverse(self.perspCollPlane)
else:
iRay.ct.traverse(self.perspCollPlane2)
@ -415,7 +423,7 @@ class DirectCameraControl(DirectObject):
# create a temp nodePath to get the position
np = NodePath('temp')
np.setPos(base.direct.camera, hitPt)
np.setPos(direct.camera, hitPt)
self.coaMarkerPos = np.getPos()
np.removeNode()
self.coaMarker.setPos(self.coaMarkerPos)
@ -425,9 +433,9 @@ class DirectCameraControl(DirectObject):
# Set at markers position in render coordinates
self.camManipRef.setPos(self.coaMarkerPos)
self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
t = Task.Task(self.mouseRotateTask)
if abs(base.direct.dr.mouseX) > 0.9:
if abs(direct.dr.mouseX) > 0.9:
t.constrainedDir = 'y'
else:
t.constrainedDir = 'x'
@ -435,36 +443,37 @@ class DirectCameraControl(DirectObject):
def mouseRotateTask(self, state):
# If the cam is orthogonal, don't rotate.
if (hasattr(base.direct.cam.node(), "getLens") and
base.direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
direct = ShowBaseGlobal.direct
if (hasattr(direct.cam.node(), "getLens") and
direct.cam.node().getLens().__class__.__name__ == "OrthographicLens"):
return
# If moving outside of center, ignore motion perpendicular to edge
if ((state.constrainedDir == 'y') and (abs(base.direct.dr.mouseX) > 0.9)):
if ((state.constrainedDir == 'y') and (abs(direct.dr.mouseX) > 0.9)):
deltaX = 0
deltaY = base.direct.dr.mouseDeltaY
elif ((state.constrainedDir == 'x') and (abs(base.direct.dr.mouseY) > 0.9)):
deltaX = base.direct.dr.mouseDeltaX
deltaY = direct.dr.mouseDeltaY
elif ((state.constrainedDir == 'x') and (abs(direct.dr.mouseY) > 0.9)):
deltaX = direct.dr.mouseDeltaX
deltaY = 0
else:
deltaX = base.direct.dr.mouseDeltaX
deltaY = base.direct.dr.mouseDeltaY
if base.direct.fShift:
base.direct.camera.setHpr(base.direct.camera,
(deltaX * base.direct.dr.fovH),
(-deltaY * base.direct.dr.fovV),
deltaX = direct.dr.mouseDeltaX
deltaY = direct.dr.mouseDeltaY
if direct.fShift:
direct.camera.setHpr(direct.camera,
(deltaX * direct.dr.fovH),
(-deltaY * direct.dr.fovV),
0.0)
if self.lockRoll:
# flatten roll
base.direct.camera.setR(0)
direct.camera.setR(0)
self.camManipRef.setPos(self.coaMarkerPos)
self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
else:
if base.direct.camera.getPos().getZ() >=0 or not self.switchDirBelowZero:
if direct.camera.getPos().getZ() >=0 or not self.switchDirBelowZero:
dirX = -1
else:
dirX = 1
wrt = base.direct.camera.getTransform(self.camManipRef)
wrt = direct.camera.getTransform(self.camManipRef)
self.camManipRef.setHpr(self.camManipRef,
(dirX * deltaX * 180.0),
(deltaY * 180.0),
@ -473,20 +482,21 @@ class DirectCameraControl(DirectObject):
if self.lockRoll:
# flatten roll
self.camManipRef.setR(0)
base.direct.camera.setTransform(self.camManipRef, wrt)
direct.camera.setTransform(self.camManipRef, wrt)
return Task.cont
def spawnMouseRollTask(self):
# Kill any existing tasks
self.__stopManipulateCamera()
# Set at markers position in render coordinates
direct = ShowBaseGlobal.direct
self.camManipRef.setPos(self.coaMarkerPos)
self.camManipRef.setHpr(base.direct.camera, DG.ZERO_POINT)
self.camManipRef.setHpr(direct.camera, DG.ZERO_POINT)
t = Task.Task(self.mouseRollTask)
t.coaCenter = getScreenXY(self.coaMarker)
t.lastAngle = getCrankAngle(t.coaCenter)
# Store the camera/manipRef offset transform
t.wrt = base.direct.camera.getTransform(self.camManipRef)
t.wrt = direct.camera.getTransform(self.camManipRef)
self.__startManipulateCamera(task = t)
def mouseRollTask(self, state):
@ -498,23 +508,23 @@ class DirectCameraControl(DirectObject):
if self.lockRoll:
# flatten roll
self.camManipRef.setR(0)
base.direct.camera.setTransform(self.camManipRef, wrt)
ShowBaseGlobal.direct.camera.setTransform(self.camManipRef, wrt)
return Task.cont
def lockCOA(self):
self.fLockCOA = 1
base.direct.message('COA Lock On')
ShowBaseGlobal.direct.message('COA Lock On')
def unlockCOA(self):
self.fLockCOA = 0
base.direct.message('COA Lock Off')
ShowBaseGlobal.direct.message('COA Lock Off')
def toggleCOALock(self):
self.fLockCOA = 1 - self.fLockCOA
if self.fLockCOA:
base.direct.message('COA Lock On')
ShowBaseGlobal.direct.message('COA Lock On')
else:
base.direct.message('COA Lock Off')
ShowBaseGlobal.direct.message('COA Lock Off')
def pickNextCOA(self):
""" Cycle through collision handler entries """
@ -524,7 +534,7 @@ class DirectCameraControl(DirectObject):
self.cqEntries = self.cqEntries[1:] + self.cqEntries[:1]
# Filter out object's under camera
nodePath = entry.getIntoNodePath()
if base.direct.camera not in nodePath.getAncestors():
if ShowBaseGlobal.direct.camera not in nodePath.getAncestors():
# Compute new hit point
hitPt = entry.getSurfacePoint(entry.getFromNodePath())
# Move coa marker to new point
@ -536,11 +546,11 @@ class DirectCameraControl(DirectObject):
def computeCOA(self, entry):
coa = Point3(0)
dr = base.direct.drList.getCurrentDr()
dr = ShowBaseGlobal.direct.drList.getCurrentDr()
if self.fLockCOA:
# COA is locked, use existing point
# Use existing point
coa.assign(self.coaMarker.getPos(base.direct.camera))
coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
# Reset hit point count
self.nullHitPointCount = 0
elif entry:
@ -553,7 +563,7 @@ class DirectCameraControl(DirectObject):
if ((hitPtDist < (1.1 * dr.near)) or
(hitPtDist > dr.far)):
# Just use existing point
coa.assign(self.coaMarker.getPos(base.direct.camera))
coa.assign(self.coaMarker.getPos(ShowBaseGlobal.direct.camera))
# Reset hit point count
self.nullHitPointCount = 0
else:
@ -565,7 +575,7 @@ class DirectCameraControl(DirectObject):
# MRM: Would be nice to be able to control this
# At least display it
dist = pow(10.0, self.nullHitPointCount)
base.direct.message('COA Distance: ' + repr(dist))
ShowBaseGlobal.direct.message('COA Distance: ' + repr(dist))
coa.set(0, dist, 0)
# Compute COA Dist
coaDist = Vec3(coa - DG.ZERO_POINT).length()
@ -583,7 +593,7 @@ class DirectCameraControl(DirectObject):
if ref is None:
# KEH: use the current display region
# ref = base.cam
ref = base.direct.drList.getCurrentDr().cam
ref = ShowBaseGlobal.direct.drList.getCurrentDr().cam
self.coaMarker.setPos(ref, self.coa)
pos = self.coaMarker.getPos()
self.coaMarker.setPosHprScale(pos, Vec3(0), Vec3(1))
@ -598,10 +608,10 @@ class DirectCameraControl(DirectObject):
def updateCoaMarkerSize(self, coaDist = None):
if not coaDist:
coaDist = Vec3(self.coaMarker.getPos(base.direct.camera)).length()
coaDist = Vec3(self.coaMarker.getPos(ShowBaseGlobal.direct.camera)).length()
# Nominal size based on default 30 degree vertical FOV
# Need to adjust size based on distance and current FOV
sf = COA_MARKER_SF * coaDist * (base.direct.drList.getCurrentDr().fovV/30.0)
sf = COA_MARKER_SF * coaDist * (ShowBaseGlobal.direct.drList.getCurrentDr().fovV/30.0)
if sf == 0.0:
sf = 0.1
self.coaMarker.setScale(sf)
@ -619,32 +629,36 @@ class DirectCameraControl(DirectObject):
def homeCam(self):
# Record undo point
base.direct.pushUndo([base.direct.camera])
base.direct.camera.reparentTo(render)
base.direct.camera.clearMat()
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
direct.camera.reparentTo(ShowBaseGlobal.base.render)
direct.camera.clearMat()
# Resize coa marker
self.updateCoaMarkerSize()
def uprightCam(self):
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Pitch camera till upright
currH = base.direct.camera.getH()
ival = base.direct.camera.hprInterval(CAM_MOVE_DURATION,
(currH, 0, 0),
other = render,
blendType = 'easeInOut',
name = 'manipulateCamera')
self.__startManipulateCamera(ival = ival)
currH = direct.camera.getH()
ival = direct.camera.hprInterval(CAM_MOVE_DURATION,
(currH, 0, 0),
other=ShowBaseGlobal.base.render,
blendType='easeInOut',
name='manipulateCamera')
self.__startManipulateCamera(ival=ival)
def orbitUprightCam(self):
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Transform camera z axis to render space
render = ShowBaseGlobal.base.render
mCam2Render = Mat4(Mat4.identMat()) # [gjeon] fixed to give required argument
mCam2Render.assign(base.direct.camera.getMat(render))
mCam2Render.assign(direct.camera.getMat(render))
zAxis = Vec3(mCam2Render.xformVec(DG.Z_AXIS))
zAxis.normalize()
# Compute rotation angle needed to upright cam
@ -665,8 +679,8 @@ class DirectCameraControl(DirectObject):
self.camManipRef.setPos(self.coaMarker, Vec3(0))
self.camManipRef.setHpr(render, rotAngle, 0, 0)
# Reparent Cam to ref Coordinate system
parent = base.direct.camera.getParent()
base.direct.camera.wrtReparentTo(self.camManipRef)
parent = direct.camera.getParent()
direct.camera.wrtReparentTo(self.camManipRef)
# Rotate ref CS to final orientation
ival = self.camManipRef.hprInterval(CAM_MOVE_DURATION,
(rotAngle, orbitAngle, 0),
@ -685,17 +699,18 @@ class DirectCameraControl(DirectObject):
def centerCamIn(self, t):
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Determine marker location
markerToCam = self.coaMarker.getPos(base.direct.camera)
markerToCam = self.coaMarker.getPos(direct.camera)
dist = Vec3(markerToCam - DG.ZERO_POINT).length()
scaledCenterVec = Y_AXIS * dist
delta = markerToCam - scaledCenterVec
self.camManipRef.setPosHpr(base.direct.camera, Point3(0), Point3(0))
ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
Point3(delta),
other = self.camManipRef,
blendType = 'easeInOut')
self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
ival = direct.camera.posInterval(CAM_MOVE_DURATION,
Point3(delta),
other=self.camManipRef,
blendType='easeInOut')
ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
name = 'manipulateCamera')
self.__startManipulateCamera(ival = ival)
@ -703,17 +718,18 @@ class DirectCameraControl(DirectObject):
def zoomCam(self, zoomFactor, t):
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Find a point zoom factor times the current separation
# of the widget and cam
zoomPtToCam = self.coaMarker.getPos(base.direct.camera) * zoomFactor
zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
# Put a target nodePath there
self.camManipRef.setPos(base.direct.camera, zoomPtToCam)
self.camManipRef.setPos(direct.camera, zoomPtToCam)
# Move to that point
ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
DG.ZERO_POINT,
other = self.camManipRef,
blendType = 'easeInOut')
ival = direct.camera.posInterval(CAM_MOVE_DURATION,
DG.ZERO_POINT,
other=self.camManipRef,
blendType='easeInOut')
ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
name = 'manipulateCamera')
self.__startManipulateCamera(ival = ival)
@ -722,7 +738,8 @@ class DirectCameraControl(DirectObject):
# Kill any existing tasks
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Calc hprOffset
hprOffset = VBase3()
if view == 8:
@ -751,7 +768,7 @@ class DirectCameraControl(DirectObject):
self.camManipRef.setPosHpr(self.coaMarker, DG.ZERO_VEC,
hprOffset)
# Scale center vec by current distance to target
offsetDistance = Vec3(base.direct.camera.getPos(self.camManipRef) -
offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) -
DG.ZERO_POINT).length()
scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
# Now put the camManipRef at that point
@ -760,11 +777,11 @@ class DirectCameraControl(DirectObject):
DG.ZERO_VEC)
# Record view for next time around
self.lastView = view
ival = base.direct.camera.posHprInterval(CAM_MOVE_DURATION,
pos = DG.ZERO_POINT,
hpr = VBase3(0, 0, self.orthoViewRoll),
other = self.camManipRef,
blendType = 'easeInOut')
ival = direct.camera.posHprInterval(CAM_MOVE_DURATION,
pos=DG.ZERO_POINT,
hpr=VBase3(0, 0, self.orthoViewRoll),
other=self.camManipRef,
blendType='easeInOut')
ival = Sequence(ival, Func(self.updateCoaMarkerSizeOnDeath),
name = 'manipulateCamera')
self.__startManipulateCamera(ival = ival)
@ -774,15 +791,16 @@ class DirectCameraControl(DirectObject):
self.__stopManipulateCamera()
# Record undo point
base.direct.pushUndo([base.direct.camera])
direct = ShowBaseGlobal.direct
direct.pushUndo([direct.camera])
# Coincident with widget
self.camManipRef.setPos(self.coaMarker, DG.ZERO_POINT)
# But aligned with render space
self.camManipRef.setHpr(DG.ZERO_POINT)
parent = base.direct.camera.getParent()
base.direct.camera.wrtReparentTo(self.camManipRef)
parent = direct.camera.getParent()
direct.camera.wrtReparentTo(self.camManipRef)
ival = self.camManipRef.hprInterval(CAM_MOVE_DURATION,
VBase3(degrees, 0, 0),
@ -792,7 +810,7 @@ class DirectCameraControl(DirectObject):
self.__startManipulateCamera(ival = ival)
def reparentCam(self, parent):
base.direct.camera.wrtReparentTo(parent)
ShowBaseGlobal.direct.camera.wrtReparentTo(parent)
self.updateCoaMarkerSize()
def fitOnWidget(self, nodePath = 'None Given'):
@ -800,75 +818,78 @@ class DirectCameraControl(DirectObject):
# stop any ongoing tasks
self.__stopManipulateCamera()
# How big is the node?
nodeScale = base.direct.widget.scalingNode.getScale(render)
direct = ShowBaseGlobal.direct
nodeScale = direct.widget.scalingNode.getScale(ShowBaseGlobal.base.render)
maxScale = max(nodeScale[0], nodeScale[1], nodeScale[2])
maxDim = min(base.direct.dr.nearWidth, base.direct.dr.nearHeight)
maxDim = min(direct.dr.nearWidth, direct.dr.nearHeight)
# At what distance does the object fill 30% of the screen?
# Assuming radius of 1 on widget
camY = base.direct.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
camY = direct.dr.near * (2.0 * maxScale) / (0.3 * maxDim)
# What is the vector through the center of the screen?
centerVec = Y_AXIS * camY
# Where is the node relative to the viewpoint
vWidget2Camera = base.direct.widget.getPos(base.direct.camera)
vWidget2Camera = direct.widget.getPos(direct.camera)
# How far do you move the camera to be this distance from the node?
deltaMove = vWidget2Camera - centerVec
# Move a target there
try:
self.camManipRef.setPos(base.direct.camera, deltaMove)
self.camManipRef.setPos(direct.camera, deltaMove)
except Exception:
#self.notify.debug
pass
parent = base.direct.camera.getParent()
base.direct.camera.wrtReparentTo(self.camManipRef)
ival = base.direct.camera.posInterval(CAM_MOVE_DURATION,
Point3(0, 0, 0),
blendType = 'easeInOut')
parent = direct.camera.getParent()
direct.camera.wrtReparentTo(self.camManipRef)
ival = direct.camera.posInterval(CAM_MOVE_DURATION,
Point3(0, 0, 0),
blendType='easeInOut')
ival = Sequence(ival, Func(self.reparentCam, parent),
name = 'manipulateCamera')
self.__startManipulateCamera(ival = ival)
name='manipulateCamera')
self.__startManipulateCamera(ival=ival)
def moveToFit(self):
# How big is the active widget?
widgetScale = base.direct.widget.scalingNode.getScale(render)
direct = ShowBaseGlobal.direct
widgetScale = direct.widget.scalingNode.getScale(ShowBaseGlobal.base.render)
maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
# At what distance does the widget fill 50% of the screen?
camY = ((2 * base.direct.dr.near * (1.5 * maxScale)) /
min(base.direct.dr.nearWidth, base.direct.dr.nearHeight))
camY = ((2 * direct.dr.near * (1.5 * maxScale)) /
min(direct.dr.nearWidth, direct.dr.nearHeight))
# Find a point this distance along the Y axis
# MRM: This needs to be generalized to support non uniform frusta
centerVec = Y_AXIS * camY
# Before moving, record the relationship between the selected nodes
# and the widget, so that this can be maintained
base.direct.selected.getWrtAll()
direct.selected.getWrtAll()
# Push state onto undo stack
base.direct.pushUndo(base.direct.selected)
direct.pushUndo(direct.selected)
# Remove the task to keep the widget attached to the object
taskMgr.remove('followSelectedNodePath')
# Spawn a task to keep the selected objects with the widget
taskMgr.add(self.stickToWidgetTask, 'stickToWidget')
# Spawn a task to move the widget
ival = base.direct.widget.posInterval(CAM_MOVE_DURATION,
Point3(centerVec),
other = base.direct.camera,
blendType = 'easeInOut')
ival = direct.widget.posInterval(CAM_MOVE_DURATION,
Point3(centerVec),
other=direct.camera,
blendType='easeInOut')
ival = Sequence(ival, Func(lambda: taskMgr.remove('stickToWidget')),
name = 'moveToFit')
ival.start()
def stickToWidgetTask(self, state):
# Move the objects with the widget
base.direct.selected.moveWrtWidgetAll()
ShowBaseGlobal.direct.selected.moveWrtWidgetAll()
# Continue
return Task.cont
def enableMouseFly(self, fKeyEvents = 1):
# disable C++ fly interface
base = ShowBaseGlobal.base
base.disableMouse()
# Enable events
for event in self.actionEvents:
@ -877,11 +898,11 @@ class DirectCameraControl(DirectObject):
for event in self.keyEvents:
self.accept(event[0], event[1], extraArgs = event[2:])
# Show marker
self.coaMarker.reparentTo(base.direct.group)
self.coaMarker.reparentTo(ShowBaseGlobal.direct.group)
def disableMouseFly(self):
# Hide the marker
self.coaMarker.reparentTo(hidden)
self.coaMarker.reparentTo(ShowBaseGlobal.hidden)
# Ignore events
for event in self.actionEvents:
self.ignore(event[0])
@ -890,7 +911,7 @@ class DirectCameraControl(DirectObject):
# Kill tasks
self.removeManipulateCameraTask()
taskMgr.remove('stickToWidget')
base.enableMouse()
ShowBaseGlobal.base.enableMouse()
def removeManipulateCameraTask(self):
self.__stopManipulateCamera()

View File

@ -1,6 +1,7 @@
import math
from panda3d.core import NodePath, Point3, VBase4
from direct.showbase.DirectObject import DirectObject
from direct.showbase import ShowBaseGlobal
from .DirectUtil import ROUND_TO, useDirectRenderStyle
from .DirectGeometry import LineNodePath
@ -14,7 +15,7 @@ class DirectGrid(NodePath, DirectObject):
# Load up grid parts to initialize grid object
# Polygon used to mark grid plane
self.gridBack = base.loader.loadModel('models/misc/gridBack')
self.gridBack = ShowBaseGlobal.loader.loadModel('models/misc/gridBack')
self.gridBack.reparentTo(self)
self.gridBack.setColor(*planeColor)
@ -36,7 +37,7 @@ class DirectGrid(NodePath, DirectObject):
self.centerLines.setThickness(3)
# Small marker to hilight snap-to-grid point
self.snapMarker = base.loader.loadModel('models/misc/sphere')
self.snapMarker = ShowBaseGlobal.loader.loadModel('models/misc/sphere')
self.snapMarker.node().setName('gridSnapMarker')
self.snapMarker.reparentTo(self)
self.snapMarker.setColor(1, 0, 0, 1)
@ -55,7 +56,7 @@ class DirectGrid(NodePath, DirectObject):
if parent:
self.reparentTo(parent)
else:
self.reparentTo(base.direct.group)
self.reparentTo(ShowBaseGlobal.direct.group)
self.updateGrid()
self.fEnabled = 1

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ from panda3d.core import (
ConfigVariableBool,
ConfigVariableString,
CSDefault,
GraphicsWindow,
NodePath,
Point3,
TextNode,
@ -35,19 +36,33 @@ from direct.gui import OnscreenText
from direct.interval.IntervalGlobal import Func, Sequence
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase.MessengerGlobal import messenger
from direct.showbase import ShowBaseGlobal
from direct.showbase.ShowBaseGlobal import ShowBase, hidden
import builtins
base: ShowBase
class DirectSession(DirectObject):
# post this to the bboard to make sure DIRECT doesn't turn on
DIRECTdisablePost = 'disableDIRECT'
cam: NodePath
camera: NodePath
oobeCamera: NodePath
def __init__(self):
# Establish a global pointer to the direct object early on
# so dependant classes can access it in their code
__builtins__["direct"] = base.direct = self
global direct, base
base = ShowBaseGlobal.base
base.direct = self
setattr(builtins, 'direct', self)
ShowBaseGlobal.direct = self
# These come early since they are used later on
self.group = render.attachNewNode('DIRECT')
self.group = base.render.attachNewNode('DIRECT')
self.font = TextNode.getDefaultFont()
self.fEnabled = 0
self.fEnabledLight = 0
@ -57,7 +72,7 @@ class DirectSession(DirectObject):
self.drList = DisplayRegionList()
self.iRayList = [x.iRay for x in self.drList]
self.dr = self.drList[0]
self.win = base.win
self.win: GraphicsWindow = base.win
self.camera = base.camera
self.cam = base.cam
self.camNode = base.camNode
@ -70,7 +85,7 @@ class DirectSession(DirectObject):
self.useObjectHandles()
self.grid = DirectGrid()
self.grid.disable()
self.lights = DirectLights(base.direct.group)
self.lights = DirectLights(self.group)
# Create some default lights
self.lights.createDefaultLights()
# But turn them off
@ -308,13 +323,16 @@ class DirectSession(DirectObject):
if base.wantTk:
from direct.tkpanels import DirectSessionPanel
self.panel = DirectSessionPanel.DirectSessionPanel(parent = base.tkRoot)
try:
clusterMode: str
if hasattr(builtins, 'clusterMode'):
# Has the clusterMode been set externally (i.e. via the
# bootstrap application?
self.clusterMode = clusterMode
except NameError:
clusterMode = builtins.clusterMode
else:
# Has the clusterMode been set via a config variable?
self.clusterMode = ConfigVariableString("cluster-mode", '').value
clusterMode = ConfigVariableString("cluster-mode", '').value
self.clusterMode = clusterMode
if self.clusterMode == 'client':
from direct.cluster.ClusterClient import createClusterClient
@ -325,7 +343,7 @@ class DirectSession(DirectObject):
else:
from direct.cluster.ClusterClient import DummyClusterClient
self.cluster = DummyClusterClient()
__builtins__['cluster'] = self.cluster
setattr(builtins, 'cluster', self.cluster)
def addPassThroughKey(self,key):
@ -412,10 +430,10 @@ class DirectSession(DirectObject):
if self.oobeMode:
# Position a target point to lerp the oobe camera to
base.direct.cameraControl.camManipRef.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
self.cameraControl.camManipRef.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
ival = self.oobeCamera.posHprInterval(
2.0, pos = Point3(0), hpr = Vec3(0),
other = base.direct.cameraControl.camManipRef,
other = self.cameraControl.camManipRef,
blendType = 'easeInOut')
ival = Sequence(ival, Func(self.endOOBE), name = 'oobeTransition')
ival.start()
@ -432,20 +450,20 @@ class DirectSession(DirectObject):
# Put camera under new oobe camera
self.cam.reparentTo(self.oobeCamera)
# Position a target point to lerp the oobe camera to
base.direct.cameraControl.camManipRef.setPos(
self.cameraControl.camManipRef.setPos(
self.trueCamera, Vec3(-2, -20, 5))
base.direct.cameraControl.camManipRef.lookAt(self.trueCamera)
self.cameraControl.camManipRef.lookAt(self.trueCamera)
ival = self.oobeCamera.posHprInterval(
2.0, pos = Point3(0), hpr = Vec3(0),
other = base.direct.cameraControl.camManipRef,
other = self.cameraControl.camManipRef,
blendType = 'easeInOut')
ival = Sequence(ival, Func(self.beginOOBE), name = 'oobeTransition')
ival.start()
def beginOOBE(self):
# Make sure we've reached our final destination
self.oobeCamera.setPosHpr(base.direct.cameraControl.camManipRef, 0, 0, 0, 0, 0, 0)
base.direct.camera = self.oobeCamera
self.oobeCamera.setPosHpr(self.cameraControl.camManipRef, 0, 0, 0, 0, 0, 0)
self.camera = self.oobeCamera
self.oobeMode = 1
def endOOBE(self):
@ -453,7 +471,7 @@ class DirectSession(DirectObject):
self.oobeCamera.setPosHpr(self.trueCamera, 0, 0, 0, 0, 0, 0)
# Disable OOBE mode.
self.cam.reparentTo(self.trueCamera)
base.direct.camera = self.trueCamera
self.camera = self.trueCamera
# Get rid of ancillary node paths
self.oobeVis.reparentTo(hidden)
self.oobeCamera.reparentTo(hidden)
@ -501,7 +519,7 @@ class DirectSession(DirectObject):
def inputHandler(self, input):
if not hasattr(self, 'oobeMode') or self.oobeMode == 0:
# [gjeon] change current camera dr, iRay, mouseWatcher accordingly to support multiple windows
if base.direct.manipulationControl.fMultiView:
if self.manipulationControl.fMultiView:
# handling orphan events
if self.fMouse1 and 'mouse1' not in input or\
self.fMouse2 and 'mouse2' not in input or\
@ -518,7 +536,7 @@ class DirectSession(DirectObject):
return
if (self.fMouse1 or self.fMouse2 or self.fMouse3) and\
input[4:7] != base.direct.camera.getName()[:3] and\
input[4:7] != self.camera.getName()[:3] and\
input.endswith('-up'):
# to handle orphan events
return
@ -551,14 +569,14 @@ class DirectSession(DirectObject):
self.cam = NodePath(winCtrl.camNode)
self.camNode = winCtrl.camNode
if hasattr(winCtrl, 'grid'):
base.direct.grid = winCtrl.grid
base.direct.dr = base.direct.drList[base.camList.index(NodePath(winCtrl.camNode))]
base.direct.iRay = base.direct.dr.iRay
self.grid = winCtrl.grid
self.dr = self.drList[base.camList.index(NodePath(winCtrl.camNode))]
self.iRay = self.dr.iRay
base.mouseWatcher = winCtrl.mouseWatcher
base.mouseWatcherNode = winCtrl.mouseWatcher.node()
base.direct.dr.mouseUpdate()
self.dr.mouseUpdate()
DG.LE_showInOneCam(self.selectedNPReadout, self.camera.getName())
base.direct.widget = base.direct.manipulationControl.widgetList[base.camList.index(NodePath(winCtrl.camNode))]
self.widget = self.manipulationControl.widgetList[base.camList.index(NodePath(winCtrl.camNode))]
input = input[8:] # get rid of camera prefix
if self.fAlt and 'alt' not in input and not input.endswith('-up'):
@ -683,20 +701,18 @@ class DirectSession(DirectObject):
if not taskMgr.hasTaskNamed('resizeObjectHandles'):
dnp = self.selected.last
if dnp:
direct = base.direct
if self.manipulationControl.fMultiView:
for i in range(3):
sf = 30.0 * direct.drList[i].orthoFactor
sf = 30.0 * self.drList[i].orthoFactor
self.manipulationControl.widgetList[i].setDirectScalingFactor(sf)
nodeCamDist = Vec3(dnp.getPos(base.camList[3])).length()
sf = 0.075 * nodeCamDist * math.tan(deg2Rad(direct.drList[3].fovV))
sf = 0.075 * nodeCamDist * math.tan(deg2Rad(self.drList[3].fovV))
self.manipulationControl.widgetList[3].setDirectScalingFactor(sf)
else:
nodeCamDist = Vec3(dnp.getPos(direct.camera)).length()
sf = 0.075 * nodeCamDist * math.tan(deg2Rad(direct.drList.getCurrentDr().fovV))
nodeCamDist = Vec3(dnp.getPos(self.camera)).length()
sf = 0.075 * nodeCamDist * math.tan(deg2Rad(self.drList.getCurrentDr().fovV))
self.widget.setDirectScalingFactor(sf)
return Task.cont
@ -755,7 +771,7 @@ class DirectSession(DirectObject):
messenger.send('DIRECT_selectedNodePath_fMulti_fTag_fLEPane', [dnp, fMultiSelect, fSelectTag, fLEPane])
def followSelectedNodePathTask(self, state):
mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(base.render)
decomposeMatrix(mCoa2Render,
self.scale, self.hpr, self.pos,
CSDefault)
@ -874,7 +890,7 @@ class DirectSession(DirectObject):
if nodePath == 'None Given':
# If nothing specified, try selected node path
nodePath = self.selected.last
base.direct.select(nodePath)
self.select(nodePath)
def fitTask(state, self = self):
self.cameraControl.fitOnWidget()
@ -1061,7 +1077,7 @@ class DirectSession(DirectObject):
def useObjectHandles(self):
self.widget = self.manipulationControl.objectHandles
self.widget.reparentTo(base.direct.group)
self.widget.reparentTo(self.group)
def hideSelectedNPReadout(self):
self.selectedNPReadout.reparentTo(hidden)
@ -1167,14 +1183,14 @@ class DisplayRegionContext(DirectObject):
self.camLens.setFov(hfov, vfov)
def getWidth(self):
prop = base.direct.win.getProperties()
prop = ShowBaseGlobal.direct.win.getProperties()
if prop.hasSize():
return prop.getXSize()
else:
return 640
def getHeight(self):
prop = base.direct.win.getProperties()
prop = ShowBaseGlobal.direct.win.getProperties()
if prop.hasSize():
return prop.getYSize()
else:
@ -1208,9 +1224,10 @@ class DisplayRegionContext(DirectObject):
# Values for this frame
# This ranges from -1 to 1
if base.mouseWatcherNode and base.mouseWatcherNode.hasMouse():
self.mouseX = base.mouseWatcherNode.getMouseX()
self.mouseY = base.mouseWatcherNode.getMouseY()
mouseWatcherNode = base.mouseWatcherNode
if mouseWatcherNode and mouseWatcherNode.hasMouse():
self.mouseX = mouseWatcherNode.getMouseX()
self.mouseY = mouseWatcherNode.getMouseY()
self.mouseX = (self.mouseX-self.originX)*self.scaleX
self.mouseY = (self.mouseY-self.originY)*self.scaleY
# Delta percent of window the mouse moved
@ -1262,6 +1279,9 @@ class DisplayRegionList(DirectObject):
def __len__(self):
return len(self.displayRegionList)
def __iter__(self):
return iter(self.displayRegionList)
def updateContext(self):
self.contextTask(None)
@ -1296,7 +1316,7 @@ class DisplayRegionList(DirectObject):
def getCurrentDr(self):
if not self.tryToGetCurrentDr:
return base.direct.dr
return ShowBaseGlobal.direct.dr
for dr in self.displayRegionList:
if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
dr.mouseY >= -1.0 and dr.mouseY <= 1.0):

View File

@ -5,7 +5,6 @@ import modulefinder
import sys
import os
import marshal
import imp
import platform
import struct
import io
@ -13,6 +12,7 @@ import sysconfig
import zipfile
import importlib
import warnings
from importlib import machinery
from . import pefile
@ -24,6 +24,16 @@ except ImportError:
from panda3d.core import Filename, Multifile, PandaSystem, StringStream
# Old imp constants.
_PY_SOURCE = 1
_PY_COMPILED = 2
_C_EXTENSION = 3
_PKG_DIRECTORY = 5
_C_BUILTIN = 6
_PY_FROZEN = 7
_PKG_NAMESPACE_DIRECTORY = object()
# Check to see if we are running python_d, which implies we have a
# debug build, and we have to build the module with debug options.
# This is only relevant on Windows.
@ -37,7 +47,7 @@ isDebugBuild = (python.lower().endswith('_d'))
# NB. if encodings are removed, be sure to remove them from the shortcut in
# deploy-stub.c.
startupModules = [
'imp', 'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery',
'importlib.util',
]
@ -262,10 +272,15 @@ class CompilationEnvironment:
self.arch = '-arch x86_64'
elif proc in ('arm64', 'aarch64'):
self.arch = '-arch arm64'
self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python"
self.linkDll = "gcc %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
self.compileObjExe = "clang -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
self.compileObjDll = "clang -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s"
self.linkExe = "clang %(arch)s -o %(basename)s %(basename)s.o"
if '/Python.framework/' in self.PythonIPath:
framework_dir = self.PythonIPath.split("/Python.framework/", 1)[0]
if framework_dir != "/System/Library/Frameworks":
self.linkExe += " -F " + framework_dir
self.linkExe += " -framework Python"
self.linkDll = "clang %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o"
else:
# Unix
@ -897,12 +912,11 @@ class Freezer:
# Suffix/extension for Python C extension modules
if self.platform == PandaSystem.getPlatform():
suffixes = imp.get_suffixes()
# Set extension for Python files to binary mode
for i, suffix in enumerate(suffixes):
if suffix[2] == imp.PY_SOURCE:
suffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
suffixes = (
[(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] +
[(s, 'rb', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] +
[(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
)
else:
suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
@ -1316,10 +1330,10 @@ class Freezer:
ext = mdef.filename.getExtension()
if ext == 'pyc' or ext == 'pyo':
fp = open(pathname, 'rb')
stuff = ("", "rb", imp.PY_COMPILED)
stuff = ("", "rb", _PY_COMPILED)
self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
else:
stuff = ("", "rb", imp.PY_SOURCE)
stuff = ("", "rb", _PY_SOURCE)
if mdef.text:
fp = io.StringIO(mdef.text)
else:
@ -1415,7 +1429,7 @@ class Freezer:
def __addPyc(self, multifile, filename, code, compressionLevel):
if code:
data = imp.get_magic() + b'\0\0\0\0\0\0\0\0'
data = importlib.util.MAGIC_NUMBER + b'\0\0\0\0\0\0\0\0'
data += marshal.dumps(code)
stream = StringStream(data)
@ -1605,7 +1619,7 @@ class Freezer:
# trouble importing it as a builtin module. Synthesize a frozen
# module that loads it as builtin.
if '.' in moduleName and self.linkExtensionModules:
code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize)
code = compile('import sys;del sys.modules["%s"];from importlib._bootstrap import _builtin_from_name;_builtin_from_name("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize)
code = marshal.dumps(code)
mangledName = self.mangleName(moduleName)
moduleDefs.append(self.makeModuleDef(mangledName, code))
@ -1887,9 +1901,19 @@ class Freezer:
if '.' in moduleName and not self.platform.startswith('android'):
if self.platform.startswith("macosx") and not use_console:
# We write the Frameworks directory to sys.path[0].
code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
direxpr = 'sys.path[0]'
else:
code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
direxpr = 'os.path.dirname(sys.executable)'
code = \
f'import sys;' \
f'del sys.modules["{moduleName}"];' \
f'import sys,os;' \
f'from importlib.machinery import ExtensionFileLoader,ModuleSpec;' \
f'from importlib._bootstrap import _load;' \
f'path=os.path.join({direxpr}, "{moduleName}{modext}");' \
f'_load(ModuleSpec(name="{moduleName}", loader=ExtensionFileLoader("{moduleName}", path), origin=path))'
code = compile(code, moduleName, 'exec', optimize=self.optimize)
code = marshal.dumps(code)
moduleList.append((moduleName, len(pool), len(code)))
@ -2400,9 +2424,6 @@ class Freezer:
return True
_PKG_NAMESPACE_DIRECTORY = object()
class PandaModuleFinder(modulefinder.ModuleFinder):
def __init__(self, *args, **kw):
@ -2415,7 +2436,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
self.builtin_module_names = kw.pop('builtin_module_names', sys.builtin_module_names)
self.suffixes = kw.pop('suffixes', imp.get_suffixes())
self.suffixes = kw.pop('suffixes', (
[(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] +
[(s, 'r', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] +
[(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES]
))
self.optimize = kw.pop('optimize', -1)
modulefinder.ModuleFinder.__init__(self, *args, **kw)
@ -2563,7 +2589,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
suffix, mode, type = file_info
self.msgin(2, "load_module", fqname, fp and "fp", pathname)
if type == imp.PKG_DIRECTORY:
if type == _PKG_DIRECTORY:
m = self.load_package(fqname, pathname)
self.msgout(2, "load_module ->", m)
return m
@ -2574,7 +2600,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
m.__path__ = pathname
return m
if type == imp.PY_SOURCE:
if type == _PY_SOURCE:
if fqname in overrideModules:
# This module has a custom override.
code = overrideModules[fqname]
@ -2598,7 +2624,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
code += b'\n' if isinstance(code, bytes) else '\n'
co = compile(code, pathname, 'exec', optimize=self.optimize)
elif type == imp.PY_COMPILED:
elif type == _PY_COMPILED:
if sys.version_info >= (3, 7):
try:
data = fp.read()
@ -2752,11 +2778,11 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
# If we have a custom override for this module, we know we have it.
if fullname in overrideModules:
return (None, '', ('.py', 'r', imp.PY_SOURCE))
return (None, '', ('.py', 'r', _PY_SOURCE))
# It's built into the interpreter.
if fullname in self.builtin_module_names:
return (None, None, ('', '', imp.C_BUILTIN))
return (None, None, ('', '', _C_BUILTIN))
# If no search path is given, look for a built-in module.
if path is None:
@ -2806,7 +2832,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
for suffix, mode, _ in self.suffixes:
init = os.path.join(basename, '__init__' + suffix)
if self._open_file(init, mode):
return (None, basename, ('', '', imp.PKG_DIRECTORY))
return (None, basename, ('', '', _PKG_DIRECTORY))
# This may be a namespace package.
if self._dir_exists(basename):
@ -2818,7 +2844,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder):
# Only if we're not looking on a particular path, though.
if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
# It's a frozen module.
return (None, name, ('', '', imp.PY_FROZEN))
return (None, name, ('', '', _PY_FROZEN))
# If we found folders on the path with this module name without an
# __init__.py file, we should consider this a namespace package.

View File

@ -13,7 +13,6 @@ import re
import shutil
import stat
import struct
import imp
import string
import tempfile
@ -717,6 +716,7 @@ class build_apps(setuptools.Command):
'CFBundlePackageType': 'APPL',
'CFBundleSignature': '', #TODO
'CFBundleExecutable': self.macos_main_app,
'NSHighResolutionCapable': 'True',
}
icon = self.icon_objects.get(
@ -1067,7 +1067,7 @@ class build_apps(setuptools.Command):
freezer_extras.update(freezer.extras)
freezer_modules.update(freezer.getAllModuleNames())
for suffix in freezer.mf.suffixes:
if suffix[2] == imp.C_EXTENSION:
if suffix[2] == 3: # imp.C_EXTENSION:
ext_suffixes.add(suffix[0])
for appname, scriptname in self.gui_apps.items():

View File

@ -23,7 +23,7 @@ GRID_Z_OFFSET = 0.0
class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
notify = directNotify.newCategory("DistributedCartesianGrid")
notify.setDebug(0)
notify.setDebug(False)
VisualizeGrid = ConfigVariableBool("visualize-cartesian-grid", False)

View File

@ -45,6 +45,9 @@ if ConfigVariableBool('astron-support', True):
CONTROL_REMOVE_RANGE = 9003
CONTROL_ADD_POST_REMOVE = 9010
CONTROL_CLEAR_POST_REMOVES = 9011
CONTROL_SET_CON_NAME = 9012
CONTROL_SET_CON_URL = 9013
CONTROL_LOG_MESSAGE = 9014
# State Server control messages:
STATESERVER_CREATE_OBJECT_WITH_REQUIRED = 2000

View File

@ -435,7 +435,8 @@ Dtool_funcToMethod(iPosHprScale, NodePath)
del iPosHprScale
#####################################################################
def place(self):
base.startDirect(fWantTk = 1)
from direct.showbase import ShowBaseGlobal
ShowBaseGlobal.base.startDirect(fWantTk = 1)
# Don't use a regular import, to prevent ModuleFinder from picking
# it up as a dependency when building a .p3d package.
import importlib
@ -446,7 +447,8 @@ Dtool_funcToMethod(place, NodePath)
del place
#####################################################################
def explore(self):
base.startDirect(fWantTk = 1)
from direct.showbase import ShowBaseGlobal
ShowBaseGlobal.base.startDirect(fWantTk = 1)
# Don't use a regular import, to prevent ModuleFinder from picking
# it up as a dependency when building a .p3d package.
import importlib
@ -457,7 +459,8 @@ Dtool_funcToMethod(explore, NodePath)
del explore
#####################################################################
def rgbPanel(self, cb = None):
base.startTk()
from direct.showbase import ShowBaseGlobal
ShowBaseGlobal.base.startTk()
# Don't use a regular import, to prevent ModuleFinder from picking
# it up as a dependency when building a .p3d package.
import importlib
@ -468,6 +471,8 @@ Dtool_funcToMethod(rgbPanel, NodePath)
del rgbPanel
#####################################################################
def select(self):
from direct.showbase import ShowBaseGlobal
base = ShowBaseGlobal.base
base.startDirect(fWantTk = 0)
base.direct.select(self)
@ -475,6 +480,8 @@ Dtool_funcToMethod(select, NodePath)
del select
#####################################################################
def deselect(self):
from direct.showbase import ShowBaseGlobal
base = ShowBaseGlobal.base
base.startDirect(fWantTk = 0)
base.direct.deselect(self)
@ -676,7 +683,8 @@ def flattenMultitex(self, stateFrom = None, target = None,
mr.setAllowTexMat(allowTexMat)
if win is None:
win = base.win
from direct.showbase import ShowBaseGlobal
win = ShowBaseGlobal.base.win
if stateFrom is None:
mr.scan(self)

View File

@ -28,7 +28,7 @@ class DirectEntry(DirectFrame):
to keyboard buttons
"""
directWtext = ConfigVariableBool('direct-wtext', 1)
directWtext = ConfigVariableBool('direct-wtext', True)
AllowCapNamePrefixes = ("Al", "Ap", "Ben", "De", "Del", "Della", "Delle", "Der", "Di", "Du",
"El", "Fitz", "La", "Las", "Le", "Les", "Lo", "Los",

View File

@ -173,7 +173,7 @@ class OnscreenText(NodePath):
self.__wordwrap = wordwrap
if decal:
textNode.setCardDecal(1)
textNode.setCardDecal(True)
if font is None:
font = DGG.getDefaultFont()

View File

@ -10,12 +10,6 @@ from direct.directnotify.DirectNotifyGlobal import directNotify
from . import Interval
#############################################################
### ###
### See examples of function intervals in IntervalTest.py ###
### ###
#############################################################
class FunctionInterval(Interval.Interval):
# Name counter
functionIntervalNum = 1

View File

@ -1,222 +0,0 @@
"""Undocumented Module"""
__all__ = ()
if __name__ == "__main__":
from panda3d.core import Filename, Point3, Vec3
from direct.showbase.DirectObject import DirectObject
from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.directutil import Mopath
from direct.showbase.MessengerGlobal import messenger
from .ActorInterval import ActorInterval
from .FunctionInterval import (
AcceptInterval,
EventInterval,
FunctionInterval,
IgnoreInterval,
PosHprInterval,
)
from .LerpInterval import LerpPosInterval, LerpHprInterval, LerpPosHprInterval
from .MopathInterval import MopathInterval
from .SoundInterval import SoundInterval
from .MetaInterval import PREVIOUS_END, PREVIOUS_START, TRACK_START, Track
base = ShowBase()
boat = base.loader.loadModel('models/misc/smiley')
boat.reparentTo(base.render)
donald = Actor()
donald.loadModel("phase_6/models/char/donald-wheel-1000")
donald.loadAnims({"steer":"phase_6/models/char/donald-wheel-wheel"})
donald.reparentTo(boat)
dock = base.loader.loadModel('models/misc/smiley')
dock.reparentTo(base.render)
sound = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_waterlap.mp3')
foghorn = base.loader.loadSfx('phase_6/audio/sfx/SZ_DD_foghorn.mp3')
mp = Mopath.Mopath()
mp.loadFile(Filename('phase_6/paths/dd-e-w'))
# Set up the boat
boatMopath = MopathInterval(mp, boat, 'boatpath')
boatTrack = Track([boatMopath], 'boattrack')
BOAT_START = boatTrack.getIntervalStartTime('boatpath')
BOAT_END = boatTrack.getIntervalEndTime('boatpath')
# This will create an anim interval that is posed every frame
donaldSteerInterval = ActorInterval(donald, 'steer')
# This will create an anim interval that is started at t = 0 and then
# loops for 10 seconds
donaldLoopInterval = ActorInterval(donald, 'steer', loop=1, duration = 10.0)
donaldSteerTrack = Track([donaldSteerInterval, donaldLoopInterval],
name = 'steerTrack')
# Make the dock lerp up so that it's up when the boat reaches the end of
# its mopath
dockLerp = LerpPosHprInterval(dock, 5.0,
pos=Point3(0, 0, -5),
hpr=Vec3(0, 0, 0),
name='dock-lerp')
# We need the dock's state to be defined before the lerp
dockPos = PosHprInterval(dock, dock.getPos(), dock.getHpr(), 1.0, 'dockpos')
dockUpTime = BOAT_END - dockLerp.getDuration()
hpr2 = Vec3(90.0, 90.0, 90.0)
dockLerp2 = LerpHprInterval(dock, 3.0, hpr2, name='hpr-lerp')
dockTrack = Track([dockLerp2, dockPos, dockLerp], 'docktrack')
dockTrack.setIntervalStartTime('dock-lerp', dockUpTime)
dockTrack.setIntervalStartTime('hpr-lerp', BOAT_START)
# Start the water sound 5 seconds after the boat starts moving
waterStartTime = BOAT_START + 5.0
waterSound = SoundInterval(sound, name='watersound')
soundTrack = Track([waterSound], 'soundtrack')
soundTrack.setIntervalStartTime('watersound', waterStartTime)
# Throw an event when the water track ends
eventTime = soundTrack.getIntervalEndTime('watersound')
waterDone = EventInterval('water-is-done')
waterEventTrack = Track([waterDone])
waterEventTrack.setIntervalStartTime('water-is-done', eventTime)
def handleWaterDone():
print('water is done')
# Interval can handle its own event
messenger.accept('water-is-done', waterDone, handleWaterDone)
foghornStartTime = BOAT_START + 4.0
foghornSound = SoundInterval(foghorn, name='foghorn')
soundTrack2 = Track([(foghornStartTime, foghornSound)], 'soundtrack2')
mtrack = MultiTrack([boatTrack, dockTrack, soundTrack, soundTrack2, waterEventTrack, # type: ignore[name-defined]
donaldSteerTrack])
# Print out MultiTrack parameters
print(mtrack)
### Using lambdas and functions ###
# Using a lambda
i1 = FunctionInterval(lambda: base.transitions.fadeOut())
i2 = FunctionInterval(lambda: base.transitions.fadeIn())
def caughtIt():
print('Caught here-is-an-event')
class DummyAcceptor(DirectObject):
pass
da = DummyAcceptor()
i3 = AcceptInterval(da, 'here-is-an-event', caughtIt)
i4 = EventInterval('here-is-an-event')
i5 = IgnoreInterval(da, 'here-is-an-event')
# Using a function
def printDone():
print('done')
i6 = FunctionInterval(printDone)
# Create track
t1 = Track([
# Fade out
(0.0, i1),
# Fade in
(2.0, i2),
# Accept event
(4.0, i3),
# Throw it,
(5.0, i4),
# Ignore event
(6.0, i5),
# Throw event again and see if ignore worked
(7.0, i4),
# Print done
(8.0, i6)], name = 'demo')
print(t1)
### Specifying interval start times during track construction ###
# Interval start time can be specified relative to three different points:
# PREVIOUS_END
# PREVIOUS_START
# TRACK_START
startTime = 0.0
def printStart():
global startTime
startTime = base.clock.getFrameTime()
print('Start')
def printPreviousStart():
global startTime
currTime = base.clock.getFrameTime()
print('PREVIOUS_END %0.2f' % (currTime - startTime))
def printPreviousEnd():
global startTime
currTime = base.clock.getFrameTime()
print('PREVIOUS_END %0.2f' % (currTime - startTime))
def printTrackStart():
global startTime
currTime = base.clock.getFrameTime()
print('TRACK_START %0.2f' % (currTime - startTime))
def printArguments(a, b, c):
print('My args were %d, %d, %d' % (a, b, c))
i1 = FunctionInterval(printStart)
# Just to take time
i2 = LerpPosInterval(base.camera, 2.0, Point3(0, 10, 5))
# This will be relative to end of camera move
i3 = FunctionInterval(printPreviousEnd) # type: ignore[assignment]
# Just to take time
i4 = LerpPosInterval(base.camera, 2.0, Point3(0, 0, 5))
# This will be relative to the start of the camera move
i5 = FunctionInterval(printPreviousStart) # type: ignore[assignment]
# This will be relative to track start
i6 = FunctionInterval(printTrackStart)
# This will print some arguments
# This will be relative to track start
i7 = FunctionInterval(printArguments, extraArgs = [1, 10, 100])
# Create the track, if you don't specify offset type in tuple it defaults to
# relative to TRACK_START (first entry below)
t2 = Track([(0.0, i1), # i1 start at t = 0, duration = 0.0
(1.0, i2, TRACK_START), # i2 start at t = 1, duration = 2.0
(2.0, i3, PREVIOUS_END), # i3 start at t = 5, duration = 0.0
(1.0, i4, PREVIOUS_END), # i4 start at t = 6, duration = 2.0
(3.0, i5, PREVIOUS_START), # i5 start at t = 9, duration = 0.0
(10.0, i6, TRACK_START), # i6 start at t = 10, duration = 0.0
(12.0, i7)], # i7 start at t = 12, duration = 0.0
name = 'startTimeDemo')
print(t2)
# Play tracks
# mtrack.play()
# t1.play()
# t2.play()
def test(n):
lerps = []
for i in range(n):
lerps.append(LerpPosHprInterval(dock, 5.0,
pos=Point3(0, 0, -5),
hpr=Vec3(0, 0, 0),
startPos=dock.getPos(),
startHpr=dock.getHpr(),
name='dock-lerp'))
lerps.append(EventInterval("joe"))
t = Track(lerps)
mt = MultiTrack([t])
# return mt
test(5)
base.run()

View File

@ -7,6 +7,7 @@ from direct.wxwidgets.WxPandaShell import WxPandaShell
from direct.wxwidgets.WxSlider import WxSlider
from direct.directtools.DirectSelection import SelectionRay
from direct.showbase.MessengerGlobal import messenger
from direct.showbase import ShowBaseGlobal
#from ViewPort import *
from . import ObjectGlobals as OG
@ -88,7 +89,7 @@ class PandaTextDropTarget(wx.TextDropTarget):
np = NodePath('temp')
np.setPos(self.view.camera, hitPt)
if base.direct.manipulationControl.fGridSnap:
if ShowBaseGlobal.direct.manipulationControl.fGridSnap:
snappedPos = self.view.grid.computeSnapPoint(np.getPos())
np.setPos(snappedPos)
@ -98,10 +99,10 @@ class PandaTextDropTarget(wx.TextDropTarget):
# transform newobj to cursor position
obj = self.editor.objectMgr.findObjectByNodePath(newobj)
action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
self.editor.actionMgr.push(action)
action2 = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
self.editor.actionMgr.push(action2)
np.remove()
action()
action2()
iRay.collisionNodePath.removeNode()
del iRay
@ -250,13 +251,13 @@ class LevelEditorUIBase(WxPandaShell):
WxPandaShell.createMenu(self)
def onGraphEditor(self, e):
if base.direct.selected.last is None:
if ShowBaseGlobal.direct.selected.last is None:
dlg = wx.MessageDialog(None, 'Please select a object first.', 'NOTICE', wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.graphEditorMenuItem.Check(False)
else:
currentObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
currentObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
self.graphEditorUI = GraphEditorUI(self, self.editor, currentObj)
self.graphEditorUI.Show()
self.graphEditorMenuItem.Check(True)
@ -298,7 +299,7 @@ class LevelEditorUIBase(WxPandaShell):
degreeUI = CurveDegreeUI(self, -1, 'Curve Degree')
degreeUI.ShowModal()
degreeUI.Destroy()
base.direct.manipulationControl.disableManipulation()
ShowBaseGlobal.direct.manipulationControl.disableManipulation()
self.editCurveMenuItem.Check(False)
def onEditCurve(self, e):
@ -313,15 +314,15 @@ class LevelEditorUIBase(WxPandaShell):
self.createCurveMenuItem.Check(False)
self.onEditCurve(None)
else:
if base.direct.selected.last is None:
if ShowBaseGlobal.direct.selected.last is None:
dlg = wx.MessageDialog(None, 'Please select a curve first.', 'NOTICE', wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.editCurveMenuItem.Check(False)
if base.direct.selected.last is not None:
base.direct.manipulationControl.enableManipulation()
if ShowBaseGlobal.direct.selected.last is not None:
ShowBaseGlobal.direct.manipulationControl.enableManipulation()
self.createCurveMenuItem.Check(False)
self.curveObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
self.curveObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
if self.curveObj[OG.OBJ_DEF].name == '__Curve__':
self.editor.mode = self.editor.EDIT_CURVE_MODE
self.editor.updateStatusReadout('Please press ENTER to end the curve editing.')
@ -339,8 +340,8 @@ class LevelEditorUIBase(WxPandaShell):
def updateMenu(self):
hotKeyDict = {}
for hotKey in base.direct.hotKeyMap.keys():
desc = base.direct.hotKeyMap[hotKey]
for hotKey in ShowBaseGlobal.direct.hotKeyMap.keys():
desc = ShowBaseGlobal.direct.hotKeyMap[hotKey]
hotKeyDict[desc[1]] = hotKey
for id in self.MENU_TEXTS.keys():
@ -401,16 +402,16 @@ class LevelEditorUIBase(WxPandaShell):
else:
mpos = evt.GetPosition()
base.direct.fMouse3 = 0
ShowBaseGlobal.direct.fMouse3 = 0
self.PopupMenu(self.contextMenu, mpos)
def onKeyDownEvent(self, evt):
if evt.GetKeyCode() == wx.WXK_ALT:
base.direct.fAlt = 1
ShowBaseGlobal.direct.fAlt = 1
elif evt.GetKeyCode() == wx.WXK_CONTROL:
base.direct.fControl = 1
ShowBaseGlobal.direct.fControl = 1
elif evt.GetKeyCode() == wx.WXK_SHIFT:
base.direct.fShift = 1
ShowBaseGlobal.direct.fShift = 1
elif evt.GetKeyCode() == wx.WXK_UP:
messenger.send('arrow_up')
elif evt.GetKeyCode() == wx.WXK_DOWN:
@ -428,11 +429,11 @@ class LevelEditorUIBase(WxPandaShell):
def onKeyUpEvent(self, evt):
if evt.GetKeyCode() == wx.WXK_ALT:
base.direct.fAlt = 0
ShowBaseGlobal.direct.fAlt = 0
elif evt.GetKeyCode() == wx.WXK_CONTROL:
base.direct.fControl = 0
ShowBaseGlobal.direct.fControl = 0
elif evt.GetKeyCode() == wx.WXK_SHIFT:
base.direct.fShift = 0
ShowBaseGlobal.direct.fShift = 0
elif evt.GetKeyCode() == wx.WXK_UP:
messenger.send('arrow_up-up')
elif evt.GetKeyCode() == wx.WXK_DOWN:
@ -473,8 +474,8 @@ class LevelEditorUIBase(WxPandaShell):
input = 'control-%s'%chr(evt.GetKeyCode())
elif evt.GetKeyCode() < 256:
input = chr(evt.GetKeyCode())
if input in base.direct.hotKeyMap.keys():
keyDesc = base.direct.hotKeyMap[input]
if input in ShowBaseGlobal.direct.hotKeyMap.keys():
keyDesc = ShowBaseGlobal.direct.hotKeyMap[input]
messenger.send(keyDesc[1])
def reset(self):
@ -533,12 +534,12 @@ class LevelEditorUIBase(WxPandaShell):
def toggleGridSnap(self, evt):
if self.gridSnapMenuItem.IsChecked():
base.direct.manipulationControl.fGridSnap = 1
ShowBaseGlobal.direct.manipulationControl.fGridSnap = 1
for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]:
grid.fXyzSnap = 1
else:
base.direct.manipulationControl.fGridSnap = 0
ShowBaseGlobal.direct.manipulationControl.fGridSnap = 0
for grid in [self.perspView.grid, self.topView.grid, self.frontView.grid, self.leftView.grid]:
grid.fXyzSnap = 0
@ -589,7 +590,7 @@ class LevelEditorUIBase(WxPandaShell):
self.contextMenu.AppendSeparator()
def replaceObject(self, evt, all=False):
currObj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
currObj = self.editor.objectMgr.findObjectByNodePath(ShowBaseGlobal.direct.selected.last)
if currObj is None:
print('No valid object is selected for replacement')
return
@ -636,13 +637,13 @@ class GridSizeUI(wx.Dialog):
vbox.Add(okButton, 1, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, 5)
self.SetSizer(vbox)
base.le.ui.bindKeyEvents(False)
ShowBaseGlobal.base.le.ui.bindKeyEvents(False)
def onApply(self, evt):
newSize = self.gridSizeSlider.GetValue()
newSpacing = self.gridSpacingSlider.GetValue()
self.parent.updateGrids(newSize, newSpacing)
base.le.ui.bindKeyEvents(True)
ShowBaseGlobal.base.le.ui.bindKeyEvents(True)
self.Destroy()

View File

@ -25,6 +25,9 @@ class LevelLoader(LevelLoaderBase):
def initLoader(self):
self.defaultPath = os.path.dirname(__file__)
from direct.showbase import ShowBaseGlobal
base = ShowBaseGlobal.base
base.objectPalette = ObjectPalette()
base.protoPalette = ProtoPalette()
base.objectHandler = ObjectHandler(None)

View File

@ -97,7 +97,7 @@ class ParticleEffect(NodePath):
def addForceGroup(self, forceGroup):
forceGroup.nodePath.reparentTo(self)
forceGroup.particleEffect = self
self.forceGroupDict[forceGroup.getName()] = forceGroup
self.forceGroupDict[forceGroup.name] = forceGroup
# Associate the force group with all particles
for force in forceGroup:

View File

@ -5,7 +5,6 @@ sound, music, shaders and fonts from disk.
__all__ = ['Loader']
from panda3d.core import (
AudioLoadRequest,
ConfigVariableBool,
Filename,
FontPool,
@ -977,6 +976,8 @@ class Loader(DirectObject):
just as in loadModel(); otherwise, the loading happens before
loadSound() returns."""
from panda3d.core import AudioLoadRequest
if not isinstance(soundPath, (tuple, list, set)):
# We were given a single sound pathname or a MovieAudio instance.
soundList = [soundPath]

View File

@ -123,6 +123,7 @@ import builtins
builtins.config = DConfig # type: ignore[attr-defined]
from direct.directnotify.DirectNotifyGlobal import directNotify, giveNotify
from direct.directnotify.Notifier import Notifier
from .MessengerGlobal import messenger
from .BulletinBoardGlobal import bulletinBoard
from direct.task.TaskManagerGlobal import taskMgr
@ -140,6 +141,7 @@ import importlib
from direct.showbase import ExceptionVarDump
from . import DirectObject
from . import SfxPlayer
from typing import ClassVar, Optional
if __debug__:
from direct.showbase import GarbageReport
from direct.directutil import DeltaProfiler
@ -160,8 +162,13 @@ def exitfunc():
class ShowBase(DirectObject.DirectObject):
#: The deprecated `.DConfig` interface for accessing config variables.
config = DConfig
notify = directNotify.newCategory("ShowBase")
config: ClassVar = DConfig
notify: ClassVar[Notifier] = directNotify.newCategory("ShowBase")
guiItems: ClassVar[dict]
render2d: NodePath
aspect2d: NodePath
pixel2d: NodePath
def __init__(self, fStartDirect=True, windowType=None):
"""Opens a window, sets up a 3-D and several 2-D scene graphs, and
@ -337,10 +344,10 @@ class ShowBase(DirectObject.DirectObject):
self.tkRootCreated = False
# This is used for syncing multiple PCs in a distributed cluster
try:
if hasattr(builtins, 'clusterSyncFlag'):
# Has the cluster sync variable been set externally?
self.clusterSyncFlag = clusterSyncFlag
except NameError:
self.clusterSyncFlag = builtins.clusterSyncFlag
else:
# Has the clusterSyncFlag been set via a config variable
self.clusterSyncFlag = ConfigVariableBool('cluster-sync', False)
@ -424,6 +431,7 @@ class ShowBase(DirectObject.DirectObject):
#: `.Loader.Loader` object.
self.loader = Loader.Loader(self)
self.graphicsEngine.setDefaultLoader(self.loader.loader)
ShowBaseGlobal.loader = self.loader
#: The global event manager, as imported from `.EventManagerGlobal`.
self.eventMgr = eventMgr
@ -712,10 +720,9 @@ class ShowBase(DirectObject.DirectObject):
except Exception:
pass
if hasattr(self, 'win'):
del self.win
del self.winList
del self.pipe
self.win = None
self.winList.clear()
self.pipe = None
def makeDefaultPipe(self, printPipeTypes = None):
"""
@ -728,7 +735,7 @@ class ShowBase(DirectObject.DirectObject):
# When the user didn't specify an explicit setting, take the value
# from the config variable. We could just omit the parameter, however
# this way we can keep backward compatibility.
printPipeTypes = ConfigVariableBool("print-pipe-types", True)
printPipeTypes = ConfigVariableBool("print-pipe-types", True).value
selection = GraphicsPipeSelection.getGlobalPtr()
if printPipeTypes:
@ -3414,7 +3421,7 @@ class ShowBase(DirectObject.DirectObject):
# Set fWantTk to 0 to avoid starting Tk with this call
self.startDirect(fWantDirect = fDirect, fWantTk = fTk, fWantWx = fWx)
def run(self): # pylint: disable=method-hidden
def run(self) -> None: # pylint: disable=method-hidden
"""This method runs the :class:`~direct.task.Task.TaskManager`
when ``self.appRunner is None``, which is to say, when we are
not running from within a p3d file. When we *are* within a p3d

View File

@ -19,9 +19,10 @@ from panda3d.core import VirtualFileSystem, Notify, ClockObject, PandaSystem
from panda3d.core import ConfigPageManager, ConfigVariableManager, ConfigVariableBool
from panda3d.core import NodePath, PGTop
from . import DConfig as config # pylint: disable=unused-import
from .Loader import Loader
import warnings
__dev__ = ConfigVariableBool('want-dev', __debug__).value
__dev__: bool = ConfigVariableBool('want-dev', __debug__).value
base: ShowBase
@ -61,6 +62,10 @@ aspect2d = render2d.attachNewNode(PGTop("aspect2d"))
#: A dummy scene graph that is not being rendered by anything.
hidden = NodePath("hidden")
loader: Loader
direct: "DirectSession"
# Set direct notify categories now that we have config
directNotify.setDconfigLevels()

View File

@ -37,4 +37,5 @@ del bordercolors
def spawnTkLoop():
"""Alias for :meth:`base.spawnTkLoop() <.ShowBase.spawnTkLoop>`."""
base.spawnTkLoop()
from direct.showbase import ShowBaseGlobal
ShowBaseGlobal.base.spawnTkLoop()

View File

@ -3,4 +3,5 @@
def spawnWxLoop():
"""Alias for :meth:`base.spawnWxLoop() <.ShowBase.spawnWxLoop>`."""
base.spawnWxLoop()
from direct.showbase import ShowBaseGlobal
ShowBaseGlobal.base.spawnWxLoop()

View File

@ -22,6 +22,7 @@ from panda3d.core import (
WindowProperties,
)
from direct.showbase.DirectObject import DirectObject
from direct.showbase import ShowBaseGlobal
from direct.task.TaskManagerGlobal import taskMgr
import math
import copy
@ -109,7 +110,7 @@ class TexMemWatcher(DirectObject):
# This is the maximum number of bitmask rows (within
# self.limit) to allocate for packing. This controls the
# value assigned to self.quantize in repack().
self.maxHeight = base.config.GetInt('tex-mem-max-height', 300)
self.maxHeight = ConfigVariableInt('tex-mem-max-height', 300).value
# The total number of texture bytes tracked, including overflow.
self.totalSize = 0
@ -122,6 +123,7 @@ class TexMemWatcher(DirectObject):
self.placedQSize = 0
# If no GSG is specified, use the main GSG.
base = ShowBaseGlobal.base
if gsg is None:
gsg = base.win.getGsg()
elif isinstance(gsg, GraphicsOutput):
@ -150,7 +152,7 @@ class TexMemWatcher(DirectObject):
# Set this to tinydisplay if you're running on a machine with
# limited texture memory. That way you won't compete for
# texture memory with the main scene.
moduleName = base.config.GetString('tex-mem-pipe', '')
moduleName = ConfigVariableString('tex-mem-pipe', '').value
if moduleName:
self.pipe = base.makeModulePipe(moduleName)
@ -202,7 +204,7 @@ class TexMemWatcher(DirectObject):
# How frequently should the texture memory window check for
# state changes?
updateInterval = base.config.GetDouble("tex-mem-update-interval", 0.5)
updateInterval = ConfigVariableDouble("tex-mem-update-interval", 0.5).value
self.task = taskMgr.doMethodLater(updateInterval, self.updateTextures, 'TexMemWatcher')
self.setLimit(limit)
@ -380,7 +382,7 @@ class TexMemWatcher(DirectObject):
self.cleanedUp = True
# Remove the window.
base.graphicsEngine.removeWindow(self.win)
self.win.engine.removeWindow(self.win)
self.win = None
self.gsg = None
self.pipe = None

View File

@ -18,7 +18,7 @@ class TexViewer(DirectObject):
# We'll put the full-resolution texture on the left.
cm = CardMaker('left')
l, r, b, t = (-1, -0.1, 0, 0.9)
l, r, b, t = (-1.0, -0.1, 0.0, 0.9)
cm.setFrame(l, r, b, t)
left = cards.attachNewNode(cm.generate())
left.setTexture(self.tex)

View File

@ -12,6 +12,8 @@ class MiniTask:
done = 0
cont = 1
name: str
def __init__(self, callback):
self.__call__ = callback

View File

@ -6,6 +6,8 @@ For more information about the task system, consult the
:ref:`tasks-and-event-handling` page in the programming manual.
"""
from __future__ import annotations
__all__ = ['Task', 'TaskManager',
'cont', 'done', 'again', 'pickup', 'exit',
'sequence', 'loop', 'pause']
@ -13,6 +15,7 @@ __all__ = ['Task', 'TaskManager',
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.showbase.PythonUtil import Functor, ScratchPad
from direct.showbase.MessengerGlobal import messenger
from typing import Any, Callable, Coroutine, Final, Generator, Sequence, TypeVar, Union
import types
import random
import importlib
@ -20,6 +23,7 @@ import sys
# On Android, there's no use handling SIGINT, and in fact we can't, since we
# run the application in a separate thread from the main thread.
signal: types.ModuleType | None
if hasattr(sys, 'getandroidapilevel'):
signal = None
else:
@ -41,8 +45,15 @@ from panda3d.core import (
)
from direct.extensions_native import HTTPChannel_extensions # pylint: disable=unused-import
# The following variables are typing constructs used in annotations
# to succinctly express all the types that can be converted into tasks.
_T = TypeVar('_T', covariant=True)
_TaskCoroutine = Union[Coroutine[Any, None, _T], Generator[Any, None, _T]]
_TaskFunction = Callable[..., Union[int, _TaskCoroutine[Union[int, None]], None]]
_FuncOrTask = Union[_TaskFunction, _TaskCoroutine[Any], AsyncTask]
def print_exc_plus():
def print_exc_plus() -> None:
"""
Print the usual traceback information, followed by a listing of all the
local variables in each frame.
@ -50,12 +61,13 @@ def print_exc_plus():
import traceback
tb = sys.exc_info()[2]
assert tb is not None
while 1:
if not tb.tb_next:
break
tb = tb.tb_next
stack = []
f = tb.tb_frame
f: types.FrameType | None = tb.tb_frame
while f:
stack.append(f)
f = f.f_back
@ -82,11 +94,11 @@ def print_exc_plus():
# these Python names, and define them both at the module level, here,
# and at the class level (below). The preferred access is via the
# class level.
done = AsyncTask.DSDone
cont = AsyncTask.DSCont
again = AsyncTask.DSAgain
pickup = AsyncTask.DSPickup
exit = AsyncTask.DSExit
done: Final = AsyncTask.DSDone
cont: Final = AsyncTask.DSCont
again: Final = AsyncTask.DSAgain
pickup: Final = AsyncTask.DSPickup
exit: Final = AsyncTask.DSExit
#: Task aliases to :class:`panda3d.core.PythonTask` for historical purposes.
Task = PythonTask
@ -110,7 +122,7 @@ gather = Task.gather
shield = Task.shield
def sequence(*taskList):
def sequence(*taskList: AsyncTask) -> AsyncTaskSequence:
seq = AsyncTaskSequence('sequence')
for task in taskList:
seq.addTask(task)
@ -120,7 +132,7 @@ def sequence(*taskList):
Task.DtoolClassDict['sequence'] = staticmethod(sequence)
def loop(*taskList):
def loop(*taskList: AsyncTask) -> AsyncTaskSequence:
seq = AsyncTaskSequence('loop')
for task in taskList:
seq.addTask(task)
@ -140,10 +152,12 @@ class TaskManager:
MaxEpochSpeed = 1.0/30.0
def __init__(self):
__prevHandler: Any
def __init__(self) -> None:
self.mgr = AsyncTaskManager.getGlobalPtr()
self.resumeFunc = None
self.resumeFunc: Callable[[], object] | None = None
self.globalClock = self.mgr.getClock()
self.stepping = False
self.running = False
@ -153,12 +167,12 @@ class TaskManager:
if signal:
self.__prevHandler = signal.default_int_handler
self._frameProfileQueue = []
self._frameProfileQueue: list[tuple[int, Any, Callable[[], object] | None]] = []
# this will be set when it's safe to import StateVar
self._profileFrames = None
self._profileFrames: Any = None
self._frameProfiler = None
self._profileTasks = None
self._profileTasks: Any = None
self._taskProfiler = None
self._taskProfileInfo = ScratchPad(
taskId = None,
@ -166,7 +180,7 @@ class TaskManager:
session = None,
)
def finalInit(self):
def finalInit(self) -> None:
# This function should be called once during startup, after
# most things are imported.
from direct.fsm.StatePush import StateVar
@ -175,7 +189,7 @@ class TaskManager:
self._profileFrames = StateVar(False)
self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())
def destroy(self):
def destroy(self) -> None:
# This should be safe to call multiple times.
self.running = False
self.notify.info("TaskManager.destroy()")
@ -183,11 +197,14 @@ class TaskManager:
self._frameProfileQueue.clear()
self.mgr.cleanup()
def setClock(self, clockObject):
def __getClock(self) -> ClockObject:
return self.mgr.getClock()
def setClock(self, clockObject: ClockObject) -> None:
self.mgr.setClock(clockObject)
self.globalClock = clockObject
clock = property(lambda self: self.mgr.getClock(), setClock)
clock = property(__getClock, setClock)
def invokeDefaultHandler(self, signalNumber, stackFrame):
print('*** allowing mid-frame keyboard interrupt.')
@ -208,13 +225,13 @@ class TaskManager:
# Next time around invoke the default handler
signal.signal(signal.SIGINT, self.invokeDefaultHandler)
def getCurrentTask(self):
def getCurrentTask(self) -> AsyncTask | None:
""" Returns the task currently executing on this thread, or
None if this is being called outside of the task manager. """
return Thread.getCurrentThread().getCurrentTask()
def hasTaskChain(self, chainName):
def hasTaskChain(self, chainName: str) -> bool:
""" Returns true if a task chain with the indicated name has
already been defined, or false otherwise. Note that
setupTaskChain() will implicitly define a task chain if it has
@ -224,9 +241,16 @@ class TaskManager:
return self.mgr.findTaskChain(chainName) is not None
def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
threadPriority = None, frameBudget = None,
frameSync = None, timeslicePriority = None):
def setupTaskChain(
self,
chainName: str,
numThreads: int | None = None,
tickClock: bool | None = None,
threadPriority: int | None = None,
frameBudget: float | None = None,
frameSync: bool | None = None,
timeslicePriority: bool | None = None,
) -> None:
"""Defines a new task chain. Each task chain executes tasks
potentially in parallel with all of the other task chains (if
numThreads is more than zero). When a new task is created, it
@ -290,40 +314,50 @@ class TaskManager:
if timeslicePriority is not None:
chain.setTimeslicePriority(timeslicePriority)
def hasTaskNamed(self, taskName):
def hasTaskNamed(self, taskName: str) -> bool:
"""Returns true if there is at least one task, active or
sleeping, with the indicated name. """
return bool(self.mgr.findTask(taskName))
def getTasksNamed(self, taskName):
def getTasksNamed(self, taskName: str) -> list[AsyncTask]:
"""Returns a list of all tasks, active or sleeping, with the
indicated name. """
return list(self.mgr.findTasks(taskName))
def getTasksMatching(self, taskPattern):
def getTasksMatching(self, taskPattern: GlobPattern | str) -> list[AsyncTask]:
"""Returns a list of all tasks, active or sleeping, with a
name that matches the pattern, which can include standard
shell globbing characters like \\*, ?, and []. """
return list(self.mgr.findTasksMatching(GlobPattern(taskPattern)))
def getAllTasks(self):
def getAllTasks(self) -> list[AsyncTask]:
"""Returns list of all tasks, active and sleeping, in
arbitrary order. """
return list(self.mgr.getTasks())
def getTasks(self):
def getTasks(self) -> list[AsyncTask]:
"""Returns list of all active tasks in arbitrary order. """
return list(self.mgr.getActiveTasks())
def getDoLaters(self):
def getDoLaters(self) -> list[AsyncTask]:
"""Returns list of all sleeping tasks in arbitrary order. """
return list(self.mgr.getSleepingTasks())
def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
sort = None, priority = None, taskChain = None,
uponDeath = None, appendTask = False, owner = None):
def doMethodLater(
self,
delayTime: float,
funcOrTask: _FuncOrTask,
name: str | None,
extraArgs: Sequence | None = None,
sort: int | None = None,
priority: int | None = None,
taskChain: str | None = None,
uponDeath: Callable[[], object] | None = None,
appendTask: bool = False,
owner = None,
) -> AsyncTask:
"""Adds a task to be performed at some time in the future.
This is identical to `add()`, except that the specified
delayTime is applied to the Task object first, which means
@ -346,9 +380,19 @@ class TaskManager:
do_method_later = doMethodLater
def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
priority = None, uponDeath = None, appendTask = False,
taskChain = None, owner = None, delay = None):
def add(
self,
funcOrTask: _FuncOrTask,
name: str | None = None,
sort: int | None = None,
extraArgs: Sequence | None = None,
priority: int | None = None,
uponDeath: Callable[[], object] | None = None,
appendTask: bool = False,
taskChain: str | None = None,
owner = None,
delay: float | None = None,
) -> AsyncTask:
"""
Add a new task to the taskMgr. The task will begin executing
immediately, or next frame if its sort value has already
@ -415,7 +459,18 @@ class TaskManager:
self.mgr.add(task)
return task
def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
def __setupTask(
self,
funcOrTask: _FuncOrTask,
name: str | None,
priority: int | None,
sort: int | None,
extraArgs: Sequence | None,
taskChain: str | None,
appendTask: bool,
owner,
uponDeath: Callable[[], object] | None,
) -> AsyncTask:
wasTask = False
if isinstance(funcOrTask, AsyncTask):
task = funcOrTask
@ -473,7 +528,7 @@ class TaskManager:
return task
def remove(self, taskOrName):
def remove(self, taskOrName: AsyncTask | str | list[AsyncTask | str]) -> int:
"""Removes a task from the task manager. The task is stopped,
almost as if it had returned task.done. (But if the task is
currently executing, it will finish out its current frame
@ -485,13 +540,15 @@ class TaskManager:
if isinstance(taskOrName, AsyncTask):
return self.mgr.remove(taskOrName)
elif isinstance(taskOrName, list):
count = 0
for task in taskOrName:
self.remove(task)
count += self.remove(task)
return count
else:
tasks = self.mgr.findTasks(taskOrName)
return self.mgr.remove(tasks)
def removeTasksMatching(self, taskPattern):
def removeTasksMatching(self, taskPattern: GlobPattern | str) -> int:
"""Removes all tasks whose names match the pattern, which can
include standard shell globbing characters like \\*, ?, and [].
See also :meth:`remove()`.
@ -501,7 +558,7 @@ class TaskManager:
tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
return self.mgr.remove(tasks)
def step(self):
def step(self) -> None:
"""Invokes the task manager for one frame, and then returns.
Normally, this executes each task exactly once, though task
chains that are in sub-threads or that have frame budgets
@ -512,7 +569,7 @@ class TaskManager:
# Replace keyboard interrupt handler during task list processing
# so we catch the keyboard interrupt but don't handle it until
# after task list processing is complete.
self.fKeyboardInterrupt = 0
self.fKeyboardInterrupt = False
self.interruptCount = 0
if signal:
@ -534,7 +591,7 @@ class TaskManager:
if self.fKeyboardInterrupt:
raise KeyboardInterrupt
def run(self):
def run(self) -> None:
"""Starts the task manager running. Does not return until an
exception is encountered (including KeyboardInterrupt). """
@ -560,11 +617,11 @@ class TaskManager:
if len(self._frameProfileQueue) > 0:
numFrames, session, callback = self._frameProfileQueue.pop(0)
def _profileFunc(numFrames=numFrames):
def _profileFunc(numFrames: int = numFrames) -> None:
self._doProfiledFrames(numFrames)
session.setFunc(_profileFunc)
session.run()
_profileFunc = None
del _profileFunc
if callback:
callback()
session.release()
@ -617,7 +674,7 @@ class TaskManager:
message = ioError
return code, message
def stop(self):
def stop(self) -> None:
# Set a flag so we will stop before beginning next frame
self.running = False
@ -782,12 +839,12 @@ class TaskManager:
task = tasks.getTask(i)
return task
def __repr__(self):
def __repr__(self) -> str:
return str(self.mgr)
# In the event we want to do frame time managment, this is the
# function to replace or overload.
def doYield(self, frameStartTime, nextScheduledTaskTime):
def doYield(self, frameStartTime: float, nextScheduledTaskTime: float) -> None:
pass
#def doYieldExample(self, frameStartTime, nextScheduledTaskTime):

View File

@ -20,6 +20,7 @@ from direct.tkwidgets import VectorWidgets
from direct.tkwidgets import SceneGraphExplorer
from direct.tkwidgets import MemoryExplorer
from direct.task.TaskManagerGlobal import taskMgr
from direct.showbase import ShowBaseGlobal
from .TaskManagerPanel import TaskManagerWidget
import Pmw
import tkinter as tk
@ -44,8 +45,8 @@ class DirectSessionPanel(AppShell):
AppShell.__init__(self, parent)
# Active light
if len(base.direct.lights) > 0:
name = base.direct.lights.getNameList()[0]
if len(ShowBaseGlobal.direct.lights) > 0:
name = ShowBaseGlobal.direct.lights.getNameList()[0]
self.lightMenu.selectitem(name)
self.selectLightNamed(name)
else:
@ -62,14 +63,14 @@ class DirectSessionPanel(AppShell):
# Initialize state
# Dictionary keeping track of all node paths selected so far
self.nodePathDict = {}
self.nodePathDict['widget'] = base.direct.widget
self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
self.nodePathNames = ['widget']
# Dictionary keeping track of all jb node paths selected so far
self.jbNodePathDict = {}
self.jbNodePathDict['none'] = 'No Node Path'
self.jbNodePathDict['widget'] = base.direct.widget
self.jbNodePathDict['camera'] = base.direct.camera
self.jbNodePathDict['widget'] = ShowBaseGlobal.direct.widget
self.jbNodePathDict['camera'] = ShowBaseGlobal.direct.camera
self.jbNodePathNames = ['camera', 'selected', 'none']
# Set up event hooks
@ -93,7 +94,7 @@ class DirectSessionPanel(AppShell):
self.menuBar.addmenu('DIRECT', 'Direct Session Panel Operations')
self.directEnabled = tk.BooleanVar()
self.directEnabled.set(1)
self.directEnabled.set(True)
self.menuBar.addmenuitem('DIRECT', 'checkbutton',
'DIRECT Enabled',
label = 'Enable',
@ -101,7 +102,7 @@ class DirectSessionPanel(AppShell):
command = self.toggleDirect)
self.directGridEnabled = tk.BooleanVar()
self.directGridEnabled.set(base.direct.grid.isEnabled())
self.directGridEnabled.set(ShowBaseGlobal.direct.grid.isEnabled())
self.menuBar.addmenuitem('DIRECT', 'checkbutton',
'DIRECT Grid Enabled',
label = 'Enable Grid',
@ -111,16 +112,16 @@ class DirectSessionPanel(AppShell):
self.menuBar.addmenuitem('DIRECT', 'command',
'Toggle Object Handles Visability',
label = 'Toggle Widget Viz',
command = base.direct.toggleWidgetVis)
command = ShowBaseGlobal.direct.toggleWidgetVis)
self.menuBar.addmenuitem(
'DIRECT', 'command',
'Toggle Widget Move/COA Mode',
label = 'Toggle Widget Mode',
command = base.direct.manipulationControl.toggleObjectHandlesMode)
command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)
self.directWidgetOnTop = tk.BooleanVar()
self.directWidgetOnTop.set(0)
self.directWidgetOnTop.set(False)
self.menuBar.addmenuitem('DIRECT', 'checkbutton',
'DIRECT Widget On Top',
label = 'Widget On Top',
@ -130,7 +131,7 @@ class DirectSessionPanel(AppShell):
self.menuBar.addmenuitem('DIRECT', 'command',
'Deselect All',
label = 'Deselect All',
command = base.direct.deselectAll)
command = ShowBaseGlobal.direct.deselectAll)
# Get a handle to the menu frame
menuFrame = self.menuFrame
@ -150,8 +151,8 @@ class DirectSessionPanel(AppShell):
self.bind(self.nodePathMenu, 'Select node path to manipulate')
self.undoButton = tk.Button(menuFrame, text = 'Undo',
command = base.direct.undo)
if base.direct.undoList:
command = ShowBaseGlobal.direct.undo)
if ShowBaseGlobal.direct.undoList:
self.undoButton['state'] = 'normal'
else:
self.undoButton['state'] = 'disabled'
@ -159,8 +160,8 @@ class DirectSessionPanel(AppShell):
self.bind(self.undoButton, 'Undo last operation')
self.redoButton = tk.Button(menuFrame, text = 'Redo',
command = base.direct.redo)
if base.direct.redoList:
command = ShowBaseGlobal.direct.redo)
if ShowBaseGlobal.direct.redoList:
self.redoButton['state'] = 'normal'
else:
self.redoButton['state'] = 'disabled'
@ -177,7 +178,7 @@ class DirectSessionPanel(AppShell):
# Scene Graph Explorer
self.SGE = SceneGraphExplorer.SceneGraphExplorer(
sgeFrame, nodePath = render,
sgeFrame, nodePath = ShowBaseGlobal.base.render,
scrolledCanvas_hull_width = 250,
scrolledCanvas_hull_height = 300)
self.SGE.pack(fill = tk.BOTH, expand = 1)
@ -218,7 +219,7 @@ class DirectSessionPanel(AppShell):
tk.Label(drFrame, text = 'Display Region',
font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
nameList = ['Display Region ' + repr(x) for x in range(len(base.direct.drList))]
nameList = ['Display Region ' + repr(x) for x in range(len(ShowBaseGlobal.direct.drList))]
self.drMenu = Pmw.ComboBox(
drFrame, labelpos = tk.W, label_text = 'Display Region:',
entry_width = 20,
@ -264,7 +265,7 @@ class DirectSessionPanel(AppShell):
frame = tk.Frame(fovFrame)
self.lockedFov = tk.BooleanVar()
self.lockedFov.set(1)
self.lockedFov.set(True)
self.lockedFovButton = tk.Checkbutton(
frame,
text = 'Locked',
@ -289,25 +290,25 @@ class DirectSessionPanel(AppShell):
self.toggleBackfaceButton = tk.Button(
toggleFrame,
text = 'Backface',
command = base.toggleBackface)
command = ShowBaseGlobal.base.toggleBackface)
self.toggleBackfaceButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
self.toggleLightsButton = tk.Button(
toggleFrame,
text = 'Lights',
command = base.direct.lights.toggle)
command = ShowBaseGlobal.direct.lights.toggle)
self.toggleLightsButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
self.toggleTextureButton = tk.Button(
toggleFrame,
text = 'Texture',
command = base.toggleTexture)
command = ShowBaseGlobal.base.toggleTexture)
self.toggleTextureButton.pack(side = tk.LEFT, fill = tk.X, expand = 1)
self.toggleWireframeButton = tk.Button(
toggleFrame,
text = 'Wireframe',
command = base.toggleWireframe)
command = ShowBaseGlobal.base.toggleWireframe)
self.toggleWireframeButton.pack(fill = tk.X, expand = 1)
toggleFrame.pack(side = tk.LEFT, fill = tk.X, expand = 1)
@ -354,7 +355,7 @@ class DirectSessionPanel(AppShell):
mainSwitchFrame.pack(fill = tk.X, expand = 0)
# Widget to select a light to configure
nameList = base.direct.lights.getNameList()
nameList = ShowBaseGlobal.direct.lights.getNameList()
lightMenuFrame = tk.Frame(lightFrame)
self.lightMenu = Pmw.ComboBox(
@ -510,36 +511,36 @@ class DirectSessionPanel(AppShell):
gridPage,
text = 'Grid Spacing',
min = 0.1,
value = base.direct.grid.getGridSpacing())
self.gridSpacing['command'] = base.direct.grid.setGridSpacing
value = ShowBaseGlobal.direct.grid.getGridSpacing())
self.gridSpacing['command'] = ShowBaseGlobal.direct.grid.setGridSpacing
self.gridSpacing.pack(fill = tk.X, expand = 0)
self.gridSize = Floater.Floater(
gridPage,
text = 'Grid Size',
min = 1.0,
value = base.direct.grid.getGridSize())
self.gridSize['command'] = base.direct.grid.setGridSize
value = ShowBaseGlobal.direct.grid.getGridSize())
self.gridSize['command'] = ShowBaseGlobal.direct.grid.setGridSize
self.gridSize.pack(fill = tk.X, expand = 0)
self.gridSnapAngle = Dial.AngleDial(
gridPage,
text = 'Snap Angle',
style = 'mini',
value = base.direct.grid.getSnapAngle())
self.gridSnapAngle['command'] = base.direct.grid.setSnapAngle
value = ShowBaseGlobal.direct.grid.getSnapAngle())
self.gridSnapAngle['command'] = ShowBaseGlobal.direct.grid.setSnapAngle
self.gridSnapAngle.pack(fill = tk.X, expand = 0)
def createDevicePage(self, devicePage):
tk.Label(devicePage, text = 'DEVICES',
font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
if base.direct.joybox is not None:
if ShowBaseGlobal.direct.joybox is not None:
joyboxFrame = tk.Frame(devicePage, borderwidth = 2, relief = 'sunken')
tk.Label(joyboxFrame, text = 'Joybox',
font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
self.enableJoybox = tk.BooleanVar()
self.enableJoybox.set(1)
self.enableJoybox.set(True)
self.enableJoyboxButton = tk.Checkbutton(
joyboxFrame,
text = 'Enabled/Disabled',
@ -581,7 +582,7 @@ class DirectSessionPanel(AppShell):
hull_relief = tk.RIDGE, hull_borderwidth = 2,
min = 1.0, max = 100.0)
self.jbXyzSF['command'] = (
lambda v: base.direct.joybox.setXyzMultiplier(v))
lambda v: ShowBaseGlobal.direct.joybox.setXyzMultiplier(v))
self.jbXyzSF.pack(fill = tk.X, expand = 0)
self.bind(self.jbXyzSF, 'Set joybox XYZ speed multiplier')
@ -592,7 +593,7 @@ class DirectSessionPanel(AppShell):
hull_relief = tk.RIDGE, hull_borderwidth = 2,
min = 1.0, max = 100.0)
self.jbHprSF['command'] = (
lambda v: base.direct.joybox.setHprMultiplier(v))
lambda v: ShowBaseGlobal.direct.joybox.setHprMultiplier(v))
self.jbHprSF.pack(fill = tk.X, expand = 0)
self.bind(self.jbHprSF, 'Set joybox HPR speed multiplier')
@ -604,30 +605,30 @@ class DirectSessionPanel(AppShell):
def createMemPage(self, memPage):
self.MemExp = MemoryExplorer.MemoryExplorer(
memPage, nodePath = render,
memPage, nodePath = ShowBaseGlobal.base.render,
scrolledCanvas_hull_width = 250,
scrolledCanvas_hull_height = 250)
self.MemExp.pack(fill = tk.BOTH, expand = 1)
def toggleDirect(self):
if self.directEnabled.get():
base.direct.enable()
ShowBaseGlobal.direct.enable()
else:
base.direct.disable()
ShowBaseGlobal.direct.disable()
def toggleDirectGrid(self):
if self.directGridEnabled.get():
base.direct.grid.enable()
ShowBaseGlobal.direct.grid.enable()
else:
base.direct.grid.disable()
ShowBaseGlobal.direct.grid.disable()
def toggleWidgetOnTop(self):
if self.directWidgetOnTop.get():
base.direct.widget.setBin('gui-popup', 0)
base.direct.widget.setDepthTest(0)
ShowBaseGlobal.direct.widget.setBin('gui-popup', 0)
ShowBaseGlobal.direct.widget.setDepthTest(0)
else:
base.direct.widget.clearBin()
base.direct.widget.setDepthTest(1)
ShowBaseGlobal.direct.widget.clearBin()
ShowBaseGlobal.direct.widget.setDepthTest(1)
def selectedNodePathHook(self, nodePath):
# Make sure node path is in nodePathDict
@ -657,7 +658,7 @@ class DirectSessionPanel(AppShell):
# Did we finally get something?
if nodePath is not None:
# Yes, select it!
base.direct.select(nodePath)
ShowBaseGlobal.direct.select(nodePath)
def addNodePath(self, nodePath):
self.addNodePathToDict(nodePath, self.nodePathNames,
@ -665,25 +666,25 @@ class DirectSessionPanel(AppShell):
def selectJBModeNamed(self, name):
if name == 'Joe Mode':
base.direct.joybox.joeMode()
ShowBaseGlobal.direct.joybox.joeMode()
elif name == 'Drive Mode':
base.direct.joybox.driveMode()
ShowBaseGlobal.direct.joybox.driveMode()
elif name == 'Orbit Mode':
base.direct.joybox.orbitMode()
ShowBaseGlobal.direct.joybox.orbitMode()
elif name == 'Look At Mode':
base.direct.joybox.lookAtMode()
ShowBaseGlobal.direct.joybox.lookAtMode()
elif name == 'Look Around Mode':
base.direct.joybox.lookAroundMode()
ShowBaseGlobal.direct.joybox.lookAroundMode()
elif name == 'Walkthru Mode':
base.direct.joybox.walkthruMode()
ShowBaseGlobal.direct.joybox.walkthruMode()
elif name == 'Demo Mode':
base.direct.joybox.demoMode()
ShowBaseGlobal.direct.joybox.demoMode()
elif name == 'HPRXYZ Mode':
base.direct.joybox.hprXyzMode()
ShowBaseGlobal.direct.joybox.hprXyzMode()
def selectJBNodePathNamed(self, name):
if name == 'selected':
nodePath = base.direct.selected.last
nodePath = ShowBaseGlobal.direct.selected.last
# Add Combo box entry for this selected object
self.addJBNodePath(nodePath)
else:
@ -708,9 +709,9 @@ class DirectSessionPanel(AppShell):
if nodePath is not None:
# Yes, select it!
if nodePath == 'No Node Path':
base.direct.joybox.setNodePath(None)
ShowBaseGlobal.direct.joybox.setNodePath(None)
else:
base.direct.joybox.setNodePath(nodePath)
ShowBaseGlobal.direct.joybox.setNodePath(nodePath)
def addJBNodePath(self, nodePath):
self.addNodePathToDict(nodePath, self.jbNodePathNames,
@ -741,14 +742,13 @@ class DirectSessionPanel(AppShell):
self.setBackgroundColorVec((r, g, b))
def setBackgroundColorVec(self, color):
base.setBackgroundColor(color[0]/255.0,
color[1]/255.0,
color[2]/255.0)
ShowBaseGlobal.base.setBackgroundColor(
color[0] / 255.0, color[1] / 255.0, color[2] / 255.0)
def selectDisplayRegionNamed(self, name):
if name.find('Display Region ') >= 0:
drIndex = int(name[-1:])
self.activeDisplayRegion = base.direct.drList[drIndex]
self.activeDisplayRegion = ShowBaseGlobal.direct.drList[drIndex]
else:
self.activeDisplayRegion = None
# Make sure info is current
@ -758,13 +758,13 @@ class DirectSessionPanel(AppShell):
dr = self.activeDisplayRegion
if dr:
dr.camLens.setNear(near)
cluster('base.camLens.setNear(%f)' % near, 0)
ShowBaseGlobal.direct.cluster('base.camLens.setNear(%f)' % near, 0)
def setFar(self, far):
dr = self.activeDisplayRegion
if dr:
dr.camLens.setFar(far)
cluster('base.camLens.setFar(%f)' % far, 0)
ShowBaseGlobal.direct.cluster('base.camLens.setFar(%f)' % far, 0)
def setHFov(self, hFov):
dr = self.activeDisplayRegion
@ -802,10 +802,10 @@ class DirectSessionPanel(AppShell):
# Lights #
def selectLightNamed(self, name):
# See if light exists
self.activeLight = base.direct.lights[name]
self.activeLight = ShowBaseGlobal.direct.lights[name]
# If not...create new one
if self.activeLight is None:
self.activeLight = base.direct.lights.create(name)
self.activeLight = ShowBaseGlobal.direct.lights.create(name)
# Do we have a valid light at this point?
if self.activeLight:
light = self.activeLight.getLight()
@ -820,28 +820,28 @@ class DirectSessionPanel(AppShell):
else:
# Restore valid data
listbox = self.lightMenu.component('scrolledlist')
listbox.setlist(base.direct.lights.getNameList())
if len(base.direct.lights) > 0:
self.lightMenu.selectitem(base.direct.lights.getNameList()[0])
listbox.setlist(ShowBaseGlobal.direct.lights.getNameList())
if len(ShowBaseGlobal.direct.lights) > 0:
self.lightMenu.selectitem(ShowBaseGlobal.direct.lights.getNameList()[0])
# Make sure info is current
self.updateLightInfo()
def addAmbient(self):
return base.direct.lights.create('ambient')
return ShowBaseGlobal.direct.lights.create('ambient')
def addDirectional(self):
return base.direct.lights.create('directional')
return ShowBaseGlobal.direct.lights.create('directional')
def addPoint(self):
return base.direct.lights.create('point')
return ShowBaseGlobal.direct.lights.create('point')
def addSpot(self):
return base.direct.lights.create('spot')
return ShowBaseGlobal.direct.lights.create('spot')
def addLight(self, light):
# Make list reflect current list of lights
listbox = self.lightMenu.component('scrolledlist')
listbox.setlist(base.direct.lights.getNameList())
listbox.setlist(ShowBaseGlobal.direct.lights.getNameList())
# Select the newly added light
self.lightMenu.selectitem(light.getName())
# And show corresponding page
@ -849,16 +849,16 @@ class DirectSessionPanel(AppShell):
def toggleLights(self):
if self.enableLights.get():
base.direct.lights.allOn()
ShowBaseGlobal.direct.lights.allOn()
else:
base.direct.lights.allOff()
ShowBaseGlobal.direct.lights.allOff()
def toggleActiveLight(self):
if self.activeLight:
if self.lightActive.get():
base.direct.lights.setOn(self.activeLight)
ShowBaseGlobal.direct.lights.setOn(self.activeLight)
else:
base.direct.lights.setOff(self.activeLight)
ShowBaseGlobal.direct.lights.setOff(self.activeLight)
def setLightColor(self, color):
if self.activeLight:
@ -893,22 +893,22 @@ class DirectSessionPanel(AppShell):
## GRID CONTROLS ##
def toggleGrid(self):
if self.enableGrid.get():
base.direct.grid.enable()
ShowBaseGlobal.direct.grid.enable()
else:
base.direct.grid.disable()
ShowBaseGlobal.direct.grid.disable()
def toggleXyzSnap(self):
base.direct.grid.setXyzSnap(self.xyzSnap.get())
ShowBaseGlobal.direct.grid.setXyzSnap(self.xyzSnap.get())
def toggleHprSnap(self):
base.direct.grid.setHprSnap(self.hprSnap.get())
ShowBaseGlobal.direct.grid.setHprSnap(self.hprSnap.get())
## DEVICE CONTROLS
def toggleJoybox(self):
if self.enableJoybox.get():
base.direct.joybox.enable()
ShowBaseGlobal.direct.joybox.enable()
else:
base.direct.joybox.disable()
ShowBaseGlobal.direct.joybox.disable()
## UPDATE INFO ##
def updateInfo(self, page = 'Environment'):
@ -920,7 +920,7 @@ class DirectSessionPanel(AppShell):
self.updateGridInfo()
def updateEnvironmentInfo(self):
bkgrdColor = base.getBackgroundColor() * 255.0
bkgrdColor = ShowBaseGlobal.base.getBackgroundColor() * 255.0
self.backgroundColor.set([bkgrdColor[0],
bkgrdColor[1],
bkgrdColor[2],
@ -936,6 +936,7 @@ class DirectSessionPanel(AppShell):
def updateLightInfo(self, page = None):
# Set main lighting button
render = ShowBaseGlobal.base.render
self.enableLights.set(
render.node().hasAttrib(LightAttrib.getClassType()))
@ -974,16 +975,16 @@ class DirectSessionPanel(AppShell):
self.pQuadraticAttenuation.set(att[2], 0)
def updateGridInfo(self):
self.enableGrid.set(base.direct.grid.isEnabled())
self.xyzSnap.set(base.direct.grid.getXyzSnap())
self.hprSnap.set(base.direct.grid.getHprSnap())
self.gridSpacing.set(base.direct.grid.getGridSpacing(), 0)
self.gridSize.set(base.direct.grid.getGridSize(), 0)
self.gridSnapAngle.set(base.direct.grid.getSnapAngle(), 0)
self.enableGrid.set(ShowBaseGlobal.direct.grid.isEnabled())
self.xyzSnap.set(ShowBaseGlobal.direct.grid.getXyzSnap())
self.hprSnap.set(ShowBaseGlobal.direct.grid.getHprSnap())
self.gridSpacing.set(ShowBaseGlobal.direct.grid.getGridSpacing(), 0)
self.gridSize.set(ShowBaseGlobal.direct.grid.getGridSize(), 0)
self.gridSnapAngle.set(ShowBaseGlobal.direct.grid.getSnapAngle(), 0)
# UNDO/REDO
def pushUndo(self, fResetRedo = 1):
base.direct.pushUndo([self['nodePath']])
ShowBaseGlobal.direct.pushUndo([self['nodePath']])
def undoHook(self, nodePathList = []):
pass
@ -997,7 +998,7 @@ class DirectSessionPanel(AppShell):
self.undoButton.configure(state = 'disabled')
def pushRedo(self):
base.direct.pushRedo([self['nodePath']])
ShowBaseGlobal.direct.pushRedo([self['nodePath']])
def redoHook(self, nodePathList = []):
pass

View File

@ -31,6 +31,8 @@ def inspect(anObject):
### private
_InspectorMap: "dict[str, str]"
def inspectorFor(anObject):
typeName = type(anObject).__name__.capitalize() + 'Type'
@ -396,7 +398,8 @@ class InspectorWindow:
self.listWidget.component('listbox').focus_set()
def showHelp(self):
help = tk.Toplevel(base.tkRoot)
from direct.showbase import ShowBaseGlobal
help = tk.Toplevel(ShowBaseGlobal.base.tkRoot)
help.title("Inspector Help")
frame = tk.Frame(help)
frame.pack()

View File

@ -9,6 +9,7 @@ from direct.tkwidgets import Dial
from direct.tkwidgets import Floater
from direct.directtools.DirectGlobals import ZERO_VEC, UNIT_VEC
from direct.showbase.MessengerGlobal import messenger
from direct.showbase import ShowBaseGlobal
from direct.task.TaskManagerGlobal import taskMgr
import Pmw
import tkinter as tk
@ -28,7 +29,7 @@ class Placer(AppShell):
INITOPT = Pmw.INITOPT
optiondefs = (
('title', self.appname, None),
('nodePath', base.direct.camera, None),
('nodePath', ShowBaseGlobal.direct.camera, None),
)
self.defineoptions(kw, optiondefs)
@ -39,23 +40,23 @@ class Placer(AppShell):
def appInit(self):
# Initialize state
self.tempCS = base.direct.group.attachNewNode('placerTempCS')
self.orbitFromCS = base.direct.group.attachNewNode(
self.tempCS = ShowBaseGlobal.direct.group.attachNewNode('placerTempCS')
self.orbitFromCS = ShowBaseGlobal.direct.group.attachNewNode(
'placerOrbitFromCS')
self.orbitToCS = base.direct.group.attachNewNode('placerOrbitToCS')
self.orbitToCS = ShowBaseGlobal.direct.group.attachNewNode('placerOrbitToCS')
self.refCS = self.tempCS
# Dictionary keeping track of all node paths manipulated so far
self.nodePathDict = {}
self.nodePathDict['camera'] = base.direct.camera
self.nodePathDict['widget'] = base.direct.widget
self.nodePathDict['camera'] = ShowBaseGlobal.direct.camera
self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
self.nodePathNames = ['camera', 'widget', 'selected']
self.refNodePathDict = {}
self.refNodePathDict['parent'] = self['nodePath'].getParent()
self.refNodePathDict['render'] = render
self.refNodePathDict['camera'] = base.direct.camera
self.refNodePathDict['widget'] = base.direct.widget
self.refNodePathDict['camera'] = ShowBaseGlobal.direct.camera
self.refNodePathDict['widget'] = ShowBaseGlobal.direct.widget
self.refNodePathNames = ['parent', 'self', 'render',
'camera', 'widget', 'selected']
@ -103,12 +104,12 @@ class Placer(AppShell):
'Placer', 'command',
'Toggle widget visability',
label = 'Toggle Widget Vis',
command = base.direct.toggleWidgetVis)
command = ShowBaseGlobal.direct.toggleWidgetVis)
self.menuBar.addmenuitem(
'Placer', 'command',
'Toggle widget manipulation mode',
label = 'Toggle Widget Mode',
command = base.direct.manipulationControl.toggleObjectHandlesMode)
command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)
# Get a handle to the menu frame
menuFrame = self.menuFrame
@ -145,8 +146,8 @@ class Placer(AppShell):
self.bind(self.refNodePathMenu, 'Select relative node path')
self.undoButton = tk.Button(menuFrame, text = 'Undo',
command = base.direct.undo)
if base.direct.undoList:
command = ShowBaseGlobal.direct.undo)
if ShowBaseGlobal.direct.undoList:
self.undoButton['state'] = 'normal'
else:
self.undoButton['state'] = 'disabled'
@ -154,8 +155,8 @@ class Placer(AppShell):
self.bind(self.undoButton, 'Undo last operation')
self.redoButton = tk.Button(menuFrame, text = 'Redo',
command = base.direct.redo)
if base.direct.redoList:
command = ShowBaseGlobal.direct.redo)
if ShowBaseGlobal.direct.redoList:
self.redoButton['state'] = 'normal'
else:
self.redoButton['state'] = 'disabled'
@ -394,7 +395,7 @@ class Placer(AppShell):
# Add Combo box entry for the initial node path
self.addNodePath(nodePath)
elif name == 'selected':
nodePath = base.direct.selected.last
nodePath = ShowBaseGlobal.direct.selected.last
# Add Combo box entry for this selected object
self.addNodePath(nodePath)
else:
@ -417,7 +418,7 @@ class Placer(AppShell):
else:
if name == 'widget':
# Record relationship between selected nodes and widget
base.direct.selected.getWrtAll()
ShowBaseGlobal.direct.selected.getWrtAll()
# Update active node path
self.setActiveNodePath(nodePath)
@ -449,7 +450,7 @@ class Placer(AppShell):
if name == 'self':
nodePath = self.tempCS
elif name == 'selected':
nodePath = base.direct.selected.last
nodePath = ShowBaseGlobal.direct.selected.last
# Add Combo box entry for this selected object
self.addRefNodePath(nodePath)
elif name == 'parent':
@ -560,13 +561,13 @@ class Placer(AppShell):
elif self.movementMode == 'Orbit:':
self.xformOrbit(value, axis)
if self.nodePathMenu.get() == 'widget':
if base.direct.manipulationControl.fSetCoa:
if ShowBaseGlobal.direct.manipulationControl.fSetCoa:
# Update coa based on current widget position
base.direct.selected.last.mCoa2Dnp.assign(
base.direct.widget.getMat(base.direct.selected.last))
ShowBaseGlobal.direct.selected.last.mCoa2Dnp.assign(
ShowBaseGlobal.direct.widget.getMat(ShowBaseGlobal.direct.selected.last))
else:
# Move the objects with the widget
base.direct.selected.moveWrtWidgetAll()
ShowBaseGlobal.direct.selected.moveWrtWidgetAll()
def xformStart(self, data):
# Record undo point
@ -575,7 +576,7 @@ class Placer(AppShell):
if self.nodePathMenu.get() == 'widget':
taskMgr.remove('followSelectedNodePath')
# Record relationship between selected nodes and widget
base.direct.selected.getWrtAll()
ShowBaseGlobal.direct.selected.getWrtAll()
# Record initial state
self.deltaHpr = self['nodePath'].getHpr(self.refCS)
# Update placer to reflect new state
@ -590,7 +591,7 @@ class Placer(AppShell):
# If moving widget restart follow task
if self.nodePathMenu.get() == 'widget':
# Restart followSelectedNodePath task
base.direct.manipulationControl.spawnFollowSelectedNodePathTask()
ShowBaseGlobal.direct.manipulationControl.spawnFollowSelectedNodePathTask()
def xformRelative(self, value, axis):
nodePath = self['nodePath']
@ -729,7 +730,7 @@ class Placer(AppShell):
self.xformStop(None)
def pushUndo(self, fResetRedo = 1):
base.direct.pushUndo([self['nodePath']])
ShowBaseGlobal.direct.pushUndo([self['nodePath']])
def undoHook(self, nodePathList = []):
# Reflect new changes
@ -744,7 +745,7 @@ class Placer(AppShell):
self.undoButton.configure(state = 'disabled')
def pushRedo(self):
base.direct.pushRedo([self['nodePath']])
ShowBaseGlobal.direct.pushRedo([self['nodePath']])
def redoHook(self, nodePathList = []):
# Reflect new changes

View File

@ -79,13 +79,12 @@ class WxAppShell(wx.Frame):
self.onDestroy(event)
# to close Panda
try:
base
except NameError:
from direct.showbase import ShowBaseGlobal
if hasattr(ShowBaseGlobal, 'base'):
ShowBaseGlobal.base.userExit()
else:
sys.exit()
base.userExit()
### USER METHODS ###
# To be overridden
def appInit(self):

View File

@ -9,6 +9,8 @@ from direct.task.TaskManagerGlobal import taskMgr
from .WxAppShell import WxAppShell
from .ViewPort import Viewport, ViewportManager
from typing import Optional
ID_FOUR_VIEW = 401
ID_TOP_VIEW = 402
ID_FRONT_VIEW = 403
@ -25,7 +27,7 @@ class WxPandaShell(WxAppShell):
copyright = ('Copyright 2010 Disney Online Studios.' +
'\nAll Rights Reserved.')
MENU_TEXTS = {
MENU_TEXTS: dict[int, tuple[str, Optional[str]]] = {
ID_FOUR_VIEW: ("Four Views", None),
ID_TOP_VIEW: ("Top View", None),
ID_FRONT_VIEW: ("Front View", None),
@ -114,6 +116,7 @@ class WxPandaShell(WxAppShell):
self.wxStep()
ViewportManager.initializeAll()
# Position the camera
base = ShowBaseGlobal.base
if base.trackball is not None:
base.trackball.node().setPos(0, 30, 0)
base.trackball.node().setHpr(0, 15, 0)
@ -125,33 +128,34 @@ class WxPandaShell(WxAppShell):
# initializing direct
if self.fStartDirect:
base.startDirect(fWantTk = 0, fWantWx = 0)
direct = ShowBaseGlobal.direct
base.direct.disableMouseEvents()
newMouseEvents = ["_le_per_%s"%x for x in base.direct.mouseEvents] +\
["_le_fro_%s"%x for x in base.direct.mouseEvents] +\
["_le_lef_%s"%x for x in base.direct.mouseEvents] +\
["_le_top_%s"%x for x in base.direct.mouseEvents]
base.direct.mouseEvents = newMouseEvents
base.direct.enableMouseEvents()
direct.disableMouseEvents()
newMouseEvents = ["_le_per_%s"%x for x in direct.mouseEvents] +\
["_le_fro_%s"%x for x in direct.mouseEvents] +\
["_le_lef_%s"%x for x in direct.mouseEvents] +\
["_le_top_%s"%x for x in direct.mouseEvents]
direct.mouseEvents = newMouseEvents
direct.enableMouseEvents()
base.direct.disableKeyEvents()
keyEvents = ["_le_per_%s"%x for x in base.direct.keyEvents] +\
["_le_fro_%s"%x for x in base.direct.keyEvents] +\
["_le_lef_%s"%x for x in base.direct.keyEvents] +\
["_le_top_%s"%x for x in base.direct.keyEvents]
base.direct.keyEvents = keyEvents
base.direct.enableKeyEvents()
direct.disableKeyEvents()
keyEvents = ["_le_per_%s"%x for x in direct.keyEvents] +\
["_le_fro_%s"%x for x in direct.keyEvents] +\
["_le_lef_%s"%x for x in direct.keyEvents] +\
["_le_top_%s"%x for x in direct.keyEvents]
direct.keyEvents = keyEvents
direct.enableKeyEvents()
base.direct.disableModifierEvents()
modifierEvents = ["_le_per_%s"%x for x in base.direct.modifierEvents] +\
["_le_fro_%s"%x for x in base.direct.modifierEvents] +\
["_le_lef_%s"%x for x in base.direct.modifierEvents] +\
["_le_top_%s"%x for x in base.direct.modifierEvents]
base.direct.modifierEvents = modifierEvents
base.direct.enableModifierEvents()
direct.disableModifierEvents()
modifierEvents = ["_le_per_%s"%x for x in direct.modifierEvents] +\
["_le_fro_%s"%x for x in direct.modifierEvents] +\
["_le_lef_%s"%x for x in direct.modifierEvents] +\
["_le_top_%s"%x for x in direct.modifierEvents]
direct.modifierEvents = modifierEvents
direct.enableModifierEvents()
base.direct.cameraControl.lockRoll = True
base.direct.setFScaleWidgetByCam(1)
direct.cameraControl.lockRoll = True
direct.setFScaleWidgetByCam(1)
unpickables = [
"z-guide",
@ -172,31 +176,31 @@ class WxPandaShell(WxAppShell):
"Sphere",]
for unpickable in unpickables:
base.direct.addUnpickable(unpickable)
direct.addUnpickable(unpickable)
base.direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
base.direct.manipulationControl.fAllowMarquee = 1
base.direct.manipulationControl.supportMultiView()
base.direct.cameraControl.useMayaCamControls = 1
base.direct.cameraControl.perspCollPlane = self.perspView.collPlane
base.direct.cameraControl.perspCollPlane2 = self.perspView.collPlane2
direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
direct.manipulationControl.fAllowMarquee = 1
direct.manipulationControl.supportMultiView()
direct.cameraControl.useMayaCamControls = 1
direct.cameraControl.perspCollPlane = self.perspView.collPlane
direct.cameraControl.perspCollPlane2 = self.perspView.collPlane2
for widget in base.direct.manipulationControl.widgetList:
for widget in direct.manipulationControl.widgetList:
widget.setBin('gui-popup', 0)
widget.setDepthTest(0)
# [gjeon] to intercept messages here
base.direct.ignore('DIRECT-delete')
base.direct.ignore('DIRECT-select')
base.direct.ignore('DIRECT-preDeselectAll')
base.direct.ignore('DIRECT-toggleWidgetVis')
base.direct.fIgnoreDirectOnlyKeyMap = 1
direct.ignore('DIRECT-delete')
direct.ignore('DIRECT-select')
direct.ignore('DIRECT-preDeselectAll')
direct.ignore('DIRECT-toggleWidgetVis')
direct.fIgnoreDirectOnlyKeyMap = 1
# [gjeon] do not use the old way of finding current DR
base.direct.drList.tryToGetCurrentDr = False
direct.drList.tryToGetCurrentDr = False
else:
base.direct=None
base.direct = None
#base.closeWindow(base.win)
base.win = base.winList[3]

View File

@ -16,7 +16,7 @@
* size.
*/
constexpr DeletedBufferChain::
DeletedBufferChain(size_t buffer_size) : _buffer_size(buffer_size) {
DeletedBufferChain(size_t buffer_size) noexcept : _buffer_size(buffer_size) {
}
/**
@ -77,22 +77,6 @@ operator < (const DeletedBufferChain &other) const {
return _buffer_size < other._buffer_size;
}
/**
* Returns a deleted chain of the given size.
*/
INLINE DeletedBufferChain *DeletedBufferChain::
get_deleted_chain(size_t buffer_size) {
// We must allocate at least this much space for bookkeeping reasons.
buffer_size = (std::max)(buffer_size, sizeof(ObjectNode));
size_t index = ((buffer_size + sizeof(void *) - 1) / sizeof(void *)) - 1;
if (index < num_small_deleted_chains) {
return &_small_deleted_chains[index];
} else {
return get_large_deleted_chain((index + 1) * sizeof(void *));
}
}
/**
* Casts an ObjectNode* to a void* buffer.
*/

View File

@ -16,32 +16,10 @@
#include <set>
DeletedBufferChain DeletedBufferChain::_small_deleted_chains[DeletedBufferChain::num_small_deleted_chains] = {
DeletedBufferChain(sizeof(void *)),
DeletedBufferChain(sizeof(void *) * 2),
DeletedBufferChain(sizeof(void *) * 3),
DeletedBufferChain(sizeof(void *) * 4),
DeletedBufferChain(sizeof(void *) * 5),
DeletedBufferChain(sizeof(void *) * 6),
DeletedBufferChain(sizeof(void *) * 7),
DeletedBufferChain(sizeof(void *) * 8),
DeletedBufferChain(sizeof(void *) * 9),
DeletedBufferChain(sizeof(void *) * 10),
DeletedBufferChain(sizeof(void *) * 11),
DeletedBufferChain(sizeof(void *) * 12),
DeletedBufferChain(sizeof(void *) * 13),
DeletedBufferChain(sizeof(void *) * 14),
DeletedBufferChain(sizeof(void *) * 15),
DeletedBufferChain(sizeof(void *) * 16),
DeletedBufferChain(sizeof(void *) * 17),
DeletedBufferChain(sizeof(void *) * 18),
DeletedBufferChain(sizeof(void *) * 19),
DeletedBufferChain(sizeof(void *) * 20),
DeletedBufferChain(sizeof(void *) * 21),
DeletedBufferChain(sizeof(void *) * 22),
DeletedBufferChain(sizeof(void *) * 23),
DeletedBufferChain(sizeof(void *) * 24),
};
// This array stores the deleted chains for smaller sizes, starting with
// sizeof(void *) and increasing in multiples thereof.
static const size_t num_small_deleted_chains = 24;
static DeletedBufferChain small_deleted_chains[num_small_deleted_chains] = {};
/**
* Allocates the memory for a new buffer of the indicated size (which must be
@ -49,6 +27,8 @@ DeletedBufferChain DeletedBufferChain::_small_deleted_chains[DeletedBufferChain:
*/
void *DeletedBufferChain::
allocate(size_t size, TypeHandle type_handle) {
assert(_buffer_size > 0);
#ifdef USE_DELETED_CHAIN
// TAU_PROFILE("void *DeletedBufferChain::allocate(size_t, TypeHandle)", "
// ", TAU_USER);
@ -161,7 +141,18 @@ deallocate(void *ptr, TypeHandle type_handle) {
* Returns a new DeletedBufferChain.
*/
DeletedBufferChain *DeletedBufferChain::
get_large_deleted_chain(size_t buffer_size) {
get_deleted_chain(size_t buffer_size) {
// Common, smaller sized chains avoid the expensive locking and set
// manipulation code further down.
size_t index = ((buffer_size + sizeof(void *) - 1) / sizeof(void *));
buffer_size = index * sizeof(void *);
index--;
if (index < num_small_deleted_chains) {
DeletedBufferChain *chain = &small_deleted_chains[index];
chain->_buffer_size = buffer_size;
return chain;
}
static MutexImpl lock;
lock.lock();
static std::set<DeletedBufferChain> deleted_chains;

View File

@ -57,10 +57,9 @@ enum DeletedChainFlag : unsigned int {
* Use MemoryHook to get a new DeletedBufferChain of a particular size.
*/
class EXPCL_DTOOL_DTOOLBASE DeletedBufferChain {
protected:
constexpr explicit DeletedBufferChain(size_t buffer_size);
public:
constexpr DeletedBufferChain() = default;
constexpr explicit DeletedBufferChain(size_t buffer_size) noexcept;
INLINE DeletedBufferChain(DeletedBufferChain &&from) noexcept;
INLINE DeletedBufferChain(const DeletedBufferChain &copy);
@ -72,11 +71,9 @@ public:
INLINE bool operator < (const DeletedBufferChain &other) const;
static INLINE DeletedBufferChain *get_deleted_chain(size_t buffer_size);
static DeletedBufferChain *get_deleted_chain(size_t buffer_size);
private:
static DeletedBufferChain *get_large_deleted_chain(size_t buffer_size);
class ObjectNode {
public:
#ifdef USE_DELETEDCHAINFLAG
@ -99,7 +96,7 @@ private:
ObjectNode *_deleted_chain = nullptr;
MutexImpl _lock;
const size_t _buffer_size;
size_t _buffer_size = 0;
#ifndef USE_DELETEDCHAINFLAG
// Without DELETEDCHAINFLAG, we don't even store the _flag member at all.
@ -110,11 +107,6 @@ private:
static const size_t flag_reserved_bytes = sizeof(AtomicAdjust::Integer);
#endif // USE_DELETEDCHAINFLAG
// This array stores the deleted chains for smaller sizes, starting with
// sizeof(void *) and increasing in multiples thereof.
static const size_t num_small_deleted_chains = 24;
static DeletedBufferChain _small_deleted_chains[num_small_deleted_chains];
friend class MemoryHook;
};

View File

@ -21,7 +21,7 @@ static PyMemberDef standard_type_members[] = {
{(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"},
// {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature),
// READONLY, (char *)"A type check signature"},
{(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
{(char *)"this_metatype", T_OBJECT_EX, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
{nullptr} /* Sentinel */
};

View File

@ -241,6 +241,18 @@ INLINE PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObje
}
#endif
/* Python 3.12 */
#if PY_VERSION_HEX < 0x030C0000
# define PyLong_IsNonNegative(value) (Py_SIZE((value)) >= 0)
#else
INLINE bool PyLong_IsNonNegative(PyObject *value) {
int overflow = 0;
long longval = PyLong_AsLongAndOverflow(value, &overflow);
return overflow == 1 || longval >= 0;
}
#endif
/* Other Python implementations */
#endif // HAVE_PYTHON

View File

@ -12,9 +12,7 @@ import os
import sys
from optparse import OptionParser
from makepandacore import *
# DO NOT CHANGE TO sysconfig - see GitHub issue #1230
from distutils.sysconfig import get_python_lib
from locations import get_python_lib
MIME_INFO = (

32
makepanda/locations.py Normal file
View File

@ -0,0 +1,32 @@
__all__ = [
'get_python_inc',
'get_config_var',
'get_python_version',
'PREFIX',
'get_python_lib',
'get_config_vars',
]
import sys
if sys.version_info < (3, 12):
from distutils.sysconfig import *
else:
from sysconfig import *
PREFIX = get_config_var('prefix')
def get_python_inc(plat_specific=False):
path_name = 'platinclude' if plat_specific else 'include'
return get_path(path_name)
def get_python_lib(plat_specific=False, standard_lib=False):
if standard_lib:
path_name = 'stdlib'
if plat_specific:
path_name = 'plat' + path_name
elif plat_specific:
path_name = 'platlib'
else:
path_name = 'purelib'
return get_path(path_name)

View File

@ -942,8 +942,7 @@ def MakeInstallerAndroid(version, **kwargs):
shutil.copy(os.path.join(source_dir, base), target)
# Copy the Python standard library to the .apk as well.
# DO NOT CHANGE TO sysconfig - see #1230
from distutils.sysconfig import get_python_lib
from locations import get_python_lib
stdlib_source = get_python_lib(False, True)
stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
copy_python_tree(stdlib_source, stdlib_target)

View File

@ -30,12 +30,10 @@ except:
print("Please install the development package of Python and try again.")
exit(1)
if sys.version_info >= (3, 10):
from sysconfig import get_platform
else:
from distutils.util import get_platform
from makepandacore import *
from sysconfig import get_platform
try:
import zlib
except:
@ -368,6 +366,12 @@ if GetHost() == "darwin":
if VERSION is None:
# Take the value from the setup.cfg file.
VERSION = GetMetadataValue('version')
match = re.match(r'^\d+\.\d+(\.\d+)+', VERSION)
if not match:
exit("Invalid version %s in setup.cfg, three digits are required" % (VERSION))
if WHLVERSION is None:
WHLVERSION = VERSION
VERSION = match.group()
if WHLVERSION is None:
WHLVERSION = VERSION
@ -2689,11 +2693,18 @@ template class CheckPandaVersion<void>;
def CreatePandaVersionFiles():
version1=int(VERSION.split(".")[0])
version2=int(VERSION.split(".")[1])
version3=int(VERSION.split(".")[2])
nversion=version1*1000000+version2*1000+version3
if (DISTRIBUTOR != "cmu"):
parts = VERSION.split(".", 2)
version1 = int(parts[0])
version2 = int(parts[1])
version3 = 0
if len(parts) > 2:
for c in parts[2]:
if c.isdigit():
version3 = version3 * 10 + ord(c) - 48
else:
break
nversion = version1 * 1000000 + version2 * 1000 + version3
if DISTRIBUTOR != "cmu":
# Subtract 1 if we are not an official version.
nversion -= 1

View File

@ -6,7 +6,6 @@
########################################################################
import configparser
from distutils import sysconfig # DO NOT CHANGE to sysconfig - see #1230
import fnmatch
import getpass
import glob
@ -21,6 +20,7 @@ import sys
import threading
import _thread as thread
import time
import locations
SUFFIX_INC = [".cxx",".cpp",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"]
SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"]
@ -2195,7 +2195,7 @@ def SdkLocatePython(prefer_thirdparty_python=False):
# On macOS, search for the Python framework directory matching the
# version number of our current Python version.
sysroot = SDK.get("MACOSX", "")
version = sysconfig.get_python_version()
version = locations.get_python_version()
py_fwx = "{0}/System/Library/Frameworks/Python.framework/Versions/{1}".format(sysroot, version)
@ -2227,19 +2227,19 @@ def SdkLocatePython(prefer_thirdparty_python=False):
LibDirectory("PYTHON", py_fwx + "/lib")
#elif GetTarget() == 'windows':
# SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc())
# SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version()
# SDK["PYTHON"] = os.path.dirname(locations.get_python_inc())
# SDK["PYTHONVERSION"] = "python" + locations.get_python_version()
# SDK["PYTHONEXEC"] = sys.executable
else:
SDK["PYTHON"] = sysconfig.get_python_inc()
SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags
SDK["PYTHON"] = locations.get_python_inc()
SDK["PYTHONVERSION"] = "python" + locations.get_python_version() + abiflags
SDK["PYTHONEXEC"] = os.path.realpath(sys.executable)
if CrossCompiling():
# We need a version of Python we can run.
SDK["PYTHONEXEC"] = sys.executable
host_version = "python" + sysconfig.get_python_version() + abiflags
host_version = "python" + locations.get_python_version() + abiflags
if SDK["PYTHONVERSION"] != host_version:
exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"]))
@ -3393,7 +3393,7 @@ def GetExtensionSuffix():
def GetPythonABI():
if not CrossCompiling():
soabi = sysconfig.get_config_var('SOABI')
soabi = locations.get_config_var('SOABI')
if soabi:
return soabi
@ -3514,8 +3514,8 @@ def GetCurrentPythonVersionInfo():
"soabi": GetPythonABI(),
"ext_suffix": GetExtensionSuffix(),
"executable": sys.executable,
"purelib": sysconfig.get_python_lib(False),
"platlib": sysconfig.get_python_lib(True),
"purelib": locations.get_python_lib(False),
"platlib": locations.get_python_lib(True),
}

View File

@ -11,10 +11,11 @@ import tempfile
import subprocess
import time
import struct
from sysconfig import get_platform, get_config_var
from optparse import OptionParser
from base64 import urlsafe_b64encode
from makepandacore import LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue, CrossCompiling, GetThirdpartyDir, SDK, GetStrip
from locations import get_config_var
from sysconfig import get_platform
def get_abi_tag():

View File

@ -40,12 +40,36 @@ CocoaGraphicsPipe(CGDirectDisplayID display) : _display(display) {
[thread start];
[thread autorelease];
// If the application is dpi-aware, iterate over all the screens to find the
// one with our display ID and get the backing scale factor to configure the
// detected display zoom. Otherwise the detected display zoom keeps its
// default value of 1.0
if (dpi_aware) {
NSScreen *screen;
NSEnumerator *e = [[NSScreen screens] objectEnumerator];
while (screen = (NSScreen *) [e nextObject]) {
NSNumber *num = [[screen deviceDescription] objectForKey: @"NSScreenNumber"];
if (_display == (CGDirectDisplayID) [num longValue]) {
set_detected_display_zoom([screen backingScaleFactor]);
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug()
<< "Display zoom is " << [screen backingScaleFactor] << "\n";
}
break;
}
}
}
// We used to also obtain the corresponding NSScreen here, but this causes
// the application icon to start bouncing, which may be undesirable for
// apps that will never open a window.
_display_width = CGDisplayPixelsWide(_display);
_display_height = CGDisplayPixelsHigh(_display);
// Although the name of these functions mention pixels, they actually return
// display points, we use the detected display zoom to transform the values
// into pixels.
_display_width = CGDisplayPixelsWide(_display) * _detected_display_zoom;
_display_height = CGDisplayPixelsHigh(_display) * _detected_display_zoom;
load_display_information();
if (cocoadisplay_cat.is_debug()) {
@ -64,19 +88,36 @@ load_display_information() {
// _display_information->_device_id = CGDisplaySerialNumber(_display);
// Display modes
CFDictionaryRef options = NULL;
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
options = CFDictionaryCreate(NULL,
(const void **)dictkeys,
(const void **)dictvalues,
1,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
size_t num_modes = 0;
CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, NULL);
CFArrayRef modes = CGDisplayCopyAllDisplayModes(_display, options);
if (modes != NULL) {
num_modes = CFArrayGetCount(modes);
_display_information->_total_display_modes = num_modes;
_display_information->_display_mode_array = new DisplayMode[num_modes];
}
if (options != NULL) {
CFRelease(options);
}
for (size_t i = 0; i < num_modes; ++i) {
CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
_display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
_display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
if (dpi_aware) {
_display_information->_display_mode_array[i].width = CGDisplayModeGetPixelWidth(mode);
_display_information->_display_mode_array[i].height = CGDisplayModeGetPixelHeight(mode);
} else {
_display_information->_display_mode_array[i].width = CGDisplayModeGetWidth(mode);
_display_information->_display_mode_array[i].height = CGDisplayModeGetHeight(mode);
}
_display_information->_display_mode_array[i].refresh_rate = CGDisplayModeGetRefreshRate(mode);
_display_information->_display_mode_array[i].fullscreen_only = false;

View File

@ -63,6 +63,7 @@ public:
void handle_minimize_event(bool minimized);
void handle_maximize_event(bool maximized);
void handle_foreground_event(bool foreground);
void handle_backing_change_event();
bool handle_close_request();
void handle_close_event();
void handle_key_event(NSEvent *event);

View File

@ -132,13 +132,20 @@ move_pointer(int device, int x, int y) {
return true;
}
// Mouse position is expressed in screen points and not pixels, but in Panda3D
// we are using pixel coordinates.
// Instead of using convertPointFromBacking and have complex logic to cope with
// the change of coordinate system, we cheat and directly use the contents scale
// of the view layer to convert pixel coordinates into screen point coordinates.
CGFloat contents_scale = _view.layer.contentsScale;
if (device == 0) {
CGPoint point;
if (_properties.get_fullscreen()) {
point = CGPointMake(x, y);
point = CGPointMake(float(x) / contents_scale,
float(y) / contents_scale);
} else {
point = CGPointMake(x + _properties.get_x_origin(),
y + _properties.get_y_origin());
point = CGPointMake((float(x) + _properties.get_x_origin()) / contents_scale,
(float(y) + _properties.get_y_origin()) / contents_scale);
}
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
@ -302,35 +309,89 @@ open_window() {
}
}
// Configure the origin and the size of the window.
// On macOS, screen coordinates are expressed in "points" which are independant
// of the pixel density of the screen. Panda3D however, expresses the size and
// origin of a window in pixels.
// So, when opening a window, we need to convert the origin and size from pixel
// units into point units. However, this conversion depends on the pixel density
// of the screen, the backing scale factor.
// As the origin and size of a window depends on the size of the screen of the
// parent view, their size must be converted first from points to pixels.
// If a window (or a view) is not configured to support high-dpi screen, macOS
// will upscale the window (or view) when displayed on a high-dpi screen.
// Therefore its backing scale factor will always be 1.0
// In a Panda3D application, windows are always configured as high resolution
// capable, but the view is only configured as high resolution if the dpi-aware
// configuration flag is set.
// If the app is not dpi-aware, we must upscale its size and origin from points
// into pixels as the window is always high resolution capable.
// Center the window if coordinates were set to -1 or -2 TODO: perhaps in
// future, in the case of -1, it should use the origin used in a previous
// run of Panda
// Size of the requested window
NSSize size = NSMakeSize(_properties.get_x_size(), _properties.get_y_size());
NSRect container;
CGFloat backing_scale_factor = screen.backingScaleFactor;
if (parent_nsview != NULL) {
container = [parent_nsview bounds];
// Convert parent view bounds into pixel units.
container = [parent_nsview convertRectToBacking:[parent_nsview bounds]];
// If the app is not dpi-aware, we must convert its size from points into
// pixels as the window is always high resolution capable
if (!dpi_aware) {
size = [parent_nsview convertSizeToBacking:size];
}
} else {
container = [screen frame];
container.origin = NSMakePoint(0, 0);
container = [screen convertRectToBacking:container];
if (!dpi_aware) {
// Weirdly NSScreen does not respond to convertSizeToBacking, so we have to
// create a dummy rect just for converting the window size.
NSRect rect;
rect.origin = NSMakePoint(0, 0);
rect.size = size;
rect = [screen convertRectToBacking:rect];
size = rect.size;
}
}
int x = _properties.get_x_origin();
int y = _properties.get_y_origin();
// As we are converting a single value and the view is not created yet, it's
// easier to simply use the backing scale factor and don't bother with
// coordinate system transformations.
if (x < 0) {
x = floor(container.size.width / 2 - _properties.get_x_size() / 2);
x = floor(container.size.width / 2 - size.width / 2);
} else if (!dpi_aware) {
x *= backing_scale_factor;
}
if (y < 0) {
y = floor(container.size.height / 2 - _properties.get_y_size() / 2);
y = floor(container.size.height / 2 - size.height / 2);
} else if (!dpi_aware) {
y *= backing_scale_factor;
}
if (dpi_aware) {
_properties.set_origin(x, y);
} else {
_properties.set_origin(x / backing_scale_factor, y / backing_scale_factor);
}
_properties.set_origin(x, y);
if (_parent_window_handle == (WindowHandle *)NULL) {
// Content rectangle
NSRect rect;
if (_properties.get_fullscreen()) {
rect = container;
rect = [screen convertRectFromBacking:container];
} else {
rect = NSMakeRect(x, container.size.height - _properties.get_y_size() - y,
_properties.get_x_size(), _properties.get_y_size());
rect = NSMakeRect(x, container.size.height - size.height - y,
size.width, size.height);
if (parent_nsview != NULL) {
rect = [parent_nsview convertRectFromBacking:rect];
} else {
rect = [screen convertRectFromBacking:rect];
}
}
// Configure the window decorations
@ -388,8 +449,10 @@ open_window() {
_parent_window_handle->attach_child(_window_handle);
}
// Always disable application HiDPI support, Cocoa will do the eventual upscaling for us.
[_view setWantsBestResolutionOpenGLSurface:NO];
// Configure the view to be high resolution capable using the dpi-aware
// configuration flag. If dpi-aware is false, macOS will upscale the view
// for us.
[_view setWantsBestResolutionOpenGLSurface:dpi_aware];
if (_properties.has_icon_filename()) {
NSImage *image = load_image(_properties.get_icon_filename());
if (image != nil) {
@ -596,22 +659,6 @@ set_properties_now(WindowProperties &properties) {
}
if (switched) {
if (_window != nil) {
// For some reason, setting the style mask makes it give up its
// first-responder status. And for some reason, we need to first
// restore the window to normal level before we switch fullscreen,
// otherwise we may get a black bar if we're currently on Z_top.
if (_properties.get_z_order() != WindowProperties::Z_normal) {
[_window setLevel: NSNormalWindowLevel];
}
if ([_window respondsToSelector:@selector(setStyleMask:)]) {
[_window setStyleMask:NSBorderlessWindowMask];
}
[_window makeFirstResponder:_view];
[_window setLevel:CGShieldingWindowLevel()];
[_window makeKeyAndOrderFront:nil];
}
// We've already set the size property this way; clear it.
properties.clear_size();
_properties.set_size(width, height);
@ -684,6 +731,9 @@ set_properties_now(WindowProperties &properties) {
NSMiniaturizableWindowMask | NSResizableWindowMask ];
}
[_window makeFirstResponder:_view];
// Resize event fired by makeFirstResponder has an invalid backing scale factor
// The actual size must be reset afterward
handle_resize_event();
}
}
@ -705,6 +755,9 @@ set_properties_now(WindowProperties &properties) {
NSMiniaturizableWindowMask | NSResizableWindowMask ];
}
[_window makeFirstResponder:_view];
// Resize event fired by makeFirstResponder has an invalid backing scale factor
// The actual size must be reset afterward
handle_resize_event();
}
properties.clear_undecorated();
@ -715,10 +768,13 @@ set_properties_now(WindowProperties &properties) {
int height = properties.get_y_size();
if (!_properties.get_fullscreen()) {
// We use the view, not the window, to convert the frame size, expressed
// in pixels, into points as the "dpi awareness" is managed by the view.
NSSize size = [_view convertSizeFromBacking:NSMakeSize(width, height)];
if (_window != nil) {
[_window setContentSize:NSMakeSize(width, height)];
[_window setContentSize:size];
}
[_view setFrameSize:NSMakeSize(width, height)];
[_view setFrameSize:size];
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug()
@ -768,12 +824,14 @@ set_properties_now(WindowProperties &properties) {
// Get the frame for the screen
NSRect frame;
NSRect container;
// Note again that we are using the view to convert the frame and container
// size from points into pixels.
if (_window != nil) {
NSRect window_frame = [_window frame];
frame = [_window contentRectForFrameRect:window_frame];
frame = [_view convertRectToBacking:[_window contentRectForFrameRect:window_frame]];
NSScreen *screen = [_window screen];
nassertv(screen != nil);
container = [screen frame];
container = [_view convertRectToBacking:[screen frame]];
// Prevent the centering from overlapping the Dock
if (y < 0) {
@ -783,8 +841,8 @@ set_properties_now(WindowProperties &properties) {
}
}
} else {
frame = [_view frame];
container = [[_view superview] frame];
frame = [_view convertRectToBacking:[_view frame]];
container = [[_view superview] convertRectToBacking:[[_view superview] frame]];
}
if (x < 0) {
@ -795,22 +853,22 @@ set_properties_now(WindowProperties &properties) {
}
_properties.set_origin(x, y);
if (!_properties.get_fullscreen()) {
// Remember, Mac OS X coordinates are flipped in the vertical axis.
frame.origin.x = x;
frame.origin.y = container.size.height - y - frame.size.height;
frame.origin.x = x;
// Y coordinate in backing store is not flipped, but origin is still at the bottom left
frame.origin.y = y - container.size.height;
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug()
<< "Setting window content origin to "
<< frame.origin.x << ", " << frame.origin.y << "\n";
}
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug()
<< "Setting window content origin to "
<< frame.origin.x << ", " << frame.origin.y << "\n";
}
if (_window != nil) {
[_window setFrame:[_window frameRectForContentRect:frame] display:NO];
} else {
[_view setFrame:frame];
}
if (_window != nil) {
frame = [_view convertRectFromBacking:frame];
[_window setFrame:[_window frameRectForContentRect:frame] display:NO];
} else {
frame = [_view convertRectFromBacking:frame];
[_view setFrame:frame];
}
properties.clear_origin();
}
@ -957,24 +1015,20 @@ unbind_context() {
CFMutableArrayRef CocoaGraphicsWindow::
find_display_modes(int width, int height) {
CFDictionaryRef options = NULL;
// On macOS 10.15+ (Catalina), we want to select the display mode with the
// samescaling factor as the current view to avoid cropping or scaling issues.
// This is a workaround until HiDPI display or scaling factor is properly
// handled. CGDisplayCopyAllDisplayModes() does not return upscaled display
// mode unless explicitly asked with kCGDisplayShowDuplicateLowResolutionModes
// We want to select the display mode with the same scaling factor as the
// current view to avoid cropping or scaling issues.
// CGDisplayCopyAllDisplayModes() does not return upscaled display modes
// nor the current mode, unless explicitly asked with
// kCGDisplayShowDuplicateLowResolutionModes
// (which is undocumented...).
bool macos_10_15_or_higher = false;
if (@available(macOS 10.15, *)) {
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
options = CFDictionaryCreate(NULL,
(const void **)dictkeys,
(const void **)dictvalues,
1,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
macos_10_15_or_higher = true;
}
const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
options = CFDictionaryCreate(NULL,
(const void **)dictkeys,
(const void **)dictvalues,
1,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFMutableArrayRef valid_modes;
valid_modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
@ -985,29 +1039,39 @@ find_display_modes(int width, int height) {
size_t num_modes = CFArrayGetCount(modes);
CGDisplayModeRef mode;
// Get the current refresh rate and pixel encoding.
CFStringRef current_pixel_encoding;
double refresh_rate;
mode = CGDisplayCopyDisplayMode(_display);
// Calculate requested display size and pixel size
CGSize display_size;
CGSize pixel_size;
if (dpi_aware) {
pixel_size = NSMakeSize(width, height);
display_size = [_view convertSizeFromBacking:pixel_size];
} else {
display_size = NSMakeSize(width, height);
// Calculate the pixel width and height of the fullscreen mode we want using
// the current display mode dimensions and pixel dimensions.
size_t pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
size_t pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
pixel_size = NSMakeSize(pixel_width, pixel_height);
}
// First check if the current mode is adequate.
// This test not done for macOS 10.15 and above as the mode resolution is
// not enough to identify a mode.
if (!macos_10_15_or_higher &&
CGDisplayModeGetWidth(mode) == width &&
CGDisplayModeGetHeight(mode) == height) {
if (CGDisplayModeGetWidth(mode) == display_size.width &&
CGDisplayModeGetHeight(mode) == display_size.height &&
CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
CGDisplayModeGetPixelHeight(mode) == pixel_size.height) {
CFArrayAppendValue(valid_modes, mode);
CGDisplayModeRelease(mode);
return valid_modes;
}
// Get the current refresh rate and pixel encoding.
CFStringRef current_pixel_encoding;
double refresh_rate;
current_pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
refresh_rate = CGDisplayModeGetRefreshRate(mode);
// Calculate the pixel width and height of the fullscreen mode we want using
// the currentdisplay mode dimensions and pixel dimensions.
size_t expected_pixel_width = (size_t(width) * CGDisplayModeGetPixelWidth(mode)) / CGDisplayModeGetWidth(mode);
size_t expected_pixel_height = (size_t(height) * CGDisplayModeGetPixelHeight(mode)) / CGDisplayModeGetHeight(mode);
CGDisplayModeRelease(mode);
for (size_t i = 0; i < num_modes; ++i) {
@ -1015,17 +1079,15 @@ find_display_modes(int width, int height) {
CFStringRef pixel_encoding = CGDisplayModeCopyPixelEncoding(mode);
// As explained above, we want to select the fullscreen display mode using
// the same scaling factor, but only for MacOS 10.15+ To do this we check
// the mode width and height but also actual pixel widh and height.
if (CGDisplayModeGetWidth(mode) == width &&
CGDisplayModeGetHeight(mode) == height &&
// We select the fullscreen display mode using he same scaling factor
// To do this we check the mode width and height but also actual pixel widh
// and height.
if (CGDisplayModeGetWidth(mode) == display_size.width &&
CGDisplayModeGetHeight(mode) == display_size.height &&
(int)(CGDisplayModeGetRefreshRate(mode) + 0.5) == (int)(refresh_rate + 0.5) &&
(!macos_10_15_or_higher ||
(CGDisplayModeGetPixelWidth(mode) == expected_pixel_width &&
CGDisplayModeGetPixelHeight(mode) == expected_pixel_height)) &&
CGDisplayModeGetPixelWidth(mode) == pixel_size.width &&
CGDisplayModeGetPixelHeight(mode) == pixel_size.height &&
CFStringCompare(pixel_encoding, current_pixel_encoding, 0) == kCFCompareEqualTo) {
if (CGDisplayModeGetRefreshRate(mode) == refresh_rate) {
// Exact match for refresh rate, prioritize this.
CFArrayInsertValueAtIndex(valid_modes, 0, mode);
@ -1103,14 +1165,25 @@ do_switch_fullscreen(CGDisplayModeRef mode) {
NSRect frame = [[[_view window] screen] frame];
if (cocoadisplay_cat.is_debug()) {
NSString *str = NSStringFromRect(frame);
NSString *str = NSStringFromSize([_view convertSizeToBacking:frame.size]);
cocoadisplay_cat.debug()
<< "Switched to fullscreen, screen rect is now " << [str UTF8String] << "\n";
<< "Switched to fullscreen, screen size is now " << [str UTF8String] << "\n";
}
if (_window != nil) {
// For some reason, setting the style mask makes it give up its
// first-responder status.
if ([_window respondsToSelector:@selector(setStyleMask:)]) {
[_window setStyleMask:NSBorderlessWindowMask];
}
[_window makeFirstResponder:_view];
[_window setLevel:CGShieldingWindowLevel()];
[_window makeKeyAndOrderFront:nil];
// Window and view frame must be updated *after* the window reconfiguration
// or the size is not set properly !
[_window setFrame:frame display:YES];
[_view setFrame:NSMakeRect(0, 0, frame.size.width, frame.size.height)];
[_view setFrame:frame];
[_window update];
}
}
@ -1252,19 +1325,25 @@ load_cursor(const Filename &filename) {
*/
void CocoaGraphicsWindow::
handle_move_event() {
// Remember, Mac OS X uses flipped coordinates
NSRect frame;
NSRect container;
int x, y;
// Again, we are using the view to convert the frame and container size from
// points to pixels.
if (_window == nil) {
frame = [_view frame];
x = frame.origin.x;
y = [[_view superview] bounds].size.height - frame.origin.y - frame.size.height;
frame = [_view convertRectToBacking:[_view frame]];
container = [_view convertRectToBacking:[[_view superview] frame]];
} else {
frame = [_window contentRectForFrameRect:[_window frame]];
x = frame.origin.x;
y = [[_window screen] frame].size.height - frame.origin.y - frame.size.height;
frame = [_view convertRectToBacking:[_window contentRectForFrameRect:[_window frame]]];
NSScreen *screen = [_window screen];
nassertv(screen != nil);
container = [_view convertRectToBacking:[screen frame]];
}
// Y coordinate in backing store is not flipped, but origin is still at the bottom left
x = frame.origin.x;
y = container.size.height + frame.origin.y;
if (x != _properties.get_x_origin() ||
y != _properties.get_y_origin()) {
@ -1290,7 +1369,7 @@ handle_resize_event() {
[_view setFrameSize:contentRect.size];
}
NSRect frame = [_view convertRect:[_view bounds] toView:nil];
NSRect frame = [_view convertRectToBacking:[_view bounds]];
WindowProperties properties;
bool changed = false;
@ -1403,6 +1482,22 @@ handle_foreground_event(bool foreground) {
}
}
/**
* Called by the window delegate when the properties of backing store of the
* window have changed.
*/
void CocoaGraphicsWindow::
handle_backing_change_event() {
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug() << "Backing store properties have changed\n";
}
// Trigger a resize event to update the window size in case the backing scale
// factor did change.
handle_resize_event();
}
/**
* Called by the window delegate when the user requests to close the window.
* This may not always be called, which is why there is also a
@ -1693,6 +1788,13 @@ void CocoaGraphicsWindow::
handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
double nx, ny;
// Mouse position is received in screen points and not pixels, but in Panda3D
// we want to have the coordinates expressed in pixels.
// Instead of using convertPointFrom/toBackingStore and have complex logic to
// cope with the change of coordinate system, we cheat and directly use the
// contents scale of the view layer to convert screen point into pixels and
// vice-versa.
CGFloat contents_scale = _view.layer.contentsScale;
if (absolute) {
if (cocoadisplay_cat.is_spam()) {
if (in_window != _input->get_pointer().get_in_window()) {
@ -1704,14 +1806,14 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
}
}
nx = x;
ny = y;
nx = x * contents_scale;
ny = y * contents_scale;
} else {
// We received deltas, so add it to the current mouse position.
PointerData md = _input->get_pointer();
nx = md.get_x() + x;
ny = md.get_y() + y;
nx = md.get_x() + x * contents_scale;
ny = md.get_y() + y * contents_scale;
}
if (_properties.get_mouse_mode() == WindowProperties::M_confined
@ -1721,11 +1823,13 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
nx = std::max(0., std::min((double) get_x_size() - 1, nx));
ny = std::max(0., std::min((double) get_y_size() - 1, ny));
// Convert back mouse position to screen space using point units
if (_properties.get_fullscreen()) {
point = CGPointMake(nx, ny);
point = CGPointMake(nx / contents_scale,
ny / contents_scale);
} else {
point = CGPointMake(nx + _properties.get_x_origin(),
ny + _properties.get_y_origin());
point = CGPointMake((nx + _properties.get_x_origin()) / contents_scale,
(ny + _properties.get_y_origin()) / contents_scale);
}
if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {

View File

@ -29,6 +29,7 @@ class CocoaGraphicsWindow;
- (void)windowDidDeminiaturize:(NSNotification *)notification;
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (void)windowDidResignKey:(NSNotification *)notification;
- (void)windowDidChangeBackingProperties:(NSNotification *)notification;
- (BOOL)windowShouldClose:(id)sender;
- (void)windowWillClose:(id)sender;

View File

@ -51,6 +51,10 @@
_graphicsWindow->handle_foreground_event(false);
}
- (void) windowDidChangeBackingProperties:(NSNotification *)notification {
_graphicsWindow->handle_backing_change_event();
}
- (BOOL) windowShouldClose:(id)sender {
if (cocoadisplay_cat.is_debug()) {
cocoadisplay_cat.debug()

View File

@ -21,6 +21,7 @@
NotifyCategoryDecl(cocoadisplay, EXPCL_PANDA_COCOADISPLAY, EXPTP_PANDA_COCOADISPLAY);
extern ConfigVariableBool cocoa_invert_wheel_x;
extern ConfigVariableBool dpi_aware;
extern EXPCL_PANDA_COCOADISPLAY void init_libcocoadisplay();

View File

@ -32,6 +32,11 @@ ConfigVariableBool cocoa_invert_wheel_x
("cocoa-invert-wheel-x", false,
PRC_DESC("Set this to true to swap the wheel_left and wheel_right mouse "
"button events, to restore to the pre-1.10.12 behavior."));
ConfigVariableBool dpi_aware
("dpi-aware", false,
PRC_DESC("The default behavior on macOS is for Panda3D to use upscaling on"
"high DPI screen. Set this to true to let the application use the"
"actual pixel density of the screen."));
/**
* Initializes the library. This must be called at least once before any of

View File

@ -64,6 +64,11 @@
using std::string;
static const LMatrix4 shadow_bias_mat(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f);
//PStatCollector GraphicsStateGuardian::_vertex_buffer_switch_pcollector("Buffer switch:Vertex");
//PStatCollector GraphicsStateGuardian::_index_buffer_switch_pcollector("Buffer switch:Index");
//PStatCollector GraphicsStateGuardian::_shader_buffer_switch_pcollector("Buffer switch:Shader");
@ -1543,9 +1548,30 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
}
const NodePath &np = _target_shader->get_shader_input_nodepath(name->get_parent());
nassertv(!np.is_empty());
const PandaNode *node = np.node();
fetch_specified_member(np, name->get_basename(), into[0]);
// This is the only matrix member we support from NodePath inputs.
if (node != nullptr && node->is_of_type(LensNode::get_class_type()) &&
name->get_basename() == "shadowViewMatrix") {
const LensNode *lnode = (const LensNode *)node;
const Lens *lens = lnode->get_lens();
LMatrix4 t = _inv_cs_transform->get_mat() *
_scene_setup->get_camera_transform()->get_mat() *
np.get_net_transform()->get_inverse()->get_mat() *
LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
if (!lnode->is_of_type(PointLight::get_class_type())) {
t *= lens->get_projection_mat() * shadow_bias_mat;
}
*(LMatrix4f *)into = LCAST(float, t);
}
else {
display_cat.error()
<< "Shader input " << *name << " requests invalid attribute "
<< name->get_basename() << " from node " << np << "\n";
*(LMatrix4f *)into = LMatrix4f::ident_mat();
}
return;
}
case Shader::SMO_vec_constant_x_attrib: {
@ -1588,12 +1614,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
}
return;
}
case Shader::SMO_apiview_to_apiclip_light_source_i: {
static const LMatrix4 biasmat(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f);
case Shader::SMO_apiview_to_apiclip_light_source_i: { // shadowViewMatrix
const LightAttrib *target_light;
_target_rs->get_attrib_def(target_light);
@ -1616,14 +1637,14 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
LMatrix4::convert_mat(_coordinate_system, lens->get_coordinate_system());
if (!lnode->is_of_type(PointLight::get_class_type())) {
t *= lens->get_projection_mat() * biasmat;
t *= lens->get_projection_mat() * shadow_bias_mat;
}
((LMatrix4f *)into)[i] = LCAST(float, t);
}
// Apply just the bias matrix otherwise.
for (; i < (size_t)count; ++i) {
((LMatrix4f *)into)[i] = LCAST(float, biasmat);
((LMatrix4f *)into)[i] = LCAST(float, shadow_bias_mat);
}
return;
}

View File

@ -1570,8 +1570,8 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
bind._func = Shader::SMF_compose;
bind._part[0] = Shader::SMO_model_to_apiview;
bind._arg[0] = nullptr;
bind._part[1] = Shader::SMO_apiview_to_apiclip_light_source_i;
bind._arg[1] = nullptr;
bind._part[1] = Shader::SMO_mat_constant_x_attrib;
bind._arg[1] = iname->get_parent()->append("shadowViewMatrix");
} else {
bind._part[0] = Shader::SMO_mat_constant_x_attrib;
bind._arg[0] = InternalName::make(param_name);

View File

@ -215,19 +215,24 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
VertexBufferContext *GeomVertexArrayData::
prepare_now(PreparedGraphicsObjects *prepared_objects,
GraphicsStateGuardianBase *gsg) {
if (_contexts == nullptr) {
if (_contexts != nullptr) {
Contexts::const_iterator ci;
ci = _contexts->find(prepared_objects);
if (ci != _contexts->end()) {
return (*ci).second;
}
} else {
_contexts = new Contexts;
}
Contexts::const_iterator ci;
ci = _contexts->find(prepared_objects);
if (ci != _contexts->end()) {
return (*ci).second;
}
VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
if (vbc != nullptr) {
(*_contexts)[prepared_objects] = vbc;
}
else if (_contexts->empty()) {
delete _contexts;
_contexts = nullptr;
}
return vbc;
}

View File

@ -652,7 +652,6 @@ cp_add_mat_spec(ShaderMatSpec &spec) {
case SMO_pixel_size:
case SMO_texpad_x:
case SMO_texpix_x:
case SMO_attr_material:
case SMO_attr_color:
case SMO_attr_colorscale:
case SMO_satten_x:
@ -684,6 +683,7 @@ cp_add_mat_spec(ShaderMatSpec &spec) {
break;
case SMO_identity:
case SMO_attr_material:
case SMO_alight_x:
case SMO_dlight_x:
case SMO_plight_x:

View File

@ -76,19 +76,24 @@ is_prepared(PreparedGraphicsObjects *prepared_objects) const {
BufferContext *ShaderBuffer::
prepare_now(PreparedGraphicsObjects *prepared_objects,
GraphicsStateGuardianBase *gsg) {
if (_contexts == nullptr) {
if (_contexts != nullptr) {
Contexts::const_iterator ci;
ci = _contexts->find(prepared_objects);
if (ci != _contexts->end()) {
return (*ci).second;
}
} else {
_contexts = new Contexts;
}
Contexts::const_iterator ci;
ci = _contexts->find(prepared_objects);
if (ci != _contexts->end()) {
return (*ci).second;
}
BufferContext *vbc = prepared_objects->prepare_shader_buffer_now(this, gsg);
if (vbc != nullptr) {
(*_contexts)[prepared_objects] = vbc;
}
else if (_contexts->empty()) {
delete _contexts;
_contexts = nullptr;
}
return vbc;
}

View File

@ -92,19 +92,7 @@ BMPlenrgbtable(int classv, unsigned long bitcount)
pm_error(er_internal, "BMPlenrgbtable");
return 0;
}
switch (classv)
{
case C_WIN:
lenrgb = 4;
break;
case C_OS2:
lenrgb = 3;
break;
default:
pm_error(er_internal, "BMPlenrgbtable");
return 0;
}
lenrgb = (classv == C_OS2) ? 3 : 4;
return (1 << bitcount) * lenrgb;
}

View File

@ -55,6 +55,7 @@ public:
unsigned long offBits;
unsigned short cBitCount;
unsigned short cCompression;
int indexed;
int classv;

View File

@ -177,6 +177,7 @@ BMPreadinfoheader(
unsigned long *pcx,
unsigned long *pcy,
unsigned short *pcBitCount,
unsigned short *pcCompression,
int *pclassv)
{
unsigned long cbFix;
@ -185,6 +186,7 @@ BMPreadinfoheader(
unsigned long cx = 0;
unsigned long cy = 0;
unsigned short cBitCount = 0;
unsigned long cCompression = 0;
int classv = 0;
cbFix = GetLong(fp);
@ -229,7 +231,9 @@ BMPreadinfoheader(
* for the required total.
*/
if (classv != C_OS2) {
for (int i = 0; i < (int)cbFix - 16; i += 4) {
cCompression = GetLong(fp);
for (int i = 0; i < (int)cbFix - 20; i += 4) {
GetLong(fp);
}
}
@ -273,11 +277,13 @@ BMPreadinfoheader(
pm_message("cy: %d", cy);
pm_message("cPlanes: %d", cPlanes);
pm_message("cBitCount: %d", cBitCount);
pm_message("cCompression: %d", cCompression);
#endif
*pcx = cx;
*pcy = cy;
*pcBitCount = cBitCount;
*pcCompression = cCompression;
*pclassv = classv;
*ppos += cbFix;
@ -401,45 +407,84 @@ BMPreadbits(xel *array, xelval *alpha_array,
unsigned long cx,
unsigned long cy,
unsigned short cBitCount,
int /* classv */,
unsigned long cCompression,
int indexed,
pixval *R,
pixval *G,
pixval *B)
{
long y;
long y;
readto(fp, ppos, offBits);
readto(fp, ppos, offBits);
if(cBitCount > 24 && cBitCount != 32)
{
pm_error("%s: cannot handle cBitCount: %d"
,ifname
,cBitCount);
if (cBitCount > 24 && cBitCount != 32) {
pm_error("%s: cannot handle cBitCount: %d", ifname, cBitCount);
}
if (cCompression == 1) {
// RLE8 compression
xel *row = array + (cy - 1) * cx;
xel *p = row;
unsigned long nbyte = 0;
while (true) {
int first = GetByte(fp);
int second = GetByte(fp);
nbyte += 2;
if (first != 0) {
// Repeated index.
for (int i = 0; i < first; ++i) {
PPM_ASSIGN(*p, R[second], G[second], B[second]);
++p;
}
/*
* The picture is stored bottom line first, top line last
*/
for (y = (long)cy - 1; y >= 0; y--)
{
int rc;
rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B);
if(rc == -1)
{
pm_error("%s: couldn't read row %d"
,ifname
,y);
}
if(rc%4)
{
pm_error("%s: row had bad number of bytes: %d"
,ifname
,rc);
}
}
else if (second == 0) {
// End of line.
row -= cx;
p = row;
}
else if (second == 1) {
// End of image.
break;
}
else if (second == 2) {
// Delta.
int xoffset = GetByte(fp);
int yoffset = GetByte(fp);
nbyte += 2;
row -= cx * yoffset;
p += xoffset - cx * yoffset;
}
else {
// Absolute run.
for (int i = 0; i < second; ++i) {
int v = GetByte(fp);
++nbyte;
PPM_ASSIGN(*p, R[v], G[v], B[v]);
++p;
}
nbyte += second;
if (second % 2) {
// Pad to 16-bit boundary.
GetByte(fp);
++nbyte;
}
}
}
*ppos += nbyte;
}
else {
// The picture is stored bottom line first, top line last
for (y = (long)cy - 1; y >= 0; y--) {
int rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B);
if (rc == -1) {
pm_error("%s: couldn't read row %d", ifname, y);
}
if (rc % 4) {
pm_error("%s: row had bad number of bytes: %d", ifname, rc);
}
}
}
}
/**
@ -474,7 +519,7 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
pos = 0;
BMPreadfileheader(file, &pos, &offBits);
BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &classv);
BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &cCompression, &classv);
if (offBits != BMPoffbits(classv, cBitCount)) {
pnmimage_bmp_cat.warning()
@ -523,9 +568,10 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
int PNMFileTypeBMP::Reader::
read_data(xel *array, xelval *alpha_array) {
BMPreadbits(array, alpha_array, _file, &pos, offBits, _x_size, _y_size,
cBitCount, classv, indexed, R, G, B);
cBitCount, cCompression, indexed, R, G, B);
if (pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) {
if (cCompression != 1 &&
pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) {
pnmimage_bmp_cat.warning()
<< "Read " << pos << " bytes, expected to read "
<< BMPlenfile(classv, cBitCount, _x_size, _y_size) << " bytes\n";

View File

@ -20,7 +20,7 @@
*/
void Extension<BitArray>::
__init__(PyObject *init_value) {
if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) {
if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) {
PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer");
return;
}
@ -76,7 +76,7 @@ __getstate__() const {
*/
void Extension<BitArray>::
__setstate__(PyObject *state) {
if (Py_SIZE(state) >= 0) {
if (PyLong_IsNonNegative(state)) {
__init__(state);
} else {
PyObject *inverted = PyNumber_Invert(state);

View File

@ -17,7 +17,7 @@
template<class BMType>
INLINE void Extension<DoubleBitMask<BMType> >::
__init__(PyObject *init_value) {
if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) {
if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) {
PyErr_SetString(PyExc_ValueError, "DoubleBitMask constructor requires a positive integer");
return;
}

View File

@ -0,0 +1,57 @@
import pytest
from panda3d import core
from direct.directnotify import DirectNotify, Logger, Notifier
CATEGORY_NAME = 'test'
@pytest.fixture
def notify():
notify = DirectNotify.DirectNotify()
notify.newCategory(CATEGORY_NAME)
return notify
def test_categories():
notify = DirectNotify.DirectNotify()
assert len(notify.getCategories()) == 0
assert notify.getCategory(CATEGORY_NAME) is None
notifier = notify.newCategory(CATEGORY_NAME, logger=Logger.Logger())
assert isinstance(notifier, Notifier.Notifier)
assert notify.getCategories() == [CATEGORY_NAME]
def test_setDconfigLevels(notify):
config = core.ConfigVariableString('notify-level-' + CATEGORY_NAME, '')
notifier = notify.getCategory(CATEGORY_NAME)
config.value = 'error'
notify.setDconfigLevels()
assert notifier.getSeverity() == core.NS_error
config.value = 'warning'
notify.setDconfigLevels()
assert notifier.getSeverity() == core.NS_warning
config.value = 'info'
notify.setDconfigLevels()
assert notifier.getSeverity() == core.NS_info
config.value = 'debug'
notify.setDconfigLevels()
assert notifier.getSeverity() == core.NS_debug
def test_setVerbose(notify):
notifier = notify.getCategory(CATEGORY_NAME)
notifier.setWarning(False)
notifier.setInfo(False)
notifier.setDebug(False)
notify.setVerbose()
assert notifier.getWarning()
assert notifier.getInfo()
assert notifier.getDebug()
def test_giveNotify(notify):
class HasNotify:
notify = None
notify.giveNotify(HasNotify)
assert isinstance(HasNotify.notify, Notifier.Notifier)

View File

@ -0,0 +1,21 @@
import re
from direct.directnotify import Logger
LOG_TEXT = 'Arbitrary log text'
def test_logging(tmp_path):
log_filename = str(tmp_path / 'log')
logger = Logger.Logger(log_filename)
assert logger.getTimeStamp()
logger.log(LOG_TEXT)
logger.setTimeStamp(False)
assert not logger.getTimeStamp()
logger.log(LOG_TEXT)
logger._Logger__closeLogFile()
log_file, = tmp_path.iterdir()
log_text = log_file.read_text()
pattern = rf'\d\d:\d\d:\d\d:\d\d: {LOG_TEXT}\n{LOG_TEXT}\n'
assert re.match(pattern, log_text)

View File

@ -0,0 +1,87 @@
import io
import re
import time
import pytest
from panda3d import core
from direct.directnotify import Logger, Notifier
NOTIFIER_NAME = 'Test notifier'
DEBUG_LOG = 'Debug log'
INFO_LOG = 'Info log'
WARNING_LOG = 'Warning log'
ERROR_LOG = 'Error log'
@pytest.fixture
def log_io():
return io.StringIO()
@pytest.fixture
def notifier(log_io):
logger = Logger.Logger()
logger.setTimeStamp(False)
logger._Logger__logFile = log_io
notifier = Notifier.Notifier(NOTIFIER_NAME, logger)
notifier.setLogging(True)
return notifier
def test_setServerDelta():
notifier = Notifier.Notifier(NOTIFIER_NAME)
notifier.setServerDelta(4.2, time.timezone)
assert Notifier.Notifier.serverDelta == 4
Notifier.Notifier.serverDelta = 0
def test_logging(notifier, log_io):
notifier.setLogging(False)
assert not notifier.getLogging()
notifier.info(INFO_LOG)
assert log_io.getvalue() == ''
notifier.setLogging(True)
assert notifier.getLogging()
notifier.info(INFO_LOG)
assert log_io.getvalue() == f':{NOTIFIER_NAME}: {INFO_LOG}\n'
@pytest.mark.parametrize('severity', (core.NS_debug, core.NS_info, core.NS_warning, core.NS_error))
def test_severity(severity, notifier, log_io):
notifier.setSeverity(severity)
assert notifier.getSeverity() == severity
with pytest.raises(Notifier.NotifierException):
notifier.error(ERROR_LOG)
warning_return = notifier.warning(WARNING_LOG)
info_return = notifier.info(INFO_LOG)
debug_return = notifier.debug(DEBUG_LOG)
assert warning_return and info_return and debug_return
expected_logs = [
f'{Notifier.NotifierException}: {NOTIFIER_NAME}(error): {ERROR_LOG}',
f':{NOTIFIER_NAME}(warning): {WARNING_LOG}',
f':{NOTIFIER_NAME}: {INFO_LOG}',
f':{NOTIFIER_NAME}(debug): {DEBUG_LOG}',
]
del expected_logs[6-severity:]
assert log_io.getvalue() == '\n'.join(expected_logs) + '\n'
def test_custom_exception(notifier):
class CustomException(Exception):
pass
with pytest.raises(CustomException):
notifier.error(ERROR_LOG, CustomException)
def test_debugCall(notifier, log_io):
notifier.setDebug(False)
return_value = notifier.debugCall(DEBUG_LOG)
assert return_value
assert log_io.getvalue() == ''
notifier.setDebug(True)
notifier.debugCall(DEBUG_LOG)
pattern = rf':\d\d:\d\d:\d\d:{NOTIFIER_NAME} "{DEBUG_LOG}" test_debugCall\(.*\)\n'
assert re.match(pattern, log_io.getvalue())

View File

@ -0,0 +1,45 @@
import pytest
from direct.directnotify import RotatingLog
LOG_TEXT = 'Arbitrary log text'
@pytest.fixture
def log_dir(tmp_path):
log_dir = tmp_path / 'logs'
log_dir.mkdir()
return log_dir
@pytest.fixture
def rotating_log(log_dir):
log_filename = str(log_dir / 'log')
rotating_log = RotatingLog.RotatingLog(log_filename)
yield rotating_log
rotating_log.close()
def test_rotation(rotating_log, log_dir):
rotating_log.sizeLimit = -1
rotating_log.write('1')
rotating_log.write('2')
written = [f.read_text() for f in log_dir.iterdir()]
assert written == ['1', '2'] or written == ['2', '1']
def test_wrapper_methods(rotating_log, log_dir):
rotating_log.write('')
log_file, = log_dir.iterdir()
assert rotating_log.fileno() == rotating_log.file.fileno()
assert rotating_log.isatty() == rotating_log.file.isatty()
rotating_log.writelines([LOG_TEXT] * 10)
assert not log_file.read_text()
rotating_log.flush()
assert log_file.read_text() == LOG_TEXT * 10
assert rotating_log.tell() == len(LOG_TEXT) * 10
rotating_log.seek(len(LOG_TEXT))
rotating_log.truncate(None)
assert log_file.read_text() == LOG_TEXT

View File

@ -1,4 +1,6 @@
import os
import platform
import pytest
from panda3d import core
@ -16,12 +18,14 @@ def run_cg_compile_check(gsg, shader_path, expect_fail=False):
assert shader is not None
@pytest.mark.skipif(platform.machine().lower() == 'arm64', reason="Cg not supported on arm64")
def test_cg_compile_error(gsg):
"""Test getting compile errors from bad Cg shaders"""
shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha')
run_cg_compile_check(gsg, shader_path, expect_fail=True)
@pytest.mark.skipif(platform.machine().lower() == 'arm64', reason="Cg not supported on arm64")
def test_cg_from_file(gsg):
"""Test compiling Cg shaders from files"""
shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha')

58
tests/dist/test_FreezeTool.py vendored Normal file
View File

@ -0,0 +1,58 @@
from direct.dist.FreezeTool import Freezer, PandaModuleFinder
import sys
def test_Freezer_moduleSuffixes():
freezer = Freezer()
for suffix, mode, type in freezer.mf.suffixes:
if type == 2: # imp.PY_SOURCE
assert mode == 'rb'
def test_Freezer_getModulePath_getModuleStar(tmp_path):
# Package 1 can be imported
package1 = tmp_path / "package1"
package1.mkdir()
(package1 / "submodule1.py").write_text("")
(package1 / "__init__.py").write_text("")
# Package 2 can not be imported
package2 = tmp_path / "package2"
package2.mkdir()
(package2 / "submodule2.py").write_text("")
(package2 / "__init__.py").write_text("raise ImportError\n")
# Module 1 can be imported
(tmp_path / "module1.py").write_text("")
# Module 2 can not be imported
(tmp_path / "module2.py").write_text("raise ImportError\n")
backup = sys.path
try:
# Don't fail if first item on path does not exist
sys.path = [str(tmp_path / "nonexistent"), str(tmp_path)]
freezer = Freezer()
assert freezer.getModulePath("nonexist") == None
assert freezer.getModulePath("package1") == [str(package1)]
assert freezer.getModulePath("package2") == [str(package2)]
assert freezer.getModulePath("package1.submodule1") == None
assert freezer.getModulePath("package1.nonexist") == None
assert freezer.getModulePath("package2.submodule2") == None
assert freezer.getModulePath("package2.nonexist") == None
assert freezer.getModulePath("module1") == None
assert freezer.getModulePath("module2") == None
assert freezer.getModuleStar("nonexist") == None
assert freezer.getModuleStar("package1") == ['submodule1']
assert freezer.getModuleStar("package2") == ['submodule2']
assert freezer.getModuleStar("package1.submodule1") == None
assert freezer.getModuleStar("package1.nonexist") == None
assert freezer.getModuleStar("package2.submodule2") == None
assert freezer.getModuleStar("package2.nonexist") == None
assert freezer.getModuleStar("module1") == None
assert freezer.getModuleStar("module2") == None
finally:
sys.path = backup

View File

@ -118,6 +118,9 @@ def test_bitarray_pickle():
ba = BitArray(123)
assert ba == pickle.loads(pickle.dumps(ba, -1))
ba = BitArray(1 << 128)
assert ba == pickle.loads(pickle.dumps(ba, -1))
ba = BitArray(94187049178237918273981729127381723)
assert ba == pickle.loads(pickle.dumps(ba, -1))

View File

@ -1,22 +1,82 @@
import pytest
from panda3d import core
from direct.task import Task
def test_TaskManager():
tm = Task.TaskManager()
tm.mgr = core.AsyncTaskManager("Test manager")
tm.setClock(core.ClockObject())
tm.setupTaskChain("default", tickClock = True)
TASK_NAME = 'Arbitrary task name'
TASK_CHAIN_NAME = 'Arbitrary task chain name'
tm._startTrackingMemLeaks = lambda: None
tm._stopTrackingMemLeaks = lambda: None
tm._checkMemLeaks = lambda: None
# check for memory leaks after every test
tm._startTrackingMemLeaks()
tm._checkMemLeaks()
def DUMMY_FUNCTION(*_):
pass
@pytest.fixture
def task_manager():
manager = Task.TaskManager()
manager.mgr = core.AsyncTaskManager('Test manager')
manager.clock = core.ClockObject()
manager.setupTaskChain('default', tickClock=True)
manager.finalInit()
yield manager
manager.destroy()
def test_sequence(task_manager):
numbers = []
def append_1(task):
numbers.append(1)
def append_2(task):
numbers.append(2)
sequence = Task.sequence(core.PythonTask(append_1), core.PythonTask(append_2))
task_manager.add(sequence)
for _ in range(3):
task_manager.step()
assert not task_manager.getTasks()
assert numbers == [1, 2]
def test_loop(task_manager):
numbers = []
def append_1(task):
numbers.append(1)
def append_2(task):
numbers.append(2)
loop = Task.loop(core.PythonTask(append_1), core.PythonTask(append_2))
task_manager.add(loop)
for _ in range(5):
task_manager.step()
assert numbers == [1, 2, 1, 2]
def test_get_current_task(task_manager):
def check_current_task(task):
assert task_manager.getCurrentTask().name == TASK_NAME
task_manager.add(check_current_task, TASK_NAME)
assert len(task_manager.getTasks()) == 1
assert task_manager.getCurrentTask() is None
task_manager.step()
assert len(task_manager.getTasks()) == 0
assert task_manager.getCurrentTask() is None
def test_has_task_chain(task_manager):
assert not task_manager.hasTaskChain(TASK_CHAIN_NAME)
task_manager.setupTaskChain(TASK_CHAIN_NAME)
assert task_manager.hasTaskChain(TASK_CHAIN_NAME)
def test_done(task_manager):
# run-once task
tm = task_manager
l = []
def _testDone(task, l=l):
@ -27,28 +87,31 @@ def test_TaskManager():
assert len(l) == 1
tm.step()
assert len(l) == 1
_testDone = None
tm._checkMemLeaks()
def test_remove_by_name(task_manager):
# remove by name
tm = task_manager
def _testRemoveByName(task):
return task.done
tm.add(_testRemoveByName, 'testRemoveByName')
assert tm.remove('testRemoveByName') == 1
assert tm.remove('testRemoveByName') == 0
_testRemoveByName = None
tm._checkMemLeaks()
def test_duplicate_named_tasks(task_manager):
# duplicate named tasks
tm = task_manager
def _testDupNamedTasks(task):
return task.done
tm.add(_testDupNamedTasks, 'testDupNamedTasks')
tm.add(_testDupNamedTasks, 'testDupNamedTasks')
assert tm.remove('testRemoveByName') == 0
_testDupNamedTasks = None
tm._checkMemLeaks()
def test_continued_task(task_manager):
# continued task
tm = task_manager
l = []
def _testCont(task, l = l):
@ -60,10 +123,11 @@ def test_TaskManager():
tm.step()
assert len(l) == 2
tm.remove('testCont')
_testCont = None
tm._checkMemLeaks()
def test_continue_until_done(task_manager):
# continue until done task
tm = task_manager
l = []
def _testContDone(task, l = l):
@ -80,20 +144,22 @@ def test_TaskManager():
tm.step()
assert len(l) == 2
assert not tm.hasTaskNamed('testContDone')
_testContDone = None
tm._checkMemLeaks()
def test_has_task_named(task_manager):
# hasTaskNamed
tm = task_manager
def _testHasTaskNamed(task):
return task.done
tm.add(_testHasTaskNamed, 'testHasTaskNamed')
assert tm.hasTaskNamed('testHasTaskNamed')
tm.step()
assert not tm.hasTaskNamed('testHasTaskNamed')
_testHasTaskNamed = None
tm._checkMemLeaks()
def test_task_sort(task_manager):
# task sort
tm = task_manager
l = []
def _testPri1(task, l = l):
@ -113,11 +179,11 @@ def test_TaskManager():
assert l == [1, 2, 1, 2,]
tm.remove('testPri1')
tm.remove('testPri2')
_testPri1 = None
_testPri2 = None
tm._checkMemLeaks()
def test_extra_args(task_manager):
# task extraArgs
tm = task_manager
l = []
def _testExtraArgs(arg1, arg2, l=l):
@ -127,10 +193,11 @@ def test_TaskManager():
tm.step()
assert len(l) == 2
assert l == [4, 5,]
_testExtraArgs = None
tm._checkMemLeaks()
def test_append_task(task_manager):
# task appendTask
tm = task_manager
l = []
def _testAppendTask(arg1, arg2, task, l=l):
@ -140,10 +207,11 @@ def test_TaskManager():
tm.step()
assert len(l) == 2
assert l == [4, 5,]
_testAppendTask = None
tm._checkMemLeaks()
def test_task_upon_death(task_manager):
# task uponDeath
tm = task_manager
l = []
def _uponDeathFunc(task, l=l):
@ -155,11 +223,11 @@ def test_TaskManager():
tm.step()
assert len(l) == 1
assert l == ['testUponDeath']
_testUponDeath = None
_uponDeathFunc = None
tm._checkMemLeaks()
def test_task_owner(task_manager):
# task owner
tm = task_manager
class _TaskOwner:
def _addTask(self, task):
self.addedTaskName = task.name
@ -175,11 +243,10 @@ def test_TaskManager():
tm.step()
assert getattr(to, 'addedTaskName', None) == 'testOwner'
assert getattr(to, 'clearedTaskName', None) == 'testOwner'
_testOwner = None
del to
_TaskOwner = None
tm._checkMemLeaks()
def test_do_laters(task_manager):
tm = task_manager
doLaterTests = [0,]
# doLater
@ -205,8 +272,6 @@ def test_TaskManager():
_testDoLater1 = None
_testDoLater2 = None
_monitorDoLater = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# doLater sort
l = []
@ -231,8 +296,6 @@ def test_TaskManager():
_testDoLaterPri1 = None
_testDoLaterPri2 = None
_monitorDoLaterPri = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# doLater extraArgs
l = []
@ -252,8 +315,6 @@ def test_TaskManager():
tm.add(_monitorDoLaterExtraArgs, 'monitorDoLaterExtraArgs', sort=10)
_testDoLaterExtraArgs = None
_monitorDoLaterExtraArgs = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# doLater appendTask
l = []
@ -275,8 +336,6 @@ def test_TaskManager():
tm.add(_monitorDoLaterAppendTask, 'monitorDoLaterAppendTask', sort=10)
_testDoLaterAppendTask = None
_monitorDoLaterAppendTask = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# doLater uponDeath
l = []
@ -302,8 +361,6 @@ def test_TaskManager():
_testUponDeathFunc = None
_testDoLaterUponDeath = None
_monitorDoLaterUponDeath = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# doLater owner
class _DoLaterOwner:
@ -335,15 +392,15 @@ def test_TaskManager():
_monitorDoLaterOwner = None
del doLaterOwner
_DoLaterOwner = None
# don't check until all the doLaters are finished
#tm._checkMemLeaks()
# run the doLater tests
while doLaterTests[0] > 0:
tm.step()
del doLaterTests
tm._checkMemLeaks()
def test_get_tasks(task_manager):
tm = task_manager
# getTasks
def _testGetTasks(task):
return task.cont
@ -361,9 +418,10 @@ def test_TaskManager():
tm.remove('testGetTasks1')
tm.remove('testGetTasks3')
assert len(tm.getTasks()) == 0
_testGetTasks = None
tm._checkMemLeaks()
def test_get_do_laters(task_manager):
tm = task_manager
# getDoLaters
def _testGetDoLaters():
pass
@ -379,9 +437,18 @@ def test_TaskManager():
tm.remove('testDoLater1')
tm.remove('testDoLater3')
assert len(tm.getDoLaters()) == 0
_testGetDoLaters = None
tm._checkMemLeaks()
def test_get_all_tasks(task_manager):
active_task = task_manager.add(DUMMY_FUNCTION, delay=None)
sleeping_task = task_manager.add(DUMMY_FUNCTION, delay=1)
assert task_manager.getTasks() == [active_task]
assert task_manager.getDoLaters() == [sleeping_task]
assert task_manager.getAllTasks() in ([active_task, sleeping_task], [sleeping_task, active_task])
def test_duplicate_named_do_laters(task_manager):
tm = task_manager
# duplicate named doLaters removed via taskMgr.remove
def _testDupNameDoLaters():
pass
@ -391,9 +458,10 @@ def test_TaskManager():
assert len(tm.getDoLaters()) == 2
tm.remove('testDupNameDoLater')
assert len(tm.getDoLaters()) == 0
_testDupNameDoLaters = None
tm._checkMemLeaks()
def test_duplicate_named_do_laters_remove(task_manager):
tm = task_manager
# duplicate named doLaters removed via remove()
def _testDupNameDoLatersRemove():
pass
@ -405,10 +473,10 @@ def test_TaskManager():
assert len(tm.getDoLaters()) == 1
dl1.remove()
assert len(tm.getDoLaters()) == 0
_testDupNameDoLatersRemove = None
# nameDict etc. isn't cleared out right away with task.remove()
tm._checkMemLeaks()
def test_get_tasks_named(task_manager):
tm = task_manager
# getTasksNamed
def _testGetTasksNamed(task):
return task.cont
@ -421,9 +489,20 @@ def test_TaskManager():
assert len(tm.getTasksNamed('testGetTasksNamed')) == 3
tm.remove('testGetTasksNamed')
assert len(tm.getTasksNamed('testGetTasksNamed')) == 0
_testGetTasksNamed = None
tm._checkMemLeaks()
def test_get_tasks_matching(task_manager):
task_manager.add(DUMMY_FUNCTION, 'task_1')
task_manager.add(DUMMY_FUNCTION, 'task_2')
task_manager.add(DUMMY_FUNCTION, 'another_task')
assert len(task_manager.getTasksMatching('task_?')) == 2
assert len(task_manager.getTasksMatching('*_task')) == 1
assert len(task_manager.getTasksMatching('*task*')) == 3
def test_remove_tasks_matching(task_manager):
tm = task_manager
# removeTasksMatching
def _testRemoveTasksMatching(task):
return task.cont
@ -445,9 +524,10 @@ def test_TaskManager():
tm.removeTasksMatching('testRemoveTasksMatching?a')
assert len(tm.getTasksNamed('testRemoveTasksMatching1a')) == 0
assert len(tm.getTasksNamed('testRemoveTasksMatching2a')) == 0
_testRemoveTasksMatching = None
tm._checkMemLeaks()
def test_task_obj(task_manager):
tm = task_manager
# create Task object and add to mgr
l = []
@ -463,9 +543,10 @@ def test_TaskManager():
tm.remove('testTaskObj')
tm.step()
assert len(l) == 2
_testTaskObj = None
tm._checkMemLeaks()
def test_task_remove(task_manager):
tm = task_manager
# remove Task via task.remove()
l = []
@ -482,9 +563,10 @@ def test_TaskManager():
tm.step()
assert len(l) == 2
del t
_testTaskObjRemove = None
tm._checkMemLeaks()
def test_task_get_sort(task_manager):
tm = task_manager
# set/get Task sort
l = []
def _testTaskObjSort(arg, task, l=l):
@ -508,11 +590,3 @@ def test_TaskManager():
t2.remove()
tm.step()
assert len(l) == 4
del t1
del t2
_testTaskObjSort = None
tm._checkMemLeaks()
del l
tm.destroy()
del tm

View File

@ -2,6 +2,7 @@
# missing imports. It is useful for a quick and dirty test to make sure
# that there are no obvious build issues.
import pytest
import sys
# This will print out imports on the command line.
#import direct.showbase.VerboseImport
@ -9,7 +10,7 @@ import pytest
def test_imports_panda3d():
import importlib, os, sys
import importlib, os
import panda3d
# Look for panda3d.* modules in builtins - pfreeze might put them there.
@ -165,7 +166,8 @@ def test_imports_direct():
import direct.showbase.TaskThreaded
import direct.showbase.ThreeUpShow
import direct.showbase.Transitions
import direct.showbase.VFSImporter
if sys.version_info < (3, 12):
import direct.showbase.VFSImporter
import direct.showbase.WxGlobal
import direct.showutil.BuildGeometry
import direct.showutil.Effects