From 286a32f48ec0a1e7a6fc01e0f1406761923a62ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:20:32 +0000 Subject: [PATCH 01/22] gui.wxpython.nviz: numpy import can safely be below --- gui/wxpython/nviz/wxnviz.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 63be18a69c1..42c4ea7326a 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -27,18 +27,6 @@ import sys from math import sqrt -try: - from numpy import matrix -except ImportError: - msg = _( - "This module requires the NumPy module, which could not be " - "imported. It probably is not installed (it's not part of the " - "standard Python distribution). See the Numeric Python site " - "(https://numpy.org) for information on downloading source or " - "binaries." - ) - print("wxnviz.py: " + msg, file=sys.stderr) - import wx try: @@ -250,6 +238,19 @@ from core.utils import autoCropImageFromFile from gui_core.wrap import Rect + +try: + from numpy import matrix +except ImportError: + msg = _( + "This module requires the NumPy module, which could not be " + "imported. It probably is not installed (it's not part of the " + "standard Python distribution). See the Numeric Python site " + "(https://numpy.org) for information on downloading source or " + "binaries." + ) + print("wxnviz.py: " + msg, file=sys.stderr) + log = None progress = None From 7cbb713fa4a2fc458eafc3eb2b458f690ee5aeba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:45:25 +0000 Subject: [PATCH 02/22] gui.wxpython.nviz: Move ctypes and grass.lib imports below normal imports There is no reason (dependency of imports) apparent here that would prevent it from working. Works locally in a Ubuntu 22.04 WSL on Windows --- gui/wxpython/nviz/wxnviz.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 42c4ea7326a..41e4bcffec2 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -28,6 +28,14 @@ from math import sqrt import wx +import grass.script as gs + +from core.debug import Debug +from core.gcmd import DecodeString +from core.globalvar import wxPythonPhoenix +from core.utils import autoCropImageFromFile +from gui_core.wrap import Rect + try: from ctypes import ( @@ -229,16 +237,6 @@ from grass.lib.vector import Vect_read_colors except (ImportError, OSError, TypeError) as e: print("wxnviz.py: {}".format(e), file=sys.stderr) - -import grass.script as gs - -from core.debug import Debug -from core.gcmd import DecodeString -from core.globalvar import wxPythonPhoenix -from core.utils import autoCropImageFromFile -from gui_core.wrap import Rect - - try: from numpy import matrix except ImportError: From 7759b78c6c266bbbb13a5175f5d1a195f0d99649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:50:45 +0000 Subject: [PATCH 03/22] gui.wxpython.nviz: Sort remaining imports grass.script was already below gui imports in 2023 --- gui/wxpython/nviz/wxnviz.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 41e4bcffec2..90cb7a96e8f 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -28,14 +28,13 @@ from math import sqrt import wx -import grass.script as gs - from core.debug import Debug from core.gcmd import DecodeString from core.globalvar import wxPythonPhoenix from core.utils import autoCropImageFromFile from gui_core.wrap import Rect +import grass.script as gs try: from ctypes import ( From e09f4cfeae76f7e8bb394422d60b25b33f5414e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:58:09 +0000 Subject: [PATCH 04/22] gui.wxpython.nviz: Set render flags dicts as a TypedDict This allows static analysis of self.render dict, that contains 4 keys and all boolean values --- gui/wxpython/nviz/mapwindow.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index 6648e3c50df..d20d4f81e58 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -26,7 +26,7 @@ import sys import time from threading import Thread -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypedDict import wx from wx.lib.newevent import NewEvent @@ -57,6 +57,16 @@ wxUpdateCPlane, EVT_UPDATE_CPLANE = NewEvent() +class RenderTypedDict(TypedDict): + """Typed dictionary to store the render flags for GLWindow. + At runtime, it is a plain dict.""" + + quick: bool + vlines: bool + vpoints: bool + overlays: bool + + class NvizThread(Thread): def __init__(self, log, progressbar, window): Thread.__init__(self) @@ -139,7 +149,7 @@ def __init__( self.context = glcanvas.GLContext(self) # render mode - self.render = { + self.render: RenderTypedDict = { "quick": False, # do not render vector lines in quick mode "vlines": False, From 8ed3f410bddc3210246339412f3e98b46bc03d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:04:15 +0000 Subject: [PATCH 05/22] gui.wxpython.nviz.mapwindow: Light typing annotations --- gui/wxpython/nviz/mapwindow.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index d20d4f81e58..d3d9d37bfad 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -68,21 +68,22 @@ class RenderTypedDict(TypedDict): class NvizThread(Thread): - def __init__(self, log, progressbar, window): + + def __init__(self, log, progressbar, window) -> None: Thread.__init__(self) Debug.msg(5, "NvizThread.__init__():") self.log = log self.progressbar = progressbar self.window = window - self._display = None + self._display: wxnviz.Nviz | None = None self.daemon = True - def run(self): + def run(self) -> None: self._display = wxnviz.Nviz(self.log, self.progressbar) - def GetDisplay(self): + def GetDisplay(self) -> wxnviz.Nviz | None: """Get display instance""" return self._display @@ -144,7 +145,7 @@ def __init__( self.init = False self.initView = False - self.context = None + self.context: glcanvas.GLContext | None = None if CheckWxVersion(version=[2, 9]): self.context = glcanvas.GLContext(self) @@ -197,7 +198,10 @@ def __init__( self.nvizThread = NvizThread(logerr, self.parent.GetProgressBar(), logmsg) self.nvizThread.start() time.sleep(0.1) - self._display = self.nvizThread.GetDisplay() + # self.nvizThread.start() invokes NvizThread.run() in a separate thread, + # which sets the _display attribute returned by GetDisplay(), + # so GetDisplay() shouldn't return None after calling self.nvizThread.start(). + self._display: wxnviz.Nviz | None = self.nvizThread.GetDisplay() # GRASS_REGION needed only for initialization del os.environ["GRASS_REGION"] @@ -780,7 +784,7 @@ def Pixel2Cell(self, xyCoords): return (x, y) - def DoZoom(self, zoomtype, pos): + def DoZoom(self, zoomtype, pos) -> None: """Change perspective and focus""" prev_value = self.view["persp"]["value"] @@ -1195,16 +1199,15 @@ def UpdateLight(self, event): if event.refresh: self.Refresh(False) - def UpdateMap(self, render=True, reRenderTool=False): + def UpdateMap(self, render: bool = True, reRenderTool: bool = False) -> None: """Updates the canvas anytime there is a change to the underlying images or to the geometry of the canvas. :param render: re-render map composition - :type render: bool - :param reRenderTool bool: enable re-render map if True, when - auto re-render map is disabled and - Render map tool is activated from the - Map Display toolbar + :param reRenderTool: enable re-render map if True, when + auto re-render map is disabled and + Render map tool is activated from the + Map Display toolbar """ if not self.parent.mapWindowProperties.autoRender and not reRenderTool: return @@ -1264,7 +1267,7 @@ def UpdateMap(self, render=True, reRenderTool=False): Debug.msg( 3, "GLWindow.UpdateMap(): quick = %d, -> time = %g" - % (self.render["quick"], (stop - start)), + % (int(self.render["quick"]), (stop - start)), ) def EraseMap(self): From 0444061d5f7272d352dbb27445681631c1f865b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:04:55 +0000 Subject: [PATCH 06/22] gui.wxpython.nviz.mapwindow: GetContentScaleFactor() typing annotations --- gui/wxpython/nviz/mapwindow.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index d3d9d37bfad..c5e46d80c82 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -265,7 +265,15 @@ def _warningDepthBuffer(self): ) GMessage(message) - def GetContentScaleFactor(self): + def GetContentScaleFactor(self) -> float: + """See note that wx.glcanvas.GLContext always uses physical pixels, even on the + platforms where wx.Window uses logical pixels, in wx.glcanvas.GLCanvas docs + https://docs.wxpython.org/wx.glcanvas.GLCanvas.html + + Docs for wx.glcanvas.GLCanvas.GetContentScaleFactor() point to + wx.Window.GetContentScaleFactor() at + https://docs.wxpython.org/wx.Window.html#wx.Window.GetContentScaleFactor + """ if sys.platform == "darwin" and not CheckWxVersion(version=[4, 1, 0]): return 1 return super().GetContentScaleFactor() From 543e09dd744932dbcd999f53da93110ece2b5cc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 01:42:27 +0000 Subject: [PATCH 07/22] gui.wxpython.nviz: Make GLWindow.ZoomToMap()'s layer argument optional --- gui/wxpython/nviz/mapwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index c5e46d80c82..a0b59e206da 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -2775,7 +2775,7 @@ def GetDisplay(self): """Get display instance""" return self._display - def ZoomToMap(self, layers): + def ZoomToMap(self, layers: list | None = None) -> None: """Reset view :param layers: so far unused From 0b7da474d9c84ec8a25c56c44fecb5e6843c153b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:41:23 +0000 Subject: [PATCH 08/22] gui.wxpython.core.utils: PIL.Image and wx.Image type annotations --- gui/wxpython/core/utils.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 77f8b5a3c5b..436058aba17 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -12,6 +12,8 @@ @author Jachym Cepicky """ +from __future__ import annotations + import os import sys import platform @@ -21,6 +23,8 @@ import inspect import operator from string import digits +from typing import TYPE_CHECKING + from grass.script import core as grass from grass.script import task as gtask @@ -31,6 +35,11 @@ from core.globalvar import wxPythonPhoenix +if TYPE_CHECKING: + import wx + import PIL.Image + + def cmp(a, b): """cmp function""" return (a > b) - (a < b) @@ -1018,7 +1027,7 @@ def GetGEventAttribsForHandler(method, event): return kwargs, missing_args -def PilImageToWxImage(pilImage, copyAlpha=True): +def PilImageToWxImage(pilImage: PIL.Image.Image, copyAlpha: bool = True) -> wx.Image: """Convert PIL image to wx.Image Based on http://wiki.wxpython.org/WorkingWithImages @@ -1047,7 +1056,7 @@ def PilImageToWxImage(pilImage, copyAlpha=True): return wxImage -def autoCropImageFromFile(filename): +def autoCropImageFromFile(filename) -> wx.Image: """Loads image from file and crops it automatically. If PIL is not installed, it does not crop it. From dc7bdf1fe297aaa609eb75db14f7576d40ff1bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:46:59 +0000 Subject: [PATCH 09/22] gui.wxpython.gui_core.wrap: wx.Image and wx.Bitmap type annotations --- gui/wxpython/gui_core/wrap.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gui/wxpython/gui_core/wrap.py b/gui/wxpython/gui_core/wrap.py index 3a0ca712694..4d332a45210 100644 --- a/gui/wxpython/gui_core/wrap.py +++ b/gui/wxpython/gui_core/wrap.py @@ -15,6 +15,8 @@ @author Anna Petrasova """ +from __future__ import annotations + import sys import wx import wx.lib.agw.floatspin as fs @@ -96,25 +98,25 @@ def luminance(c): return luminance(fg) - luminance(bg) > 0.2 -def BitmapFromImage(image, depth=-1): +def BitmapFromImage(image: wx.Image, depth=-1) -> wx.Bitmap: if wxPythonPhoenix: return wx.Bitmap(img=image, depth=depth) return wx.BitmapFromImage(image, depth=depth) -def ImageFromBitmap(bitmap): +def ImageFromBitmap(bitmap: wx.Bitmap) -> wx.Image: if wxPythonPhoenix: return bitmap.ConvertToImage() return wx.ImageFromBitmap(bitmap) -def EmptyBitmap(width, height, depth=-1): +def EmptyBitmap(width, height, depth=-1) -> wx.Bitmap: if wxPythonPhoenix: return wx.Bitmap(width=width, height=height, depth=depth) return wx.EmptyBitmap(width=width, height=height, depth=depth) -def EmptyImage(width, height, clear=True): +def EmptyImage(width, height, clear=True) -> wx.Image: if wxPythonPhoenix: return wx.Image(width=width, height=height, clear=clear) return wx.EmptyImage(width=width, height=height, clear=clear) From 2f577e5476ff08e1c5ace25b28e4b595221f2554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:48:19 +0000 Subject: [PATCH 10/22] gui.wxpython.gui_core.wrap: wx.Rect and other type annotations --- gui/wxpython/gui_core/wrap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gui/wxpython/gui_core/wrap.py b/gui/wxpython/gui_core/wrap.py index 4d332a45210..b163b069e20 100644 --- a/gui/wxpython/gui_core/wrap.py +++ b/gui/wxpython/gui_core/wrap.py @@ -82,7 +82,7 @@ def convertToInt(argsOrKwargs, roundVal=False): return result -def IsDark(): +def IsDark() -> bool: """Detects if used theme is dark. Wraps wx method for different versions.""" @@ -682,17 +682,17 @@ def __init__(self, *args, **kwargs): kwargs = convertToInt(argsOrKwargs=kwargs) wx.Rect.__init__(self, *args, **kwargs) - def ContainsXY(self, x, y): + def ContainsXY(self, x: float, y: float) -> bool: if wxPythonPhoenix: return wx.Rect.Contains(self, x=int(x), y=int(y)) return wx.Rect.ContainsXY(self, int(x), int(y)) - def ContainsRect(self, rect): + def ContainsRect(self, rect: wx.Rect) -> bool: if wxPythonPhoenix: return wx.Rect.Contains(self, rect=rect) return wx.Rect.ContainsRect(self, rect) - def OffsetXY(self, dx, dy): + def OffsetXY(self, dx: float, dy: float) -> wx.Rect: if wxPythonPhoenix: return wx.Rect.Offset(self, int(dx), int(dy)) return wx.Rect.OffsetXY(self, int(dx), int(dy)) From 318ffeb4ec51f6131a0c40578fc2bdb5ffc5b488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:00:26 +0000 Subject: [PATCH 11/22] gui.wxpython.core.gcmd: Add typing and overloads for EncodeString and DecodeString --- gui/wxpython/core/gcmd.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/gui/wxpython/core/gcmd.py b/gui/wxpython/core/gcmd.py index d8254b4184f..0b8d57fe2fe 100644 --- a/gui/wxpython/core/gcmd.py +++ b/gui/wxpython/core/gcmd.py @@ -36,7 +36,7 @@ import time import traceback from threading import Thread -from typing import TYPE_CHECKING, TextIO +from typing import TYPE_CHECKING, AnyStr, TextIO, overload import wx from core.debug import Debug @@ -59,7 +59,17 @@ from io import TextIOWrapper -def DecodeString(string): +@overload +def DecodeString(string: AnyStr) -> AnyStr | str: + pass + + +@overload +def DecodeString(string: None) -> None: + pass + + +def DecodeString(string: AnyStr | None) -> AnyStr | str | None: """Decode string using system encoding :param string: string to be decoded @@ -75,7 +85,17 @@ def DecodeString(string): return string -def EncodeString(string): +@overload +def EncodeString(string: str) -> bytes | str: + pass + + +@overload +def EncodeString(string: None) -> None: + pass + + +def EncodeString(string: str | None) -> bytes | str | None: """Return encoded string using system locales :param string: string to be encoded From d38c9fa26bff46de10774bd5294188dece9e971f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:14:00 +0000 Subject: [PATCH 12/22] gui: isort mapswipe.mapwindow imports --- gui/wxpython/mapswipe/mapwindow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gui/wxpython/mapswipe/mapwindow.py b/gui/wxpython/mapswipe/mapwindow.py index a203ef2e6d1..e13bd982eff 100644 --- a/gui/wxpython/mapswipe/mapwindow.py +++ b/gui/wxpython/mapswipe/mapwindow.py @@ -18,12 +18,10 @@ """ import wx - from core.debug import Debug from core.settings import UserSettings +from gui_core.wrap import NewId, Rect from mapwin.buffered import BufferedMapWindow -from gui_core.wrap import Rect, NewId - EVT_MY_MOUSE_EVENTS = wx.NewEventType() EVT_MY_MOTION = wx.NewEventType() From ba1d9d385480f7a6255bae44d81afb3beb98d1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:15:03 +0000 Subject: [PATCH 13/22] gui.wxpython.mapswipe.mapwindow: Add type hints related to wx.Size --- gui/wxpython/mapswipe/mapwindow.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gui/wxpython/mapswipe/mapwindow.py b/gui/wxpython/mapswipe/mapwindow.py index e13bd982eff..279ec103609 100644 --- a/gui/wxpython/mapswipe/mapwindow.py +++ b/gui/wxpython/mapswipe/mapwindow.py @@ -17,6 +17,10 @@ @author Anna Kratochvilova """ +from __future__ import annotations + +from typing import Literal + import wx from core.debug import Debug from core.settings import UserSettings @@ -36,17 +40,17 @@ class SwipeBufferedWindow(BufferedMapWindow): Special mouse events with changed coordinates are used. """ - def __init__(self, parent, giface, Map, properties, **kwargs): + def __init__(self, parent, giface, Map, properties, **kwargs) -> None: BufferedMapWindow.__init__( self, parent=parent, giface=giface, Map=Map, properties=properties, **kwargs ) Debug.msg(2, "SwipeBufferedWindow.__init__()") - self.specialSize = super().GetClientSize() + self.specialSize: wx.Size = super().GetClientSize() self.specialCoords = [0, 0] self.imageId = 99 self.movingSash = False - self._mode = "swipe" + self._mode: Literal["swipe", "mirror"] = "swipe" self.lineid = NewId() def _bindMouseEvents(self): @@ -75,18 +79,18 @@ def _mouseActions(self, event): def _mouseMotion(self, event): self._RaiseMouseEvent(event, EVT_MY_MOTION) - def GetClientSize(self): + def GetClientSize(self) -> wx.Size: """Overridden method which returns simulated window size.""" if self._mode == "swipe": return self.specialSize return super().GetClientSize() - def SetClientSize(self, size): + def SetClientSize(self, size: wx.Size) -> None: """Overridden method which sets simulated window size.""" Debug.msg(3, "SwipeBufferedWindow.SetClientSize(): size = %s" % size) self.specialSize = size - def SetMode(self, mode): + def SetMode(self, mode: Literal["swipe", "mirror"]) -> None: """Sets mode of the window. :param mode: mode can be 'swipe' or 'mirror' @@ -99,7 +103,7 @@ def GetImageCoords(self): return self.specialCoords return (0, 0) - def SetImageCoords(self, coords): + def SetImageCoords(self, coords) -> None: """Sets coordinates of rendered image""" Debug.msg( 3, From 581cf77f7f645e3d95fc1fa04b1edb29d8a9fb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:36:26 +0000 Subject: [PATCH 14/22] gui.wxpython.nviz.wxnviz: Add type hints for OGSF/Nviz wrappers Includes using different aliases for different type of ids, to be able to check for usage mismatches --- gui/wxpython/nviz/wxnviz.py | 554 ++++++++++++++++++++++++++++-------- 1 file changed, 443 insertions(+), 111 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 90cb7a96e8f..a38da2ab8b4 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -22,10 +22,13 @@ @author Anna Kratochvilova (Google SoC 2011) """ +from __future__ import annotations + import locale import struct import sys from math import sqrt +from typing import Literal, overload import wx from core.debug import Debug @@ -251,6 +254,28 @@ log = None progress = None +PointId = int +"""Point set id, as used with GP_site_exists(id)""" + +VectorId = int +"""Vector set id, as used with GV_vect_exists(id)""" + +VolumeId = int +"""Volume set id, as used with GVL_vol_exists(id)""" + +SurfaceId = int +"""Surface id, as used with GS_surf_exists(id)""" + +IsosurfaceId = int +"""Isosurface id (0 - MAX_ISOSURFS), as used with GVL_isosurf_get_att, for isosurf_id""" + +SliceId = int +"""Slice id, as used with volume sets in GVL_slice_del(id, slice_id)""" + +ClipPlaneId = int +"""Clip plane id (cplane), as returned by Nviz_get_current_cplane()""" + + def print_error(msg, type): """Redirect stderr""" @@ -335,7 +360,7 @@ def Init(self): GVL_libinit() GVL_init_region() - def ResizeWindow(self, width, height, scale=1): + def ResizeWindow(self, width: int, height: int, scale: float = 1) -> Literal[1, 0]: """GL canvas resized :param width: window width @@ -456,7 +481,7 @@ def SetViewdir(self, x: float, y: float, z: float) -> None: dir[i] = coord GS_set_viewdir(byref(dir)) - def SetZExag(self, z_exag): + def SetZExag(self, z_exag: float) -> Literal[1]: """Set z-exag value :param z_exag: value @@ -629,7 +654,7 @@ def AddConstant(self, value, color): Debug.msg(1, "Nviz::AddConstant(): id=%d", id) return id - def UnloadSurface(self, id): + def UnloadSurface(self, id: SurfaceId) -> Literal[1, 0]: """Unload surface :param id: surface id @@ -681,7 +706,15 @@ def LoadVector(self, name, points): return id, baseId - def UnloadVector(self, id, points): + @overload + def UnloadVector(self, id: PointId, points: Literal[True]) -> Literal[1, 0]: + pass + + @overload + def UnloadVector(self, id: VectorId, points: Literal[False]) -> Literal[1, 0]: + pass + + def UnloadVector(self, id: PointId | VectorId, points: bool) -> Literal[1, 0]: """Unload vector set :param id: vector set id @@ -785,7 +818,7 @@ def LoadVolume(self, name, color_name, color_value): return id - def UnloadVolume(self, id): + def UnloadVolume(self, id: VolumeId) -> Literal[0, 1]: """Unload volume :param id: volume id @@ -803,7 +836,21 @@ def UnloadVolume(self, id): return 1 - def SetSurfaceTopo(self, id, map, value): + @overload + def SetSurfaceTopo( + self, id: SurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceTopo( + self, id: SurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceTopo( + self, id: SurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface topography :param id: surface id @@ -816,7 +863,21 @@ def SetSurfaceTopo(self, id, map, value): """ return self.SetSurfaceAttr(id, ATT_TOPO, map, value) - def SetSurfaceColor(self, id, map, value): + @overload + def SetSurfaceColor( + self, id: SurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceColor( + self, id: SurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceColor( + self, id: SurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface color :param id: surface id @@ -829,14 +890,16 @@ def SetSurfaceColor(self, id, map, value): """ return self.SetSurfaceAttr(id, ATT_COLOR, map, value) - def SetSurfaceMask(self, id, invert, value): + def SetSurfaceMask( + self, id: SurfaceId, invert: bool, value: str + ) -> Literal[1, -1, -2]: """Set surface mask .. todo:: invert :param id: surface id - :param invert: if true invert mask + :param invert: if true invert mask (unimplemented, always true) :param value: map name of value :return: 1 on success @@ -845,7 +908,21 @@ def SetSurfaceMask(self, id, invert, value): """ return self.SetSurfaceAttr(id, ATT_MASK, True, value) - def SetSurfaceTransp(self, id, map, value): + @overload + def SetSurfaceTransp( + self, id: SurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceTransp( + self, id: SurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceTransp( + self, id: SurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface mask ..todo:: @@ -861,7 +938,21 @@ def SetSurfaceTransp(self, id, map, value): """ return self.SetSurfaceAttr(id, ATT_TRANSP, map, value) - def SetSurfaceShine(self, id, map, value): + @overload + def SetSurfaceShine( + self, id: SurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceShine( + self, id: SurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceShine( + self, id: SurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface shininess :param id: surface id @@ -874,7 +965,21 @@ def SetSurfaceShine(self, id, map, value): """ return self.SetSurfaceAttr(id, ATT_SHINE, map, value) - def SetSurfaceEmit(self, id, map, value): + @overload + def SetSurfaceEmit( + self, id: SurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceEmit( + self, id: SurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceEmit( + self, id: SurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface emission (currently unused) :param id: surface id @@ -887,7 +992,33 @@ def SetSurfaceEmit(self, id, map, value): """ return self.SetSurfaceAttr(id, ATT_EMIT, map, value) - def SetSurfaceAttr(self, id, attr, map, value): + @overload + def SetSurfaceAttr( + self, id: SurfaceId, attr: int, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceAttr( + self, id: SurfaceId, attr: int, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceAttr( + self, id: SurfaceId, attr: Literal[2], map: Literal[False], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetSurfaceAttr( + self, id: SurfaceId, attr: int, map: bool, value: str | float + ) -> Literal[1, -1, -2]: + pass + + def SetSurfaceAttr( + self, id: SurfaceId, attr: int, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set surface attribute :param id: surface id @@ -905,7 +1036,9 @@ def SetSurfaceAttr(self, id, attr, map, value): if map: ret = Nviz_set_attr(id, MAP_OBJ_SURF, attr, MAP_ATT, value, -1.0, self.data) else: - val = Nviz_color_from_str(value) if attr == ATT_COLOR else float(value) + val: int | float = ( + Nviz_color_from_str(value) if attr == ATT_COLOR else float(value) + ) ret = Nviz_set_attr(id, MAP_OBJ_SURF, attr, CONST_ATT, None, val, self.data) Debug.msg( @@ -922,7 +1055,7 @@ def SetSurfaceAttr(self, id, attr, map, value): return 1 - def UnsetSurfaceMask(self, id): + def UnsetSurfaceMask(self, id: SurfaceId) -> Literal[1, -1, -2]: """Unset surface mask :param id: surface id @@ -934,7 +1067,7 @@ def UnsetSurfaceMask(self, id): """ return self.UnsetSurfaceAttr(id, ATT_MASK) - def UnsetSurfaceTransp(self, id): + def UnsetSurfaceTransp(self, id: SurfaceId) -> Literal[1, -1, -2]: """Unset surface transparency :param id: surface id @@ -945,7 +1078,7 @@ def UnsetSurfaceTransp(self, id): """ return self.UnsetSurfaceAttr(id, ATT_TRANSP) - def UnsetSurfaceEmit(self, id): + def UnsetSurfaceEmit(self, id: SurfaceId) -> Literal[1, -1, -2]: """Unset surface emission (currently unused) :param id: surface id @@ -956,7 +1089,7 @@ def UnsetSurfaceEmit(self, id): """ return self.UnsetSurfaceAttr(id, ATT_EMIT) - def UnsetSurfaceAttr(self, id, attr): + def UnsetSurfaceAttr(self, id: SurfaceId, attr: int) -> Literal[1, -1, -2]: """Unset surface attribute :param id: surface id @@ -978,7 +1111,9 @@ def UnsetSurfaceAttr(self, id, attr): return 1 - def SetSurfaceRes(self, id, fine, coarse): + def SetSurfaceRes( + self, id: SurfaceId, fine: int, coarse: int + ) -> Literal[1, -1, -2]: """Set surface resolution :param id: surface id @@ -1004,7 +1139,7 @@ def SetSurfaceRes(self, id, fine, coarse): return 1 - def SetSurfaceStyle(self, id, style): + def SetSurfaceStyle(self, id: SurfaceId, style: int) -> Literal[1, -1, -2]: """Set draw style Draw styles: @@ -1041,7 +1176,7 @@ def SetSurfaceStyle(self, id, style): return 1 - def SetWireColor(self, id, color_str): + def SetWireColor(self, id: SurfaceId, color_str: str) -> Literal[1, -1]: """Set color of wire .. todo:: @@ -1053,7 +1188,6 @@ def SetWireColor(self, id, color_str): :return: 1 on success :return: -1 surface not found :return: -2 setting attributes failed - :return: 1 on success :return: 0 on failure """ Debug.msg(3, "Nviz::SetWireColor(): id=%d, color=%s", id, color_str) @@ -1102,7 +1236,9 @@ def GetSurfacePosition(self, id): return [x.value, y.value, z.value] - def SetSurfacePosition(self, id, x, y, z): + def SetSurfacePosition( + self, id: SurfaceId, x: float, y: float, z: float + ) -> Literal[1, -1]: """Set surface position :param id: surface id @@ -1121,13 +1257,15 @@ def SetSurfacePosition(self, id, x, y, z): return 1 - def SetVectorLineMode(self, id, color_str, width, use_z): + def SetVectorLineMode( + self, id: VectorId, color_str: str, width: int, use_z: bool | int + ) -> Literal[1, -1, -2]: """Set mode of vector line overlay :param id: vector id :param color_str: color string :param width: line width - :param use_z: display 3d or on surface + :param use_z: display 3d or on surface, true or non-zero to use z :return: -1 vector set not found :return: -2 on failure @@ -1145,7 +1283,7 @@ def SetVectorLineMode(self, id, color_str, width, use_z): use_z, ) - color = Nviz_color_from_str(color_str) + color: int = Nviz_color_from_str(color_str) # use memory by default if GV_set_style(id, 1, color, width, use_z) < 0: @@ -1153,7 +1291,7 @@ def SetVectorLineMode(self, id, color_str, width, use_z): return 1 - def SetVectorLineHeight(self, id, height): + def SetVectorLineHeight(self, id: VectorId, height: float) -> Literal[1, -1]: """Set vector height above surface (lines) :param id: vector set id @@ -1171,7 +1309,9 @@ def SetVectorLineHeight(self, id, height): return 1 - def SetVectorLineSurface(self, id, surf_id): + def SetVectorLineSurface( + self, id: VectorId, surf_id: SurfaceId + ) -> Literal[1, -1, -2, -3]: """Set reference surface of vector set (lines) :param id: vector set id @@ -1193,7 +1333,9 @@ def SetVectorLineSurface(self, id, surf_id): return 1 - def UnsetVectorLineSurface(self, id, surf_id): + def UnsetVectorLineSurface( + self, id: VectorId, surf_id: SurfaceId + ) -> Literal[1, -1, -2, -3]: """Unset reference surface of vector set (lines) :param id: vector set id @@ -1215,7 +1357,9 @@ def UnsetVectorLineSurface(self, id, surf_id): return 1 - def SetVectorPointMode(self, id, color_str, width, size, marker): + def SetVectorPointMode( + self, id: PointId, color_str: str, width: int, size: float, marker: int + ) -> Literal[1, -1, -2]: """Set mode of vector point overlay :param id: vector id @@ -1244,17 +1388,17 @@ def SetVectorPointMode(self, id, color_str, width, size, marker): marker, ) - color = Nviz_color_from_str(color_str) + color: int = Nviz_color_from_str(color_str) if GP_set_style(id, color, width, size, marker) < 0: return -2 return 1 - def SetVectorPointHeight(self, id, height): + def SetVectorPointHeight(self, id: PointId, height: float) -> Literal[1, -1]: """Set vector height above surface (points) - :param id: vector set id + :param id: point set id :param height: :return: -1 vector set not found @@ -1269,7 +1413,9 @@ def SetVectorPointHeight(self, id, height): return 1 - def SetVectorPointSurface(self, id, surf_id): + def SetVectorPointSurface( + self, id: PointId, surf_id: SurfaceId + ) -> Literal[1, -1, -2, -3]: """Set reference surface of vector set (points) :param id: vector set id @@ -1291,7 +1437,7 @@ def SetVectorPointSurface(self, id, surf_id): return 1 - def ReadVectorColors(self, name, mapset): + def ReadVectorColors(self, name: str, mapset: str) -> Literal[1, 0, -1]: r"""Read vector colors :param name: vector map name @@ -1303,7 +1449,9 @@ def ReadVectorColors(self, name, mapset): """ return Vect_read_colors(name, mapset, self.color) - def CheckColorTable(self, id, type): + def CheckColorTable( + self, id: PointId | VectorId, type: Literal["points", "lines"] + ) -> Literal[1, 0, -1, -2]: """Check if color table exists. :param id: vector set id @@ -1328,14 +1476,14 @@ def CheckColorTable(self, id, type): def SetPointsStyleThematic( self, - id, - layer, - color=None, - colorTable=False, - width=None, - size=None, - symbol=None, - ): + id: PointId, + layer: int, + color: str | None = None, + colorTable: bool = False, + width: str | None = None, + size: str | None = None, + symbol: str | None = None, + ) -> Literal[-1] | None: """Set thematic style for vector points :param id: vector set id @@ -1361,8 +1509,13 @@ def SetPointsStyleThematic( GP_set_style_thematic(id, layer, color, width, size, symbol, None) def SetLinesStyleThematic( - self, id, layer, color=None, colorTable=False, width=None - ): + self, + id: VectorId, + layer: int, + color: str | None = None, + colorTable: bool = False, + width: str | None = None, + ) -> Literal[-1] | None: """Set thematic style for vector lines :param id: vector set id @@ -1385,18 +1538,20 @@ def SetLinesStyleThematic( else: GV_set_style_thematic(id, layer, color, width, None) - def UnsetLinesStyleThematic(self, id): - """Unset thematic style for vector points""" + def UnsetLinesStyleThematic(self, id: VectorId) -> None: + """Unset thematic style for vector lines""" GV_unset_style_thematic(id) - def UnsetPointsStyleThematic(self, id): - """Unset thematic style for vector lines""" + def UnsetPointsStyleThematic(self, id: PointId) -> None: + """Unset thematic style for vector points""" GP_unset_style_thematic(id) - def UnsetVectorPointSurface(self, id, surf_id): + def UnsetVectorPointSurface( + self, id: PointId, surf_id: SurfaceId + ) -> Literal[1, -1, -2, -3]: """Unset reference surface of vector set (points) - :param id: vector set id + :param id: vector point set id :param surf_id: surface id :return: 1 on success @@ -1415,10 +1570,10 @@ def UnsetVectorPointSurface(self, id, surf_id): return 1 - def SetVectorPointZMode(self, id, zMode): + def SetVectorPointZMode(self, id: PointId, zMode: bool) -> Literal[1, 0, -1]: """Set z mode (use z coordinate or not) - :param id: volume id + :param id: vector point set id :param zMode: bool :return: -1 on failure @@ -1430,7 +1585,9 @@ def SetVectorPointZMode(self, id, zMode): return GP_set_zmode(id, int(zMode)) - def AddIsosurface(self, id, level, isosurf_id=None): + def AddIsosurface( + self, id: VolumeId, level: float, isosurf_id: IsosurfaceId | None = None + ) -> Literal[1, -1]: """Add new isosurface :param id: volume id @@ -1456,7 +1613,9 @@ def AddIsosurface(self, id, level, isosurf_id=None): return GVL_isosurf_set_att_const(id, nisosurfs - 1, ATT_TOPO, level) - def AddSlice(self, id, slice_id=None): + def AddSlice( + self, id: VolumeId, slice_id: SliceId | None = None + ) -> int | Literal[-1]: """Add new slice :param id: volume id @@ -1478,7 +1637,9 @@ def AddSlice(self, id, slice_id=None): return GVL_slice_num_slices(id) - def DeleteIsosurface(self, id, isosurf_id): + def DeleteIsosurface( + self, id: VolumeId, isosurf_id: IsosurfaceId + ) -> Literal[1, -1, -2, -3]: """Delete isosurface :param id: volume id @@ -1502,7 +1663,7 @@ def DeleteIsosurface(self, id, isosurf_id): return 1 - def DeleteSlice(self, id, slice_id): + def DeleteSlice(self, id: VolumeId, slice_id: SliceId) -> Literal[1, -1, -2, -3]: """Delete slice :param id: volume id @@ -1526,7 +1687,9 @@ def DeleteSlice(self, id, slice_id): return 1 - def MoveIsosurface(self, id, isosurf_id, up): + def MoveIsosurface( + self, id: VolumeId, isosurf_id: IsosurfaceId, up: bool + ) -> Literal[1, -1, -2, -3]: """Move isosurface up/down in the list :param id: volume id @@ -1554,7 +1717,9 @@ def MoveIsosurface(self, id, isosurf_id, up): return 1 - def MoveSlice(self, id, slice_id, up): + def MoveSlice( + self, id: VolumeId, slice_id: SliceId, up: bool + ) -> Literal[1, -1, -2, -3]: """Move slice up/down in the list :param id: volume id @@ -1582,7 +1747,21 @@ def MoveSlice(self, id, slice_id, up): return 1 - def SetIsosurfaceTopo(self, id, isosurf_id, map, value): + @overload + def SetIsosurfaceTopo( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2, -3]: + pass + + @overload + def SetIsosurfaceTopo( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2, -3]: + pass + + def SetIsosurfaceTopo( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2, -3]: """Set isosurface level :param id: volume id @@ -1597,7 +1776,9 @@ def SetIsosurfaceTopo(self, id, isosurf_id, map, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_TOPO, map, value) - def SetIsosurfaceColor(self, id, isosurf_id, map, value): + def SetIsosurfaceColor( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: bool, value: str + ) -> Literal[1, -1, -2, -3]: """Set isosurface color :param id: volume id @@ -1612,7 +1793,9 @@ def SetIsosurfaceColor(self, id, isosurf_id, map, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_COLOR, map, value) - def SetIsosurfaceMask(self, id, isosurf_id, invert, value): + def SetIsosurfaceMask( + self, id: VolumeId, isosurf_id: IsosurfaceId, invert: bool, value: str + ) -> Literal[1, -1, -2, -3]: """Set isosurface mask .. todo:: @@ -1630,7 +1813,21 @@ def SetIsosurfaceMask(self, id, isosurf_id, invert, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_MASK, True, value) - def SetIsosurfaceTransp(self, id, isosurf_id, map, value): + @overload + def SetIsosurfaceTransp( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceTransp( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetIsosurfaceTransp( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set isosurface transparency :param id: volume id @@ -1645,7 +1842,21 @@ def SetIsosurfaceTransp(self, id, isosurf_id, map, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_TRANSP, map, value) - def SetIsosurfaceShine(self, id, isosurf_id, map, value): + @overload + def SetIsosurfaceShine( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceShine( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetIsosurfaceShine( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set isosurface shininess :param id: volume id @@ -1660,7 +1871,21 @@ def SetIsosurfaceShine(self, id, isosurf_id, map, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_SHINE, map, value) - def SetIsosurfaceEmit(self, id, isosurf_id, map, value): + @overload + def SetIsosurfaceEmit( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[True], value: str + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceEmit( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: Literal[False], value: float + ) -> Literal[1, -1, -2]: + pass + + def SetIsosurfaceEmit( + self, id: VolumeId, isosurf_id: IsosurfaceId, map: bool, value: str | float + ) -> Literal[1, -1, -2]: """Set isosurface emission (currently unused) :param id: volume id @@ -1675,7 +1900,58 @@ def SetIsosurfaceEmit(self, id, isosurf_id, map, value): """ return self.SetIsosurfaceAttr(id, isosurf_id, ATT_EMIT, map, value) - def SetIsosurfaceAttr(self, id, isosurf_id, attr, map, value): + @overload + def SetIsosurfaceAttr( + self, + id: VolumeId, + isosurf_id: IsosurfaceId, + attr: int, + map: Literal[True], + value: str, + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceAttr( + self, + id: VolumeId, + isosurf_id: IsosurfaceId, + attr: int, + map: Literal[False], + value: float, + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceAttr( + self, + id: VolumeId, + isosurf_id: IsosurfaceId, + attr: Literal[2], + map: Literal[False], + value: str, + ) -> Literal[1, -1, -2]: + pass + + @overload + def SetIsosurfaceAttr( + self, + id: VolumeId, + isosurf_id: IsosurfaceId, + attr: int, + map: bool, + value: str | float, + ) -> Literal[1, -1, -2]: + pass + + def SetIsosurfaceAttr( + self, + id: VolumeId, + isosurf_id: IsosurfaceId, + attr: int, + map: bool, + value: str | float, + ) -> Literal[1, -1, -2]: """Set isosurface attribute :param id: volume id @@ -1698,8 +1974,10 @@ def SetIsosurfaceAttr(self, id, isosurf_id, attr, map, value): if map: ret = GVL_isosurf_set_att_map(id, isosurf_id, attr, value) else: - val = Nviz_color_from_str(value) if attr == ATT_COLOR else float(value) - ret = GVL_isosurf_set_att_const(id, isosurf_id, attr, val) + val: int | float = ( + Nviz_color_from_str(value) if attr == ATT_COLOR else float(value) + ) + ret: int = GVL_isosurf_set_att_const(id, isosurf_id, attr, val) Debug.msg( 3, @@ -1717,7 +1995,9 @@ def SetIsosurfaceAttr(self, id, isosurf_id, attr, map, value): return 1 - def UnsetIsosurfaceMask(self, id, isosurf_id): + def UnsetIsosurfaceMask( + self, id: VolumeId, isosurf_id: IsosurfaceId + ) -> Literal[1, -1, -2]: """Unset isosurface mask :param id: volume id @@ -1730,7 +2010,9 @@ def UnsetIsosurfaceMask(self, id, isosurf_id): """ return self.UnsetIsosurfaceAttr(id, isosurf_id, ATT_MASK) - def UnsetIsosurfaceTransp(self, id, isosurf_id): + def UnsetIsosurfaceTransp( + self, id: VolumeId, isosurf_id: IsosurfaceId + ) -> Literal[1, -1, -2]: """Unset isosurface transparency :param id: volume id @@ -1743,7 +2025,9 @@ def UnsetIsosurfaceTransp(self, id, isosurf_id): """ return self.UnsetIsosurfaceAttr(id, isosurf_id, ATT_TRANSP) - def UnsetIsosurfaceEmit(self, id, isosurf_id): + def UnsetIsosurfaceEmit( + self, id: VolumeId, isosurf_id: IsosurfaceId + ) -> Literal[1, -1, -2]: """Unset isosurface emission (currently unused) :param id: volume id @@ -1756,10 +2040,12 @@ def UnsetIsosurfaceEmit(self, id, isosurf_id): """ return self.UnsetIsosurfaceAttr(id, isosurf_id, ATT_EMIT) - def UnsetIsosurfaceAttr(self, id, isosurf_id, attr): + def UnsetIsosurfaceAttr( + self, id: VolumeId, isosurf_id: IsosurfaceId, attr: int + ) -> Literal[1, -1, -2]: """Unset surface attribute - :param id: surface id + :param id: volume id :param isosurf_id: isosurface id (0 - MAX_ISOSURFS) :param attr: attribute descriptor @@ -1789,10 +2075,10 @@ def UnsetIsosurfaceAttr(self, id, isosurf_id, attr): return 1 - def SetIsosurfaceMode(self, id, mode): + def SetIsosurfaceMode(self, id: VolumeId, mode: int) -> Literal[1, -1, -2]: """Set draw mode for isosurfaces - :param id: isosurface id + :param id: volume set id :param mode: isosurface draw mode :return: 1 on success @@ -1809,10 +2095,10 @@ def SetIsosurfaceMode(self, id, mode): return 1 - def SetSliceMode(self, id, mode): + def SetSliceMode(self, id: VolumeId, mode: int) -> Literal[1, -1, -2]: """Set draw mode for slices - :param id: slice id + :param id: volume set id :param mode: slice draw mode :return: 1 on success @@ -1829,10 +2115,10 @@ def SetSliceMode(self, id, mode): return 1 - def SetIsosurfaceRes(self, id, res): + def SetIsosurfaceRes(self, id: VolumeId, res: int) -> Literal[1, -1, -2]: """Set draw resolution for isosurfaces - :param id: isosurface id + :param id: volume set id :param res: resolution value :return: 1 on success @@ -1849,10 +2135,10 @@ def SetIsosurfaceRes(self, id, res): return 1 - def SetSliceRes(self, id, res): + def SetSliceRes(self, id: VolumeId, res: int) -> Literal[1, -1, -2]: """Set draw resolution for slices - :param id: slice id + :param id: volume set id :param res: resolution value :return: 1 on success @@ -1869,7 +2155,18 @@ def SetSliceRes(self, id, res): return 1 - def SetSlicePosition(self, id, slice_id, x1, x2, y1, y2, z1, z2, dir): + def SetSlicePosition( + self, + id: VolumeId, + slice_id: SliceId, + x1: float, + x2: float, + y1: float, + y2: float, + z1: float, + z2: float, + dir: int, + ) -> Literal[1, -1, -2, -3]: """Set slice position :param id: volume id @@ -1891,11 +2188,13 @@ def SetSlicePosition(self, id, slice_id, x1, x2, y1, y2, z1, z2, dir): ret = GVL_slice_set_pos(id, slice_id, x1, x2, y1, y2, z1, z2, dir) if ret < 0: - return -2 + return -3 return 1 - def SetSliceTransp(self, id, slice_id, value): + def SetSliceTransp( + self, id: VolumeId, slice_id: SliceId, value: int + ) -> Literal[1, -1, -2, -3]: """Set slice transparency :param id: volume id @@ -1917,11 +2216,13 @@ def SetSliceTransp(self, id, slice_id, value): ret = GVL_slice_set_transp(id, slice_id, value) if ret < 0: - return -2 + return -3 return 1 - def SetIsosurfaceInOut(self, id, isosurf_id, inout): + def SetIsosurfaceInOut( + self, id: VolumeId, isosurf_id: IsosurfaceId, inout: bool + ) -> Literal[1, -1, -2, -3]: """Set inout mode :param id: volume id @@ -1939,7 +2240,7 @@ def SetIsosurfaceInOut(self, id, isosurf_id, inout): if isosurf_id > GVL_isosurf_num_isosurfs(id) - 1: return -2 - ret = GVL_isosurf_set_flags(id, isosurf_id, inout) + ret: int = GVL_isosurf_set_flags(id, isosurf_id, int(inout)) if ret < 0: return -3 @@ -1971,7 +2272,9 @@ def GetVolumePosition(self, id): return [x.value, y.value, z.value] - def SetVolumePosition(self, id, x, y, z): + def SetVolumePosition( + self, id: VolumeId, x: float, y: float, z: float + ) -> Literal[1, -1]: """Set volume position :param id: volume id @@ -1979,7 +2282,6 @@ def SetVolumePosition(self, id, x, y, z): :return: 1 on success :return: -1 volume not found - :return: -2 setting position failed """ if not GVL_vol_exists(id): return -1 @@ -1990,25 +2292,24 @@ def SetVolumePosition(self, id, x, y, z): return 1 - def SetVolumeDrawBox(self, id, ifBox): + def SetVolumeDrawBox(self, id: VolumeId, ifBox: bool) -> Literal[1, -1]: """Display volume wire box :param id: volume id :param ifBox: True to draw wire box, False otherwise - :type ifBox: bool :return: 1 on success :return: -1 volume not found """ if not GVL_vol_exists(id): return -1 - Debug.msg(3, "Nviz::SetVolumeDrawBox(): id=%d, ifBox=%d", id, ifBox) + Debug.msg(3, "Nviz::SetVolumeDrawBox(): id=%d, ifBox=%d", id, int(ifBox)) GVL_set_draw_wire(id, int(ifBox)) return 1 - def GetCPlaneCurrent(self): + def GetCPlaneCurrent(self) -> ClipPlaneId: return Nviz_get_current_cplane(self.data) def GetCPlanesCount(self) -> int: @@ -2019,7 +2320,7 @@ def GetCPlaneRotation(self) -> tuple[float, float, float]: """Returns rotation parameters of current cutting plane""" x, y, z = c_float(), c_float(), c_float() - current = Nviz_get_current_cplane(self.data) + current: ClipPlaneId = Nviz_get_current_cplane(self.data) Nviz_get_cplane_rotation(self.data, current, byref(x), byref(y), byref(z)) return x.value, y.value, z.value @@ -2028,7 +2329,7 @@ def GetCPlaneTranslation(self) -> tuple[float, float, float]: """Returns translation parameters of current cutting plane""" x, y, z = c_float(), c_float(), c_float() - current = Nviz_get_current_cplane(self.data) + current: ClipPlaneId = Nviz_get_current_cplane(self.data) Nviz_get_cplane_translation(self.data, current, byref(x), byref(y), byref(z)) return x.value, y.value, z.value @@ -2038,7 +2339,7 @@ def SetCPlaneRotation(self, x: float, y: float, z: float) -> None: :param x,y,z: rotation parameters """ - current = Nviz_get_current_cplane(self.data) + current: ClipPlaneId = Nviz_get_current_cplane(self.data) Nviz_set_cplane_rotation(self.data, current, x, y, z) Nviz_draw_cplane(self.data, -1, -1) @@ -2047,15 +2348,17 @@ def SetCPlaneTranslation(self, x: float, y: float, z: float) -> None: :param x,y,z: translation parameters """ - current = Nviz_get_current_cplane(self.data) + current: ClipPlaneId = Nviz_get_current_cplane(self.data) Nviz_set_cplane_translation(self.data, current, x, y, z) Nviz_draw_cplane(self.data, -1, -1) Debug.msg( 3, "Nviz::SetCPlaneTranslation(): id=%d, x=%f, y=%f, z=%f", current, x, y, z ) - def SetCPlaneInteractively(self, x, y): - current = Nviz_get_current_cplane(self.data) + def SetCPlaneInteractively( + self, x: float, y: float + ) -> tuple[float, float, float] | tuple[None, None, None]: + current: ClipPlaneId = Nviz_get_current_cplane(self.data) ret = Nviz_set_cplane_here(self.data, current, x, y) if ret: Nviz_draw_cplane(self.data, -1, -1) @@ -2063,14 +2366,14 @@ def SetCPlaneInteractively(self, x, y): return x, y, z return None, None, None - def SelectCPlane(self, index): + def SelectCPlane(self, index: ClipPlaneId) -> None: """Select cutting plane :param index: index of cutting plane """ Nviz_on_cplane(self.data, index) - def UnselectCPlane(self, index): + def UnselectCPlane(self, index: ClipPlaneId) -> None: """Unselect cutting plane :param index: index of cutting plane @@ -2094,7 +2397,13 @@ def GetZRange(self) -> tuple[float, float]: Nviz_get_zrange(self.data, byref(min), byref(max)) return min.value, max.value - def SaveToFile(self, filename, width=20, height=20, itype="ppm"): + def SaveToFile( + self, + filename: str, + width: int = 20, + height: int = 20, + itype: Literal["ppm", "tif"] = "ppm", + ) -> None: """Save current GL screen to ppm/tif file :param filename: file name @@ -2124,7 +2433,16 @@ def DrawFringe(self) -> None: """Draw fringe""" Nviz_draw_fringe(self.data) - def SetFringe(self, sid, color, elev, nw=False, ne=False, sw=False, se=False): + def SetFringe( + self, + sid: SurfaceId, + color: tuple[int, int, int] | tuple[int, int, int, int], + elev: float, + nw: bool = False, + ne: bool = False, + sw: bool = False, + se: bool = False, + ) -> None: """Set fringe :param sid: surface id @@ -2144,16 +2462,18 @@ def SetFringe(self, sid, color, elev, nw=False, ne=False, sw=False, se=False): int(se), ) - def DrawArrow(self): + def DrawArrow(self) -> Literal[1]: """Draw north arrow""" return Nviz_draw_arrow(self.data) - def SetArrow(self, sx, sy, size, color): + def SetArrow(self, sx: int, sy: int, size: float, color: str) -> Literal[1, 0]: """Set north arrow from canvas coordinates :param sx,sy: canvas coordinates :param size: arrow length :param color: arrow color + :return: 1 on success + :return: 0 on failure (no surfaces found) """ return Nviz_set_arrow(self.data, sx, sy, size, Nviz_color_from_str(color)) @@ -2183,7 +2503,9 @@ def DeleteScalebar(self, id: int) -> None: """Delete scalebar""" Nviz_delete_scalebar(self.data, id) - def GetPointOnSurface(self, sx, sy, scale=1): + def GetPointOnSurface( + self, sx, sy, scale: float = 1 + ) -> tuple[SurfaceId, float, float, float] | tuple[None, None, None, None]: """Get point on surface :param sx,sy: canvas coordinates (LL) @@ -2226,7 +2548,13 @@ def QueryMap(self, sx, sy, scale=1): "color": DecodeString(valstr.value), } - def GetDistanceAlongSurface(self, sid, p1, p2, useExag=True): + def GetDistanceAlongSurface( + self, + sid: SurfaceId, + p1: tuple[float, float], + p2: tuple[float, float], + useExag: bool = True, + ) -> float: """Get distance measured along surface""" d = c_float() @@ -2325,11 +2653,15 @@ def __init__(self, filepath, overlayId, coords): :param coords: image coordinates """ self.path = filepath - self.image = autoCropImageFromFile(filepath) + self.image: wx.Image = autoCropImageFromFile(filepath) + self.width: int + self.orig_width: int + self.height: int + self.orig_height: int self.width = self.orig_width = self.image.GetWidth() self.height = self.orig_height = self.image.GetHeight() - self.id = overlayId - self.coords = coords + self.id: int = overlayId + self.coords: tuple[int, int] = coords self.active = True # alpha needs to be initialized From a4b781ae6d535ea3fc209f9d87f111a10b8b4fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:39:58 +0000 Subject: [PATCH 15/22] gui.wxpython.nviz.wxnviz: Add type hints for QueryMap with TypedDict gui.wxpython.nviz.wxnviz: Add some type hints for Texture and ImageTexture --- gui/wxpython/nviz/wxnviz.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index a38da2ab8b4..4430c96f5d1 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -28,7 +28,7 @@ import struct import sys from math import sqrt -from typing import Literal, overload +from typing import TYPE_CHECKING, Literal, TypedDict, overload import wx from core.debug import Debug @@ -251,6 +251,13 @@ ) print("wxnviz.py: " + msg, file=sys.stderr) +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping + from os import PathLike + + from _typeshed import StrPath + + log = None progress = None @@ -276,6 +283,14 @@ """Clip plane id (cplane), as returned by Nviz_get_current_cplane()""" +class QueryMapResult(TypedDict): + id: SurfaceId + x: int + y: int + z: int + elevation: str + color: str + def print_error(msg, type): """Redirect stderr""" @@ -2525,7 +2540,7 @@ def GetPointOnSurface( return (sid.value, x.value, y.value, z.value) - def QueryMap(self, sx, sy, scale=1): + def QueryMap(self, sx, sy, scale: float = 1) -> QueryMapResult | None: """Query surface map :param sx,sy: canvas coordinates (LL) @@ -2626,7 +2641,9 @@ def SetRotationMatrix(self, matrix): def Start2D(self): Nviz_set_2D(self.width, self.height) - def FlyThrough(self, flyInfo, mode, exagInfo): + def FlyThrough( + self, flyInfo: Iterable[float], mode: int, exagInfo: Mapping[str, float | int] + ): """Fly through the scene :param flyInfo: fly parameters @@ -2645,7 +2662,9 @@ def FlyThrough(self, flyInfo, mode, exagInfo): class Texture: """Class representing OpenGL texture""" - def __init__(self, filepath, overlayId, coords): + def __init__( + self, filepath: StrPath, overlayId: int, coords: tuple[int, int] + ) -> None: """Load image to texture :param filepath: path to image file @@ -2773,7 +2792,9 @@ def IsActive(self) -> bool: class ImageTexture(Texture): """Class representing OpenGL texture as an overlay image""" - def __init__(self, filepath, overlayId, coords, cmd): + def __init__( + self, filepath: StrPath, overlayId, coords: tuple[int, int], cmd + ) -> None: """Load image to texture :param filepath: path to image file From 4150039ddfd2d5033847443ab18c3741d18c4326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:41:36 +0000 Subject: [PATCH 16/22] gui.wxpython.nviz.wxnviz: Add typing to functions that return empty vectors, considered tuples --- gui/wxpython/nviz/wxnviz.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 4430c96f5d1..1e0ee86f878 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -464,7 +464,9 @@ def LookAtCenter(self): Nviz_set_focus_map(MAP_OBJ_UNDEFINED, -1) Debug.msg(3, "Nviz::LookAtCenter()") - def GetFocus(self): + def GetFocus( + self, + ) -> tuple[float, float, float] | tuple[Literal[-1], Literal[-1], Literal[-1]]: """Get focus""" Debug.msg(3, "Nviz::GetFocus()") if Nviz_has_focus(self.data): @@ -473,7 +475,7 @@ def GetFocus(self): z = c_float() Nviz_get_focus(self.data, byref(x), byref(y), byref(z)) return x.value, y.value, z.value - return -1, -1, -1 + return (-1, -1, -1) def SetFocus(self, x: float, y: float, z: float) -> None: """Set focus""" @@ -1226,7 +1228,9 @@ def SetWireColor(self, id: SurfaceId, color_str: str) -> Literal[1, -1]: return 1 - def GetSurfacePosition(self, id): + def GetSurfacePosition( + self, id: SurfaceId + ) -> tuple[()] | tuple[float, float, float]: """Get surface position :param id: surface id @@ -1235,7 +1239,7 @@ def GetSurfacePosition(self, id): :return: zero-length vector on error """ if not GS_surf_exists(id): - return [] + return () x, y, z = c_float(), c_float(), c_float() GS_get_trans(id, byref(x), byref(y), byref(z)) @@ -1249,7 +1253,7 @@ def GetSurfacePosition(self, id): z.value, ) - return [x.value, y.value, z.value] + return (x.value, y.value, z.value) def SetSurfacePosition( self, id: SurfaceId, x: float, y: float, z: float @@ -2262,7 +2266,7 @@ def SetIsosurfaceInOut( return 1 - def GetVolumePosition(self, id): + def GetVolumePosition(self, id: VolumeId) -> tuple[()] | tuple[float, float, float]: """Get volume position :param id: volume id @@ -2271,7 +2275,7 @@ def GetVolumePosition(self, id): :return: zero-length vector on error """ if not GVL_vol_exists(id): - return [] + return () x, y, z = c_float(), c_float(), c_float() GVL_get_trans(id, byref(x), byref(y), byref(z)) @@ -2285,7 +2289,7 @@ def GetVolumePosition(self, id): z.value, ) - return [x.value, y.value, z.value] + return (x.value, y.value, z.value) def SetVolumePosition( self, id: VolumeId, x: float, y: float, z: float From c47d892c2164c3beb0af249960e66c68dbf0ffbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:43:02 +0000 Subject: [PATCH 17/22] gui.wxpython.nviz.wxnviz: Address type checking warnings in QueryMap When results of GetPointOnSurface() is None, ensure that we return None, and the other branch is assured to contain floats --- gui/wxpython/nviz/wxnviz.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 1e0ee86f878..5dc69d23b23 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -2550,9 +2550,8 @@ def QueryMap(self, sx, sy, scale: float = 1) -> QueryMapResult | None: :param sx,sy: canvas coordinates (LL) """ sid, x, y, z = self.GetPointOnSurface(sx, sy, scale) - if not sid: + if not sid or (x is None or y is None or z is None): return None - catstr = create_string_buffer(256) valstr = create_string_buffer(256) GS_get_cat_at_xy(sid, ATT_TOPO, catstr, x, y) From 43a383ae8fdaa9664a3322f00e4ecd01c25f9b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:45:20 +0000 Subject: [PATCH 18/22] gui.wxpython.nviz.wxnviz: Remove unused import --- gui/wxpython/nviz/wxnviz.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 5dc69d23b23..3d646019803 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -253,7 +253,6 @@ if TYPE_CHECKING: from collections.abc import Iterable, Mapping - from os import PathLike from _typeshed import StrPath From 1b6d041550147c82c6a20d8d1cec73ff8d70e85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:47:56 +0000 Subject: [PATCH 19/22] gui.wxpython.nviz.mapwindow: Sort imports with an isort split group --- gui/wxpython/nviz/mapwindow.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index a0b59e206da..8de45cd0e1c 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -28,24 +28,25 @@ from threading import Thread from typing import TYPE_CHECKING, TypedDict -import wx -from wx.lib.newevent import NewEvent -from wx import glcanvas -from wx.glcanvas import WX_GL_DEPTH_SIZE, WX_GL_DOUBLEBUFFER, WX_GL_RGBA - import grass.script as gs from grass.pydispatch.signal import Signal -from core.gcmd import GMessage, GException, GError +# isort: split + +import wx from core.debug import Debug -from mapwin.base import MapWindowBase -from core.settings import UserSettings -from nviz.workspace import NvizSettings -from nviz.animation import Animation -from nviz import wxnviz +from core.gcmd import GError, GException, GMessage +from core.giface import Notification from core.globalvar import CheckWxVersion +from core.settings import UserSettings from core.utils import str2rgb -from core.giface import Notification +from mapwin.base import MapWindowBase +from nviz import wxnviz +from nviz.animation import Animation +from nviz.workspace import NvizSettings +from wx import glcanvas +from wx.glcanvas import WX_GL_DEPTH_SIZE, WX_GL_DOUBLEBUFFER, WX_GL_RGBA +from wx.lib.newevent import NewEvent if TYPE_CHECKING: import lmgr.frame From d8e84fc9f6949be127380282daf3d6aab620d1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:56:02 +0000 Subject: [PATCH 20/22] gui.wxpython.nviz.mapwindow: Add some type hints --- gui/wxpython/nviz/mapwindow.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index 8de45cd0e1c..051f9a8a9f9 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -26,7 +26,7 @@ import sys import time from threading import Thread -from typing import TYPE_CHECKING, TypedDict +from typing import TYPE_CHECKING, Literal, TypedDict import grass.script as gs from grass.pydispatch.signal import Signal @@ -773,7 +773,7 @@ def OnDragging(self, event): event.Skip() - def Pixel2Cell(self, xyCoords): + def Pixel2Cell(self, xyCoords) -> tuple[float, float] | None: """Convert image coordinates to real word coordinates :param xyCoords: image coordinates @@ -924,11 +924,11 @@ def FocusPanning(self, event): self.render["quick"] = True self.Refresh(False) - def HorizontalPanning(self, event): + def HorizontalPanning(self, event) -> None: """Move all layers in horizontal (x, y) direction. Currently not used. """ - size = self.GetClientSize() + size: wx.Size | tuple[int, int] = self.GetClientSize() id1, x1, y1, z1 = self._display.GetPointOnSurface( self.mouse["tmp"][0], size[1] - self.mouse["tmp"][1], @@ -2086,7 +2086,9 @@ def UpdateSurfaceProperties(self, id, data): data["position"].pop("update") data["draw"]["all"] = False - def UpdateVolumeProperties(self, id, data, isosurfId=None): + def UpdateVolumeProperties( + self, id: wxnviz.VolumeId, data, isosurfId: wxnviz.IsosurfaceId | None = None + ) -> None: """Update volume (isosurface/slice) map object properties""" if "update" in data["draw"]["resolution"]: if data["draw"]["mode"]["value"] == 0: @@ -2122,7 +2124,7 @@ def UpdateVolumeProperties(self, id, data, isosurfId=None): # # isosurface attributes # - isosurfId = 0 + isosurfId: wxnviz.IsosurfaceId = 0 for isosurf in data["isosurface"]: self._display.AddIsosurface(id, 0, isosurf_id=isosurfId) for attrb in ("topo", "color", "mask", "transp", "shine"): @@ -2209,7 +2211,7 @@ def UpdateVectorProperties(self, id, data, type): else: self.UpdateVectorLinesProperties(id, data[type]) - def UpdateVectorLinesProperties(self, id, data): + def UpdateVectorLinesProperties(self, id: wxnviz.VectorId, data) -> None: """Update vector line map object properties""" # mode if ( @@ -2276,7 +2278,7 @@ def UpdateVectorLinesProperties(self, id, data): if "update" in data["mode"]: data["mode"].pop("update") - def UpdateVectorPointsProperties(self, id, data): + def UpdateVectorPointsProperties(self, id: wxnviz.PointId, data) -> None: """Update vector point map object properties""" if ( "update" in data["size"] @@ -2751,7 +2753,7 @@ def OnNvizCmd(self): """Generate and write command to command output""" self.log.WriteLog(self.NvizCmdCommand(), notification=Notification.RAISE_WINDOW) - def SaveToFile(self, FileName, FileType, width, height): + def SaveToFile(self, FileName, FileType: Literal["ppm", "tif"], width, height): """This draws the DC to a buffer that can be saved to a file. .. todo:: From 3263de2ef849a4ada64dd3c86347a60d9cd4e0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:57:50 +0000 Subject: [PATCH 21/22] gui.wxpython.mapdisp.frame: Sort imports with an isort split group --- gui/wxpython/mapdisp/frame.py | 39 ++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/gui/wxpython/mapdisp/frame.py b/gui/wxpython/mapdisp/frame.py index 119a14a8159..1f5b021f4f7 100644 --- a/gui/wxpython/mapdisp/frame.py +++ b/gui/wxpython/mapdisp/frame.py @@ -29,18 +29,30 @@ from typing import TYPE_CHECKING from core import globalvar + +# isort: split import wx import wx.aui - -from mapdisp.toolbars import MapToolbar, NvizIcons -from mapdisp.gprint import PrintOptions -from core.gcmd import GError, GMessage, RunCommand -from core.utils import ListOfCatsToRange, GetLayerNameFromCmd -from gui_core.dialogs import GetImageHandlers, ImageSizeDialog from core.debug import Debug +from core.gcmd import GError, GMessage, RunCommand +from core.giface import Notification from core.settings import UserSettings -from gui_core.mapdisp import SingleMapPanel, FrameMixin -from gui_core.query import QueryDialog, PrepareQueryResults +from core.utils import GetLayerNameFromCmd, ListOfCatsToRange +from gui_core.dialogs import GetImageHandlers, ImageSizeDialog +from gui_core.forms import GUI +from gui_core.mapdisp import FrameMixin, SingleMapPanel +from gui_core.query import PrepareQueryResults, QueryDialog +from gui_core.vselect import VectorSelectBase, VectorSelectHighlighter +from gui_core.wrap import Menu +from main_window.page import MainPageBase +from mapdisp import statusbar as sb +from mapdisp.gprint import PrintOptions +from mapdisp.toolbars import MapToolbar, NvizIcons +from mapwin.analysis import ( + MeasureAreaController, + MeasureDistanceController, + ProfileController, +) from mapwin.buffered import BufferedMapWindow from mapwin.decorations import ( ArrowController, @@ -49,17 +61,6 @@ LegendController, LegendVectController, ) -from mapwin.analysis import ( - MeasureAreaController, - MeasureDistanceController, - ProfileController, -) -from gui_core.forms import GUI -from core.giface import Notification -from gui_core.vselect import VectorSelectBase, VectorSelectHighlighter -from gui_core.wrap import Menu -from mapdisp import statusbar as sb -from main_window.page import MainPageBase import grass.script as gs from grass.pydispatch.signal import Signal From 8b576c73a8031729cf5df7e002d452150efb9b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Wed, 25 Dec 2024 21:12:58 +0000 Subject: [PATCH 22/22] gui.wxpython.nviz.mapwindow: Handle some cases where self._display is None or other variables are missing --- gui/wxpython/nviz/mapwindow.py | 51 ++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index 051f9a8a9f9..c40e2156625 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -795,7 +795,9 @@ def Pixel2Cell(self, xyCoords) -> tuple[float, float] | None: def DoZoom(self, zoomtype, pos) -> None: """Change perspective and focus""" - + if self.view is None: + # Cannot do any useful actions here if self.view is None + return prev_value = self.view["persp"]["value"] if zoomtype > 0: value = -1 * self.view["persp"]["step"] @@ -808,7 +810,7 @@ def DoZoom(self, zoomtype, pos) -> None: self.view["persp"]["value"] = 180 if prev_value != self.view["persp"]["value"]: - if hasattr(self.lmgr, "nviz"): + if hasattr(self.lmgr, "nviz") and self._display is not None: self.lmgr.nviz.UpdateSettings() x, y = pos[0], self.GetClientSize()[1] - pos[1] result = self._display.GetPointOnSurface( @@ -818,14 +820,16 @@ def DoZoom(self, zoomtype, pos) -> None: self._display.LookHere(x, y, self.GetContentScaleFactor()) focus = self._display.GetFocus() for i, coord in enumerate(("x", "y", "z")): - self.iview["focus"][coord] = focus[i] - self._display.SetView( - self.view["position"]["x"], - self.view["position"]["y"], - self.iview["height"]["value"], - self.view["persp"]["value"], - self.view["twist"]["value"], - ) + if self.iview is not None: + self.iview["focus"][coord] = focus[i] + if self.iview is not None: + self._display.SetView( + self.view["position"]["x"], + self.view["position"]["y"], + self.iview["height"]["value"], + self.view["persp"]["value"], + self.view["twist"]["value"], + ) self.saveHistory = True # redraw map self.DoPaint() @@ -901,6 +905,9 @@ def OnDClick(self, event): def FocusPanning(self, event): """Simulation of panning using focus""" size = self.GetClientSize() + if self._display is None: + msg = "self._display should not be None after starting NvizThread" + raise GException(msg) id1, x1, y1, z1 = self._display.GetPointOnSurface( self.mouse["tmp"][0], size[1] - self.mouse["tmp"][1], @@ -909,6 +916,18 @@ def FocusPanning(self, event): id2, x2, y2, z2 = self._display.GetPointOnSurface( event.GetX(), size[1] - event.GetY(), self.GetContentScaleFactor() ) + if ( + id1 is None + or id2 is None + or x1 is None + or x2 is None + or y1 is None + or y2 is None + or z1 is None + or z2 is None + or self.iview is None + ): + return if id1 and id1 == id2: dx, dy, dz = x2 - x1, y2 - y1, z2 - z1 focus = self.iview["focus"] @@ -929,6 +948,9 @@ def HorizontalPanning(self, event) -> None: Currently not used. """ size: wx.Size | tuple[int, int] = self.GetClientSize() + if self._display is None: + msg = "self._display should not be None after starting NvizThread" + raise GException(msg) id1, x1, y1, z1 = self._display.GetPointOnSurface( self.mouse["tmp"][0], size[1] - self.mouse["tmp"][1], @@ -1065,7 +1087,11 @@ def GoTo(self, e, n): def QuerySurface(self, x, y): """Query surface on given position""" size = self.GetClientSize() - result = self._display.QueryMap(x, size[1] - y, self.GetContentScaleFactor()) + result = None + if self._display is not None: + result = self._display.QueryMap( + x, size[1] - y, self.GetContentScaleFactor() + ) if result: self.qpoints.append((result["x"], result["y"], result["z"])) self.log.WriteLog("%-30s: %.3f" % (_("Easting"), result["x"])) @@ -2280,6 +2306,9 @@ def UpdateVectorLinesProperties(self, id: wxnviz.VectorId, data) -> None: def UpdateVectorPointsProperties(self, id: wxnviz.PointId, data) -> None: """Update vector point map object properties""" + if self._display is None: + msg = "self._display should not be None after starting NvizThread" + raise GException(msg) if ( "update" in data["size"] or "update" in data["width"]