From 8abcf061392227ec413b27865a61dd971abbbce6 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:15:32 +1000 Subject: [PATCH 01/14] mp_tile: factor out the default tile cache path the same HOME/LOCALAPPDATA/tempdir fallback is needed by the 3D map for its terrain tile cache --- MAVProxy/modules/mavproxy_map/mp_tile.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/MAVProxy/modules/mavproxy_map/mp_tile.py b/MAVProxy/modules/mavproxy_map/mp_tile.py index cfe864c270..2dec29ff49 100755 --- a/MAVProxy/modules/mavproxy_map/mp_tile.py +++ b/MAVProxy/modules/mavproxy_map/mp_tile.py @@ -218,6 +218,17 @@ def __init__(self, tile, zoom, scale, src, dst, service): (self.dstx, self.dsty) = dst +def default_cache_path(): + '''default root of the on-disk tile cache. HOME is not set on native + Windows, where the cwd may well be unwritable (eg. Program Files)''' + if 'HOME' in os.environ: + return os.path.join(os.environ['HOME'], '.tilecache') + if 'LOCALAPPDATA' in os.environ: + return os.path.join(os.environ['LOCALAPPDATA'], '.tilecache') + import tempfile + return os.path.join(tempfile.gettempdir(), '.tilecache') + + class MPTile: '''map tile object''' def __init__(self, cache_path=None, download=True, cache_size=500, @@ -225,14 +236,7 @@ def __init__(self, cache_path=None, download=True, cache_size=500, max_zoom=19, refresh_age=30*24*60*60): if cache_path is None: - try: - cache_path = os.path.join(os.environ['HOME'], '.tilecache') - except Exception: - if 'LOCALAPPDATA' in os.environ: - cache_path = os.path.join(os.environ['LOCALAPPDATA'], '.tilecache') - else: - import tempfile - cache_path = os.path.join(tempfile.gettempdir(), '.tilecache') + cache_path = default_cache_path() if not os.path.exists(cache_path): mp_util.mkdir_p(cache_path) From 942b3953697e1934646c5727e3d65c0f2b380024 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:15:42 +1000 Subject: [PATCH 02/14] mp_tile: fix the filesystem fallback in mp_icon open() was called in text mode and .read() ran before the with, so the fallback entered a str as a context manager and always raised. It is only reached when importlib.resources fails, which is what happens in a frozen build where the data directory is not an importable package. --- MAVProxy/modules/mavproxy_map/mp_tile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAVProxy/modules/mavproxy_map/mp_tile.py b/MAVProxy/modules/mavproxy_map/mp_tile.py index 2dec29ff49..95ac452f64 100755 --- a/MAVProxy/modules/mavproxy_map/mp_tile.py +++ b/MAVProxy/modules/mavproxy_map/mp_tile.py @@ -700,7 +700,7 @@ def mp_icon(filename): with importlib.resources.open_binary(package, filename) as stream: raw = np.frombuffer(stream.read(), dtype=np.uint8) except Exception: - with open(os.path.join(os.path.dirname(__file__), 'data', filename)).read() as stream: + with open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb') as stream: raw = np.frombuffer(stream.read(), dtype=np.uint8) img = cv2.imdecode(raw, cv2.IMREAD_COLOR) return img From 9b30dd2ba2e588c6506c255eb1837fdbd8c87929 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:16:40 +1000 Subject: [PATCH 03/14] map3d: keep the terrain cache out of the current directory HOME is not set on native Windows, so the quantized terrain tiles were cached relative to the cwd. For an installed build that is the install directory under Program Files, which is not writable. --- MAVProxy/modules/mavproxy_map3d/terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAVProxy/modules/mavproxy_map3d/terrain.py b/MAVProxy/modules/mavproxy_map3d/terrain.py index f404df0432..2170213f17 100644 --- a/MAVProxy/modules/mavproxy_map3d/terrain.py +++ b/MAVProxy/modules/mavproxy_map3d/terrain.py @@ -25,7 +25,7 @@ R = 6378137.0 QUANTIZED_BASE = "https://plot.ardupilot.org/quantized" -CACHE_DIR = os.path.join(os.environ.get("HOME", "."), ".tilecache", "quantized") +CACHE_DIR = os.path.join(mp_tile.default_cache_path(), "quantized") # ArduPilot tiles include the optional lighting extension. This decoder warns # when it skips that extension, but map3d computes its own VTK normals anyway. From 133d10617f61914f6cdf490902849b711c93094b Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:16:51 +1000 Subject: [PATCH 04/14] map3d: import the VTK helpers via vtkmodules vtk is a plain module that fakes a package by pointing __path__ at vtkmodules, which pyinstaller cannot follow. The frozen Windows build died with ModuleNotFoundError: No module named 'vtk.wx'. --- MAVProxy/modules/mavproxy_map3d/map3d_ui.py | 2 +- MAVProxy/modules/mavproxy_map3d/terrain.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MAVProxy/modules/mavproxy_map3d/map3d_ui.py b/MAVProxy/modules/mavproxy_map3d/map3d_ui.py index eed8432cb9..f7192fd142 100644 --- a/MAVProxy/modules/mavproxy_map3d/map3d_ui.py +++ b/MAVProxy/modules/mavproxy_map3d/map3d_ui.py @@ -9,7 +9,7 @@ import time from MAVProxy.modules.lib.wx_loader import wx -from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor +from vtkmodules.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor from MAVProxy.modules.mavproxy_map import mp_tile from MAVProxy.modules.mavproxy_map3d.camera import TerrainCamera, TerrainStyle diff --git a/MAVProxy/modules/mavproxy_map3d/terrain.py b/MAVProxy/modules/mavproxy_map3d/terrain.py index 2170213f17..e6cb5ccd5b 100644 --- a/MAVProxy/modules/mavproxy_map3d/terrain.py +++ b/MAVProxy/modules/mavproxy_map3d/terrain.py @@ -16,7 +16,9 @@ import numpy as np import vtk -from vtk.util import numpy_support +# import via vtkmodules, not the vtk.* aliases: vtk is a plain module that fakes +# a package with __path__, which pyinstaller cannot follow into a frozen build +from vtkmodules.util import numpy_support from quantized_mesh_tile import decode as qmt_decode from quantized_mesh_tile.global_geodetic import GlobalGeodetic From aa3c977b723d1ca56fe3f3128c34982cda669f98 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:17:02 +1000 Subject: [PATCH 05/14] windows: ship the map icons with MAVExplorer MAVExplorer's pyinstaller analysis did not include mavproxy_map/data, so both its 2D map and its 3D map failed to load loading.jpg. --- windows/mavproxy.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/windows/mavproxy.spec b/windows/mavproxy.spec index d564a3c838..243134065e 100755 --- a/windows/mavproxy.spec +++ b/windows/mavproxy.spec @@ -33,7 +33,10 @@ MAVExpAny = Analysis(['.\\tools\\MAVExplorer.py'], 'wx.lib.embeddedimage', 'wx.lib.imageutils', 'wx.lib.agw.aquabutton', 'wx.lib.agw.gradientbutton', 'FileDialog', 'Dialog', ] + collect_submodules('pymavlink'), - datas= [ ('tools\\graphs\\*.*', 'MAVProxy\\tools\\graphs' ) ], + # mavproxy_map\data holds the map icons, needed by both the 2D map + # and the 3D map that MAVExplorer can open + datas= [ ('tools\\graphs\\*.*', 'MAVProxy\\tools\\graphs' ), + ('modules\\mavproxy_map\\data\\*.*', 'MAVProxy\\modules\\mavproxy_map\\data' )], hookspath=None, runtime_hooks=None, excludes= ['sphinx', 'docutils', 'alabaster', 'FixTk', 'tcl', 'tk', 'Tkinter']) From b7f87783b238c11f751df0c5fb3708806544ec7c Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:17:02 +1000 Subject: [PATCH 06/14] windows: install the map3d extra in the installer build without vtk and quantized-mesh-tile the released installer has no 3D map --- .github/workflows/windows_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows_build.yml b/.github/workflows/windows_build.yml index 7a7c0191f1..8e17d2d896 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -33,7 +33,7 @@ jobs: run: ./installer.exe /verysilent /allusers /dir=inst - name: Build MAVProxy run: | - python3 -m pip install .[recommended] --user + python3 -m pip install .[recommended,map3d] --user python3 -m pip list - name: Prepare installer run: | From b25c724e54896e5b4dc0bf7bd3ce0f7c29abddeb Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 11:26:42 +1000 Subject: [PATCH 07/14] console: add a Show Map3d menu item sits below Show Map and loads the 3D map module --- MAVProxy/modules/mavproxy_console.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MAVProxy/modules/mavproxy_console.py b/MAVProxy/modules/mavproxy_console.py index a6d9e6c065..be4f05fa94 100644 --- a/MAVProxy/modules/mavproxy_console.py +++ b/MAVProxy/modules/mavproxy_console.py @@ -116,6 +116,7 @@ def __init__(self, mpstate): self.add_menu(MPMenuSubMenu('MAVProxy', items=[MPMenuItem('Settings', 'Settings', 'menuSettings'), MPMenuItem('Show Map', 'Load Map', '# module load map'), + MPMenuItem('Show Map3d', 'Load 3D Map', '# module load map3d'), MPMenuItem('Show HUD', 'Load HUD', '# module load horizon'), MPMenuItem('Show Checklist', 'Load Checklist', '# module load checklist')])) self.vehicle_menu = MPMenuSubMenu('Vehicle', items=[]) From 31eada7c59b2914eb965360da7c2e5ca42e74c1f Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 17:47:17 +1000 Subject: [PATCH 08/14] map3d: say what is missing instead of dying silently the viewer imports VTK in a child process, so with the packages absent the child died where nobody could see it and idle_task quietly dropped the map. find_spec keeps VTK out of the parent process. --- MAVProxy/modules/mavproxy_map3d/__init__.py | 7 ++++++- MAVProxy/modules/mavproxy_map3d/map3d.py | 22 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/MAVProxy/modules/mavproxy_map3d/__init__.py b/MAVProxy/modules/mavproxy_map3d/__init__.py index c5e71db50b..1ddb52ac14 100644 --- a/MAVProxy/modules/mavproxy_map3d/__init__.py +++ b/MAVProxy/modules/mavproxy_map3d/__init__.py @@ -13,7 +13,8 @@ from MAVProxy.modules.lib import mp_module from MAVProxy.modules.lib import mp_settings -from MAVProxy.modules.mavproxy_map3d.map3d import Map3D +from MAVProxy.modules.mavproxy_map3d.map3d import ( + Map3D, missing_packages, missing_packages_message) # fence colours as the 2D map's PolyFence layer uses them (OpenCV BGR) FENCE_INCLUSION_BGR = (0, 255, 0) @@ -107,6 +108,10 @@ def start_map(self): if self.map is not None and self.map.is_alive(): print("map3d already running") return + missing = missing_packages() + if missing: + print(missing_packages_message(missing)) + return self.map = Map3D(title="MAVProxy 3D Map", service=self.map3d_settings.service, zexag=self.map3d_settings.zexag, diff --git a/MAVProxy/modules/mavproxy_map3d/map3d.py b/MAVProxy/modules/mavproxy_map3d/map3d.py index b42f6373f9..213b790730 100644 --- a/MAVProxy/modules/mavproxy_map3d/map3d.py +++ b/MAVProxy/modules/mavproxy_map3d/map3d.py @@ -3,11 +3,33 @@ (mirrors mp_slipmap) and pushes element/camera updates over a queue. ''' +import importlib.util import time import queue from MAVProxy.modules.lib import multiproc +PACKAGES = ('vtk', 'quantized_mesh_tile') + + +def missing_packages(): + '''optional 3D map packages that are not installed. The viewer imports them + in a child process, where an ImportError would go unseen, so callers check + before starting a viewer. find_spec avoids importing VTK into the parent.''' + missing = [] + for name in PACKAGES: + try: + if importlib.util.find_spec(name) is None: + missing.append(name) + except Exception: + missing.append(name) + return missing + + +def missing_packages_message(missing): + return ("map3d needs extra packages: pip install vtk quantized-mesh-tile " + "(missing %s)" % ', '.join(missing)) + class Map3D: def __init__(self, title="3D Map", service="MicrosoftSat", zexag=1.0, From 959e282ed63d83a3c5c77653fff819b39d740002 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 17:47:17 +1000 Subject: [PATCH 09/14] MAVExplorer: check the map3d packages are really present the existing guard wrapped the map3d import, but map3d.py does not import VTK, so a missing vtk never raised there --- MAVProxy/tools/MAVExplorer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/MAVProxy/tools/MAVExplorer.py b/MAVProxy/tools/MAVExplorer.py index 86b97661a2..8456af7014 100755 --- a/MAVProxy/tools/MAVExplorer.py +++ b/MAVProxy/tools/MAVExplorer.py @@ -661,10 +661,16 @@ def cmd_map(args): def cmd_map3d(args): '''show a 3D map view: draped satellite imagery over terrain''' try: - from MAVProxy.modules.mavproxy_map3d.map3d import Map3D + from MAVProxy.modules.mavproxy_map3d.map3d import ( + Map3D, missing_packages, missing_packages_message) except ImportError as ex: print("map3d needs extra packages: pip install vtk quantized-mesh-tile (%s)" % ex) return + # map3d.py itself does not import VTK; the viewer child process does + missing = missing_packages() + if missing: + print(missing_packages_message(missing)) + return mlog = mestate.mlog path = [] From dc31cbd15749637aa654f8b1b10fd411ce316b12 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 21:46:04 +1000 Subject: [PATCH 10/14] mavproxy: report the first reason a module failed to import the modpaths loop falls back to a bare module name, and its 'No module named X' was overwriting the real failure from the MAVProxy.modules path --- MAVProxy/mavproxy.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MAVProxy/mavproxy.py b/MAVProxy/mavproxy.py index e0a97c2371..9c93511c4f 100755 --- a/MAVProxy/mavproxy.py +++ b/MAVProxy/mavproxy.py @@ -394,9 +394,12 @@ def load_module(self, modname, quiet=False, **kwargs): ex = "%s.init did not return a MPModule instance" % modname break except ImportError as msg: - ex = msg + # keep the first failure: the later modpaths are fallbacks, and + # their "No module named X" hides why the real one failed + if ex is None: + ex = msg if mpstate.settings.moddebug > 1: - print(get_exception_stacktrace(ex)) + print(get_exception_stacktrace(msg)) help_traceback = "" if mpstate.settings.moddebug < 3: help_traceback = " Use 'set moddebug 3' in the MAVProxy console to enable traceback" From 40c1606a260352c8bcf6a1740e6ac5000d441fbe Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 31 Jul 2026 21:46:04 +1000 Subject: [PATCH 11/14] map: explain an unusable cv2 rather than failing obscurely a distro opencv is built against one numpy major version, so pulling in a newer numpy leaves cv2 unimportable and the map failed to load with no clue why. MAVProxy itself keeps running either way. --- MAVProxy/modules/lib/mp_util.py | 13 +++++++++++++ MAVProxy/modules/mavproxy_map/mp_slipmap.py | 3 ++- MAVProxy/modules/mavproxy_map/mp_tile.py | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/MAVProxy/modules/lib/mp_util.py b/MAVProxy/modules/lib/mp_util.py index 0e5aa80410..b7368bc4cf 100644 --- a/MAVProxy/modules/lib/mp_util.py +++ b/MAVProxy/modules/lib/mp_util.py @@ -595,3 +595,16 @@ def natural_sort_key(s): def sorted_natural(lst): '''sort using a 'natural' sort order''' return sorted(lst, key=natural_sort_key) + +def import_cv2(): + '''import cv2, explaining the usual cause of failure. A distro opencv is + built against one numpy major version, so pulling in a newer numpy (some + packages require numpy>=2) leaves cv2 unimportable''' + try: + import cv2 + return cv2 + except ImportError as ex: + raise ImportError( + "opencv (cv2) is not usable: %s. If numpy was upgraded then the " + "installed opencv may be built for the previous numpy; try " + "'pip install -U opencv-python'" % ex) diff --git a/MAVProxy/modules/mavproxy_map/mp_slipmap.py b/MAVProxy/modules/mavproxy_map/mp_slipmap.py index 9329626a06..96e7a09fee 100755 --- a/MAVProxy/modules/mavproxy_map/mp_slipmap.py +++ b/MAVProxy/modules/mavproxy_map/mp_slipmap.py @@ -7,7 +7,6 @@ ''' import time -import cv2 from MAVProxy.modules.mavproxy_map import mp_tile from MAVProxy.modules.lib import mp_util @@ -15,6 +14,8 @@ from MAVProxy.modules.lib import multiproc from MAVProxy.modules.mavproxy_map.mp_slipmap_util import * +cv2 = mp_util.import_cv2() + class MPSlipMap(): ''' diff --git a/MAVProxy/modules/mavproxy_map/mp_tile.py b/MAVProxy/modules/mavproxy_map/mp_tile.py index 95ac452f64..b59cce82d3 100755 --- a/MAVProxy/modules/mavproxy_map/mp_tile.py +++ b/MAVProxy/modules/mavproxy_map/mp_tile.py @@ -26,7 +26,6 @@ import pathlib import string import time -import cv2 import numpy as np from math import log, tan, radians, degrees, sin, cos, exp, pi, asin, atan @@ -44,6 +43,8 @@ from MAVProxy.modules.lib import mp_util +cv2 = mp_util.import_cv2() + class TileException(Exception): '''tile error class''' From f178b22cd47471251b6a69d8614256bde6766fdd Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 1 Aug 2026 08:16:33 +1000 Subject: [PATCH 12/14] setup: make the map3d extra numpy-2 consistent quantized-mesh-tile requires numpy>=2, which leaves a distro opencv or matplotlib built against numpy 1 unimportable, taking out the 2D map and the graphs. The floors are needed because pip leaves an unpinned requirement alone when the distro build already satisfies it. --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 168351a904..56b627a7c2 100755 --- a/setup.py +++ b/setup.py @@ -98,8 +98,11 @@ def package_files(directory): install_requires=requirements, extras_require={ 'cesium': ['tornado'], - # map3d module (native 3D terrain map) - 'map3d': ['vtk', 'quantized-mesh-tile'], + # map3d module (native 3D terrain map). quantized-mesh-tile needs + # numpy>=2, which leaves a distro opencv/matplotlib built against + # numpy 1 unimportable, so pull in builds that match + 'map3d': ['vtk', 'quantized-mesh-tile', + 'opencv-python>=4.10', 'matplotlib>=3.9'], # restserver module 'server': ['flask'], 'recommended': ['flask', 'PyYAML', 'lxml', 'wxpython', From cea31b407bf9a4144d3e24c45264e2224e3c66f5 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 1 Aug 2026 09:52:55 +1000 Subject: [PATCH 13/14] mavproxy: do not let a missing modpath hide a real import failure keeping the first error stopped the fallback bare module name reporting 'No module named X' over the real reason, but it also hid the reason an external plugin failed. A ModuleNotFoundError naming the modpath we tried only says that path does not exist, so let a later attempt replace it. --- MAVProxy/mavproxy.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MAVProxy/mavproxy.py b/MAVProxy/mavproxy.py index 9c93511c4f..d30fcdb25e 100755 --- a/MAVProxy/mavproxy.py +++ b/MAVProxy/mavproxy.py @@ -377,6 +377,7 @@ def load_module(self, modname, quiet=False, **kwargs): # don't report an error return True ex = None + ex_modpath = None for modpath in modpaths: try: m = import_package(modpath) @@ -394,10 +395,14 @@ def load_module(self, modname, quiet=False, **kwargs): ex = "%s.init did not return a MPModule instance" % modname break except ImportError as msg: - # keep the first failure: the later modpaths are fallbacks, and - # their "No module named X" hides why the real one failed - if ex is None: + # keep the first real failure, so a fallback modpath's "No + # module named X" cannot hide it. A ModuleNotFoundError naming + # the modpath we just tried only says that path does not exist, + # so let a later attempt replace it with its own reason + if (ex is None or + (isinstance(ex, ModuleNotFoundError) and ex.name == ex_modpath)): ex = msg + ex_modpath = modpath if mpstate.settings.moddebug > 1: print(get_exception_stacktrace(msg)) help_traceback = "" From 4b2221a34535247ea4a5e4026f0934dd497f0ca7 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 1 Aug 2026 09:52:55 +1000 Subject: [PATCH 14/14] map3d: report why the viewer child failed to start find_spec only catches packages that are absent, not a VTK or wx that is installed but unusable, and the child's stderr goes nowhere when MAVProxy is started from a GUI. wx raises SystemExit rather than an Exception when it cannot open the display, so catch that too. Also point at the MAVProxy[map3d] extra: 'pip install vtk quantized-mesh-tile' pulls numpy>=2 without the opencv and matplotlib builds to match, which breaks the 2D map on older distros. --- MAVProxy/modules/mavproxy_map3d/__init__.py | 13 ++++++--- MAVProxy/modules/mavproxy_map3d/map3d.py | 29 ++++++++++++++------- MAVProxy/tools/MAVExplorer.py | 2 +- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/MAVProxy/modules/mavproxy_map3d/__init__.py b/MAVProxy/modules/mavproxy_map3d/__init__.py index 1ddb52ac14..2f9e7c9377 100644 --- a/MAVProxy/modules/mavproxy_map3d/__init__.py +++ b/MAVProxy/modules/mavproxy_map3d/__init__.py @@ -371,11 +371,13 @@ def send_kml(self, kml_mod=None): def idle_task(self): if self.map is None: return - if not self.map.is_alive(): - self.map = None - return + # drain events before dropping a dead child, so we don't lose the + # reason it failed to start + alive = self.map.is_alive() for event in self.map.check_events(): - if event[0] == 'render_settings': + if event[0] == 'startup_error': + print("map3d: the 3D view failed to start:\n%s" % event[1]) + elif event[0] == 'render_settings': (_, brightness, shading, wireframe, fpvfov) = event self.map3d_settings.terrainbrightness = brightness self.map3d_settings.terrainshading = shading @@ -383,6 +385,9 @@ def idle_task(self): self.map3d_settings.fpvfov = fpvfov elif event[0] == 'follow': self.follow = bool(event[1]) + if not alive: + self.map = None + return kml_mod = self.module('kmlread') if self._kml_state(kml_mod) != self.kml_change_state: self.send_kml(kml_mod) diff --git a/MAVProxy/modules/mavproxy_map3d/map3d.py b/MAVProxy/modules/mavproxy_map3d/map3d.py index 213b790730..7ec27d363e 100644 --- a/MAVProxy/modules/mavproxy_map3d/map3d.py +++ b/MAVProxy/modules/mavproxy_map3d/map3d.py @@ -6,6 +6,7 @@ import importlib.util import time import queue +import traceback from MAVProxy.modules.lib import multiproc @@ -27,7 +28,9 @@ def missing_packages(): def missing_packages_message(missing): - return ("map3d needs extra packages: pip install vtk quantized-mesh-tile " + # install via the extra, not 'pip install vtk quantized-mesh-tile': the + # latter pulls numpy>=2 without the opencv/matplotlib builds to match + return ("map3d needs extra packages: pip install 'MAVProxy[map3d]' " "(missing %s)" % ', '.join(missing)) @@ -60,14 +63,22 @@ def __init__(self, title="3D Map", service="MicrosoftSat", zexag=1.0, def child_task(self): from MAVProxy.modules.lib import mp_util mp_util.child_close_fds() - from MAVProxy.modules.lib import wx_processguard # noqa: F401 - from MAVProxy.modules.lib.wx_loader import wx - from MAVProxy.modules.mavproxy_map3d.map3d_ui import Map3DFrame - - app = wx.App(False) - app.SetExitOnFrameDelete(True) - frame = Map3DFrame(self) - frame.Show() + try: + from MAVProxy.modules.lib import wx_processguard # noqa: F401 + from MAVProxy.modules.lib.wx_loader import wx + from MAVProxy.modules.mavproxy_map3d.map3d_ui import Map3DFrame + + app = wx.App(False) + app.SetExitOnFrameDelete(True) + frame = Map3DFrame(self) + frame.Show() + except (Exception, SystemExit): + # our stderr goes nowhere when MAVProxy is started from a GUI, so + # hand the failure to the parent rather than dying unexplained. + # wx exits rather than raising when it cannot open the display, + # hence SystemExit + self.event_queue.put(('startup_error', traceback.format_exc())) + return self.app_ready.set() app.MainLoop() diff --git a/MAVProxy/tools/MAVExplorer.py b/MAVProxy/tools/MAVExplorer.py index 8456af7014..69fd79d728 100755 --- a/MAVProxy/tools/MAVExplorer.py +++ b/MAVProxy/tools/MAVExplorer.py @@ -664,7 +664,7 @@ def cmd_map3d(args): from MAVProxy.modules.mavproxy_map3d.map3d import ( Map3D, missing_packages, missing_packages_message) except ImportError as ex: - print("map3d needs extra packages: pip install vtk quantized-mesh-tile (%s)" % ex) + print("map3d needs extra packages: pip install 'MAVProxy[map3d]' (%s)" % ex) return # map3d.py itself does not import VTK; the viewer child process does missing = missing_packages()