molde 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- molde/__init__.py +5 -0
- molde/__main__.py +63 -0
- molde/actors/__init__.py +4 -0
- molde/actors/ghost_actor.py +12 -0
- molde/actors/lines_actor.py +31 -0
- molde/actors/round_points_actor.py +7 -0
- molde/actors/square_points_actor.py +32 -0
- molde/colors/__init__.py +2 -0
- molde/colors/color.py +120 -0
- molde/colors/color_names.py +125 -0
- molde/fonts/IBMPlexMono-Bold.ttf +0 -0
- molde/fonts/IBMPlexMono-Regular.ttf +0 -0
- molde/icons/arrow_down_dark_theme.svg +1 -0
- molde/icons/arrow_down_disabled_dark_theme.svg +1 -0
- molde/icons/arrow_down_disabled_light_theme.svg +1 -0
- molde/icons/arrow_down_light_theme.svg +1 -0
- molde/icons/arrow_left_dark_theme.svg +1 -0
- molde/icons/arrow_left_light_theme.svg +1 -0
- molde/icons/arrow_right_dark_theme.svg +1 -0
- molde/icons/arrow_right_light_theme.svg +1 -0
- molde/icons/arrow_up_dark_theme.svg +1 -0
- molde/icons/arrow_up_disabled_dark_theme.svg +1 -0
- molde/icons/arrow_up_disabled_light_theme.svg +1 -0
- molde/icons/arrow_up_light_theme.svg +1 -0
- molde/icons/check_box_image.svg +1 -0
- molde/interactor_styles/__init__.py +2 -0
- molde/interactor_styles/arcball_camera_style.py +272 -0
- molde/interactor_styles/box_selection_style.py +70 -0
- molde/pickers/__init__.py +2 -0
- molde/pickers/cell_area_picker.py +61 -0
- molde/pickers/cell_property_area_picker.py +84 -0
- molde/poly_data/__init__.py +2 -0
- molde/poly_data/lines_data.py +23 -0
- molde/poly_data/vertices_data.py +24 -0
- molde/render_widgets/__init__.py +2 -0
- molde/render_widgets/animated_render_widget.py +164 -0
- molde/render_widgets/common_render_widget.py +433 -0
- molde/stylesheets/__init__.py +119 -0
- molde/stylesheets/common.qss +16 -0
- molde/stylesheets/create_color_page.py +61 -0
- molde/stylesheets/mainwindow.ui +611 -0
- molde/stylesheets/qcheckbox.qss +19 -0
- molde/stylesheets/qinputs.qss +79 -0
- molde/stylesheets/qlayouts.qss +22 -0
- molde/stylesheets/qmenubar.qss +12 -0
- molde/stylesheets/qprogressbar.qss +12 -0
- molde/stylesheets/qpushbutton.qss +91 -0
- molde/stylesheets/qradiobutton.qss +31 -0
- molde/stylesheets/qscrollbar.qss +30 -0
- molde/stylesheets/qslider.qss +61 -0
- molde/stylesheets/qtablewidget.qss +27 -0
- molde/stylesheets/qtabwidget.qss +30 -0
- molde/stylesheets/qtoolbar.qss +62 -0
- molde/stylesheets/qtoolbuttons.qss +14 -0
- molde/stylesheets/qtreewidget.qss +25 -0
- molde/utils/__init__.py +8 -0
- molde/utils/format_sequences.py +44 -0
- molde/utils/poly_data_utils.py +25 -0
- molde/utils/tree_info.py +52 -0
- molde-0.1.0.dist-info/METADATA +44 -0
- molde-0.1.0.dist-info/RECORD +62 -0
- molde-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
from threading import Lock
|
2
|
+
from time import time
|
3
|
+
import numpy as np
|
4
|
+
from pathlib import Path
|
5
|
+
from PIL import Image
|
6
|
+
import logging
|
7
|
+
|
8
|
+
from .common_render_widget import CommonRenderWidget
|
9
|
+
|
10
|
+
|
11
|
+
class AnimatedRenderWidget(CommonRenderWidget):
|
12
|
+
def __init__(self, parent=None):
|
13
|
+
super().__init__(parent)
|
14
|
+
|
15
|
+
self.playing_animation = False
|
16
|
+
self._animation_lock = Lock()
|
17
|
+
self._animation_frame = 0
|
18
|
+
self._animation_last_time = 0
|
19
|
+
self._animation_total_frames = 30
|
20
|
+
self._animation_fps = 30
|
21
|
+
self._animation_cycles = 0
|
22
|
+
self._animation_current_cycle = 0
|
23
|
+
self._animation_timer = self.render_interactor.CreateRepeatingTimer(500)
|
24
|
+
self.render_interactor.AddObserver("TimerEvent", self._animation_callback)
|
25
|
+
|
26
|
+
def start_animation(self, fps=None, frames=None, cycles=None):
|
27
|
+
if isinstance(fps, int | float):
|
28
|
+
self._animation_fps = fps
|
29
|
+
|
30
|
+
if isinstance(frames, int):
|
31
|
+
self._animation_total_frames = frames
|
32
|
+
|
33
|
+
if isinstance(cycles, int):
|
34
|
+
self._animation_cycles = cycles
|
35
|
+
self._animation_current_cycle = 0
|
36
|
+
else:
|
37
|
+
self._animation_cycles = 0
|
38
|
+
self._animation_current_cycle = 0
|
39
|
+
|
40
|
+
if self.playing_animation:
|
41
|
+
return
|
42
|
+
|
43
|
+
self.playing_animation = True
|
44
|
+
|
45
|
+
def stop_animation(self):
|
46
|
+
if not self.playing_animation:
|
47
|
+
return
|
48
|
+
|
49
|
+
if self._animation_timer is None:
|
50
|
+
return
|
51
|
+
|
52
|
+
self.playing_animation = False
|
53
|
+
|
54
|
+
def toggle_animation(self):
|
55
|
+
if self.playing_animation:
|
56
|
+
self.stop_animation()
|
57
|
+
else:
|
58
|
+
self.start_animation()
|
59
|
+
|
60
|
+
def _animation_callback(self, obj, event):
|
61
|
+
"""
|
62
|
+
Common function with controls that are meaningfull to
|
63
|
+
all kinds of animations.
|
64
|
+
"""
|
65
|
+
|
66
|
+
if not self.playing_animation:
|
67
|
+
return
|
68
|
+
|
69
|
+
# Wait the rendering of the last frame
|
70
|
+
# before starting a new one
|
71
|
+
if self._animation_lock.locked():
|
72
|
+
return
|
73
|
+
|
74
|
+
# Only needed because vtk CreateRepeatingTimer(n)
|
75
|
+
# does not work =/
|
76
|
+
dt = time() - self._animation_last_time
|
77
|
+
if (dt) < 1 / self._animation_fps:
|
78
|
+
return
|
79
|
+
|
80
|
+
if (self._animation_cycles != 0) and (self._animation_current_cycle >= self._animation_cycles):
|
81
|
+
self.stop_animation()
|
82
|
+
return
|
83
|
+
|
84
|
+
if self._animation_frame == 0:
|
85
|
+
self._animation_current_cycle += 1
|
86
|
+
|
87
|
+
with self._animation_lock:
|
88
|
+
self._animation_frame = (
|
89
|
+
self._animation_frame + 1
|
90
|
+
) % self._animation_total_frames
|
91
|
+
self.update_animation(self._animation_frame)
|
92
|
+
self._animation_last_time = time()
|
93
|
+
|
94
|
+
def update_animation(self, frame: int):
|
95
|
+
raise NotImplementedError(
|
96
|
+
'The function "update_animation" was not implemented!'
|
97
|
+
)
|
98
|
+
|
99
|
+
def save_video(self, path: str | Path, n_loops=20):
|
100
|
+
'''
|
101
|
+
Saves a video of multiple cycles of the current animation.
|
102
|
+
Supported formats are MP4, AVI, OGV, and WEBM.
|
103
|
+
'''
|
104
|
+
from moviepy.editor import ImageSequenceClip
|
105
|
+
|
106
|
+
# Stop animation to prevent conflicts
|
107
|
+
previous_state = self.playing_animation
|
108
|
+
self.stop_animation()
|
109
|
+
|
110
|
+
logging.info("Generating video frames...")
|
111
|
+
images = list()
|
112
|
+
for i in range(self._animation_total_frames):
|
113
|
+
self.update_animation(i)
|
114
|
+
screenshot = self.get_screenshot().resize([1920, 1080])
|
115
|
+
images.append(screenshot)
|
116
|
+
frames = [np.array(img) for img in images]
|
117
|
+
|
118
|
+
# recover the playing animation status
|
119
|
+
self.playing_animation = previous_state
|
120
|
+
|
121
|
+
logging.info("Saving video to file...")
|
122
|
+
clip = ImageSequenceClip(frames, self._animation_fps)
|
123
|
+
clip = clip.loop(duration = clip.duration * n_loops)
|
124
|
+
clip.write_videofile(str(path), preset="veryfast", logger=None)
|
125
|
+
|
126
|
+
def save_animation(self, path: str | Path):
|
127
|
+
'''
|
128
|
+
Saves an animated image file of a animation cycle.
|
129
|
+
Supported formats are WEBP and GIF.
|
130
|
+
|
131
|
+
We strongly recomend you to use webp, because of the reduced
|
132
|
+
file size and the superior visual quality.
|
133
|
+
Despite that, gifs are usefull sometimes because of its popularity.
|
134
|
+
'''
|
135
|
+
|
136
|
+
path = Path(path)
|
137
|
+
is_gif = path.suffix == ".gif"
|
138
|
+
|
139
|
+
# Stop animation to prevent conflicts
|
140
|
+
previous_state = self.playing_animation
|
141
|
+
self.stop_animation()
|
142
|
+
|
143
|
+
logging.info("Generating animation frames...")
|
144
|
+
images:list[Image.Image] = list()
|
145
|
+
for i in range(self._animation_total_frames):
|
146
|
+
self.update_animation(i)
|
147
|
+
screenshot = self.get_screenshot().convert("RGB").resize([1280, 720])
|
148
|
+
if is_gif:
|
149
|
+
screenshot = screenshot.quantize(method=Image.Quantize.FASTOCTREE, kmeans=2)
|
150
|
+
images.append(screenshot)
|
151
|
+
|
152
|
+
# recover the playing animation status
|
153
|
+
self.playing_animation = previous_state
|
154
|
+
|
155
|
+
logging.info("Saving animation to file...")
|
156
|
+
images[0].save(
|
157
|
+
path,
|
158
|
+
save_all=True,
|
159
|
+
append_images=images[1:],
|
160
|
+
optimize=False,
|
161
|
+
duration=self._animation_total_frames / self._animation_fps,
|
162
|
+
loop=0,
|
163
|
+
quality=90
|
164
|
+
)
|
@@ -0,0 +1,433 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from threading import Lock
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
from PIL import Image
|
6
|
+
from PyQt5.QtCore import pyqtSignal
|
7
|
+
from PyQt5.QtWidgets import QFrame, QStackedLayout
|
8
|
+
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
|
9
|
+
from vtkmodules.util.numpy_support import vtk_to_numpy
|
10
|
+
from vtkmodules.vtkCommonCore import VTK_FONT_FILE, vtkLookupTable
|
11
|
+
from vtkmodules.vtkInteractionWidgets import (
|
12
|
+
vtkLogoRepresentation,
|
13
|
+
vtkOrientationMarkerWidget,
|
14
|
+
)
|
15
|
+
from vtkmodules.vtkIOImage import vtkPNGReader
|
16
|
+
from vtkmodules.vtkRenderingAnnotation import (
|
17
|
+
vtkAxesActor,
|
18
|
+
vtkLegendScaleActor,
|
19
|
+
vtkScalarBarActor,
|
20
|
+
)
|
21
|
+
from vtkmodules.vtkRenderingCore import (
|
22
|
+
vtkLight,
|
23
|
+
vtkActor,
|
24
|
+
vtkRenderer,
|
25
|
+
vtkTextActor,
|
26
|
+
vtkInteractorStyle,
|
27
|
+
vtkTextProperty,
|
28
|
+
vtkWindowToImageFilter,
|
29
|
+
)
|
30
|
+
|
31
|
+
from molde import MOLDE_DIR
|
32
|
+
from molde.colors import Color
|
33
|
+
from molde.interactor_styles import ArcballCameraInteractorStyle
|
34
|
+
|
35
|
+
|
36
|
+
class CommonRenderWidget(QFrame):
|
37
|
+
"""
|
38
|
+
This class is needed to show vtk renderers in pyqt.
|
39
|
+
|
40
|
+
A vtk widget must always have a renderer, even if it is empty.
|
41
|
+
"""
|
42
|
+
|
43
|
+
left_clicked = pyqtSignal(int, int)
|
44
|
+
left_released = pyqtSignal(int, int)
|
45
|
+
right_clicked = pyqtSignal(int, int)
|
46
|
+
right_released = pyqtSignal(int, int)
|
47
|
+
|
48
|
+
def __init__(self, parent=None, *, theme="dark"):
|
49
|
+
super().__init__(parent)
|
50
|
+
|
51
|
+
self.renderer = vtkRenderer()
|
52
|
+
self.interactor_style = ArcballCameraInteractorStyle()
|
53
|
+
self.render_interactor = QVTKRenderWindowInteractor(self)
|
54
|
+
|
55
|
+
self.render_interactor.Initialize()
|
56
|
+
self.render_interactor.GetRenderWindow().AddRenderer(self.renderer)
|
57
|
+
self.render_interactor.SetInteractorStyle(self.interactor_style)
|
58
|
+
|
59
|
+
self.renderer.ResetCamera()
|
60
|
+
|
61
|
+
self.render_interactor.AddObserver(
|
62
|
+
"LeftButtonPressEvent",
|
63
|
+
self.left_click_press_event
|
64
|
+
)
|
65
|
+
self.render_interactor.AddObserver(
|
66
|
+
"LeftButtonReleaseEvent",
|
67
|
+
self.left_click_release_event
|
68
|
+
)
|
69
|
+
self.render_interactor.AddObserver(
|
70
|
+
"RightButtonPressEvent",
|
71
|
+
self.right_click_press_event
|
72
|
+
)
|
73
|
+
self.render_interactor.AddObserver(
|
74
|
+
"RightButtonReleaseEvent",
|
75
|
+
self.right_click_release_event
|
76
|
+
)
|
77
|
+
|
78
|
+
layout = QStackedLayout()
|
79
|
+
layout.addWidget(self.render_interactor)
|
80
|
+
self.setLayout(layout)
|
81
|
+
|
82
|
+
self._current_theme = theme
|
83
|
+
self._widget_actors = list()
|
84
|
+
self.update_lock = Lock()
|
85
|
+
|
86
|
+
self.create_info_text()
|
87
|
+
self.set_theme(self._current_theme)
|
88
|
+
|
89
|
+
def update_plot(self):
|
90
|
+
raise NotImplementedError("The function update_plot was not implemented")
|
91
|
+
|
92
|
+
def add_actors(self, *actors: vtkActor):
|
93
|
+
for actor in actors:
|
94
|
+
self.renderer.AddActor(actor)
|
95
|
+
if actor not in self._widget_actors:
|
96
|
+
self._widget_actors.append(actor)
|
97
|
+
|
98
|
+
def remove_actors(self, *actors: vtkActor):
|
99
|
+
for actor in actors:
|
100
|
+
self.renderer.RemoveActor(actor)
|
101
|
+
try:
|
102
|
+
self._widget_actors.remove(actor)
|
103
|
+
except ValueError:
|
104
|
+
continue
|
105
|
+
|
106
|
+
def remove_all_actors(self):
|
107
|
+
self.remove_actors(*self.get_widget_actors())
|
108
|
+
|
109
|
+
def remove_all_props(self):
|
110
|
+
self.renderer.RemoveAllViewProps()
|
111
|
+
|
112
|
+
def get_widget_actors(self):
|
113
|
+
return [i for i in self._widget_actors]
|
114
|
+
|
115
|
+
def set_interactor_style(self, interactor_style: vtkInteractorStyle):
|
116
|
+
self.interactor_style = interactor_style
|
117
|
+
self.render_interactor.SetInteractorStyle(interactor_style)
|
118
|
+
|
119
|
+
def get_interactor_style(self) -> vtkInteractorStyle:
|
120
|
+
return self.render_interactor.GetInteractorStyle()
|
121
|
+
|
122
|
+
def update(self):
|
123
|
+
if self.update_lock.locked():
|
124
|
+
return
|
125
|
+
|
126
|
+
ren_win = self.render_interactor.GetRenderWindow()
|
127
|
+
if ren_win is not None:
|
128
|
+
self.renderer.ResetCameraClippingRange()
|
129
|
+
ren_win.Render()
|
130
|
+
|
131
|
+
def left_click_press_event(self, obj, event):
|
132
|
+
x, y, *_ = self.render_interactor.GetEventPosition()
|
133
|
+
self.left_clicked.emit(x, y)
|
134
|
+
|
135
|
+
def left_click_release_event(self, obj, event):
|
136
|
+
x, y, *_ = self.render_interactor.GetEventPosition()
|
137
|
+
self.left_released.emit(x, y)
|
138
|
+
|
139
|
+
def right_click_press_event(self, obj, event):
|
140
|
+
x, y, *_ = self.render_interactor.GetEventPosition()
|
141
|
+
self.right_clicked.emit(x, y)
|
142
|
+
|
143
|
+
def right_click_release_event(self, obj, event):
|
144
|
+
x, y, *_ = self.render_interactor.GetEventPosition()
|
145
|
+
self.right_released.emit(x, y)
|
146
|
+
|
147
|
+
def get_screenshot(self) -> Image.Image:
|
148
|
+
image_filter = vtkWindowToImageFilter()
|
149
|
+
image_filter.SetInput(self.render_interactor.GetRenderWindow())
|
150
|
+
image_filter.Update()
|
151
|
+
|
152
|
+
vtk_image = image_filter.GetOutput()
|
153
|
+
width, height, _ = vtk_image.GetDimensions()
|
154
|
+
vtk_array = vtk_image.GetPointData().GetScalars()
|
155
|
+
components = vtk_array.GetNumberOfComponents()
|
156
|
+
|
157
|
+
array = vtk_to_numpy(vtk_array).reshape(height, width, components)
|
158
|
+
image = Image.fromarray(array).transpose(Image.FLIP_TOP_BOTTOM)
|
159
|
+
return image
|
160
|
+
|
161
|
+
def get_thumbnail(self):
|
162
|
+
image = self.get_screenshot()
|
163
|
+
return image.thumbnail((512, 512))
|
164
|
+
|
165
|
+
def save_image(self, path: str | Path):
|
166
|
+
'''
|
167
|
+
Saves the render as an image.
|
168
|
+
Supported formats are JPEG, JPG, PNG, BMP, ICO, TIFF, PPM and others.
|
169
|
+
'''
|
170
|
+
image = self.get_screenshot()
|
171
|
+
with open(path, "w") as file:
|
172
|
+
image.save(file)
|
173
|
+
|
174
|
+
def create_axes(self):
|
175
|
+
axes_actor = vtkAxesActor()
|
176
|
+
|
177
|
+
axes_actor.SetXAxisLabelText(" X")
|
178
|
+
axes_actor.SetYAxisLabelText(" Y")
|
179
|
+
axes_actor.SetZAxisLabelText(" Z")
|
180
|
+
|
181
|
+
axes_actor.GetXAxisShaftProperty().LightingOff()
|
182
|
+
axes_actor.GetYAxisShaftProperty().LightingOff()
|
183
|
+
axes_actor.GetZAxisShaftProperty().LightingOff()
|
184
|
+
axes_actor.GetXAxisTipProperty().LightingOff()
|
185
|
+
axes_actor.GetYAxisTipProperty().LightingOff()
|
186
|
+
axes_actor.GetZAxisTipProperty().LightingOff()
|
187
|
+
|
188
|
+
x_property = axes_actor.GetXAxisCaptionActor2D().GetCaptionTextProperty()
|
189
|
+
y_property = axes_actor.GetYAxisCaptionActor2D().GetCaptionTextProperty()
|
190
|
+
z_property = axes_actor.GetZAxisCaptionActor2D().GetCaptionTextProperty()
|
191
|
+
|
192
|
+
for text_property in [x_property, y_property, z_property]:
|
193
|
+
text_property: vtkTextProperty
|
194
|
+
text_property.ItalicOff()
|
195
|
+
text_property.BoldOn()
|
196
|
+
|
197
|
+
self.axes = vtkOrientationMarkerWidget()
|
198
|
+
self.axes.SetViewport(0, 0, 0.18, 0.18)
|
199
|
+
self.axes.SetOrientationMarker(axes_actor)
|
200
|
+
self.axes.SetInteractor(self.render_interactor)
|
201
|
+
self.axes.EnabledOn()
|
202
|
+
self.axes.InteractiveOff()
|
203
|
+
|
204
|
+
def create_scale_bar(self):
|
205
|
+
self.scale_bar_actor = vtkLegendScaleActor()
|
206
|
+
self.scale_bar_actor.AllAxesOff()
|
207
|
+
self.renderer.AddActor(self.scale_bar_actor)
|
208
|
+
|
209
|
+
font_file = MOLDE_DIR / "fonts/IBMPlexMono-Regular.ttf"
|
210
|
+
|
211
|
+
title_property: vtkTextProperty
|
212
|
+
title_property = self.scale_bar_actor.GetLegendTitleProperty()
|
213
|
+
title_property.SetFontSize(16)
|
214
|
+
title_property.ShadowOff()
|
215
|
+
title_property.ItalicOff()
|
216
|
+
title_property.BoldOn()
|
217
|
+
title_property.SetLineOffset(-55)
|
218
|
+
title_property.SetVerticalJustificationToTop()
|
219
|
+
title_property.SetFontFamily(VTK_FONT_FILE)
|
220
|
+
title_property.SetFontFile(font_file)
|
221
|
+
|
222
|
+
label_property: vtkTextProperty
|
223
|
+
label_property = self.scale_bar_actor.GetLegendLabelProperty()
|
224
|
+
label_property.SetFontSize(16)
|
225
|
+
label_property.SetColor((0.8, 0.8, 0.8))
|
226
|
+
label_property.ShadowOff()
|
227
|
+
label_property.ItalicOff()
|
228
|
+
label_property.BoldOff()
|
229
|
+
label_property.SetLineOffset(-35)
|
230
|
+
label_property.SetFontFamily(VTK_FONT_FILE)
|
231
|
+
label_property.SetFontFile(font_file)
|
232
|
+
|
233
|
+
# set the text color based on current theme
|
234
|
+
self.set_theme(self._current_theme)
|
235
|
+
|
236
|
+
def create_color_bar(self, lookup_table=None):
|
237
|
+
if lookup_table is None:
|
238
|
+
lookup_table = vtkLookupTable()
|
239
|
+
lookup_table.Build()
|
240
|
+
|
241
|
+
font_file = MOLDE_DIR / "fonts/IBMPlexMono-Regular.ttf"
|
242
|
+
|
243
|
+
self.colorbar_actor = vtkScalarBarActor()
|
244
|
+
self.colorbar_actor.SetLabelFormat("%1.0e ")
|
245
|
+
self.colorbar_actor.SetLookupTable(lookup_table)
|
246
|
+
self.colorbar_actor.SetWidth(0.02)
|
247
|
+
self.colorbar_actor.SetPosition(0.94, 0.17)
|
248
|
+
self.colorbar_actor.SetHeight(0.7)
|
249
|
+
self.colorbar_actor.SetMaximumNumberOfColors(400)
|
250
|
+
self.colorbar_actor.SetVerticalTitleSeparation(20)
|
251
|
+
self.colorbar_actor.UnconstrainedFontSizeOn()
|
252
|
+
self.colorbar_actor.SetTextPositionToPrecedeScalarBar()
|
253
|
+
self.renderer.AddActor(self.colorbar_actor)
|
254
|
+
|
255
|
+
colorbar_title: vtkTextProperty = self.colorbar_actor.GetTitleTextProperty()
|
256
|
+
colorbar_title.ShadowOff()
|
257
|
+
colorbar_title.ItalicOff()
|
258
|
+
colorbar_title.BoldOn()
|
259
|
+
colorbar_title.SetFontSize(16)
|
260
|
+
colorbar_title.SetJustificationToLeft()
|
261
|
+
colorbar_title.SetFontFamily(VTK_FONT_FILE)
|
262
|
+
colorbar_title.SetFontFile(font_file)
|
263
|
+
|
264
|
+
colorbar_label:vtkTextProperty = self.colorbar_actor.GetLabelTextProperty()
|
265
|
+
colorbar_label.ShadowOff()
|
266
|
+
colorbar_label.ItalicOff()
|
267
|
+
colorbar_label.BoldOn()
|
268
|
+
colorbar_label.SetFontSize(16)
|
269
|
+
colorbar_label.SetJustificationToLeft()
|
270
|
+
colorbar_label.SetFontFamily(VTK_FONT_FILE)
|
271
|
+
colorbar_label.SetFontFile(font_file)
|
272
|
+
|
273
|
+
# set the text color based on current theme
|
274
|
+
self.set_theme(self._current_theme)
|
275
|
+
|
276
|
+
def create_info_text(self):
|
277
|
+
font_file = MOLDE_DIR / "fonts/IBMPlexMono-Bold.ttf"
|
278
|
+
|
279
|
+
self.text_actor = vtkTextActor()
|
280
|
+
self.renderer.AddActor2D(self.text_actor)
|
281
|
+
|
282
|
+
info_text_property = self.text_actor.GetTextProperty()
|
283
|
+
info_text_property.SetFontSize(14)
|
284
|
+
info_text_property.SetVerticalJustificationToTop()
|
285
|
+
info_text_property.SetColor((0.2, 0.2, 0.2))
|
286
|
+
info_text_property.SetLineSpacing(1.3)
|
287
|
+
info_text_property.SetFontFamilyToTimes()
|
288
|
+
info_text_property.SetFontFamily(VTK_FONT_FILE)
|
289
|
+
info_text_property.SetFontFile(font_file)
|
290
|
+
|
291
|
+
coord = self.text_actor.GetPositionCoordinate()
|
292
|
+
coord.SetCoordinateSystemToNormalizedViewport()
|
293
|
+
coord.SetValue(0.01, 0.98)
|
294
|
+
|
295
|
+
# set the text color based on current theme
|
296
|
+
self.set_theme(self._current_theme)
|
297
|
+
|
298
|
+
def create_logo(self, path: str | Path) -> vtkLogoRepresentation:
|
299
|
+
path = Path(path)
|
300
|
+
|
301
|
+
image_reader = vtkPNGReader()
|
302
|
+
image_reader.SetFileName(path)
|
303
|
+
image_reader.Update()
|
304
|
+
|
305
|
+
logo = vtkLogoRepresentation()
|
306
|
+
logo.SetImage(image_reader.GetOutput())
|
307
|
+
logo.ProportionalResizeOn()
|
308
|
+
logo.GetImageProperty().SetOpacity(0.9)
|
309
|
+
logo.GetImageProperty().SetDisplayLocationToBackground()
|
310
|
+
|
311
|
+
self.renderer.AddViewProp(logo)
|
312
|
+
logo.SetRenderer(self.renderer)
|
313
|
+
return logo
|
314
|
+
|
315
|
+
def create_camera_light(self, offset_x=0, offset_y=0):
|
316
|
+
light = vtkLight()
|
317
|
+
light.SetLightTypeToCameraLight()
|
318
|
+
light.SetPosition(offset_x, offset_y, 1)
|
319
|
+
self.renderer.AddLight(light)
|
320
|
+
|
321
|
+
def set_colorbar_unit(self, text):
|
322
|
+
if not hasattr(self, "colorbar_actor"):
|
323
|
+
return
|
324
|
+
self.colorbar_actor.SetTitle(text)
|
325
|
+
|
326
|
+
def set_info_text(self, text):
|
327
|
+
self.text_actor.SetInput(text)
|
328
|
+
|
329
|
+
def set_theme(self, theme: Literal["dark", "light", "custom"], **kwargs):
|
330
|
+
self._current_theme = theme
|
331
|
+
|
332
|
+
if theme == "dark":
|
333
|
+
bkg_1 = Color("#0b0f17")
|
334
|
+
bkg_2 = Color("#3e424d")
|
335
|
+
font_color = Color("#CCCCCC")
|
336
|
+
|
337
|
+
elif theme == "light":
|
338
|
+
bkg_1 = Color("#8092A6")
|
339
|
+
bkg_2 = Color("#EEF2F3")
|
340
|
+
font_color = Color("#111111")
|
341
|
+
|
342
|
+
elif theme == "custom":
|
343
|
+
bkg_1 = kwargs.get("bkg_1")
|
344
|
+
bkg_2 = kwargs.get("bkg_2")
|
345
|
+
font_color = kwargs.get("font_color")
|
346
|
+
|
347
|
+
if bkg_1 is None:
|
348
|
+
raise ValueError('Missing value "bkg_1"')
|
349
|
+
if bkg_2 is None:
|
350
|
+
raise ValueError('Missing value "bkg_2"')
|
351
|
+
if font_color is None:
|
352
|
+
raise ValueError('Missing value "font_color"')
|
353
|
+
|
354
|
+
else:
|
355
|
+
return
|
356
|
+
|
357
|
+
self.renderer.GradientBackgroundOn()
|
358
|
+
self.renderer.SetBackground(bkg_1.to_rgb_f())
|
359
|
+
self.renderer.SetBackground2(bkg_2.to_rgb_f())
|
360
|
+
|
361
|
+
if hasattr(self, "text_actor"):
|
362
|
+
self.text_actor.GetTextProperty().SetColor(font_color.to_rgb_f())
|
363
|
+
|
364
|
+
if hasattr(self, "colorbar_actor"):
|
365
|
+
self.colorbar_actor.GetTitleTextProperty().SetColor(font_color.to_rgb_f())
|
366
|
+
self.colorbar_actor.GetLabelTextProperty().SetColor(font_color.to_rgb_f())
|
367
|
+
|
368
|
+
if hasattr(self, "scale_bar_actor"):
|
369
|
+
self.scale_bar_actor.GetLegendTitleProperty().SetColor(font_color.to_rgb_f())
|
370
|
+
self.scale_bar_actor.GetLegendLabelProperty().SetColor(font_color.to_rgb_f())
|
371
|
+
|
372
|
+
#
|
373
|
+
def set_custom_view(self, position, view_up):
|
374
|
+
self.renderer.GetActiveCamera().SetPosition(position)
|
375
|
+
self.renderer.GetActiveCamera().SetViewUp(view_up)
|
376
|
+
self.renderer.GetActiveCamera().SetParallelProjection(True)
|
377
|
+
self.renderer.ResetCamera(*self.renderer.ComputeVisiblePropBounds())
|
378
|
+
self.update()
|
379
|
+
|
380
|
+
def set_top_view(self):
|
381
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
382
|
+
position = (x, y + 1, z)
|
383
|
+
view_up = (0, 0, -1)
|
384
|
+
self.set_custom_view(position, view_up)
|
385
|
+
|
386
|
+
def set_bottom_view(self):
|
387
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
388
|
+
position = (x, y - 1, z)
|
389
|
+
view_up = (0, 0, 1)
|
390
|
+
self.set_custom_view(position, view_up)
|
391
|
+
|
392
|
+
def set_left_view(self):
|
393
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
394
|
+
position = (x - 1, y, z)
|
395
|
+
view_up = (0, 1, 0)
|
396
|
+
self.set_custom_view(position, view_up)
|
397
|
+
|
398
|
+
def set_right_view(self):
|
399
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
400
|
+
position = (x + 1, y, z)
|
401
|
+
view_up = (0, 1, 0)
|
402
|
+
self.set_custom_view(position, view_up)
|
403
|
+
|
404
|
+
def set_front_view(self):
|
405
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
406
|
+
position = (x, y, z + 1)
|
407
|
+
view_up = (0, 1, 0)
|
408
|
+
self.set_custom_view(position, view_up)
|
409
|
+
|
410
|
+
def set_back_view(self):
|
411
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
412
|
+
position = (x, y, z - 1)
|
413
|
+
view_up = (0, 1, 0)
|
414
|
+
self.set_custom_view(position, view_up)
|
415
|
+
|
416
|
+
def set_isometric_view(self):
|
417
|
+
x, y, z = self.renderer.GetActiveCamera().GetFocalPoint()
|
418
|
+
position = (x + 1, y + 1, z + 1)
|
419
|
+
view_up = (0, 1, 0)
|
420
|
+
self.set_custom_view(position, view_up)
|
421
|
+
|
422
|
+
def copy_camera_from(self, other):
|
423
|
+
if isinstance(other, CommonRenderWidget):
|
424
|
+
other_camera = other.renderer.GetActiveCamera()
|
425
|
+
elif isinstance(other, vtkRenderer):
|
426
|
+
other_camera = other.GetActiveCamera()
|
427
|
+
else:
|
428
|
+
return
|
429
|
+
|
430
|
+
self.renderer.GetActiveCamera().DeepCopy(other_camera)
|
431
|
+
self.renderer.ResetCameraClippingRange()
|
432
|
+
self.renderer.GetActiveCamera().Modified()
|
433
|
+
self.update()
|