Source code for wolfhece.pyvertexvectors._triangulation

"""Triangulation GUI class with OpenGL and matplotlib rendering."""

import wx
import logging
from OpenGL.GL import *
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from matplotlib.tri import Triangulation as mpl_tri

from ..PyTranslate import _
from ..drawing_obj import Element_To_Draw
from ._models import TriangulationModel

[docs] class Triangulation(TriangulationModel, Element_To_Draw): """Triangulation with OpenGL rendering and matplotlib plotting. Inherits all data operations from :class:`TriangulationModel` and adds ``Element_To_Draw`` integration, OpenGL display-list rendering, and matplotlib visualisation helpers. """ # ================================================================ # Constructor # ================================================================ def __init__(self, fn='', pts=None, tri=None, idx: str = '', plotted: bool = True, mapviewer=None, need_for_wx: bool = False) -> None: """Initialise a GUI-enabled triangulation. :param fn: File path to read (empty string to skip). :param pts: Initial point array. :param tri: Initial triangle connectivity array. :param idx: Identifier string. :param plotted: Whether the triangulation is plotted. :param mapviewer: Parent map viewer instance. :param need_for_wx: Whether wx integration is required. """ if pts is None: pts = [] if tri is None: tri = [] # Must exist before TriangulationModel.__init__(), which may call self.read().
[docs] self.idgllist = -99999
# Initialise the drawing interface (plotted, mapviewer, bounds) Element_To_Draw.__init__(self, idx=idx, plotted=plotted, mapviewer=mapviewer, need_for_wx=need_for_wx) # Initialise the model (geometry, I/O, transforms). This must run after # Element_To_Draw so model-computed bounds are not overwritten to 0. TriangulationModel.__init__(self, fn=fn, pts=pts, tri=tri, idx=idx) # ================================================================ # Factory overrides # ================================================================
[docs] def _make_triangulation(self, **kwargs) -> "Triangulation": """Factory: create a GUI-enabled triangulation.""" return Triangulation(**kwargs)
# ================================================================ # Properties # ================================================================ @property
[docs] def id_list(self): """Backward-compatible alias for the OpenGL display-list id.""" return self.idgllist
@id_list.setter def id_list(self, value): self.idgllist = value # ================================================================ # I/O operations # ================================================================
[docs] def read(self, fn: str): """Read a file, then reset the GL display list. :param fn: File path to read. """ super().read(fn) self.reset_plot()
[docs] def import_from_gltf(self, fn=''): """Import a GLTF/GLB file, with optional wx file dialog. :param fn: File path (empty to show a file dialog). """ wx_exists = wx.GetApp() is not None if fn == '' and wx_exists: dlg = wx.FileDialog(None, _('Choose filename'), wildcard='binary gltf2 (*.glb)|*.glb|gltf2 (*.gltf)|*.gltf|All (*.*)|*.*', style=wx.FD_OPEN) ret = dlg.ShowModal() if ret == wx.ID_CANCEL: dlg.Destroy() return fn = dlg.GetPath() dlg.Destroy() super().import_from_gltf(fn) self.reset_plot()
[docs] def export_to_gltf(self, fn=''): """Export to GLTF/GLB, with optional wx file dialog. :param fn: File path (empty to show a file dialog). """ if fn == '': dlg = wx.FileDialog(None, _('Choose filename'), wildcard='binary gltf2 (*.glb)|*.glb|gltf2 (*.gltf)|*.gltf|All (*.*)|*.*', style=wx.FD_SAVE) ret = dlg.ShowModal() if ret == wx.ID_CANCEL: dlg.Destroy() return fn = dlg.GetPath() dlg.Destroy() return super().export_to_gltf(fn)
# ================================================================ # Object operations # ================================================================
[docs] def copy(self) -> "Triangulation": """Return a GUI-enabled copy of the triangulation.""" newtri = self._make_triangulation(pts=self.pts.copy(), tri=self.tri.copy(), idx=self.idx + '_copy') return newtri
# ================================================================ # OpenGL rendering # ================================================================
[docs] def reset_listogl(self): """Reset the OpenGL display list for the triangulation.""" if getattr(self, 'idgllist', -99999) != -99999: try: glDeleteLists(self.idgllist, 1) except Exception: logging.warning('Problem while resetting OpenGL plot - Triangulation.reset_listogl') finally: self.idgllist = -99999
[docs] def reset_plot(self): """Backward-compatible alias used by the viewer to invalidate the GL plot.""" self.reset_listogl()
[docs] def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None ): """Plot the triangulation using OpenGL. :param sx: Scale factor along X. :param sy: Scale factor along Y. :param xmin: Minimum X of the viewport. :param ymin: Minimum Y of the viewport. :param xmax: Maximum X of the viewport. :param ymax: Maximum Y of the viewport. :param size: Reference size for rendering. """ if self.idgllist == -99999: try: self.idgllist = glGenLists(1) glNewList(self.idgllist, GL_COMPILE) glPolygonMode(GL_FRONT_AND_BACK,GL_LINE) for curtri in self.tri: glBegin(GL_LINE_STRIP) glColor3ub(int(0),int(0),int(0)) glVertex2d(self.pts[curtri[0]][0], self.pts[curtri[0]][1]) glVertex2d(self.pts[curtri[1]][0], self.pts[curtri[1]][1]) glVertex2d(self.pts[curtri[2]][0], self.pts[curtri[2]][1]) glVertex2d(self.pts[curtri[0]][0], self.pts[curtri[0]][1]) glEnd() glPolygonMode(GL_FRONT_AND_BACK,GL_LINE) glEndList() glCallList(self.idgllist) except Exception: logging.exception( 'Problem with OpenGL plot - Triangulation.plot -- idx=%s npts=%s ntri=%s mapviewer=%s', self.idx, len(self.pts) if self.pts is not None else 0, len(self.tri) if self.tri is not None else 0, type(self.get_mapviewer()).__name__ if self.get_mapviewer() is not None else None, ) else: glCallList(self.idgllist)
# ================================================================ # Matplotlib plotting # ================================================================
[docs] def plot_matplotlib(self, ax:Axes | tuple[Figure, Axes] = None, color='black', alpha=1., lw=1.5, **kwargs): """Plot the triangulation using Matplotlib. :param ax: Axes, (Figure, Axes) tuple, or None to create a new figure. :param color: Line colour. :param alpha: Opacity (0–1). :param lw: Line width. :param kwargs: Extra keyword arguments forwarded to ``ax.plot``. :return: ``(fig, ax)`` tuple. """ if isinstance(ax, tuple): fig, ax = ax elif ax is None: fig, ax = plt.subplots() else: fig = ax.figure if self.nb_tri>0: for curtri in self.tri: x = [self.pts[curtri[0]][0], self.pts[curtri[1]][0], self.pts[curtri[2]][0], self.pts[curtri[0]][0]] y = [self.pts[curtri[0]][1], self.pts[curtri[1]][1], self.pts[curtri[2]][1], self.pts[curtri[0]][1]] ax.plot(x, y, color=color, alpha=alpha, lw=lw, **kwargs) else: logging.warning('No triangles to plot') return fig, ax
@property
[docs] def mpl_triangulation(self) -> mpl_tri: """Return the triangulation as a Matplotlib Triangulation object. :return: ``matplotlib.tri.Triangulation`` or *None* if empty. """ if self.nb_tri>0: return mpl_tri(self.pts[:,0], self.pts[:,1], self.tri) else: logging.warning('No triangles to plot') return None
[docs] def plot_matplotlib_3D(self, ax:Axes | tuple[Figure, Axes] = None, color='black', alpha=0.2, lw=1.5, edgecolor='k', shade=True, **kwargs): """Plot the triangulation as a 3-D surface using Matplotlib. :param ax: 3-D Axes, (Figure, Axes) tuple, or None to create one. :param color: Surface colour. :param alpha: Surface opacity. :param lw: Line width of triangle edges. :param edgecolor: Edge colour. :param shade: Whether to shade the surface. :param kwargs: Extra keyword arguments forwarded to ``plot_trisurf``. :return: ``(fig, ax)`` tuple, or ``(None, None)`` if empty. """ if self.nb_tri>0: if isinstance(ax, tuple): fig, ax = ax elif ax is None: fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) else: fig = ax.figure ax.plot_trisurf(self.mpl_triangulation, Z=self.pts[:,2], color=color, alpha=alpha, lw=lw, edgecolor=edgecolor, shade=shade, **kwargs) return fig, ax else: logging.warning('No triangles to plot') return None, None