Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/gui/sparrow/elements/axes_box.py: 22%
256 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-06 06:59 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-06 06:59 +0000
1# https://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5import vtk
6import numpy as num
8from pyrocko import geometry, cake, orthodrome as od
9from pyrocko.guts import Bool, Float
10from pyrocko.gui.qt_compat import qw, qc
12from pyrocko.gui.vtk_util import Color
14from .base import Element, ElementState
16import string
18from .. import common, state as vstate
21guts_prefix = 'sparrow'
23km = 1e3
26class AxisPipe(object):
28 def __init__(self, start_point, end_point, axis_range, axis_type):
30 available_types = ['X', 'Y', 'Z']
31 if axis_type not in available_types:
32 raise TypeError('Axis type %s not available!' % axis_type)
34 self._axis_type = axis_type
36 self.start_point = start_point
37 self.end_point = end_point
38 self.axis_range = axis_range
40 min_bounds = num.minimum(start_point, end_point)
41 max_bounds = num.maximum(start_point, end_point)
42 bounds = num.vstack((min_bounds, max_bounds)).ravel('f')
44 ax = vtk.vtkAxisActor()
45 ax.SetPoint1(*start_point)
46 ax.SetPoint2(*end_point)
47 ax.SetRange(*axis_range)
48 ax.SetBounds(*bounds)
50 self.actor = ax
52 def vector(self):
53 return self.start_point - self.end_point
55 def length(self):
56 return num.linalg.norm(self.vector())
58 def unit_vector(self):
59 xyz = self.vector()
60 return xyz / num.linalg.norm(xyz)
62 def set_label_size(self, label_size):
63 self.actor.SetTitleScale(label_size)
64 self.actor.SetLabelScale(label_size)
66 def set_tick_size(self, tick_size):
67 self.actor.SetMajorTickSize(tick_size)
69 def set_ticks(self, delta):
71 self.actor.SetDeltaRangeMajor(delta)
72 self.actor.SetMajorRangeStart(self.axis_range[0])
73 self.actor.SetMajorStart(0, self.axis_range[0])
74 self.actor.SetTickLocationToInside() # Outside/Both/Inside
75 nticks = int(
76 round((self.axis_range[1] - self.axis_range[0]) / delta)) + 1
78 labels = vtk.vtkStringArray()
79 labels.SetNumberOfTuples(nticks)
80 for i in range(nticks):
81 if self._axis_type == 'Z':
82 tick_value = (self.axis_range[1] - i * delta) / 1e3
83 else:
84 tick_value = (self.axis_range[0] + i * delta) / 1e3
86 labels.SetValue(i, '%0.1f' % tick_value)
88 self.actor.SetLabels(labels)
89 self.actor.SetCalculateLabelOffset(0)
91 def set_colors(self, color):
92 tprop = self.actor.GetTitleTextProperty()
93 tprop.SetColor(color.rgb)
94 tprop.SetBackgroundColor(color.rgb)
95 tprop.SetFontFamilyToArial()
97 lprop = self.actor.GetLabelTextProperty()
98 lprop.SetColor(color.rgb)
99 lprop.SetFontFamilyToArial()
101 self.actor.GetAxisLinesProperty().SetColor(color.rgb)
102 self.actor.GetAxisMajorTicksProperty().SetColor(color.rgb)
104 def set_linewidth(self, linewidth):
105 self.actor.GetAxisLinesProperty().SetLineWidth(linewidth)
106 self.actor.GetAxisMajorTicksProperty().SetLineWidth(linewidth)
108 def set_axes_base(self, basex, basey, basez):
109 self.actor.SetAxisBaseForX(*basex)
110 self.actor.SetAxisBaseForY(*basey)
111 self.actor.SetAxisBaseForZ(*basez)
113 getattr(self.actor, "SetAxisTypeTo%s" % self._axis_type)()
116class BoxPipe(object):
118 def __init__(self, width, length, height, lat, lon, depth, camera):
120 self._line_width = None
121 self._color = Color('white')
123 origin_xyz = geometry.latlondepth2xyz(
124 num.atleast_2d(num.array((lat, lon, depth))),
125 planetradius=cake.earthradius)
126 z_top = geometry.latlondepth2xyz(
127 num.atleast_2d(num.array((lat, lon, depth - height))),
128 planetradius=cake.earthradius)
130 lat2, lon2 = od.ne_to_latlon(lat, lon, 0, length)
131 x_east = geometry.latlondepth2xyz(
132 num.atleast_2d(num.array((lat2, lon2, depth))),
133 planetradius=cake.earthradius)
135 lat3, lon3 = od.ne_to_latlon(lat, lon, width, 0)
136 y_north = geometry.latlondepth2xyz(
137 num.atleast_2d(num.array((lat3, lon3, depth))),
138 planetradius=cake.earthradius)
140 xax_range = (0, length)
141 yax_range = (0, width)
142 zax_range = (depth - height, depth)
144 end_points = [x_east, y_north, z_top]
145 axis_ranges = [xax_range, yax_range, zax_range]
146 labels = ['E-Distance [km]', 'N-Distance [km]', 'Depth [km]']
147 axis_types = ['X', 'Y', 'Z']
149 vector_base = []
150 self.axes = []
151 for end_point, axis_range, label, axis_type in zip(
152 end_points, axis_ranges, labels, axis_types):
154 ax = AxisPipe(
155 start_point=origin_xyz,
156 end_point=end_point,
157 axis_range=axis_range,
158 axis_type=axis_type)
160 ax.actor.SetTitle(label)
161 ax.set_colors(self._color)
162 ax.actor.SetCamera(camera)
164 # correct tick rotation around the globe
165 vector_base.append(ax.unit_vector())
166 self.axes.append(ax)
168 for ax in self.axes:
169 ax.set_axes_base(*vector_base)
171 @property
172 def actor(self):
173 return [ax.actor for ax in self.axes]
175 def set_color(self, color):
176 if color != self._color:
177 for ax in self.axes:
178 ax.set_colors(color)
180 self._color = color
182 def set_label_size(self, label_size):
183 for ax in self.axes:
184 ax.set_label_size(label_size)
186 self._label_size = label_size
188 def set_line_width(self, linewidth):
189 linewidth = float(linewidth)
190 if self._line_width != linewidth:
191 for ax in self.axes:
192 ax.set_linewidth(linewidth)
194 self._line_width = linewidth
196 def set_ticks(self, tick_size, delta):
197 '''
198 Set tick size of all axes to smallest tick-size.
199 '''
200 for ax in self.axes:
201 ax.set_ticks(delta=delta)
202 ax.set_tick_size(tick_size)
205box_ranges = {
206 'lat': {'min': -90., 'max': 90., 'step': 1, 'ini': 0.},
207 'lon': {'min': -180., 'max': 180., 'step': 1, 'ini': 0.},
208 'depth': {'min': 0., 'max': 600 * km, 'step': 1 * km, 'ini': 10. * km},
209 'width': {'min': 0.1, 'max': 1000. * km, 'step': 1 * km, 'ini': 10. * km},
210 'length': {'min': 0.1, 'max': 1000. * km, 'step': 1 * km, 'ini': 50. * km},
211 'height': {'min': 0.1, 'max': 500. * km, 'step': 1 * km, 'ini': 10. * km},
212 'delta': {'min': 0.01, 'max': 250. * km, 'step': 1 * km, 'ini': 5. * km}}
215class AxesBoxState(ElementState):
216 visible = Bool.T(default=True)
217 width = Float.T(default=20.0 * km)
218 length = Float.T(default=20.0 * km)
219 height = Float.T(default=10.0 * km)
220 lat = Float.T(default=0.0)
221 lon = Float.T(default=0.0)
222 depth = Float.T(default=5. * km)
223 color = Color.T(default=Color.D('white'))
224 line_width = Float.T(default=1.0)
225 label_size = Float.T(default=0.0001)
226 tick_size = Float.T(default=0.0001)
227 delta = Float.T(default=5 * km)
229 def create(self):
230 element = AxesBoxElement()
231 return element
234class AxesBoxElement(Element):
236 def __init__(self):
237 Element.__init__(self)
238 self._pipe = []
239 self._controls = None
241 def bind_state(self, state):
242 Element.bind_state(self, state)
244 self.talkie_connect(
245 state,
246 ['visible', 'width', 'length', 'height', 'lat', 'lon', 'depth',
247 'color', 'line_width', 'label_size', 'tick_size', 'delta'],
248 self.update)
250 def _state_bind_box(self, *args, **kwargs):
251 vstate.state_bind(self, self._state, *args, **kwargs)
253 def get_name(self):
254 return 'AxesBox'
256 def set_parent(self, parent):
257 self._parent = parent
259 self._parent.add_panel(
260 self.get_title_label(),
261 self._get_controls(),
262 visible=True,
263 title_controls=[
264 self.get_title_control_remove(),
265 self.get_title_control_visible()])
267 self.update()
269 def unset_parent(self):
270 self.unbind_state()
271 if self._parent:
273 if self._pipe:
274 for pipe in self._pipe:
275 if isinstance(pipe.actor, list):
276 for act in pipe.actor:
277 self._parent.remove_actor(act)
278 else:
279 self._parent.remove_actor(pipe.actor)
280 self._pipe = []
282 if self._controls is not None:
283 self._parent.remove_panel(self._controls)
284 self._controls = None
286 self._parent.update_view()
287 self._parent = None
289 def update_box(self):
291 state = self._state
293 camera = self._parent.ren.GetActiveCamera()
294 box_pipe = BoxPipe(
295 state.width, state.length, state.height,
296 state.lat, state.lon, state.depth, camera)
297 box_pipe.set_color(state.color)
298 box_pipe.set_line_width(state.line_width)
299 box_pipe.set_ticks(state.tick_size, state.delta)
300 box_pipe.set_label_size(state.label_size)
302 self._pipe.append(box_pipe)
304 for actor in box_pipe.actor:
305 self._parent.add_actor(actor)
307 def update(self, *args):
308 state = self._state
310 if self._pipe:
311 for pipe in self._pipe:
312 if isinstance(pipe.actor, list):
313 for actor in pipe.actor:
314 self._parent.remove_actor(actor)
315 else:
316 self._parent.remove_actor(pipe.actor)
318 self._pipe = []
320 if state.visible:
321 self.update_box()
323 self._parent.update_view()
325 def _get_controls(self):
326 if not self._controls:
327 from ..state import state_bind_slider, state_bind_combobox_color
329 frame = qw.QFrame()
330 layout = qw.QGridLayout()
331 frame.setLayout(layout)
333 def state_to_lineedit(state, attribute, widget):
334 sel = getattr(state, attribute)
336 widget.setText('%g' % sel)
337 # if sel:
338 # widget.selectAll()
340 def lineedit_to_state(widget, state, attribute):
341 s = float(widget.text())
342 try:
343 setattr(state, attribute, s)
344 except Exception:
345 raise ValueError(
346 'Value of %s needs to be a float or integer'
347 % string.capwords(attribute))
349 for il, label in enumerate(box_ranges.keys()):
350 layout.addWidget(qw.QLabel(
351 string.capwords(label) + ':'), il, 0)
353 slider = qw.QSlider(qc.Qt.Horizontal)
354 slider.setSizePolicy(
355 qw.QSizePolicy(
356 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
357 slider.setMinimum(
358 int(round(box_ranges[label]['min'])))
359 slider.setMaximum(
360 int(round(box_ranges[label]['max'])))
361 slider.setSingleStep(
362 int(round(box_ranges[label]['step'])))
363 slider.setPageStep(
364 int(round(box_ranges[label]['step'])))
366 layout.addWidget(slider, il, 1)
368 try:
369 state_bind_slider(
370 self, self._state, label, slider,
371 factor=box_ranges[label]['fac'])
372 except Exception:
373 state_bind_slider(
374 self, self._state, label, slider)
376 le = qw.QLineEdit()
377 layout.addWidget(le, il, 2)
379 self._state_bind_box(
380 [label], lineedit_to_state, le,
381 [le.editingFinished, le.returnPressed],
382 state_to_lineedit, attribute=label)
384 # color
385 il += 1
386 layout.addWidget(qw.QLabel('Color'), il, 0)
388 cb = common.strings_to_combobox(
389 ['black', 'white'])
391 layout.addWidget(cb, il, 1)
392 state_bind_combobox_color(
393 self, self._state, 'color', cb)
395 # linewidth
396 il += 1
397 layout.addWidget(qw.QLabel('Line width'), il, 0)
399 slider = qw.QSlider(qc.Qt.Horizontal)
400 slider.setSizePolicy(
401 qw.QSizePolicy(
402 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
403 slider.setMinimum(0)
404 slider.setMaximum(100)
405 layout.addWidget(slider, il, 1)
406 state_bind_slider(
407 self, self._state, 'line_width', slider, factor=0.1)
409 # tick size
410 il += 1
411 layout.addWidget(qw.QLabel('tick size'), il, 0)
413 slider = qw.QSlider(qc.Qt.Horizontal)
414 slider.setSizePolicy(
415 qw.QSizePolicy(
416 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
417 slider.setMinimum(1)
418 slider.setMaximum(1000)
419 layout.addWidget(slider, il, 1)
420 state_bind_slider(
421 self, self._state, 'tick_size', slider, factor=0.00001)
423 # label size
424 il += 1
425 layout.addWidget(qw.QLabel('label size'), il, 0)
427 slider = qw.QSlider(qc.Qt.Horizontal)
428 slider.setSizePolicy(
429 qw.QSizePolicy(
430 qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed))
431 slider.setMinimum(1)
432 slider.setMaximum(1000)
433 layout.addWidget(slider, il, 1)
434 state_bind_slider(
435 self, self._state, 'label_size', slider, factor=0.00001)
437 self._controls = frame
439 return self._controls
442__all__ = [
443 'AxesBoxElement',
444 'AxesBoxState'
445]