Source code for vtkplotlib.figures.QtGuiFigure

# -*- coding: utf-8 -*-

import numpy as np
import sys
from pathlib import Path
from itertools import zip_longest

from vtkplotlib.figures import QtFigure, save_fig, view
from vtkplotlib.figures.QtFigure import QtWidgets, QtGui, QtCore
from vtkplotlib.data import ICONS_FOLDER

SCREENSHOT_ICON_PATH = ICONS_FOLDER / "screenshot.png"


[docs]class QtFigure2(QtFigure.QtFigure): """This is intended to be used as/for a more sophisticated GUI when one is needed. By providing some common features here, hopefully this can speed up the tedious process of building a GUI. Any contributions here would be very welcome. I want to write this so that each extra feature is optional allowing custom GUIs can be built quickly. This is still under development. Currently it has: 1. A screenshot button. 2. A panel for preset camera views. 3. An actor table to show / hide / color plots interactively (although it needs some way to group them). 4. A cursor tracker displaying 3D coordinates of the mouse. I hope/intend to add: 1. Suggestions welcome here... Use this class the same way you would use `QtFigure` (see there first.) Each feature is added with a ``fig.add_***()`` method. .. code-block:: python import vtkplotlib as vpl import numpy as np # Create the figure. This as-is looks like just a QtFigure. fig = vpl.QtFigure2() # Add each feature you want. Pass arguments to customise each one. fig.add_screenshot_button(pixels=1080) fig.add_preset_views() fig.add_show_plot_table_button() # Use ``fig.add_all()`` to add all them all. # You will likely want to dump the above into a function. Or a class # inheriting from ``vpl.QtFigure2``. # The usual, plot something super exciting. vpl.polygon(np.array([[1, 0, 0], [1, 1, 0], [0, 1, 1], [0, 0, 1]]), color="grey") # Then either ``vpl.show()`` or fig.show() """ def __init__(self, name="qt vtk figure", parent=None): super().__init__(name, parent) self.menu = QtWidgets.QHBoxLayout() self.vl.insertLayout(0, self.menu) self.plot_table = None self.save_fig_kwargs = {} def add_preset_views(self, names=None, view_params=None, icons=()): if view_params is None: self.view_buttons = ViewButtons.default(self) else: self.view_buttons = ViewButtons(self, names, view_params, icons=icons) self.menu.addLayout(self.view_buttons.to_layout()) return self def add_preset_views_from_directions(self, directions, ups, mirrors=True): self.view_buttons = ViewButtons.from_directions( directions, ups, mirrors=mirrors, figure=self) self.menu.addLayout(self.view_buttons.to_layout()) return self def add_screenshot_button(self, **save_fig_kwargs): self.default_screenshot_path = Path() / (self.window_name + ".jpg") self.screenshot_button = Button("Screenshot", self.screenshot, SCREENSHOT_ICON_PATH) self.menu.addWidget(self.screenshot_button) self.save_fig_kwargs = save_fig_kwargs return self def add_cursor_tracker(self): from vtkplotlib.figures.QtGuiFigure.cursor_tracker import CursorTracker self.cursor_tracker = CursorTracker(self) def screenshot(self): path = QtWidgets.QFileDialog.getSaveFileName( self, "Save screenshot", None, "(*.jpg);;(*.png)")[0] if path: save_fig(path, fig=self, **self.save_fig_kwargs) def add_show_plot_table_button(self): self.show_plot_table_button = Button("Show plots menu", self.show_plot_table) self.menu.addWidget(self.show_plot_table_button) return self def show_plot_table(self): self.plot_table = table = PlotTable(self) table.show() def update(self): super().update() if self.plot_table is not None: self.plot_table.update() def add_all(self): self.add_preset_views() self.add_screenshot_button() self.add_show_plot_table_button() self.add_cursor_tracker() return self
class Button(QtWidgets.QPushButton): def __init__(self, name, callback=None, icon=None, parent=None): super().__init__(parent) if callback is None: callback = self.default_callback self.released.connect(callback) self.setIconSize(QtCore.QSize(40, 40)) p = self.sizePolicy() p.setHorizontalPolicy(p.Policy.Minimum) p.setVerticalPolicy(p.Policy.Minimum) self.setSizePolicy(p) if icon is not None: self.setIcon(as_qicon(icon)) else: self.setText(name) def default_callback(self): raise NotImplementedError def as_qicon(obj): pixmap = None if isinstance(obj, QtGui.QIcon): return obj from vtkplotlib.nuts_and_bolts import isinstance_PathLike, isinstance_no_import if isinstance_PathLike(obj): pixmap = QtGui.QPixmap(str(obj)) if isinstance_no_import(obj, "PIL.Image", "Image"): pixmap = obj.toqpixmap() if pixmap is not None: return QtGui.QIcon(pixmap) else: raise TypeError("""Icons can be created from any of the following: - str - os.Pathlike - QtGui.QIcon - PIL.Image.Image Received {}""".format(type(obj))) class ViewButton(Button): def __init__(self, name, parent, icon=None, view_params=None): if view_params is None: view_params = {} super().__init__(name, self.set_view, icon, parent) self.args = view_params def set_view(self): view(fig=self.parent(), **self.args) self.parent().reset_camera() self.parent().update() class ViewButtons(object): DEFAULT_NAMES = ("Right", "Left", "Front", "Back", "Top", "Bottom") DEFAULT_ICONS = tuple(ICONS_FOLDER / (i + ".jpg") for i in DEFAULT_NAMES) def __init__(self, fig, names=DEFAULT_NAMES, view_params=(), icons=DEFAULT_ICONS): self.buttons = [] for (name, args, icon) in zip_longest(names, view_params, icons): button = ViewButton(name, fig, icon) if args is not None: button.args = args self.buttons.append(button) @classmethod def default(cls, figure): self = cls(figure) self.init_default() return self def init_default(self): directions = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) ups = np.array([[0, 0, 1], [0, 1, 0]]) self.init_from_directions(directions, ups) def init_from_directions(self, directions, ups, mirrors=True): if mirrors: signs = (1, -1) else: signs = (1,) view_params = [] for d_ in directions: for sign in signs: d = d_ * sign args = {"camera_position": d} for up in ups: if np.cross(d, up).any(): args["up_view"] = up break else: raise ValueError( "All `up_views` are parallel to direction {}".format(d)) view_params.append(args) for (button, args) in zip_longest(self.buttons, view_params): button.args = args def to_layout(self, parent=None): out = QtWidgets.QHBoxLayout(parent) [out.addWidget(i) for i in self.buttons] return out def rotate(self, M): for button in self.buttons: args = button.args for key in args: args[key] = np.matmul(args[key], M) class PlotTable(QtWidgets.QWidget): def __init__(self, figure): super().__init__() self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint) self.figure = figure self.plots = self.figure.plots self.rows = dict() self.grid = QtWidgets.QGridLayout() self.setLayout(self.grid) self.timer = timer = QtCore.QTimer() timer.setInterval(50) #timer.timeout.connect(lambda: print(row.text.underMouse())) timer.timeout.connect(self.update) timer.start() self.update() def add_plot(self, plot): row = PlotTableRow(plot, len(self.rows)) # + 1 row.add_to_grid(self.grid) self.rows[plot] = row def remove_plot(self, plot): self.rows.pop(plot) def update(self): for plot in self.plots - set(self.rows.keys()): self.add_plot(plot) for plot in set(self.rows.keys()) - self.plots: self.remove_plot(plot) class PlotTableRow(object): def __init__(self, plot, row_num): self.plot = plot self.row_num = row_num self.visible_checkbox = QtWidgets.QCheckBox() self.visible_checkbox.setChecked(self.plot.visible) self.visible_checkbox.stateChanged.connect(self.chk_box_change_cb) if hasattr(self.plot, "name"): name = self.plot.name else: name = repr(self.plot) self.text = QLabel_alterada(name) self.text.released.connect(self.toggle_visible) def chk_box_change_cb(self): CheckState = getattr(QtCore.Qt, "CheckState", QtCore.Qt) if self.visible_checkbox.checkState() == CheckState.Unchecked: self.plot.visible = 0 else: self.plot.visible = 1 self.plot.fig.update() def toggle_visible(self): self.visible_checkbox.toggle() def add_to_grid(self, grid): grid.addWidget(self.visible_checkbox, self.row_num, 0) grid.addWidget(self.text, self.row_num, 1) class QLabel_alterada(QtWidgets.QLabel): released = (getattr(QtCore, "Signal", None) or QtCore.pyqtSignal)() def mouseReleaseEvent(self, ev): self.released.emit() if __name__ == "__main__": import vtkplotlib as vpl app = None app = QtWidgets.QApplication(sys.argv) self = vpl.QtFigure2("Rabbits") plot = vpl.mesh_plot(vpl.data.get_rabbit_stl()) # plot.name = "rabbit" # mesh_2 = Mesh.from_file(vpl.data.get_rabbit_stl()) # mesh_2.translate(np.array([100, 0, 0])) # vpl.scatter(np.random.uniform(-100, 100, (3, 3))) self.add_all() fig, self = self, self.view_buttons # M = np.roll(np.eye(3), 1, 0) # self.rotate(M) fig.show() # ## self.show(False) # ## app.processEvents() # plot = vpl.mesh_plot(mesh_2, color="g") # plot.name = "green rabbit" ## self.update() # # # ## app.processEvents() ## row = table.rows[plot] # # self.show() ## app.exec() #