From 552e00b8978ede232f134a4303124bc99a06d4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:49:52 -0500 Subject: [PATCH 1/6] gui(nviz): Type hinting to the nviz library wrapper and various related improvements (#4899) * gui.wxpython.nviz: numpy import can safely be below * 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: Sort remaining imports grass.script was already below gui imports in 2023 * 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: Light typing annotations * gui.wxpython.nviz.mapwindow: GetContentScaleFactor() typing annotations * gui.wxpython.nviz: Make GLWindow.ZoomToMap()'s layer argument optional * gui.wxpython.core.utils: PIL.Image and wx.Image type annotations * gui.wxpython.gui_core.wrap: wx.Image and wx.Bitmap type annotations * gui.wxpython.gui_core.wrap: wx.Rect and other type annotations * gui.wxpython.core.gcmd: Add typing and overloads for EncodeString and DecodeString * gui: isort mapswipe.mapwindow imports * gui.wxpython.mapswipe.mapwindow: Add type hints related to wx.Size * 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: Add type hints for QueryMap with TypedDict gui.wxpython.nviz.wxnviz: Add some type hints for Texture and ImageTexture * gui.wxpython.nviz.wxnviz: Add typing to functions that return empty vectors, considered tuples * 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: Remove unused import * gui.wxpython.nviz.mapwindow: Sort imports with an isort split group * gui.wxpython.nviz.mapwindow: Add some type hints * gui.wxpython.mapdisp.frame: Sort imports with an isort split group * gui.wxpython.nviz.mapwindow: Handle some cases where self._display is None or other variables are missing --- gui/wxpython/core/gcmd.py | 26 +- gui/wxpython/core/utils.py | 13 +- gui/wxpython/gui_core/wrap.py | 18 +- gui/wxpython/mapdisp/frame.py | 39 +- gui/wxpython/mapswipe/mapwindow.py | 22 +- gui/wxpython/nviz/mapwindow.py | 151 ++++--- gui/wxpython/nviz/wxnviz.py | 639 ++++++++++++++++++++++------- 7 files changed, 674 insertions(+), 234 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 diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 47fd14003b3..9ae88cb05a4 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) @@ -1011,7 +1020,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 @@ -1040,7 +1049,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. diff --git a/gui/wxpython/gui_core/wrap.py b/gui/wxpython/gui_core/wrap.py index 3a0ca712694..b163b069e20 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 @@ -80,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.""" @@ -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) @@ -680,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)) diff --git a/gui/wxpython/mapdisp/frame.py b/gui/wxpython/mapdisp/frame.py index 0be010f3432..52eae9f6224 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 diff --git a/gui/wxpython/mapswipe/mapwindow.py b/gui/wxpython/mapswipe/mapwindow.py index a203ef2e6d1..279ec103609 100644 --- a/gui/wxpython/mapswipe/mapwindow.py +++ b/gui/wxpython/mapswipe/mapwindow.py @@ -17,13 +17,15 @@ @author Anna Kratochvilova """ -import wx +from __future__ import annotations + +from typing import Literal +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() @@ -38,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): @@ -77,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' @@ -101,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, diff --git a/gui/wxpython/nviz/mapwindow.py b/gui/wxpython/nviz/mapwindow.py index cf0bd87b740..0498bd21bbe 100644 --- a/gui/wxpython/nviz/mapwindow.py +++ b/gui/wxpython/nviz/mapwindow.py @@ -26,26 +26,27 @@ import sys import time from threading import Thread -from typing import TYPE_CHECKING - -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 +from typing import TYPE_CHECKING, Literal, TypedDict 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 @@ -57,22 +58,33 @@ 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): + + 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 @@ -134,12 +146,12 @@ 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) # render mode - self.render = { + self.render: RenderTypedDict = { "quick": False, # do not render vector lines in quick mode "vlines": False, @@ -187,7 +199,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"] @@ -251,7 +266,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() @@ -750,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 @@ -770,9 +793,11 @@ def Pixel2Cell(self, xyCoords): return (x, y) - def DoZoom(self, zoomtype, pos): + 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"] @@ -785,7 +810,7 @@ def DoZoom(self, zoomtype, pos): 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( @@ -795,14 +820,16 @@ def DoZoom(self, zoomtype, pos): 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() @@ -878,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], @@ -886,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"] @@ -901,11 +943,14 @@ 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() + 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], @@ -1042,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"])) @@ -1185,16 +1234,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 @@ -1254,7 +1302,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): @@ -2063,7 +2111,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: @@ -2099,7 +2149,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"): @@ -2186,7 +2236,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 ( @@ -2253,8 +2303,11 @@ 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 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"] @@ -2728,7 +2781,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:: @@ -2753,7 +2806,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 diff --git a/gui/wxpython/nviz/wxnviz.py b/gui/wxpython/nviz/wxnviz.py index 63be18a69c1..3d646019803 100644 --- a/gui/wxpython/nviz/wxnviz.py +++ b/gui/wxpython/nviz/wxnviz.py @@ -22,24 +22,22 @@ @author Anna Kratochvilova (Google SoC 2011) """ +from __future__ import annotations + import locale import struct 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) +from typing import TYPE_CHECKING, Literal, TypedDict, overload import wx +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 ( @@ -241,18 +239,57 @@ from grass.lib.vector import Vect_read_colors except (ImportError, OSError, TypeError) as e: print("wxnviz.py: {}".format(e), file=sys.stderr) +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 grass.script as gs +if TYPE_CHECKING: + from collections.abc import Iterable, Mapping + + from _typeshed import StrPath -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 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()""" + + +class QueryMapResult(TypedDict): + id: SurfaceId + x: int + y: int + z: int + elevation: str + color: str + def print_error(msg, type): """Redirect stderr""" @@ -337,7 +374,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 @@ -426,7 +463,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): @@ -435,7 +474,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""" @@ -458,7 +497,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 @@ -631,7 +670,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 @@ -683,7 +722,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 @@ -787,7 +834,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 @@ -805,7 +852,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 @@ -818,7 +879,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 @@ -831,14 +906,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 @@ -847,7 +924,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:: @@ -863,7 +954,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 @@ -876,7 +981,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 @@ -889,7 +1008,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 @@ -907,7 +1052,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( @@ -924,7 +1071,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 @@ -936,7 +1083,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 @@ -947,7 +1094,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 @@ -958,7 +1105,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 @@ -980,7 +1127,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 @@ -1006,7 +1155,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: @@ -1043,7 +1192,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:: @@ -1055,7 +1204,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) @@ -1079,7 +1227,9 @@ def SetWireColor(self, id, color_str): return 1 - def GetSurfacePosition(self, id): + def GetSurfacePosition( + self, id: SurfaceId + ) -> tuple[()] | tuple[float, float, float]: """Get surface position :param id: surface id @@ -1088,7 +1238,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)) @@ -1102,9 +1252,11 @@ def GetSurfacePosition(self, id): z.value, ) - return [x.value, y.value, z.value] + 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 @@ -1123,13 +1275,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 @@ -1147,7 +1301,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: @@ -1155,7 +1309,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 @@ -1173,7 +1327,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 @@ -1195,7 +1351,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 @@ -1217,7 +1375,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 @@ -1246,17 +1406,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 @@ -1271,7 +1431,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 @@ -1293,7 +1455,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 @@ -1305,7 +1467,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 @@ -1330,14 +1494,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 @@ -1363,8 +1527,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 @@ -1387,18 +1556,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 @@ -1417,10 +1588,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 @@ -1432,7 +1603,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 @@ -1458,7 +1631,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 @@ -1480,7 +1655,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 @@ -1504,7 +1681,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 @@ -1528,7 +1705,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 @@ -1556,7 +1735,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 @@ -1584,7 +1765,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 @@ -1599,7 +1794,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 @@ -1614,7 +1811,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:: @@ -1632,7 +1831,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 @@ -1647,7 +1860,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 @@ -1662,7 +1889,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 @@ -1677,7 +1918,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 @@ -1700,8 +1992,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, @@ -1719,7 +2013,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 @@ -1732,7 +2028,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 @@ -1745,7 +2043,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 @@ -1758,10 +2058,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 @@ -1791,10 +2093,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 @@ -1811,10 +2113,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 @@ -1831,10 +2133,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 @@ -1851,10 +2153,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 @@ -1871,7 +2173,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 @@ -1893,11 +2206,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 @@ -1919,11 +2234,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 @@ -1941,14 +2258,14 @@ 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 return 1 - def GetVolumePosition(self, id): + def GetVolumePosition(self, id: VolumeId) -> tuple[()] | tuple[float, float, float]: """Get volume position :param id: volume id @@ -1957,7 +2274,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)) @@ -1971,9 +2288,11 @@ def GetVolumePosition(self, id): z.value, ) - return [x.value, y.value, z.value] + 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 @@ -1981,7 +2300,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 @@ -1992,25 +2310,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: @@ -2021,7 +2338,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 @@ -2030,7 +2347,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 @@ -2040,7 +2357,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) @@ -2049,15 +2366,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) @@ -2065,14 +2384,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 @@ -2096,7 +2415,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 @@ -2126,7 +2451,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 @@ -2146,16 +2480,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)) @@ -2185,7 +2521,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) @@ -2205,15 +2543,14 @@ def GetPointOnSurface(self, sx, sy, scale=1): 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) """ 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) @@ -2228,7 +2565,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() @@ -2300,7 +2643,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 @@ -2319,7 +2664,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 @@ -2327,11 +2674,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 @@ -2443,7 +2794,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 54553dff8b6855307ca43640e3843ef86d871f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:35:14 -0500 Subject: [PATCH 2/6] Windows: Apply OSGeo4W patches for Postgres (#4996) --- mswindows/osgeo4w/build_osgeo4w.sh | 7 ++++++- mswindows/osgeo4w/package.sh | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mswindows/osgeo4w/build_osgeo4w.sh b/mswindows/osgeo4w/build_osgeo4w.sh index 252b615339f..3a8c767a563 100755 --- a/mswindows/osgeo4w/build_osgeo4w.sh +++ b/mswindows/osgeo4w/build_osgeo4w.sh @@ -21,6 +21,11 @@ export C_INCLUDE_PATH=".:${OSGEO4W_ROOT_MSYS}/include:${SRC}/dist.${ARCH}/includ export PYTHONHOME=${OSGEO4W_ROOT_MSYS}/apps/Python312 export ARCH=x86_64-w64-mingw32 + +mkdir -p mswindows/osgeo4w/lib +rm -f $OSGEO4W_ROOT_MSYS/lib/libpq.a +cp -uv $OSGEO4W_ROOT_MSYS/lib/libpq.lib mswindows/osgeo4w/lib/libpq.lib + CFLAGS="$CFLAGS -pipe" \ CXXFLAGS="$CXXFLAGS -pipe" \ ./configure \ @@ -55,7 +60,7 @@ CXXFLAGS="$CXXFLAGS -pipe" \ --with-openmp \ --with-postgres \ --with-postgres-includes=${OSGEO4W_ROOT_MSYS}/include \ - --with-postgres-libs=${OSGEO4W_ROOT_MSYS}/lib \ + --with-postgres-libs=${SRC}/mswindows/osgeo4w/lib \ --with-proj-includes=${OSGEO4W_ROOT_MSYS}/include \ --with-proj-libs=${OSGEO4W_ROOT_MSYS}/lib \ --with-proj-share=${OSGEO4W_ROOT_MSYS}/share/proj \ diff --git a/mswindows/osgeo4w/package.sh b/mswindows/osgeo4w/package.sh index 50d5b9ddc4d..011ddec8254 100755 --- a/mswindows/osgeo4w/package.sh +++ b/mswindows/osgeo4w/package.sh @@ -117,6 +117,8 @@ if ! [ -f mswindows/osgeo4w/configure-stamp ]; then rm -f mswindows/osgeo4w/package.log.* mkdir -p mswindows/osgeo4w/lib + rm -f $OSGEO4W_ROOT_MSYS/lib/libpq.a + cp -uv $OSGEO4W_ROOT_MSYS/lib/libpq.lib mswindows/osgeo4w/lib/libpq.lib cp -uv $OSGEO4W_ROOT_MSYS/lib/sqlite3_i.lib mswindows/osgeo4w/lib/sqlite3.lib @@ -155,7 +157,7 @@ if ! [ -f mswindows/osgeo4w/configure-stamp ]; then --with-openmp \ --with-postgres \ --with-postgres-includes=${OSGEO4W_ROOT_MSYS}/include \ - --with-postgres-libs=${OSGEO4W_ROOT_MSYS}/lib \ + --with-postgres-libs=${SRC}/mswindows/osgeo4w/lib \ --with-proj-includes=${OSGEO4W_ROOT_MSYS}/include \ --with-proj-libs=${OSGEO4W_ROOT_MSYS}/lib \ --with-proj-share=${OSGEO4W_ROOT_MSYS}/share/proj \ From 8ea57d68b633a14ea64e56f2b65bf7c653a892a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 04:51:00 +0000 Subject: [PATCH 3/6] CI(deps): Update ruff to v0.9.3 (#4982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CI(deps): Update ruff to v0.9.3 * style: fix RUF058 [*] `itertools.starmap` called on `zip` iterable * style: fix RUF047 [*] Empty `else` clause * style: fix RUF058 [*] `itertools.starmap` called on `zip` iterable * style: Fix for-loop-writes (FURB122): Use of `f.write` in a for loop * style: Manual fixes for for-loop-writes (FURB122): Use of `f.write` in a for loop --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Edouard Choinière <27212526+echoix@users.noreply.github.com> --- .github/workflows/python-code-quality.yml | 2 +- .pre-commit-config.yaml | 2 +- gui/wxpython/animation/controller.py | 3 +- gui/wxpython/core/utils.py | 6 +-- gui/wxpython/gcp/manager.py | 8 ++-- gui/wxpython/gmodeler/panels.py | 3 +- gui/wxpython/image2target/ii2t_manager.py | 8 ++-- gui/wxpython/lmgr/workspace.py | 3 +- gui/wxpython/mapwin/buffered.py | 4 -- gui/wxpython/photo2image/ip2i_manager.py | 5 ++- gui/wxpython/wxplot/dialogs.py | 2 - gui/wxpython/wxplot/profile.py | 6 ++- lib/gis/testsuite/test_gis_lib_getl.py | 7 ++-- lib/init/grass.py | 6 +-- python/grass/gunittest/multireport.py | 12 +++--- python/grass/gunittest/reporters.py | 14 ++++--- .../gunittest/testsuite/test_checkers.py | 15 +++---- python/grass/jupyter/timeseriesmap.py | 2 - raster/r.topidx/gridatb_to_arc.py | 3 +- scripts/d.polar/d.polar.py | 24 +++++------ scripts/db.univar/db.univar.py | 3 +- scripts/g.extension/g.extension.py | 42 ++++++++++--------- scripts/i.oif/i.oif.py | 3 +- scripts/i.spectral/i.spectral.py | 12 ++---- scripts/v.in.mapgen/v.in.mapgen.py | 16 ++++--- utils/create_python_init_file.py | 3 +- 26 files changed, 99 insertions(+), 115 deletions(-) diff --git a/.github/workflows/python-code-quality.yml b/.github/workflows/python-code-quality.yml index 31819c081aa..8c49211705f 100644 --- a/.github/workflows/python-code-quality.yml +++ b/.github/workflows/python-code-quality.yml @@ -38,7 +38,7 @@ jobs: # renovate: datasource=pypi depName=bandit BANDIT_VERSION: "1.8.2" # renovate: datasource=pypi depName=ruff - RUFF_VERSION: "0.9.2" + RUFF_VERSION: "0.9.3" runs-on: ${{ matrix.os }} permissions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49674d971cb..9834ad4f121 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: ) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.9.2 + rev: v0.9.3 hooks: # Run the linter. - id: ruff diff --git a/gui/wxpython/animation/controller.py b/gui/wxpython/animation/controller.py index 808e9b20ef4..6cf3269eeb1 100644 --- a/gui/wxpython/animation/controller.py +++ b/gui/wxpython/animation/controller.py @@ -36,7 +36,6 @@ HashCmds, ) from animation.data import AnimationData -from itertools import starmap class AnimationController(wx.EvtHandler): @@ -369,7 +368,7 @@ def _updateAnimations(self, activeIndices, mapNamesDict=None): if anim.viewMode == "3d": regions = [None] * len(regions) self.animations[i].SetFrames( - list(starmap(HashCmds, zip(anim.cmdMatrix, regions))) + list(map(HashCmds, anim.cmdMatrix, regions)) ) self.animations[i].SetActive(True) else: diff --git a/gui/wxpython/core/utils.py b/gui/wxpython/core/utils.py index 9ae88cb05a4..a1722fec64b 100644 --- a/gui/wxpython/core/utils.py +++ b/gui/wxpython/core/utils.py @@ -871,12 +871,10 @@ def StoreEnvVariable(key, value=None, envFile=None): return expCmd = "set" if windows else "export" - for key, value in environ.items(): - fd.write("%s %s=%s\n" % (expCmd, key, value)) + fd.writelines("%s %s=%s\n" % (expCmd, key, value) for key, value in environ.items()) # write also skipped lines - for line in lineSkipped: - fd.write(line + os.linesep) + fd.writelines(line + os.linesep for line in lineSkipped) fd.close() diff --git a/gui/wxpython/gcp/manager.py b/gui/wxpython/gcp/manager.py index 43bbfdc1b8e..ddeb19c7302 100644 --- a/gui/wxpython/gcp/manager.py +++ b/gui/wxpython/gcp/manager.py @@ -347,8 +347,9 @@ def SetSrcEnv(self, location, mapset): try: f = open(self.source_gisrc, mode="w") - for line in self.gisrc_dict.items(): - f.write(line[0] + ": " + line[1] + "\n") + f.writelines( + line[0] + ": " + line[1] + "\n" for line in self.gisrc_dict.items() + ) finally: f.close() @@ -2769,8 +2770,7 @@ def MakeVGroup(self): f = open(self.vgrpfile, mode="w") try: - for vect in vgrouplist: - f.write(vect + "\n") + f.writelines(vect + "\n" for vect in vgrouplist) finally: f.close() diff --git a/gui/wxpython/gmodeler/panels.py b/gui/wxpython/gmodeler/panels.py index fbdc2048620..e567706cd19 100644 --- a/gui/wxpython/gmodeler/panels.py +++ b/gui/wxpython/gmodeler/panels.py @@ -683,8 +683,7 @@ def WriteModelFile(self, filename): try: mfile = open(filename, "w") tmpfile.seek(0) - for line in tmpfile.readlines(): - mfile.write(line) + mfile.writelines(tmpfile.readlines()) except OSError: wx.MessageBox( parent=self, diff --git a/gui/wxpython/image2target/ii2t_manager.py b/gui/wxpython/image2target/ii2t_manager.py index 56adf91775b..a658897a5fb 100644 --- a/gui/wxpython/image2target/ii2t_manager.py +++ b/gui/wxpython/image2target/ii2t_manager.py @@ -364,8 +364,9 @@ def SetSrcEnv(self, location, mapset): try: f = open(self.source_gisrc, mode="w") - for line in self.gisrc_dict.items(): - f.write(line[0] + ": " + line[1] + "\n") + f.writelines( + line[0] + ": " + line[1] + "\n" for line in self.gisrc_dict.items() + ) finally: f.close() @@ -2709,8 +2710,7 @@ def MakeVGroup(self): f = open(self.vgrpfile, mode="w") try: - for vect in vgrouplist: - f.write(vect + "\n") + f.writelines(vect + "\n" for vect in vgrouplist) finally: f.close() diff --git a/gui/wxpython/lmgr/workspace.py b/gui/wxpython/lmgr/workspace.py index dd83c1a6f22..6bad27d7674 100644 --- a/gui/wxpython/lmgr/workspace.py +++ b/gui/wxpython/lmgr/workspace.py @@ -448,8 +448,7 @@ def SaveToFile(self, filename): try: mfile = open(filename, "wb") tmpfile.seek(0) - for line in tmpfile.readlines(): - mfile.write(line) + mfile.writelines(tmpfile.readlines()) except OSError: GError( parent=self.lmgr, diff --git a/gui/wxpython/mapwin/buffered.py b/gui/wxpython/mapwin/buffered.py index 7e42ebc57c9..fe2a5b0ae69 100644 --- a/gui/wxpython/mapwin/buffered.py +++ b/gui/wxpython/mapwin/buffered.py @@ -1542,8 +1542,6 @@ def OnLeftDown(self, event): if idlist != []: self.dragid = idlist[0] # drag whatever is on top - else: - pass coords = self.Pixel2Cell(self.mouse["begin"]) self.mouseLeftDown.emit(x=coords[0], y=coords[1]) @@ -1592,8 +1590,6 @@ def OnLeftUp(self, event): self.textdict[self.dragid]["bbox"] = self.pdc.GetIdBounds( self.dragid ) - else: - pass self.dragid = None self.mouseLeftUpPointer.emit(x=coordinates[0], y=coordinates[1]) diff --git a/gui/wxpython/photo2image/ip2i_manager.py b/gui/wxpython/photo2image/ip2i_manager.py index dc5713e961a..9f78d717fee 100644 --- a/gui/wxpython/photo2image/ip2i_manager.py +++ b/gui/wxpython/photo2image/ip2i_manager.py @@ -255,8 +255,9 @@ def SetSrcEnv(self, location, mapset): try: f = open(self.source_gisrc, mode="w") - for line in self.gisrc_dict.items(): - f.write(line[0] + ": " + line[1] + "\n") + f.writelines( + line[0] + ": " + line[1] + "\n" for line in self.gisrc_dict.items() + ) finally: f.close() diff --git a/gui/wxpython/wxplot/dialogs.py b/gui/wxpython/wxplot/dialogs.py index cdb74c64a9e..d1564d4fc8a 100755 --- a/gui/wxpython/wxplot/dialogs.py +++ b/gui/wxpython/wxplot/dialogs.py @@ -547,8 +547,6 @@ def OnHistMap(self, event): self.gselection.Enable() self.rselection.Disable() self.rselection.SetValue("") - else: - pass def OnRasterSelection(self, event): """Handler for selecting a single raster map""" diff --git a/gui/wxpython/wxplot/profile.py b/gui/wxpython/wxplot/profile.py index e0994f1a8a5..5b63f3cabab 100644 --- a/gui/wxpython/wxplot/profile.py +++ b/gui/wxpython/wxplot/profile.py @@ -446,8 +446,10 @@ def SaveProfileToFile(self, event): dlg.Destroy() return - for datapair in self.raster[r]["datalist"]: - fd.write("%.6f,%.6f\n" % (float(datapair[0]), float(datapair[1]))) + fd.writelines( + "%.6f,%.6f\n" % (float(datapair[0]), float(datapair[1])) + for datapair in self.raster[r]["datalist"] + ) fd.close() diff --git a/lib/gis/testsuite/test_gis_lib_getl.py b/lib/gis/testsuite/test_gis_lib_getl.py index 1ac9907f3d9..dec5c0b40a2 100644 --- a/lib/gis/testsuite/test_gis_lib_getl.py +++ b/lib/gis/testsuite/test_gis_lib_getl.py @@ -29,10 +29,9 @@ def read_lines_and_assert(self, get_line_function, newline): """Write and read lines and then assert they are as expected""" lines = ["Line 1", "Line 2", "Line 3"] with open(self.file_path, mode="w", newline=newline) as stream: - for line in lines: - # Python text newline here. - # The specific newline is added by the stream. - stream.write(f"{line}\n") + # Python text newline here. + # The specific newline is added by the stream. + stream.writelines(f"{line}\n" for line in lines) file_ptr = self.libc.fopen(str(self.file_path).encode("utf-8"), b"r") if not file_ptr: diff --git a/lib/init/grass.py b/lib/init/grass.py index c2ac803e6a2..befec465b3a 100755 --- a/lib/init/grass.py +++ b/lib/init/grass.py @@ -525,8 +525,7 @@ def write_gisrcrc(gisrcrc, gisrc, skip_variable=None): del lines[number] number += 1 with open(gisrcrc, "w") as f: - for line in lines: - f.write(line) + f.writelines(lines) def read_env_file(path): @@ -543,8 +542,7 @@ def write_gisrc(kv, filename, append=False): # use append=True to avoid a race condition between write_gisrc() and # grass_prompt() on startup (PR #548) f = open(filename, "a" if append else "w") - for k, v in kv.items(): - f.write("%s: %s\n" % (k, v)) + f.writelines("%s: %s\n" % (k, v) for k, v in kv.items()) f.close() diff --git a/python/grass/gunittest/multireport.py b/python/grass/gunittest/multireport.py index 5a0350f1786..9deb0528ada 100644 --- a/python/grass/gunittest/multireport.py +++ b/python/grass/gunittest/multireport.py @@ -413,13 +413,13 @@ def main_page( ) ) page.write("") - for image, caption in itertools.izip(images, captions): - page.write( - "

{caption}

" - '{caption}'.format( - image=image, caption=caption - ) + page.writelines( + "

{caption}

" + '{caption}'.format( + image=image, caption=caption ) + for image, caption in itertools.izip(images, captions) + ) page.write("") diff --git a/python/grass/gunittest/reporters.py b/python/grass/gunittest/reporters.py index 6302c8e2248..d6ff1669bbc 100644 --- a/python/grass/gunittest/reporters.py +++ b/python/grass/gunittest/reporters.py @@ -52,8 +52,9 @@ def replace_in_file(file_path, pattern, repl): # using tmp file to store the replaced content tmp_file_path = file_path + ".tmp" with open(file_path) as old_file, open(tmp_file_path, "w") as new_file: - for line in old_file: - new_file.write(re.sub(pattern=pattern, string=line, repl=repl)) + new_file.writelines( + re.sub(pattern=pattern, string=line, repl=repl) for line in old_file + ) # remove old file since it must not exist for rename/move os.remove(file_path) # replace old file by new file @@ -448,8 +449,7 @@ def wrap_stdstream_to_html(infile, outfile, module, stream): after = "" with open(outfile, "w") as html, open(infile) as text: html.write(before) - for line in text: - html.write(color_error_line(html_escape(line))) + html.writelines(color_error_line(html_escape(line)) for line in text) html.write(after) @@ -795,8 +795,10 @@ def end_file_test( file_index.write(files_section) if supplementary_files: - for f in supplementary_files: - file_index.write('
  • {f}
  • '.format(f=f)) + file_index.writelines( + '
  • {f}
  • '.format(f=f) + for f in supplementary_files + ) file_index.write("") diff --git a/python/grass/gunittest/testsuite/test_checkers.py b/python/grass/gunittest/testsuite/test_checkers.py index b11b25778a3..f79598aa165 100644 --- a/python/grass/gunittest/testsuite/test_checkers.py +++ b/python/grass/gunittest/testsuite/test_checkers.py @@ -369,17 +369,14 @@ class TestMd5Sums(TestCase): @classmethod def setUpClass(cls): with open(cls.correct_file_name_platform_nl, "w") as f: - for line in CORRECT_LINES: - # \n should be converted to platform newline - f.write(line + "\n") + # \n should be converted to platform newline + f.writelines(line + "\n" for line in CORRECT_LINES) with open(cls.correct_file_name_unix_nl, "w") as f: - for line in CORRECT_LINES: - # binary mode will write pure \n - f.write(line + "\n") + # binary mode will write pure \n + f.writelines(line + "\n" for line in CORRECT_LINES) with open(cls.wrong_file_name, "w") as f: - for line in INCORRECT_LINES: - # \n should be converted to platform newline - f.write(line + "\n") + # \n should be converted to platform newline + f.writelines(line + "\n" for line in INCORRECT_LINES) @classmethod def tearDownClass(cls): diff --git a/python/grass/jupyter/timeseriesmap.py b/python/grass/jupyter/timeseriesmap.py index 0379e19a4c0..5d32cc83e93 100644 --- a/python/grass/jupyter/timeseriesmap.py +++ b/python/grass/jupyter/timeseriesmap.py @@ -28,8 +28,6 @@ def fill_none_values(names): for i, name in enumerate(names): if name == "None": names[i] = names[i - 1] - else: - pass return names diff --git a/raster/r.topidx/gridatb_to_arc.py b/raster/r.topidx/gridatb_to_arc.py index d80f6797a65..351cb07afad 100755 --- a/raster/r.topidx/gridatb_to_arc.py +++ b/raster/r.topidx/gridatb_to_arc.py @@ -54,8 +54,7 @@ NODATA_value 9999 """ ) -for inline in inf: - outf.write(inline) +outf.writelines(inf) inf.close() outf.close() diff --git a/scripts/d.polar/d.polar.py b/scripts/d.polar/d.polar.py index d7e67486a0e..2d5f6d50a7b 100755 --- a/scripts/d.polar/d.polar.py +++ b/scripts/d.polar/d.polar.py @@ -283,10 +283,10 @@ def plot_eps(psout): (x, y) = outercircle[1] outf.write("%.2f %.2f moveto\n" % (x * scale + halfframe, y * scale + halfframe)) - for x, y in outercircle[2:]: - outf.write( - "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) - ) + outf.writelines( + "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) + for x, y in outercircle[2:] + ) t = string.Template( """ @@ -338,10 +338,10 @@ def plot_eps(psout): (x, y) = sine_cosine_replic[1] outf.write("%.2f %.2f moveto\n" % (x * scale + halfframe, y * scale + halfframe)) - for x, y in sine_cosine_replic[2:]: - outf.write( - "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) - ) + outf.writelines( + "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) + for x, y in sine_cosine_replic[2:] + ) t = string.Template( """ @@ -363,10 +363,10 @@ def plot_eps(psout): (x, y) = vector[1] outf.write("%.2f %.2f moveto\n" % (x * scale + halfframe, y * scale + halfframe)) - for x, y in vector[2:]: - outf.write( - "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) - ) + outf.writelines( + "%.2f %.2f lineto\n" % (x * scale + halfframe, y * scale + halfframe) + for x, y in vector[2:] + ) t = string.Template( """ diff --git a/scripts/db.univar/db.univar.py b/scripts/db.univar/db.univar.py index 859e0fd831c..3969d4f7caf 100755 --- a/scripts/db.univar/db.univar.py +++ b/scripts/db.univar/db.univar.py @@ -89,8 +89,7 @@ def sortfile(infile, outfile): for i in range(len(lines)): lines[i] = float(lines[i].rstrip("\r\n")) lines.sort() - for line in lines: - outf.write(str(line) + "\n") + outf.writelines(str(line) + "\n" for line in lines) inf.close() outf.close() diff --git a/scripts/g.extension/g.extension.py b/scripts/g.extension/g.extension.py index 01f5b0a969f..aa707912f38 100644 --- a/scripts/g.extension/g.extension.py +++ b/scripts/g.extension/g.extension.py @@ -1041,11 +1041,11 @@ def write_xml_modules(name, tree=None): if bnode is not None: file_.write("%s\n" % (" " * indent)) indent += 4 - for fnode in bnode.findall("file"): - file_.write( - "%s%s\n" - % (" " * indent, os.path.join(options["prefix"], fnode.text)) - ) + file_.writelines( + "%s%s\n" + % (" " * indent, os.path.join(options["prefix"], fnode.text)) + for fnode in bnode.findall("file") + ) indent -= 4 file_.write("%s\n" % (" " * indent)) file_.write('%s\n' % (" " * indent, libgis_revison)) @@ -1091,11 +1091,11 @@ def write_xml_extensions(name, tree=None): if bnode is not None: file_.write("%s\n" % (" " * indent)) indent += 4 - for fnode in bnode.findall("file"): - file_.write( - "%s%s\n" - % (" " * indent, os.path.join(options["prefix"], fnode.text)) - ) + file_.writelines( + "%s%s\n" + % (" " * indent, os.path.join(options["prefix"], fnode.text)) + for fnode in bnode.findall("file") + ) indent -= 4 file_.write("%s\n" % (" " * indent)) # extension modules @@ -1103,8 +1103,10 @@ def write_xml_extensions(name, tree=None): if mnode is not None: file_.write("%s\n" % (" " * indent)) indent += 4 - for fnode in mnode.findall("module"): - file_.write("%s%s\n" % (" " * indent, fnode.text)) + file_.writelines( + "%s%s\n" % (" " * indent, fnode.text) + for fnode in mnode.findall("module") + ) indent -= 4 file_.write("%s\n" % (" " * indent)) @@ -1136,14 +1138,14 @@ def write_xml_toolboxes(name, tree=None): % (" " * indent, tnode.get("name"), tnode.get("code")) ) indent += 4 - for cnode in tnode.findall("correlate"): - file_.write( - '%s\n' % (" " * indent, tnode.get("code")) - ) - for mnode in tnode.findall("task"): - file_.write( - '%s\n' % (" " * indent, mnode.get("name")) - ) + file_.writelines( + '%s\n' % (" " * indent, tnode.get("code")) + for cnode in tnode.findall("correlate") + ) + file_.writelines( + '%s\n' % (" " * indent, mnode.get("name")) + for mnode in tnode.findall("task") + ) indent -= 4 file_.write("%s\n" % (" " * indent)) diff --git a/scripts/i.oif/i.oif.py b/scripts/i.oif/i.oif.py index cb156675927..81ef0b25f7b 100755 --- a/scripts/i.oif/i.oif.py +++ b/scripts/i.oif/i.oif.py @@ -146,8 +146,7 @@ def main(): sys.stdout.write(fmt % (p + (v,))) else: outf = open(output, "w") - for v, p in oif: - outf.write(fmt % (p + (v,))) + outf.writelines(fmt % (p + (v,)) for v, p in oif) outf.close() diff --git a/scripts/i.spectral/i.spectral.py b/scripts/i.spectral/i.spectral.py index 07efd8af31f..62f4a3397a3 100755 --- a/scripts/i.spectral/i.spectral.py +++ b/scripts/i.spectral/i.spectral.py @@ -104,8 +104,7 @@ def draw_gnuplot(what, xlabels, output, img_format, coord_legend): outfile = os.path.join(tmp_dir, "data_%d" % i) outf = open(outfile, "w") xrange = max(xrange, len(row) - 2) - for j, val in enumerate(row[3:]): - outf.write("%d %s\n" % (j + 1, val)) + outf.writelines("%d %s\n" % (j + 1, val) for j, val in enumerate(row[3:])) outf.close() # build gnuplot script @@ -147,8 +146,7 @@ def draw_gnuplot(what, xlabels, output, img_format, coord_legend): plotfile = os.path.join(tmp_dir, "spectrum.gnuplot") plotf = open(plotfile, "w") - for line in lines: - plotf.write(line + "\n") + plotf.writelines(line + "\n" for line in lines) plotf.close() if output: @@ -163,15 +161,13 @@ def draw_linegraph(what): xfile = os.path.join(tmp_dir, "data_x") xf = open(xfile, "w") - for j, val in enumerate(what[0][3:]): - xf.write("%d\n" % (j + 1)) + xf.writelines("%d\n" % (j + 1) for j, val in enumerate(what[0][3:])) xf.close() for i, row in enumerate(what): yfile = os.path.join(tmp_dir, "data_y_%d" % i) yf = open(yfile, "w") - for j, val in enumerate(row[3:]): - yf.write("%s\n" % val) + yf.writelines("%s\n" % val for j, val in enumerate(row[3:])) yf.close() yfiles.append(yfile) diff --git a/scripts/v.in.mapgen/v.in.mapgen.py b/scripts/v.in.mapgen/v.in.mapgen.py index efe7af1075d..e76486b75b4 100755 --- a/scripts/v.in.mapgen/v.in.mapgen.py +++ b/scripts/v.in.mapgen/v.in.mapgen.py @@ -106,8 +106,10 @@ def main(): if f[0].lower() == "nan": if points != []: outf.write("L %d 1\n" % len(points)) - for point in points: - outf.write(" %.15g %.15g %.15g\n" % tuple(map(float, point))) + outf.writelines( + " %.15g %.15g %.15g\n" % tuple(map(float, point)) + for point in points + ) outf.write(" 1 %d\n" % cat) cat += 1 points = [] @@ -134,8 +136,9 @@ def main(): if line[0] == "#": if points != []: outf.write("L %d 1\n" % len(points)) - for point in points: - outf.write(" %.15g %.15g\n" % tuple(map(float, point))) + outf.writelines( + " %.15g %.15g\n" % tuple(map(float, point)) for point in points + ) outf.write(" 1 %d\n" % cat) cat += 1 points = [] @@ -144,8 +147,9 @@ def main(): if points != []: outf.write("L %d 1\n" % len(points)) - for point in points: - outf.write(" %.15g %.15g\n" % tuple(map(float, point))) + outf.writelines( + " %.15g %.15g\n" % tuple(map(float, point)) for point in points + ) outf.write(" 1 %d\n" % cat) cat += 1 outf.close() diff --git a/utils/create_python_init_file.py b/utils/create_python_init_file.py index 567c8b95bba..5642bbc2e75 100755 --- a/utils/create_python_init_file.py +++ b/utils/create_python_init_file.py @@ -34,8 +34,7 @@ def main(path): with open(os.path.join(path, "__init__.py"), "w") as fd: fd.write("all = [%s" % os.linesep) - for m in modules: - fd.write(" '%s',%s" % (m, os.linesep)) + fd.writelines(" '%s',%s" % (m, os.linesep) for m in modules) fd.write(" ]%s" % os.linesep) return 0 From 76ec747e8da2cc1bbda0b9c83aab6687b2e725c5 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Tue, 28 Jan 2025 09:04:59 -0500 Subject: [PATCH 4/6] contributing: add more gitignores and remove accidentally added DS_Store (#4995) gitignore: add more ignores and remove accidently added DS_Store --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 80 ++++++++++++++++++++++++++++++++++++++++++++++- raster/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 79 insertions(+), 1 deletion(-) delete mode 100644 .DS_Store delete mode 100644 raster/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a2120322ac33d503cdcdaff6729e6668364fbdcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHLu}T9$5S=xrgrqQ~P2fOlB|jji#9j!NLW~KCka!^`0So26KpQ&+wYIQOu(wF9 zg}-27DF|rHG;(k+%XnZ~!&Qo2kxcrBCt#M5}L?%*(m0aKt;wFL|?PW#XO1 ztL<3k1IcW*juYM__p9R;`APQi2+40w17b))6V#TyWgUa(@B3|ze>uB`tYcbCtd5&aszc2wfO|Gsnw~c`RX`O`1*8J>e(Lgt&gpY rjf~eFgLbOGuPX2b7{kA+ diff --git a/.gitignore b/.gitignore index 192fad55de8..fc8d6fbafd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /emacs.desktop -*~ *.lock !flake.lock *.pyc @@ -68,3 +67,82 @@ lib/*/latex/ .coverage .coverage.* coverage.xml + +# Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos diff --git a/raster/.DS_Store b/raster/.DS_Store deleted file mode 100644 index 034e2f30fa5befb35d2313541b89c5e2ae86de57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKy-veG4EB{u6j&<6z?dgMidYy!6`r6kKz~p>BwR$(Jr96*3toXo;2qdl*_fF5 ze0EEtv{G0AAzSi&7oVN;eTm|jh}>W~jfwh1)Ppj14lryHUT1AcM=iYQ!aYiw&^(*P z!-;Qro8d1qz|U@X7z2Kf6BK^YUpN-%snFoYHW*o8R==G;qg zOr)47R)Sc8I0*$xsM8k1NjU70`X!2$poEjt=ELdEPCFDAcE|mr;7%?Pw9yza2I>s# z$z`AG|JCmPzaC^)#(**KuNZLMbeIlsB(1HD!*Q*Rpx00q_NxTfA((_x3|}tAr_dm< XM?3*0ij^QN5c?4bG}vGa{3rvTUIb&$ From 13182063a8fa78cd62c0e4e48879bdfd67fd4e3c Mon Sep 17 00:00:00 2001 From: ShubhamDesai <42180509+ShubhamDesai@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:06:09 -0500 Subject: [PATCH 5/6] v.net: Fix Resource Leak issue in arcs.c (#4991) Variable Overwritten --- vector/v.net/arcs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/v.net/arcs.c b/vector/v.net/arcs.c index 20554fc19f9..2ea03111041 100644 --- a/vector/v.net/arcs.c +++ b/vector/v.net/arcs.c @@ -32,7 +32,6 @@ int create_arcs(FILE *file, struct Map_info *Pnts, struct Map_info *Out, points = Vect_new_line_struct(); points2 = Vect_new_line_struct(); - points = Vect_new_line_struct(); cats = Vect_new_cats_struct(); narcs = 0; From 71410de250b93f9d93fabdff6effadbf4c9422a9 Mon Sep 17 00:00:00 2001 From: Arohan Ajit Date: Tue, 28 Jan 2025 10:50:23 -0500 Subject: [PATCH 6/6] wxGUI: Fixed star imports in wxdisplay.py (#4974) --- .flake8 | 1 - gui/wxpython/vdigit/wxdisplay.py | 100 ++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/.flake8 b/.flake8 index 03013aa973a..c1e09d417a1 100644 --- a/.flake8 +++ b/.flake8 @@ -25,7 +25,6 @@ per-file-ignores = gui/wxpython/image2target/g.gui.image2target.py: E501 gui/wxpython/photo2image/g.gui.photo2image.py: E501 gui/wxpython/psmap/*: E501 - gui/wxpython/vdigit/*: F405, F403 gui/wxpython/animation/g.gui.animation.py: E501 gui/wxpython/tplot/g.gui.tplot.py: E501 gui/wxpython/iclass/g.gui.iclass.py: E501 diff --git a/gui/wxpython/vdigit/wxdisplay.py b/gui/wxpython/vdigit/wxdisplay.py index 6679e5c5814..f7381fe763f 100644 --- a/gui/wxpython/vdigit/wxdisplay.py +++ b/gui/wxpython/vdigit/wxdisplay.py @@ -18,22 +18,106 @@ """ import locale - import os import sys -import wx +from ctypes import CFUNCTYPE, byref, c_double, c_int, pointer +from grass.lib.ctypes_preamble import UNCHECKED, String +import wx from core.debug import Debug -from core.settings import UserSettings from core.gcmd import DecodeString -from gui_core.wrap import Rect +from core.settings import UserSettings try: - from grass.lib.gis import * - from grass.lib.vector import * - from grass.lib.vedit import * + from grass.lib.gis import ( + G_gisinit, + G_set_error_routine, + G_set_percent_routine, + G_unset_error_routine, + G_unset_percent_routine, + ) + from grass.lib.vector import ( # Types; Functions; Classes + GV_BOUNDARY, + GV_BUILD_NONE, + GV_CENTROID, + GV_LINE, + GV_LINES, + GV_MODE_RW, + GV_POINT, + GV_POINTS, + WITHOUT_Z, + Map_info, + Vect_append_point, + Vect_box_copy, + Vect_box_extend, + Vect_build, + Vect_build_partial, + Vect_close, + Vect_destroy_cats_struct, + Vect_destroy_line_struct, + Vect_destroy_list, + Vect_find_line_list, + Vect_get_area_box, + Vect_get_area_centroid, + Vect_get_centroid_area, + Vect_get_line_box, + Vect_get_map_box, + Vect_get_num_areas, + Vect_get_num_lines, + Vect_is_3d, + Vect_line_alive, + Vect_line_check_duplicate, + Vect_line_distance, + Vect_list_append, + Vect_new_cats_struct, + Vect_new_line_struct, + Vect_new_list, + Vect_open_old, + Vect_open_tmp_old, + Vect_open_tmp_update, + Vect_open_update, + Vect_point_in_area, + Vect_point_in_poly, + Vect_points_distance, + Vect_read_line, + Vect_select_lines_by_polygon, + Vect_set_updated, + bound_box, + PORT_DOUBLE_MAX, + ) + from grass.lib.vedit import ( # Types; Draw flags; Functions + DRAW_AREA, + DRAW_BOUNDARYNO, + DRAW_BOUNDARYONE, + DRAW_BOUNDARYTWO, + DRAW_CENTROIDDUP, + DRAW_CENTROIDIN, + DRAW_CENTROIDOUT, + DRAW_DIRECTION, + DRAW_LINE, + DRAW_NODEONE, + DRAW_NODETWO, + DRAW_POINT, + DRAW_VERTEX, + TYPE_AREA, + TYPE_BOUNDARYNO, + TYPE_BOUNDARYONE, + TYPE_BOUNDARYTWO, + TYPE_CENTROIDDUP, + TYPE_CENTROIDIN, + TYPE_CENTROIDOUT, + TYPE_DIRECTION, + TYPE_ISLE, + TYPE_LINE, + TYPE_NODEONE, + TYPE_NODETWO, + TYPE_POINT, + TYPE_VERTEX, + Vedit_render_map, + ) except (ImportError, OSError, TypeError) as e: - print("wxdigit.py: {}".format(e), file=sys.stderr) + print("wxdisplay.py: {}".format(e), file=sys.stderr) +from gui_core.wrap import Rect log = None progress = None