Source code for pyrocko.gui.sparrow.elements.axes_box

# https://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------
import vtk
import numpy as num

from pyrocko import geometry, cake, orthodrome as od
from pyrocko.guts import Bool, Float
from pyrocko.gui.qt_compat import qw, qc

from pyrocko.gui.vtk_util import Color

from .base import Element, ElementState

import string

from .. import common, state as vstate


guts_prefix = 'sparrow'

km = 1e3


class AxisPipe(object):

    def __init__(self, start_point, end_point, axis_range, axis_type):

        available_types = ['X', 'Y', 'Z']
        if axis_type not in available_types:
            raise TypeError('Axis type %s not available!' % axis_type)

        self._axis_type = axis_type

        self.start_point = start_point
        self.end_point = end_point
        self.axis_range = axis_range

        min_bounds = num.minimum(start_point, end_point)
        max_bounds = num.maximum(start_point, end_point)
        bounds = num.vstack((min_bounds, max_bounds)).ravel('f')

        ax = vtk.vtkAxisActor()
        ax.SetPoint1(*start_point)
        ax.SetPoint2(*end_point)
        ax.SetRange(*axis_range)
        ax.SetBounds(*bounds)

        self.actor = ax

    def vector(self):
        return self.start_point - self.end_point

    def length(self):
        return num.linalg.norm(self.vector())

    def unit_vector(self):
        xyz = self.vector()
        return xyz / num.linalg.norm(xyz)

    def set_label_size(self, label_size):
        self.actor.SetTitleScale(label_size)
        self.actor.SetLabelScale(label_size)

    def set_tick_size(self, tick_size):
        self.actor.SetMajorTickSize(tick_size)

    def set_ticks(self, delta):

        self.actor.SetDeltaRangeMajor(delta)
        self.actor.SetMajorRangeStart(self.axis_range[0])
        self.actor.SetMajorStart(0, self.axis_range[0])
        self.actor.SetTickLocationToInside()  # Outside/Both/Inside
        nticks = int(
            round((self.axis_range[1] - self.axis_range[0]) / delta)) + 1

        labels = vtk.vtkStringArray()
        labels.SetNumberOfTuples(nticks)
        for i in range(nticks):
            if self._axis_type == 'Z':
                tick_value = (self.axis_range[1] - i * delta) / 1e3
            else:
                tick_value = (self.axis_range[0] + i * delta) / 1e3

            labels.SetValue(i, '%0.1f' % tick_value)

        self.actor.SetLabels(labels)
        self.actor.SetCalculateLabelOffset(0)

    def set_colors(self, color):
        tprop = self.actor.GetTitleTextProperty()
        tprop.SetColor(color.rgb)
        tprop.SetBackgroundColor(color.rgb)
        tprop.SetFontFamilyToArial()

        lprop = self.actor.GetLabelTextProperty()
        lprop.SetColor(color.rgb)
        lprop.SetFontFamilyToArial()

        self.actor.GetAxisLinesProperty().SetColor(color.rgb)
        self.actor.GetAxisMajorTicksProperty().SetColor(color.rgb)

    def set_linewidth(self, linewidth):
        self.actor.GetAxisLinesProperty().SetLineWidth(linewidth)
        self.actor.GetAxisMajorTicksProperty().SetLineWidth(linewidth)

    def set_axes_base(self, basex, basey, basez):
        self.actor.SetAxisBaseForX(*basex)
        self.actor.SetAxisBaseForY(*basey)
        self.actor.SetAxisBaseForZ(*basez)

        getattr(self.actor, "SetAxisTypeTo%s" % self._axis_type)()


class BoxPipe(object):

    def __init__(self, width, length, height, lat, lon, depth, camera):

        self._line_width = None
        self._color = Color('white')

        origin_xyz = geometry.latlondepth2xyz(
                num.atleast_2d(num.array((lat, lon, depth))),
                planetradius=cake.earthradius)
        z_top = geometry.latlondepth2xyz(
                num.atleast_2d(num.array((lat, lon, depth - height))),
                planetradius=cake.earthradius)

        lat2, lon2 = od.ne_to_latlon(lat, lon, 0, length)
        x_east = geometry.latlondepth2xyz(
                num.atleast_2d(num.array((lat2, lon2, depth))),
                planetradius=cake.earthradius)

        lat3, lon3 = od.ne_to_latlon(lat, lon, width, 0)
        y_north = geometry.latlondepth2xyz(
                num.atleast_2d(num.array((lat3, lon3, depth))),
                planetradius=cake.earthradius)

        xax_range = (0, length)
        yax_range = (0, width)
        zax_range = (depth - height, depth)

        end_points = [x_east, y_north, z_top]
        axis_ranges = [xax_range, yax_range, zax_range]
        labels = ['E-Distance [km]', 'N-Distance [km]', 'Depth [km]']
        axis_types = ['X', 'Y', 'Z']

        vector_base = []
        self.axes = []
        for end_point, axis_range, label, axis_type in zip(
                end_points, axis_ranges, labels, axis_types):

            ax = AxisPipe(
                start_point=origin_xyz,
                end_point=end_point,
                axis_range=axis_range,
                axis_type=axis_type)

            ax.actor.SetTitle(label)
            ax.set_colors(self._color)
            ax.actor.SetCamera(camera)

            # correct tick rotation around the globe
            vector_base.append(ax.unit_vector())
            self.axes.append(ax)

        for ax in self.axes:
            ax.set_axes_base(*vector_base)

    @property
    def actor(self):
        return [ax.actor for ax in self.axes]

    def set_color(self, color):
        if color != self._color:
            for ax in self.axes:
                ax.set_colors(color)

            self._color = color

    def set_label_size(self, label_size):
        for ax in self.axes:
            ax.set_label_size(label_size)

        self._label_size = label_size

    def set_line_width(self, linewidth):
        linewidth = float(linewidth)
        if self._line_width != linewidth:
            for ax in self.axes:
                ax.set_linewidth(linewidth)

            self._line_width = linewidth

    def set_ticks(self, tick_size, delta):
        '''
        Set tick size of all axes to smallest tick-size.
        '''
        for ax in self.axes:
            ax.set_ticks(delta=delta)
            ax.set_tick_size(tick_size)


box_ranges = {
    'lat': {'min': -90., 'max': 90., 'step': 1, 'ini': 0.},
    'lon': {'min': -180., 'max': 180., 'step': 1, 'ini': 0.},
    'depth': {'min': 0., 'max': 600 * km, 'step': 1 * km, 'ini': 10. * km},
    'width': {'min': 0.1, 'max': 1000. * km, 'step': 1 * km, 'ini': 10. * km},
    'length': {'min': 0.1, 'max': 1000. * km, 'step': 1 * km, 'ini': 50. * km},
    'height': {'min': 0.1, 'max': 500. * km, 'step': 1 * km, 'ini': 10. * km},
    'delta': {'min': 0.01, 'max': 250. * km, 'step': 1 * km, 'ini': 5. * km}}


[docs]class AxesBoxState(ElementState): visible = Bool.T(default=True) width = Float.T(default=20.0 * km) length = Float.T(default=20.0 * km) height = Float.T(default=10.0 * km) lat = Float.T(default=0.0) lon = Float.T(default=0.0) depth = Float.T(default=5. * km) color = Color.T(default=Color.D('white')) line_width = Float.T(default=1.0) label_size = Float.T(default=0.0001) tick_size = Float.T(default=0.0001) delta = Float.T(default=5 * km) def create(self): element = AxesBoxElement() return element
class AxesBoxElement(Element): def __init__(self): Element.__init__(self) self._pipe = [] self._controls = None def bind_state(self, state): Element.bind_state(self, state) self.talkie_connect( state, ['visible', 'width', 'length', 'height', 'lat', 'lon', 'depth', 'color', 'line_width', 'label_size', 'tick_size', 'delta'], self.update) def _state_bind_box(self, *args, **kwargs): vstate.state_bind(self, self._state, *args, **kwargs) def get_name(self): return 'AxesBox' def set_parent(self, parent): self._parent = parent self._parent.add_panel( self.get_title_label(), self._get_controls(), visible=True, title_controls=[ self.get_title_control_remove(), self.get_title_control_visible()]) self.update() def unset_parent(self): self.unbind_state() if self._parent: if self._pipe: for pipe in self._pipe: if isinstance(pipe.actor, list): for act in pipe.actor: self._parent.remove_actor(act) else: self._parent.remove_actor(pipe.actor) self._pipe = [] if self._controls is not None: self._parent.remove_panel(self._controls) self._controls = None self._parent.update_view() self._parent = None def update_box(self): state = self._state camera = self._parent.ren.GetActiveCamera() box_pipe = BoxPipe( state.width, state.length, state.height, state.lat, state.lon, state.depth, camera) box_pipe.set_color(state.color) box_pipe.set_line_width(state.line_width) box_pipe.set_ticks(state.tick_size, state.delta) box_pipe.set_label_size(state.label_size) self._pipe.append(box_pipe) for actor in box_pipe.actor: self._parent.add_actor(actor) def update(self, *args): state = self._state if self._pipe: for pipe in self._pipe: if isinstance(pipe.actor, list): for actor in pipe.actor: self._parent.remove_actor(actor) else: self._parent.remove_actor(pipe.actor) self._pipe = [] if state.visible: self.update_box() self._parent.update_view() def _get_controls(self): if not self._controls: from ..state import state_bind_slider, state_bind_combobox_color frame = qw.QFrame() layout = qw.QGridLayout() frame.setLayout(layout) def state_to_lineedit(state, attribute, widget): sel = getattr(state, attribute) widget.setText('%g' % sel) # if sel: # widget.selectAll() def lineedit_to_state(widget, state, attribute): s = float(widget.text()) try: setattr(state, attribute, s) except Exception: raise ValueError( 'Value of %s needs to be a float or integer' % string.capwords(attribute)) for il, label in enumerate(box_ranges.keys()): layout.addWidget(qw.QLabel( string.capwords(label) + ':'), il, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum( int(round(box_ranges[label]['min']))) slider.setMaximum( int(round(box_ranges[label]['max']))) slider.setSingleStep( int(round(box_ranges[label]['step']))) slider.setPageStep( int(round(box_ranges[label]['step']))) layout.addWidget(slider, il, 1) try: state_bind_slider( self, self._state, label, slider, factor=box_ranges[label]['fac']) except Exception: state_bind_slider( self, self._state, label, slider) le = qw.QLineEdit() layout.addWidget(le, il, 2) self._state_bind_box( [label], lineedit_to_state, le, [le.editingFinished, le.returnPressed], state_to_lineedit, attribute=label) # color il += 1 layout.addWidget(qw.QLabel('Color'), il, 0) cb = common.strings_to_combobox( ['black', 'white']) layout.addWidget(cb, il, 1) state_bind_combobox_color( self, self._state, 'color', cb) # linewidth il += 1 layout.addWidget(qw.QLabel('Line width'), il, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(0) slider.setMaximum(100) layout.addWidget(slider, il, 1) state_bind_slider( self, self._state, 'line_width', slider, factor=0.1) # tick size il += 1 layout.addWidget(qw.QLabel('tick size'), il, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(1) slider.setMaximum(1000) layout.addWidget(slider, il, 1) state_bind_slider( self, self._state, 'tick_size', slider, factor=0.00001) # label size il += 1 layout.addWidget(qw.QLabel('label size'), il, 0) slider = qw.QSlider(qc.Qt.Horizontal) slider.setSizePolicy( qw.QSizePolicy( qw.QSizePolicy.Expanding, qw.QSizePolicy.Fixed)) slider.setMinimum(1) slider.setMaximum(1000) layout.addWidget(slider, il, 1) state_bind_slider( self, self._state, 'label_size', slider, factor=0.00001) self._controls = frame return self._controls __all__ = [ 'AxesBoxElement', 'AxesBoxState' ]