Przeglądaj źródła

showbase: Annotate some debug functions (#1767)

WMOkiishi 3 miesięcy temu
rodzic
commit
67e3ccada7

+ 2 - 2
direct/src/directtools/DirectUtil.py

@@ -1,4 +1,4 @@
-from panda3d.core import VBase4
+from panda3d.core import NodePath, VBase4
 from direct.task.Task import Task
 from direct.task.Task import Task
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
 
 
@@ -55,7 +55,7 @@ def lerpBackgroundColor(r, g, b, duration):
 
 
 # Set direct drawing style for an object
 # Set direct drawing style for an object
 # Never light object or draw in wireframe
 # Never light object or draw in wireframe
-def useDirectRenderStyle(nodePath, priority = 0):
+def useDirectRenderStyle(nodePath: NodePath, priority: int = 0) -> None:
     """
     """
     Function to force a node path to use direct render style:
     Function to force a node path to use direct render style:
     no lighting, and no wireframe
     no lighting, and no wireframe

+ 27 - 15
direct/src/showbase/BufferViewer.py

@@ -12,6 +12,8 @@ Or, you can enable the following variable in your Config.prc::
     show-buffers true
     show-buffers true
 """
 """
 
 
+from __future__ import annotations
+
 __all__ = ['BufferViewer']
 __all__ = ['BufferViewer']
 
 
 from panda3d.core import (
 from panda3d.core import (
@@ -39,14 +41,19 @@ from direct.task.TaskManagerGlobal import taskMgr
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.showbase.DirectObject import DirectObject
 from direct.showbase.DirectObject import DirectObject
 import math
 import math
+from typing import Literal, Union
+
+# The following variables are typing constructs used in annotations
+# to succinctly express complex type structures.
+_Texture = Union[Texture, GraphicsOutput, Literal['all']]
 
 
 
 
 class BufferViewer(DirectObject):
 class BufferViewer(DirectObject):
     notify = directNotify.newCategory('BufferViewer')
     notify = directNotify.newCategory('BufferViewer')
 
 
-    def __init__(self, win, parent):
+    def __init__(self, win: GraphicsOutput | None, parent: NodePath) -> None:
         """Access: private.  Constructor."""
         """Access: private.  Constructor."""
-        self.enabled = 0
+        self.enabled = False
         size = ConfigVariableDouble('buffer-viewer-size', '0 0')
         size = ConfigVariableDouble('buffer-viewer-size', '0 0')
         self.sizex = size[0]
         self.sizex = size[0]
         self.sizey = size[1]
         self.sizey = size[1]
@@ -59,23 +66,23 @@ class BufferViewer(DirectObject):
         self.win = win
         self.win = win
         self.engine = GraphicsEngine.getGlobalPtr()
         self.engine = GraphicsEngine.getGlobalPtr()
         self.renderParent = parent
         self.renderParent = parent
-        self.cards = []
+        self.cards: list[NodePath] = []
         self.cardindex = 0
         self.cardindex = 0
         self.cardmaker = CardMaker("cubemaker")
         self.cardmaker = CardMaker("cubemaker")
         self.cardmaker.setFrame(-1,1,-1,1)
         self.cardmaker.setFrame(-1,1,-1,1)
         self.task = 0
         self.task = 0
-        self.dirty = 1
+        self.dirty = True
         self.accept("render-texture-targets-changed", self.refreshReadout)
         self.accept("render-texture-targets-changed", self.refreshReadout)
         if ConfigVariableBool("show-buffers", 0):
         if ConfigVariableBool("show-buffers", 0):
-            self.enable(1)
+            self.enable(True)
 
 
-    def refreshReadout(self):
+    def refreshReadout(self) -> None:
         """Force the readout to be refreshed.  This is usually invoked
         """Force the readout to be refreshed.  This is usually invoked
         by GraphicsOutput::add_render_texture (via an event handler).
         by GraphicsOutput::add_render_texture (via an event handler).
         However, it is also possible to invoke it manually.  Currently,
         However, it is also possible to invoke it manually.  Currently,
         the only time I know of that this is necessary is after a
         the only time I know of that this is necessary is after a
         window resize (and I ought to fix that)."""
         window resize (and I ought to fix that)."""
-        self.dirty = 1
+        self.dirty = True
 
 
         # Call enabled again, mainly to ensure that the task has been
         # Call enabled again, mainly to ensure that the task has been
         # started.
         # started.
@@ -95,14 +102,14 @@ class BufferViewer(DirectObject):
         """Returns true if the buffer viewer is currently enabled."""
         """Returns true if the buffer viewer is currently enabled."""
         return self.enabled
         return self.enabled
 
 
-    def enable(self, x):
+    def enable(self, x: bool) -> None:
         """Turn the buffer viewer on or off.  The initial state of the
         """Turn the buffer viewer on or off.  The initial state of the
         buffer viewer depends on the Config variable 'show-buffers'."""
         buffer viewer depends on the Config variable 'show-buffers'."""
         if x != 0 and x != 1:
         if x != 0 and x != 1:
             BufferViewer.notify.error('invalid parameter to BufferViewer.enable')
             BufferViewer.notify.error('invalid parameter to BufferViewer.enable')
             return
             return
         self.enabled = x
         self.enabled = x
-        self.dirty = 1
+        self.dirty = True
         if (x and self.task == 0):
         if (x and self.task == 0):
             self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout",
             self.task = taskMgr.add(self.maintainReadout, "buffer-viewer-maintain-readout",
                                     priority=1)
                                     priority=1)
@@ -218,7 +225,11 @@ class BufferViewer(DirectObject):
         self.renderParent = renderParent
         self.renderParent = renderParent
         self.dirty = 1
         self.dirty = 1
 
 
-    def analyzeTextureSet(self, x, set):
+    def analyzeTextureSet(
+        self,
+        x: _Texture | GraphicsEngine | list[_Texture | GraphicsEngine],
+        set: dict[Texture, int],
+    ) -> None:
         """Access: private.  Converts a list of GraphicsObject,
         """Access: private.  Converts a list of GraphicsObject,
         GraphicsEngine, and Texture into a table of Textures."""
         GraphicsEngine, and Texture into a table of Textures."""
 
 
@@ -240,7 +251,7 @@ class BufferViewer(DirectObject):
         else:
         else:
             return
             return
 
 
-    def makeFrame(self, sizex, sizey):
+    def makeFrame(self, sizex: int, sizey: int) -> NodePath:
         """Access: private.  Each texture card is displayed with
         """Access: private.  Each texture card is displayed with
         a two-pixel wide frame (a ring of black and a ring of white).
         a two-pixel wide frame (a ring of black and a ring of white).
         This routine builds the frame geometry.  It is necessary to
         This routine builds the frame geometry.  It is necessary to
@@ -287,7 +298,7 @@ class BufferViewer(DirectObject):
         geomnode.addGeom(geom)
         geomnode.addGeom(geom)
         return NodePath(geomnode)
         return NodePath(geomnode)
 
 
-    def maintainReadout(self, task):
+    def maintainReadout(self, task: object) -> int:
         """Access: private.  Whenever necessary, rebuilds the entire
         """Access: private.  Whenever necessary, rebuilds the entire
         display from scratch.  This is only done when the configuration
         display from scratch.  This is only done when the configuration
         parameters have changed."""
         parameters have changed."""
@@ -295,7 +306,7 @@ class BufferViewer(DirectObject):
         # If nothing has changed, don't update.
         # If nothing has changed, don't update.
         if not self.dirty:
         if not self.dirty:
             return Task.cont
             return Task.cont
-        self.dirty = 0
+        self.dirty = False
 
 
         # Delete the old set of cards.
         # Delete the old set of cards.
         for card in self.cards:
         for card in self.cards:
@@ -308,8 +319,8 @@ class BufferViewer(DirectObject):
             return Task.done
             return Task.done
 
 
         # Generate the include and exclude sets.
         # Generate the include and exclude sets.
-        exclude = {}
-        include = {}
+        exclude: dict[Texture, int] = {}
+        include: dict[Texture, int] = {}
         self.analyzeTextureSet(self.exclude, exclude)
         self.analyzeTextureSet(self.exclude, exclude)
         self.analyzeTextureSet(self.include, include)
         self.analyzeTextureSet(self.include, include)
 
 
@@ -407,6 +418,7 @@ class BufferViewer(DirectObject):
 
 
         bordersize = 4.0
         bordersize = 4.0
 
 
+        assert self.win is not None
         if float(self.sizex) == 0.0 and float(self.sizey) == 0.0:
         if float(self.sizex) == 0.0 and float(self.sizey) == 0.0:
             sizey = int(0.4266666667 * self.win.getYSize())
             sizey = int(0.4266666667 * self.win.getYSize())
             sizex = (sizey * aspectx) // aspecty
             sizex = (sizey * aspectx) // aspecty

+ 4 - 4
direct/src/showbase/ExceptionVarDump.py

@@ -35,7 +35,7 @@ def _varDump__init__(self, *args, **kArgs):
 sReentry = 0
 sReentry = 0
 
 
 
 
-def _varDump__print(exc):
+def _varDump__print(exc) -> None:
     global sReentry
     global sReentry
     global notify
     global notify
     if sReentry > 0:
     if sReentry > 0:
@@ -176,7 +176,7 @@ def _excepthookDumpVars(eType, eValue, tb):
     oldExcepthook(eType, eValue, origTb)
     oldExcepthook(eType, eValue, origTb)
 
 
 
 
-def install(log, upload):
+def install(log: bool, upload: bool) -> None:
     """Installs the exception hook."""
     """Installs the exception hook."""
     global oldExcepthook
     global oldExcepthook
     global wantStackDumpLog
     global wantStackDumpLog
@@ -192,8 +192,8 @@ def install(log, upload):
         # thrown by the interpreter don't get created until the
         # thrown by the interpreter don't get created until the
         # stack has been unwound and an except block has been reached
         # stack has been unwound and an except block has been reached
         if not hasattr(Exception, '_moved__init__'):
         if not hasattr(Exception, '_moved__init__'):
-            Exception._moved__init__ = Exception.__init__
-            Exception.__init__ = _varDump__init__
+            Exception._moved__init__ = Exception.__init__  # type: ignore[attr-defined]
+            Exception.__init__ = _varDump__init__  # type: ignore[method-assign]
     else:
     else:
         if sys.excepthook is not _excepthookDumpVars:
         if sys.excepthook is not _excepthookDumpVars:
             oldExcepthook = sys.excepthook
             oldExcepthook = sys.excepthook

+ 29 - 12
direct/src/showbase/GarbageReport.py

@@ -1,5 +1,7 @@
 """Contains utility classes for debugging memory leaks."""
 """Contains utility classes for debugging memory leaks."""
 
 
+from __future__ import annotations
+
 __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
 __all__ = ['FakeObject', '_createGarbage', 'GarbageReport', 'GarbageLogger']
 
 
 from direct.directnotify.DirectNotifyGlobal import directNotify
 from direct.directnotify.DirectNotifyGlobal import directNotify
@@ -10,6 +12,7 @@ from direct.showbase.JobManagerGlobal import jobMgr
 from direct.showbase.MessengerGlobal import messenger
 from direct.showbase.MessengerGlobal import messenger
 from panda3d.core import ConfigVariableBool
 from panda3d.core import ConfigVariableBool
 import gc
 import gc
+from collections.abc import Callable
 
 
 GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num'
 GarbageCycleCountAnnounceEvent = 'announceGarbageCycleDesc2num'
 
 
@@ -41,9 +44,21 @@ class GarbageReport(Job):
     If you just want to dump the report to the log, use GarbageLogger."""
     If you just want to dump the report to the log, use GarbageLogger."""
     notify = directNotify.newCategory("GarbageReport")
     notify = directNotify.newCategory("GarbageReport")
 
 
-    def __init__(self, name, log=True, verbose=False, fullReport=False, findCycles=True,
-                 threaded=False, doneCallback=None, autoDestroy=False, priority=None,
-                 safeMode=False, delOnly=False, collect=True):
+    def __init__(
+        self,
+        name: str,
+        log: bool = True,
+        verbose: bool = False,
+        fullReport: bool = False,
+        findCycles: bool = True,
+        threaded: bool = False,
+        doneCallback: Callable[[GarbageReport], object] | None = None,
+        autoDestroy: bool = False,
+        priority: int | None = None,
+        safeMode: bool = False,
+        delOnly: bool = False,
+        collect: bool = True
+    ) -> None:
         # if autoDestroy is True, GarbageReport will self-destroy after logging
         # if autoDestroy is True, GarbageReport will self-destroy after logging
         # if false, caller is responsible for calling destroy()
         # if false, caller is responsible for calling destroy()
         # if threaded is True, processing will be performed over multiple frames
         # if threaded is True, processing will be performed over multiple frames
@@ -399,7 +414,7 @@ class GarbageReport(Job):
         if self._args.autoDestroy:
         if self._args.autoDestroy:
             self.destroy()
             self.destroy()
 
 
-    def destroy(self):
+    def destroy(self) -> None:
         #print 'GarbageReport.destroy'
         #print 'GarbageReport.destroy'
         del self._args
         del self._args
         del self.garbage
         del self.garbage
@@ -417,13 +432,13 @@ class GarbageReport(Job):
             del self._reportStr
             del self._reportStr
         Job.destroy(self)
         Job.destroy(self)
 
 
-    def getNumCycles(self):
+    def getNumCycles(self) -> int:
         # if the job hasn't run yet, we don't have a numCycles yet
         # if the job hasn't run yet, we don't have a numCycles yet
         return self.numCycles
         return self.numCycles
 
 
-    def getDesc2numDict(self):
+    def getDesc2numDict(self) -> dict[str, int]:
         # dict of python-syntax leak -> number of that type of leak
         # dict of python-syntax leak -> number of that type of leak
-        desc2num = {}
+        desc2num: dict[str, int] = {}
         for cycleBySyntax in self.cyclesBySyntax:
         for cycleBySyntax in self.cyclesBySyntax:
             desc2num.setdefault(cycleBySyntax, 0)
             desc2num.setdefault(cycleBySyntax, 0)
             desc2num[cycleBySyntax] += 1
             desc2num[cycleBySyntax] += 1
@@ -563,7 +578,7 @@ class _CFGLGlobals:
     LastNumCycles = 0
     LastNumCycles = 0
 
 
 
 
-def checkForGarbageLeaks():
+def checkForGarbageLeaks() -> int:
     gc.collect()
     gc.collect()
     numGarbage = len(gc.garbage)
     numGarbage = len(gc.garbage)
     if numGarbage > 0 and ConfigVariableBool('auto-garbage-logging', False):
     if numGarbage > 0 and ConfigVariableBool('auto-garbage-logging', False):
@@ -576,6 +591,7 @@ def checkForGarbageLeaks():
             messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()])
             messenger.send(GarbageCycleCountAnnounceEvent, [gr.getDesc2numDict()])
             gr.destroy()
             gr.destroy()
         notify = directNotify.newCategory("GarbageDetect")
         notify = directNotify.newCategory("GarbageDetect")
+        func: Callable[[str], object]
         if ConfigVariableBool('allow-garbage-cycles', True):
         if ConfigVariableBool('allow-garbage-cycles', True):
             func = notify.warning
             func = notify.warning
         else:
         else:
@@ -584,7 +600,8 @@ def checkForGarbageLeaks():
     return numGarbage
     return numGarbage
 
 
 
 
-def b_checkForGarbageLeaks(wantReply=False):
+def b_checkForGarbageLeaks(wantReply: bool = False) -> int:
+    from direct.showbase.ShowBaseGlobal import base, __dev__
     if not __dev__:
     if not __dev__:
         return 0
         return 0
     # does a garbage collect on the client and the AI
     # does a garbage collect on the client and the AI
@@ -592,10 +609,10 @@ def b_checkForGarbageLeaks(wantReply=False):
     # logs leak info and terminates (if configured to do so)
     # logs leak info and terminates (if configured to do so)
     try:
     try:
         # if this is the client, tell the AI to check for leaks too
         # if this is the client, tell the AI to check for leaks too
-        base.cr.timeManager
+        base.cr.timeManager  # type: ignore[attr-defined]
     except Exception:
     except Exception:
         pass
         pass
     else:
     else:
-        if base.cr.timeManager:
-            base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply)
+        if base.cr.timeManager:  # type: ignore[attr-defined]
+            base.cr.timeManager.d_checkForGarbageLeaks(wantReply=wantReply)  # type: ignore[attr-defined]
     return checkForGarbageLeaks()
     return checkForGarbageLeaks()

+ 1 - 1
direct/src/showbase/Messenger.py

@@ -596,7 +596,7 @@ class Messenger:
                         break
                         break
         return matches
         return matches
 
 
-    def __methodRepr(self, method):
+    def __methodRepr(self, method: object) -> str:
         """
         """
         return string version of class.method or method.
         return string version of class.method or method.
         """
         """

+ 13 - 7
direct/src/showbase/OnScreenDebug.py

@@ -1,7 +1,11 @@
 """Contains the OnScreenDebug class."""
 """Contains the OnScreenDebug class."""
 
 
+from __future__ import annotations
+
 __all__ = ['OnScreenDebug']
 __all__ = ['OnScreenDebug']
 
 
+from typing import Any
+
 from panda3d.core import (
 from panda3d.core import (
     ConfigVariableBool,
     ConfigVariableBool,
     ConfigVariableDouble,
     ConfigVariableDouble,
@@ -18,13 +22,13 @@ class OnScreenDebug:
 
 
     enabled = ConfigVariableBool("on-screen-debug-enabled", False)
     enabled = ConfigVariableBool("on-screen-debug-enabled", False)
 
 
-    def __init__(self):
-        self.onScreenText = None
+    def __init__(self) -> None:
+        self.onScreenText: OnscreenText.OnscreenText | None = None
         self.frame = 0
         self.frame = 0
         self.text = ""
         self.text = ""
-        self.data = {}
+        self.data: dict[str, tuple[int, Any]] = {}
 
 
-    def load(self):
+    def load(self) -> None:
         if self.onScreenText:
         if self.onScreenText:
             return
             return
 
 
@@ -40,6 +44,7 @@ class OnScreenDebug:
         fgColor.setW(ConfigVariableDouble("on-screen-debug-fg-alpha", 0.85).value)
         fgColor.setW(ConfigVariableDouble("on-screen-debug-fg-alpha", 0.85).value)
         bgColor.setW(ConfigVariableDouble("on-screen-debug-bg-alpha", 0.85).value)
         bgColor.setW(ConfigVariableDouble("on-screen-debug-bg-alpha", 0.85).value)
 
 
+        from direct.showbase.ShowBaseGlobal import base
         font = base.loader.loadFont(fontPath)
         font = base.loader.loadFont(fontPath)
         if not font.isValid():
         if not font.isValid():
             print("failed to load OnScreenDebug font %s" % fontPath)
             print("failed to load OnScreenDebug font %s" % fontPath)
@@ -47,15 +52,16 @@ class OnScreenDebug:
         self.onScreenText = OnscreenText.OnscreenText(
         self.onScreenText = OnscreenText.OnscreenText(
                 parent = base.a2dTopLeft, pos = (0.0, -0.1),
                 parent = base.a2dTopLeft, pos = (0.0, -0.1),
                 fg=fgColor, bg=bgColor, scale = (fontScale, fontScale, 0.0),
                 fg=fgColor, bg=bgColor, scale = (fontScale, fontScale, 0.0),
-                align = TextNode.ALeft, mayChange = 1, font = font)
+                align = TextNode.ALeft, mayChange = True, font = font)
         # Make sure readout is never lit or drawn in wireframe
         # Make sure readout is never lit or drawn in wireframe
         DirectUtil.useDirectRenderStyle(self.onScreenText)
         DirectUtil.useDirectRenderStyle(self.onScreenText)
 
 
-    def render(self):
+    def render(self) -> None:
         if not self.enabled:
         if not self.enabled:
             return
             return
         if not self.onScreenText:
         if not self.onScreenText:
             self.load()
             self.load()
+        assert self.onScreenText is not None
         self.onScreenText.clearText()
         self.onScreenText.clearText()
         for k, v in sorted(self.data.items()):
         for k, v in sorted(self.data.items()):
             if v[0] == self.frame:
             if v[0] == self.frame:
@@ -75,7 +81,7 @@ class OnScreenDebug:
         self.onScreenText.appendText(self.text)
         self.onScreenText.appendText(self.text)
         self.frame += 1
         self.frame += 1
 
 
-    def clear(self):
+    def clear(self) -> None:
         self.text = ""
         self.text = ""
         if self.onScreenText:
         if self.onScreenText:
             self.onScreenText.clearText()
             self.onScreenText.clearText()

+ 17 - 10
direct/src/showbase/PythonUtil.py

@@ -233,7 +233,7 @@ if __debug__:
 
 
     #-----------------------------------------------------------------------------
     #-----------------------------------------------------------------------------
 
 
-    def traceFunctionCall(frame):
+    def traceFunctionCall(frame: types.FrameType) -> str:
         """
         """
         return a string that shows the call frame with calling arguments.
         return a string that shows the call frame with calling arguments.
         e.g.
         e.g.
@@ -274,7 +274,7 @@ if __debug__:
                 r+="*** undefined ***"
                 r+="*** undefined ***"
         return r+')'
         return r+')'
 
 
-    def traceParentCall():
+    def traceParentCall() -> str:
         return traceFunctionCall(sys._getframe(2))
         return traceFunctionCall(sys._getframe(2))
 
 
     def printThisCall():
     def printThisCall():
@@ -1002,7 +1002,7 @@ def lineupPos(i, num, spacing):
     return pos - ((float(spacing) * (num-1))/2.)
     return pos - ((float(spacing) * (num-1))/2.)
 
 
 
 
-def formatElapsedSeconds(seconds):
+def formatElapsedSeconds(seconds: float) -> str:
     """
     """
     Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
     Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
     representing the indicated elapsed time in seconds.
     representing the indicated elapsed time in seconds.
@@ -1408,14 +1408,16 @@ def _getSafeReprNotify():
     return safeReprNotify
     return safeReprNotify
 
 
 
 
-def safeRepr(obj):
+def safeRepr(obj: object) -> str:
     global dtoolSuperBase
     global dtoolSuperBase
     if dtoolSuperBase is None:
     if dtoolSuperBase is None:
         _getDtoolSuperBase()
         _getDtoolSuperBase()
+    assert dtoolSuperBase is not None
 
 
     global safeReprNotify
     global safeReprNotify
     if safeReprNotify is None:
     if safeReprNotify is None:
         _getSafeReprNotify()
         _getSafeReprNotify()
+    assert safeReprNotify is not None
 
 
     if isinstance(obj, dtoolSuperBase):
     if isinstance(obj, dtoolSuperBase):
         # repr of C++ object could crash, particularly if the object has been deleted
         # repr of C++ object could crash, particularly if the object has been deleted
@@ -1447,7 +1449,12 @@ def safeReprTypeOnFail(obj):
         return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj)))
         return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj)))
 
 
 
 
-def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
+def fastRepr(
+    obj: object,
+    maxLen: int = 200,
+    strFactor: int = 10,
+    _visitedIds: set[int] | None = None,
+) -> str:
     """ caps the length of iterable types, so very large objects will print faster.
     """ caps the length of iterable types, so very large objects will print faster.
     also prevents infinite recursion """
     also prevents infinite recursion """
     try:
     try:
@@ -1456,9 +1463,9 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
         if id(obj) in _visitedIds:
         if id(obj) in _visitedIds:
             return '<ALREADY-VISITED %s>' % itype(obj)
             return '<ALREADY-VISITED %s>' % itype(obj)
         if type(obj) in (tuple, list):
         if type(obj) in (tuple, list):
+            assert isinstance(obj, (tuple, list))
             s = ''
             s = ''
-            s += {tuple: '(',
-                  list:  '[',}[type(obj)]
+            s += '(' if type(obj) == tuple else '['
             if maxLen is not None and len(obj) > maxLen:
             if maxLen is not None and len(obj) > maxLen:
                 o = obj[:maxLen]
                 o = obj[:maxLen]
                 ellips = '...'
                 ellips = '...'
@@ -1471,8 +1478,7 @@ def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
                 s += ', '
                 s += ', '
             _visitedIds.remove(id(obj))
             _visitedIds.remove(id(obj))
             s += ellips
             s += ellips
-            s += {tuple: ')',
-                  list:  ']',}[type(obj)]
+            s += ')' if type(obj) == tuple else ']'
             return s
             return s
         elif type(obj) is dict:
         elif type(obj) is dict:
             s = '{'
             s = '{'
@@ -1624,7 +1630,7 @@ class Sync:
                               self._name, self._series, self._value)
                               self._name, self._series, self._value)
 
 
 
 
-def itype(obj):
+def itype(obj: object) -> type | str:
     # version of type that gives more complete information about instance types
     # version of type that gives more complete information about instance types
     global dtoolSuperBase
     global dtoolSuperBase
     t = type(obj)
     t = type(obj)
@@ -1632,6 +1638,7 @@ def itype(obj):
     # check if this is a C++ object
     # check if this is a C++ object
     if dtoolSuperBase is None:
     if dtoolSuperBase is None:
         _getDtoolSuperBase()
         _getDtoolSuperBase()
+    assert dtoolSuperBase is not None
     if isinstance(obj, dtoolSuperBase):
     if isinstance(obj, dtoolSuperBase):
         return "<type 'instance' of %s>" % (obj.__class__)
         return "<type 'instance' of %s>" % (obj.__class__)
     return t
     return t

+ 4 - 2
direct/src/showbase/ShowBase.py

@@ -177,6 +177,8 @@ class ShowBase(DirectObject.DirectObject):
     aspect2d: NodePath
     aspect2d: NodePath
     pixel2d: NodePath
     pixel2d: NodePath
 
 
+    a2dTopLeft: NodePath
+
     cluster: Any | None
     cluster: Any | None
 
 
     def __init__(self, fStartDirect: bool = True, windowType: str | None = None) -> None:
     def __init__(self, fStartDirect: bool = True, windowType: str | None = None) -> None:
@@ -438,7 +440,7 @@ class ShowBase(DirectObject.DirectObject):
         self.useTrackball()
         self.useTrackball()
 
 
         #: `.Loader.Loader` object.
         #: `.Loader.Loader` object.
-        self.loader = ShowBaseGlobal.loader
+        self.loader: Loader.Loader = ShowBaseGlobal.loader
         self.loader._init_base(self)
         self.loader._init_base(self)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
 
 
@@ -654,7 +656,7 @@ class ShowBase(DirectObject.DirectObject):
     def getExitErrorCode(self):
     def getExitErrorCode(self):
         return 0
         return 0
 
 
-    def printEnvDebugInfo(self):
+    def printEnvDebugInfo(self) -> None:
         """Print some information about the environment that we are running
         """Print some information about the environment that we are running
         in.  Stuff like the model paths and other paths.  Feel free to
         in.  Stuff like the model paths and other paths.  Feel free to
         add stuff to this.
         add stuff to this.