diff --git a/direct/src/directbase/DirectStart.py b/direct/src/directbase/DirectStart.py index 8b6b5b9539..ea51d43fc9 100644 --- a/direct/src/directbase/DirectStart.py +++ b/direct/src/directbase/DirectStart.py @@ -4,7 +4,7 @@ opening a graphical window and setting up the scene graph. This example demonstrates its use: import direct.directbase.DirectStart - run() + base.run() While it may be considered useful for quick prototyping in the interactive Python shell, using it in applications is not considered good style. diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index c97d842144..846faa00f7 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -1333,7 +1333,8 @@ class Freezer: for moduleName, module in list(self.mf.modules.items()): if module.__code__: origPathname = module.__code__.co_filename - replace_paths.append((origPathname, moduleName)) + if origPathname: + replace_paths.append((origPathname, moduleName)) self.mf.replace_paths = replace_paths # Now that we have built up the replacement mapping, go back @@ -1721,6 +1722,8 @@ class Freezer: def generateRuntimeFromStub(self, target, stub_file, use_console, fields={}, log_append=False): + self.__replacePaths() + # We must have a __main__ module to make an exe file. if not self.__writingModule('__main__'): message = "Can't generate an executable without a __main__ module." diff --git a/direct/src/distributed/ConnectionRepository.py b/direct/src/distributed/ConnectionRepository.py index 348a4390b6..d77802c7ff 100644 --- a/direct/src/distributed/ConnectionRepository.py +++ b/direct/src/distributed/ConnectionRepository.py @@ -260,6 +260,7 @@ class ConnectionRepository( searchPath = getModelPath().getValue() for dcFileName in dcFileNames: pathname = Filename(dcFileName) + vfs = VirtualFileSystem.getGlobalPtr() vfs.resolveFilename(pathname, searchPath) readResult = dcFile.read(pathname) if not readResult: diff --git a/direct/src/distributed/ServerRepository.py b/direct/src/distributed/ServerRepository.py index ac0f2c6856..6e0ef837a0 100644 --- a/direct/src/distributed/ServerRepository.py +++ b/direct/src/distributed/ServerRepository.py @@ -225,6 +225,7 @@ class ServerRepository: searchPath = getModelPath().getValue() for dcFileName in dcFileNames: pathname = Filename(dcFileName) + vfs = VirtualFileSystem.getGlobalPtr() vfs.resolveFilename(pathname, searchPath) readResult = dcFile.read(pathname) if not readResult: diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py index 80aa5f09e2..fd6424d917 100644 --- a/direct/src/showbase/Loader.py +++ b/direct/src/showbase/Loader.py @@ -24,14 +24,14 @@ class Loader(DirectObject): _loadedPythonFileTypes = False - class Callback: + class _Callback: """Returned by loadModel when used asynchronously. This class is modelled after Future, and can be awaited.""" # This indicates that this class behaves like a Future. _asyncio_future_blocking = False - class ResultAwaiter(object): + class _ResultAwaiter(object): """Reinvents generators because of PEP 479, sigh. See #513.""" __slots__ = 'requestList', 'index' @@ -126,9 +126,9 @@ class Loader(DirectObject): self._asyncio_future_blocking = True if self.gotList: - return self.ResultAwaiter([self]) + return self._ResultAwaiter([self]) else: - return self.ResultAwaiter(self.requestList) + return self._ResultAwaiter(self.requestList) def __aiter__(self): """ This allows using `async for` to iterate asynchronously over @@ -138,7 +138,7 @@ class Loader(DirectObject): requestList = self.requestList assert requestList is not None, "Request was cancelled." - return self.ResultAwaiter(requestList) + return self._ResultAwaiter(requestList) # special methods def __init__(self, base): @@ -308,7 +308,7 @@ class Loader(DirectObject): # requested models have been loaded, we'll invoke the # callback (passing it the models on the parameter list). - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for modelPath in modelList: request = self.loader.makeAsyncRequest(Filename(modelPath), loaderOptions) @@ -476,7 +476,7 @@ class Loader(DirectObject): # requested models have been saved, we'll invoke the # callback (passing it the models on the parameter list). - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for modelPath, node in modelList: request = self.loader.makeAsyncSaveRequest(Filename(modelPath), loaderOptions, node) @@ -1013,7 +1013,7 @@ class Loader(DirectObject): # requested sounds have been loaded, we'll invoke the # callback (passing it the sounds on the parameter list). - cb = Loader.Callback(self, len(soundList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(soundList), gotList, callback, extraArgs) for i, soundPath in enumerate(soundList): request = AudioLoadRequest(manager, soundPath, positional) request.setDoneEvent(self.hook) @@ -1078,7 +1078,7 @@ class Loader(DirectObject): callback = self.__asyncFlattenDone gotList = True - cb = Loader.Callback(self, len(modelList), gotList, callback, extraArgs) + cb = Loader._Callback(self, len(modelList), gotList, callback, extraArgs) i = 0 for model in modelList: request = ModelFlattenRequest(model.node()) diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py index c8d72cb6b4..e4ba5ccda8 100644 --- a/direct/src/showbase/Messenger.py +++ b/direct/src/showbase/Messenger.py @@ -1,5 +1,5 @@ """This defines the Messenger class, which is responsible for most of the -event handling that happens on the Python side. +:ref:`event handling ` that happens on the Python side. """ __all__ = ['Messenger'] diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py index 0b390aca7f..662ab42c16 100644 --- a/direct/src/showbase/ShowBase.py +++ b/direct/src/showbase/ShowBase.py @@ -1,4 +1,4 @@ -""" This module contains ShowBase, an application framework responsible +""" This module contains `.ShowBase`, an application framework responsible for opening a graphical display, setting up input devices and creating the scene graph. @@ -19,14 +19,15 @@ Built-in global variables Some key variables used in all Panda3D scripts are actually attributes of the ShowBase instance. When creating an instance of this class, it will write many of these variables to the built-in scope of the Python interpreter, so that -they are accessible to any Python module. +they are accessible to any Python module, without the need fors extra imports. While these are handy for prototyping, we do not recommend using them in bigger projects, as it can make the code confusing to read to other Python developers, to whom it may not be obvious where these variables are originating. -Some of these built-in variables are documented further in the -:mod:`~direct.showbase.ShowBaseGlobal` module. +Refer to the :mod:`builtins` page for a listing of the variables written to the +built-in scope. + """ __all__ = ['ShowBase', 'WindowControls'] @@ -2016,7 +2017,7 @@ class ShowBase(DirectObject.DirectObject): def enableAllAudio(self): """ Reenables the SFX and music managers that were active at the time - `disableAllAudio() was called. Meant to be called when the app regains + `disableAllAudio()` was called. Meant to be called when the app regains audio focus. """ self.AppHasAudioFocus = 1 diff --git a/direct/src/showbase/ShowBaseGlobal.py b/direct/src/showbase/ShowBaseGlobal.py index 6f39b2dab7..d4a1a09138 100644 --- a/direct/src/showbase/ShowBaseGlobal.py +++ b/direct/src/showbase/ShowBaseGlobal.py @@ -2,9 +2,14 @@ :class:`~.ShowBase.ShowBase` instance, as an alternative to using the builtin scope. -Note that you cannot directly import `base` from this module since ShowBase -may not have been created yet; instead, ShowBase dynamically adds itself to -this module's scope when instantiated.""" +Many of the variables contained in this module are also automatically written +to the :mod:`builtins` module when ShowBase is instantiated, making them +available to any Python code. Importing them from this module instead can make +it easier to see where these variables are coming from. + +Note that you cannot directly import :data:`~builtins.base` from this module +since ShowBase may not have been created yet; instead, ShowBase dynamically +adds itself to this module's scope when instantiated.""" __all__ = [] @@ -17,8 +22,8 @@ from . import DConfig as config __dev__ = config.GetBool('want-dev', __debug__) -#: The global instance of the :class:`~panda3d.core.VirtualFileSystem`, as -#: obtained using :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`. +#: The global instance of the :ref:`virtual-file-system`, as obtained using +#: :meth:`panda3d.core.VirtualFileSystem.getGlobalPtr()`. vfs = VirtualFileSystem.getGlobalPtr() #: The default Panda3D output stream for notifications and logging, as diff --git a/dtool/src/dtoolbase/cmath.I b/dtool/src/dtoolbase/cmath.I index d353a99ce4..b303671fbb 100644 --- a/dtool/src/dtoolbase/cmath.I +++ b/dtool/src/dtoolbase/cmath.I @@ -351,7 +351,7 @@ cnan(double v) { #if __FINITE_MATH_ONLY__ // GCC's isnan breaks when using -ffast-math. union { double d; uint64_t x; } u = { v }; - return ((u.x << 1) > 0xff70000000000000ull); + return ((u.x << 1) > 0xffe0000000000000ull); #elif !defined(_WIN32) return std::isnan(v); #else @@ -383,7 +383,7 @@ cinf(double v) { #if __FINITE_MATH_ONLY__ // GCC's isinf breaks when using -ffast-math. union { double d; uint64_t x; } u = { v }; - return ((u.x << 1) == 0xff70000000000000ull); + return ((u.x << 1) == 0xffe0000000000000ull); #elif !defined(_WIN32) return std::isinf(v); #else diff --git a/panda/src/mathutil/triangulator.cxx b/panda/src/mathutil/triangulator.cxx index da29f022be..f179b24e83 100644 --- a/panda/src/mathutil/triangulator.cxx +++ b/panda/src/mathutil/triangulator.cxx @@ -150,7 +150,11 @@ triangulate() { } */ + int attempts = 0; + while (construct_trapezoids(num_segments) != 0) { + nassertv_always(attempts++ < 100); + // If there's an error, re-shuffle the index and try again. Randomizer randomizer; for (i = 0; i < num_segments; ++i) { diff --git a/panda/src/pgraphnodes/shaderGenerator.cxx b/panda/src/pgraphnodes/shaderGenerator.cxx index 3edcdc4aa0..91e8498287 100644 --- a/panda/src/pgraphnodes/shaderGenerator.cxx +++ b/panda/src/pgraphnodes/shaderGenerator.cxx @@ -1485,7 +1485,9 @@ synthesize_shader(const RenderState *rs, const GeomVertexAnimationSpec &anim) { // Combine in alpha, which bypasses lighting calculations. Use of lerp // here is a workaround for a radeon driver bug. if (key._calc_primary_alpha) { - if (key._color_type == ColorAttrib::T_vertex) { + if (key._material_flags & Material::F_diffuse) { + text << "\t result.a = attr_material[1].w;\n"; + } else if (key._color_type == ColorAttrib::T_vertex) { text << "\t result.a = l_color.a;\n"; } else if (key._color_type == ColorAttrib::T_flat) { text << "\t result.a = attr_color.a;\n"; diff --git a/panda/src/pgui/pgEntry.cxx b/panda/src/pgui/pgEntry.cxx index b6a86754d7..c729a23038 100644 --- a/panda/src/pgui/pgEntry.cxx +++ b/panda/src/pgui/pgEntry.cxx @@ -203,6 +203,11 @@ press(const MouseWatcherParameter ¶m, bool background) { ButtonHandle button = param.get_button(); + if (button == KeyboardButton::tab()) { + // Tab. Ignore the entry. + return; + } + if (button == MouseButton::one() || button == MouseButton::two() || button == MouseButton::three() || diff --git a/tests/display/test_color_buffer.py b/tests/display/test_color_buffer.py index bb2bf3deb1..1450981f37 100644 --- a/tests/display/test_color_buffer.py +++ b/tests/display/test_color_buffer.py @@ -46,6 +46,16 @@ def material_attrib(request): return core.MaterialAttrib.make(mat) +@pytest.fixture(scope='session') +def light_attrib(): + light = core.AmbientLight('amb') + light.color = (1, 1, 1, 1) + light_attrib = core.LightAttrib.make() + light_attrib = light_attrib.add_on_light(core.NodePath(light)) + + return light_attrib + + @pytest.fixture(scope='module', params=[False, True], ids=["srgb:off", "srgb:on"]) def color_region(request, graphics_pipe): """Creates and returns a DisplayRegion with a depth buffer.""" @@ -297,3 +307,63 @@ def test_scaled_color_off_vertex(color_region, shader_attrib, material_attrib): result = render_color_pixel(color_region, state, vertex_color=TEST_COLOR) assert result.almost_equal(TEST_COLOR_SCALE, FUZZ) + +def test_color_transparency(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_flat(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + core.ColorAttrib.make_flat(TEST_COLOR), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_vertex(color_region, shader_attrib, light_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + core.ColorAttrib.make_vertex(), + light_attrib, + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state, vertex_color=(1, 1, 1, 0.5)) + assert result.x == pytest.approx(0.75, 0.1) + + +def test_color_transparency_no_light(color_region, shader_attrib): + mat = core.Material() + mat.diffuse = (1, 1, 1, 0.75) + material_attrib = core.MaterialAttrib.make(mat) + + state = core.RenderState.make( + core.TransparencyAttrib.make(core.TransparencyAttrib.M_alpha), + shader_attrib, + material_attrib, + ) + result = render_color_pixel(color_region, state) + assert result.x == pytest.approx(1.0, 0.1) diff --git a/tests/dtoolutil/test_filename.py b/tests/dtoolutil/test_filename.py index a42305ae12..d1a6fe5dd4 100644 --- a/tests/dtoolutil/test_filename.py +++ b/tests/dtoolutil/test_filename.py @@ -20,4 +20,4 @@ def test_filename_ctor_pathlib(): path = pathlib.Path(__file__) fn = Filename(path) - assert fn.to_os_specific_w() == str(path) + assert fn.to_os_specific_w().lower() == str(path).lower() diff --git a/tests/gobj/test_lenses.py b/tests/gobj/test_lenses.py index a51e497c8e..c5c6961f5c 100644 --- a/tests/gobj/test_lenses.py +++ b/tests/gobj/test_lenses.py @@ -1,4 +1,4 @@ -from panda3d.core import PerspectiveLens, Point3, Point2 +from panda3d.core import PerspectiveLens, Point3, Point2, CS_zup_right def test_perspectivelens_extrude(): @@ -75,3 +75,25 @@ def test_perspectivelens_project(): assert lens.project((100, 100, 0), point) assert point.almost_equal((1, 0), 0.001) + + +def test_perspectivelens_far_inf(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(2, float("inf")) + lens.coordinate_system = CS_zup_right + + mat = lens.get_projection_mat() + assert mat[1][2] == 1 + assert mat[3][2] == -4 + + +def test_perspectivelens_near_inf(): + lens = PerspectiveLens() + lens.set_fov(90, 90) + lens.set_near_far(float("inf"), 2) + lens.coordinate_system = CS_zup_right + + mat = lens.get_projection_mat() + assert mat[1][2] == -1 + assert mat[3][2] == 4 diff --git a/tests/linmath/test_lvector2.py b/tests/linmath/test_lvector2.py index d6bff7c5aa..e44fa17729 100644 --- a/tests/linmath/test_lvector2.py +++ b/tests/linmath/test_lvector2.py @@ -89,3 +89,31 @@ def test_vec2_compare(): assert Vec2(1, 0).compare_to(Vec2(0, 1)) == 1 assert Vec2(0, 1).compare_to(Vec2(1, 0)) == -1 assert Vec2(0, 1).compare_to(Vec2(0, 1)) == 0 + + +def test_vec2_nan(): + nan = float("nan") + inf = float("inf") + assert not Vec2F(0, 0).is_nan() + assert not Vec2F(1, 0).is_nan() + assert Vec2F(nan, 0).is_nan() + assert Vec2F(0, nan).is_nan() + assert Vec2F(nan, nan).is_nan() + assert Vec2F(-nan, 0).is_nan() + assert Vec2F(-nan, nan).is_nan() + assert Vec2F(inf, nan).is_nan() + assert not Vec2F(inf, 0).is_nan() + assert not Vec2F(inf, inf).is_nan() + assert not Vec2F(-inf, 0).is_nan() + + assert not Vec2D(0, 0).is_nan() + assert not Vec2D(1, 0).is_nan() + assert Vec2D(nan, 0).is_nan() + assert Vec2D(0, nan).is_nan() + assert Vec2D(nan, nan).is_nan() + assert Vec2D(-nan, 0).is_nan() + assert Vec2D(-nan, nan).is_nan() + assert Vec2D(inf, nan).is_nan() + assert not Vec2D(inf, 0).is_nan() + assert not Vec2D(inf, inf).is_nan() + assert not Vec2D(-inf, 0).is_nan()