[docs]classUI(with_metaclass(abc.ABCMeta,object)):"""An umbrella class for all UI elements. While adding UI elements to the scene, we go over all the sub-elements that come with it and add those to the scene automatically. Attributes ---------- position : (float, float) Absolute coordinates (x, y) of the lower-left corner of this UI component. center : (float, float) Absolute coordinates (x, y) of the center of this UI component. size : (int, int) Width and height in pixels of this UI component. on_left_mouse_button_pressed: function Callback function for when the left mouse button is pressed. on_left_mouse_button_released: function Callback function for when the left mouse button is released. on_left_mouse_button_clicked: function Callback function for when clicked using the left mouse button (i.e. pressed -> released). on_left_mouse_button_dragged: function Callback function for when dragging using the left mouse button. on_right_mouse_button_pressed: function Callback function for when the right mouse button is pressed. on_right_mouse_button_released: function Callback function for when the right mouse button is released. on_right_mouse_button_clicked: function Callback function for when clicking using the right mouse button (i.e. pressed -> released). on_right_mouse_button_dragged: function Callback function for when dragging using the right mouse button. on_middle_mouse_button_pressed: function Callback function for when the middle mouse button is pressed. on_middle_mouse_button_released: function Callback function for when the middle mouse button is released. on_middle_mouse_button_clicked: function Callback function for when clicking using the middle mouse button (i.e. pressed -> released). on_middle_mouse_button_dragged: function Callback function for when dragging using the middle mouse button. on_key_press: function Callback function for when a keyboard key is pressed. """
[docs]def__init__(self,position=(0,0)):"""Init scene. Parameters ---------- position : (float, float) Absolute coordinates (x, y) of the lower-left corner of this UI component. """self._scene=object()self._position=np.array([0,0])self._callbacks=[]self._setup()# Setup needed actors and sub UI components.self.position=positionself.left_button_state="released"self.right_button_state="released"self.middle_button_state="released"self.on_left_mouse_button_pressed=lambdai_ren,obj,element:Noneself.on_left_mouse_button_dragged=lambdai_ren,obj,element:Noneself.on_left_mouse_button_released=lambdai_ren,obj,element:Noneself.on_left_mouse_button_clicked=lambdai_ren,obj,element:Noneself.on_right_mouse_button_pressed=lambdai_ren,obj,element:Noneself.on_right_mouse_button_released=lambdai_ren,obj,element:Noneself.on_right_mouse_button_clicked=lambdai_ren,obj,element:Noneself.on_right_mouse_button_dragged=lambdai_ren,obj,element:Noneself.on_middle_mouse_button_pressed=lambdai_ren,obj,element:Noneself.on_middle_mouse_button_released=lambdai_ren,obj,element:Noneself.on_middle_mouse_button_clicked=lambdai_ren,obj,element:Noneself.on_middle_mouse_button_dragged=lambdai_ren,obj,element:Noneself.on_key_press=lambdai_ren,obj,element:None
@abc.abstractmethoddef_setup(self):"""Set up this UI component. This is where you should create all your needed actors and sub UI components. """msg="Subclasses of UI must implement `_setup(self)`."raiseNotImplementedError(msg)@abc.abstractmethoddef_get_actors(self):"""Get the actors composing this UI component."""msg="Subclasses of UI must implement `_get_actors(self)`."raiseNotImplementedError(msg)@propertydefactors(self):"""Actors composing this UI component."""returnself._get_actors()@abc.abstractmethoddef_add_to_scene(self,_scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- _scene : scene """msg="Subclasses of UI must implement `_add_to_scene(self, scene)`."raiseNotImplementedError(msg)
[docs]defadd_to_scene(self,scene):"""Allow UI objects to add their own props to the scene. Parameters ---------- scene : scene """self._add_to_scene(scene)# Get a hold on the current interactor style.iren=scene.GetRenderWindow().GetInteractor().GetInteractorStyle()forcallbackinself._callbacks:ifnotisinstance(iren,CustomInteractorStyle):msg=("The ShowManager requires `CustomInteractorStyle` in"" order to use callbacks.")raiseTypeError(msg)ifcallback[0]==self._scene:iren.add_callback(iren,callback[1],callback[2],args=[self])else:iren.add_callback(*callback,args=[self])
[docs]defadd_callback(self,prop,event_type,callback,priority=0):"""Add a callback to a specific event for this UI component. Parameters ---------- prop : vtkProp The prop on which is callback is to be added. event_type : string The event code. callback : function The callback function. priority : int Higher number is higher priority. """# Actually since we need an interactor style we will add the callback# only when this UI component is added to the scene.self._callbacks.append((prop,event_type,callback,priority))
@propertydefposition(self):returnself._position@position.setterdefposition(self,coords):coords=np.asarray(coords)self._set_position(coords)self._position=coords@abc.abstractmethoddef_set_position(self,_coords):"""Position the lower-left corner of this UI component. Parameters ---------- _coords: (float, float) Absolute pixel coordinates (x, y). """msg="Subclasses of UI must implement `_set_position(self, coords)`."raiseNotImplementedError(msg)@propertydefsize(self):returnnp.asarray(self._get_size(),dtype=int)@abc.abstractmethoddef_get_size(self):msg="Subclasses of UI must implement property `size`."raiseNotImplementedError(msg)@propertydefcenter(self):returnself.position+self.size/2.@center.setterdefcenter(self,coords):"""Position the center of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """ifnothasattr(self,"size"):msg="Subclasses of UI must implement the `size` property."raiseNotImplementedError(msg)new_center=np.array(coords)size=np.array(self.size)new_lower_left_corner=new_center-size/2.self.position=new_lower_left_corner
[docs]defset_visibility(self,visibility):"""Set visibility of this UI component."""foractorinself.actors:actor.SetVisibility(visibility)
[docs]classButton2D(UI):"""A 2D overlay button and is of type vtkTexturedActor2D. Currently supports:: - Multiple icons. - Switching between icons. """
[docs]def__init__(self,icon_fnames,position=(0,0),size=(30,30)):"""Init class instance. Parameters ---------- icon_fnames : List(string, string) ((iconname, filename), (iconname, filename), ....) position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the button. size : (int, int), optional Width and height in pixels of the button. """super(Button2D,self).__init__(position)self.icon_extents=dict()self.icons=self._build_icons(icon_fnames)self.icon_names=[icon[0]foriconinself.icons]self.current_icon_id=0self.current_icon_name=self.icon_names[self.current_icon_id]self.set_icon(self.icons[self.current_icon_id][1])self.resize(size)
def_get_size(self):lower_left_corner=self.texture_points.GetPoint(0)upper_right_corner=self.texture_points.GetPoint(2)size=np.array(upper_right_corner)-np.array(lower_left_corner)returnabs(size[:2])def_build_icons(self,icon_fnames):"""Convert file names to vtkImageDataGeometryFilters. A pre-processing step to prevent re-read of file names during every state change. Parameters ---------- icon_fnames : List(string, string) ((iconname, filename), (iconname, filename), ....) Returns ------- icons : List A list of corresponding vtkImageDataGeometryFilters. """icons=[]foricon_name,icon_fnameinicon_fnames:ificon_fname.split(".")[-1]notin["png","PNG"]:error_msg="Skipping {}: not in the PNG format."warn(Warning(error_msg.format(icon_fname)))else:png=vtk.vtkPNGReader()png.SetFileName(icon_fname)png.Update()icons.append((icon_name,png.GetOutput()))returniconsdef_setup(self):"""Set up this UI component. Creating the button actor used internally. """# This is highly inspired by# https://github.com/Kitware/VTK/blob/c3ec2495b183e3327820e927af7f8f90d34c3474/Interaction/Widgets/vtkBalloonRepresentation.cxx#L47self.texture_polydata=vtk.vtkPolyData()self.texture_points=vtk.vtkPoints()self.texture_points.SetNumberOfPoints(4)polys=vtk.vtkCellArray()polys.InsertNextCell(4)polys.InsertCellPoint(0)polys.InsertCellPoint(1)polys.InsertCellPoint(2)polys.InsertCellPoint(3)self.texture_polydata.SetPolys(polys)tc=vtk.vtkFloatArray()tc.SetNumberOfComponents(2)tc.SetNumberOfTuples(4)tc.InsertComponent(0,0,0.0)tc.InsertComponent(0,1,0.0)tc.InsertComponent(1,0,1.0)tc.InsertComponent(1,1,0.0)tc.InsertComponent(2,0,1.0)tc.InsertComponent(2,1,1.0)tc.InsertComponent(3,0,0.0)tc.InsertComponent(3,1,1.0)self.texture_polydata.GetPointData().SetTCoords(tc)texture_mapper=vtk.vtkPolyDataMapper2D()texture_mapper=set_input(texture_mapper,self.texture_polydata)button=vtk.vtkTexturedActor2D()button.SetMapper(texture_mapper)self.texture=vtk.vtkTexture()button.SetTexture(self.texture)button_property=vtk.vtkProperty2D()button_property.SetOpacity(1.0)button.SetProperty(button_property)self.actor=button# Add default events listener to the VTK actor.self.handle_events(self.actor)def_get_actors(self):"""Get the actors composing this UI component."""return[self.actor]def_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """scene.add(self.actor)
[docs]defresize(self,size):"""Resize the button. Parameters ---------- size : (float, float) Button size (width, height) in pixels. """# Update actor.self.texture_points.SetPoint(0,0,0,0.0)self.texture_points.SetPoint(1,size[0],0,0.0)self.texture_points.SetPoint(2,size[0],size[1],0.0)self.texture_points.SetPoint(3,0,size[1],0.0)self.texture_polydata.SetPoints(self.texture_points)
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.actor.SetPosition(*coords)@propertydefcolor(self):""" Gets the button's color. """color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):""" Sets the button's color. Parameters ---------- color : (float, float, float) RGB. Must take values in [0, 1]. """self.actor.GetProperty().SetColor(*color)
[docs]defscale(self,factor):""" Scales the button. Parameters ---------- factor : (float, float) Scaling factor (width, height) in pixels. """self.resize(self.size*factor)
[docs]defset_icon_by_name(self,icon_name):""" Set the button icon using its name. Parameters ---------- icon_name : str """icon_id=self.icon_names.index(icon_name)self.set_icon(self.icons[icon_id][1])
[docs]defset_icon(self,icon):""" Modifies the icon used by the vtkTexturedActor2D. Parameters ---------- icon : imageDataGeometryFilter """self.texture=set_input(self.texture,icon)
[docs]defnext_icon_id(self):""" Sets the next icon ID while cycling through icons. """self.current_icon_id+=1ifself.current_icon_id==len(self.icons):self.current_icon_id=0self.current_icon_name=self.icon_names[self.current_icon_id]
[docs]defnext_icon(self):""" Increments the state of the Button. Also changes the icon. """self.next_icon_id()self.set_icon(self.icons[self.current_icon_id][1])
[docs]classRectangle2D(UI):""" A 2D rectangle sub-classed from UI. """
[docs]def__init__(self,size=(0,0),position=(0,0),color=(1,1,1),opacity=1.0):""" Initializes a rectangle. Parameters ---------- size : (int, int) The size of the rectangle (width, height) in pixels. position : (float, float) Coordinates (x, y) of the lower-left corner of the rectangle. color : (float, float, float) Must take values in [0, 1]. opacity : float Must take values in [0, 1]. """super(Rectangle2D,self).__init__(position)self.color=colorself.opacity=opacityself.resize(size)
def_setup(self):""" Setup this UI component. Creating the polygon actor used internally. """# Setup four pointssize=(1,1)self._points=vtk.vtkPoints()self._points.InsertNextPoint(0,0,0)self._points.InsertNextPoint(size[0],0,0)self._points.InsertNextPoint(size[0],size[1],0)self._points.InsertNextPoint(0,size[1],0)# Create the polygonpolygon=vtk.vtkPolygon()polygon.GetPointIds().SetNumberOfIds(4)# make a quadpolygon.GetPointIds().SetId(0,0)polygon.GetPointIds().SetId(1,1)polygon.GetPointIds().SetId(2,2)polygon.GetPointIds().SetId(3,3)# Add the polygon to a list of polygonspolygons=vtk.vtkCellArray()polygons.InsertNextCell(polygon)# Create a PolyDataself._polygonPolyData=vtk.vtkPolyData()self._polygonPolyData.SetPoints(self._points)self._polygonPolyData.SetPolys(polygons)# Create a mapper and actormapper=vtk.vtkPolyDataMapper2D()mapper=set_input(mapper,self._polygonPolyData)self.actor=vtk.vtkActor2D()self.actor.SetMapper(mapper)# Add default events listener to the VTK actor.self.handle_events(self.actor)def_get_actors(self):""" Get the actors composing this UI component. """return[self.actor]def_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """scene.add(self.actor)def_get_size(self):# Get 2D coordinates of two opposed corners of the rectangle.lower_left_corner=np.array(self._points.GetPoint(0)[:2])upper_right_corner=np.array(self._points.GetPoint(2)[:2])size=abs(upper_right_corner-lower_left_corner)returnsize@propertydefwidth(self):returnself._points.GetPoint(2)[0]@width.setterdefwidth(self,width):self.resize((width,self.height))@propertydefheight(self):returnself._points.GetPoint(2)[1]@height.setterdefheight(self,height):self.resize((self.width,height))
[docs]defresize(self,size):""" Sets the button size. Parameters ---------- size : (float, float) Button size (width, height) in pixels. """self._points.SetPoint(0,0,0,0.0)self._points.SetPoint(1,size[0],0,0.0)self._points.SetPoint(2,size[0],size[1],0.0)self._points.SetPoint(3,0,size[1],0.0)self._polygonPolyData.SetPoints(self._points)mapper=vtk.vtkPolyDataMapper2D()mapper=set_input(mapper,self._polygonPolyData)self.actor.SetMapper(mapper)
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.actor.SetPosition(*coords)@propertydefcolor(self):""" Gets the rectangle's color. """color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):""" Sets the rectangle's color. Parameters ---------- color : (float, float, float) RGB. Must take values in [0, 1]. """self.actor.GetProperty().SetColor(*color)@propertydefopacity(self):""" Gets the rectangle's opacity. """returnself.actor.GetProperty().GetOpacity()@opacity.setterdefopacity(self,opacity):""" Sets the rectangle's opacity. Parameters ---------- opacity : float Degree of transparency. Must be between [0, 1]. """self.actor.GetProperty().SetOpacity(opacity)
[docs]classDisk2D(UI):""" A 2D disk UI component. """
[docs]def__init__(self,outer_radius,inner_radius=0,center=(0,0),color=(1,1,1),opacity=1.0):""" Initializes a rectangle. Parameters ---------- outer_radius : int Outer radius of the disk. inner_radius : int, optional Inner radius of the disk. A value > 0, makes a ring. center : (float, float), optional Coordinates (x, y) of the center of the disk. color : (float, float, float), optional Must take values in [0, 1]. opacity : float, optional Must take values in [0, 1]. """super(Disk2D,self).__init__()self.outer_radius=outer_radiusself.inner_radius=inner_radiusself.color=colorself.opacity=opacityself.center=center
def_setup(self):""" Setup this UI component. Creating the disk actor used internally. """# Setting up disk actor.self._disk=vtk.vtkDiskSource()self._disk.SetRadialResolution(10)self._disk.SetCircumferentialResolution(50)self._disk.Update()# Mappermapper=vtk.vtkPolyDataMapper2D()mapper=set_input(mapper,self._disk.GetOutputPort())# Actorself.actor=vtk.vtkActor2D()self.actor.SetMapper(mapper)# Add default events listener to the VTK actor.self.handle_events(self.actor)def_get_actors(self):""" Get the actors composing this UI component. """return[self.actor]def_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """scene.add(self.actor)def_get_size(self):diameter=2*self.outer_radiussize=(diameter,diameter)returnsizedef_set_position(self,coords):""" Position the lower-left corner of this UI component's bounding box. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# Disk actor are positioned with respect to their center.self.actor.SetPosition(*coords+self.outer_radius)@propertydefcolor(self):""" Gets the rectangle's color. """color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):""" Sets the rectangle's color. Parameters ---------- color : (float, float, float) RGB. Must take values in [0, 1]. """self.actor.GetProperty().SetColor(*color)@propertydefopacity(self):""" Gets the rectangle's opacity. """returnself.actor.GetProperty().GetOpacity()@opacity.setterdefopacity(self,opacity):""" Sets the rectangle's opacity. Parameters ---------- opacity : float Degree of transparency. Must be between [0, 1]. """self.actor.GetProperty().SetOpacity(opacity)@propertydefinner_radius(self):returnself._disk.GetInnerRadius()@inner_radius.setterdefinner_radius(self,radius):self._disk.SetInnerRadius(radius)self._disk.Update()@propertydefouter_radius(self):returnself._disk.GetOuterRadius()@outer_radius.setterdefouter_radius(self,radius):self._disk.SetOuterRadius(radius)self._disk.Update()
[docs]classPanel2D(UI):""" A 2D UI Panel. Can contain one or more UI elements. Attributes ---------- alignment : [left, right] Alignment of the panel with respect to the overall screen. """
[docs]def__init__(self,size,position=(0,0),color=(0.1,0.1,0.1),opacity=0.7,align="left"):""" Parameters ---------- size : (int, int) Size (width, height) in pixels of the panel. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of the panel. color : (float, float, float) Must take values in [0, 1]. opacity : float Must take values in [0, 1]. align : [left, right] Alignment of the panel with respect to the overall screen. """super(Panel2D,self).__init__(position)self.resize(size)self.alignment=alignself.color=colorself.opacity=opacityself.position=positionself._drag_offset=None
def_setup(self):""" Setup this UI component. Create the background (Rectangle2D) of the panel. """self._elements=[]self.element_offsets=[]self.background=Rectangle2D()self.add_element(self.background,(0,0))# Add default events listener for this UI component.self.background.on_left_mouse_button_pressed=self.left_button_pressedself.background.on_left_mouse_button_dragged=self.left_button_draggeddef_get_actors(self):""" Get the actors composing this UI component. """actors=[]forelementinself._elements:actors+=element.actorsreturnactorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """forelementinself._elements:element.add_to_scene(scene)def_get_size(self):returnself.background.size
[docs]defresize(self,size):""" Sets the panel size. Parameters ---------- size : (float, float) Panel size (width, height) in pixels. """self.background.resize(size)
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """coords=np.array(coords)forelement,offsetinself.element_offsets:element.position=coords+offset@propertydefcolor(self):returnself.background.color@color.setterdefcolor(self,color):self.background.color=color@propertydefopacity(self):returnself.background.opacity@opacity.setterdefopacity(self,opacity):self.background.opacity=opacity
[docs]defadd_element(self,element,coords,anchor="position"):""" Adds a UI component to the panel. The coordinates represent an offset from the lower left corner of the panel. Parameters ---------- element : UI The UI item to be added. coords : (float, float) or (int, int) If float, normalized coordinates are assumed and they must be between [0,1]. If int, pixels coordinates are assumed and it must fit within the panel's size. """coords=np.array(coords)ifnp.issubdtype(coords.dtype,np.floating):ifnp.any(coords<0)ornp.any(coords>1):raiseValueError("Normalized coordinates must be in [0,1].")coords=coords*self.sizeifanchor=="center":element.center=self.position+coordselifanchor=="position":element.position=self.position+coordselse:msg=("Unknown anchor {}. Supported anchors are 'position'"" and 'center'.")raiseValueError(msg)self._elements.append(element)offset=element.position-self.positionself.element_offsets.append((element,offset))
[docs]defremove_element(self,element):""" Removes a UI component from the panel. Parameters ---------- element : UI The UI item to be removed. """idx=self._elements.index(element)delself._elements[idx]delself.element_offsets[idx]
[docs]defupdate_element(self,element,coords,anchor="position"):""" Updates the position of a UI component in the panel. Parameters ---------- element : UI The UI item to be updated. coords : (float, float) or (int, int) New coordinates. If float, normalized coordinates are assumed and they must be between [0,1]. If int, pixels coordinates are assumed and it must fit within the panel's size. """self.remove_element(element)self.add_element(element,coords,anchor)
[docs]defleft_button_pressed(self,i_ren,_obj,panel2d_object):click_pos=np.array(i_ren.event.position)self._drag_offset=click_pos-panel2d_object.positioni_ren.event.abort()# Stop propagating the event.
[docs]defre_align(self,window_size_change):""" Re-organises the elements in case the window size is changed. Parameters ---------- window_size_change : (int, int) New window size (width, height) in pixels. """ifself.alignment=="left":passelifself.alignment=="right":self.position+=np.array(window_size_change)else:msg="You can only left-align or right-align objects in a panel."raiseValueError(msg)
[docs]classTextBlock2D(UI):""" Wraps over the default vtkTextActor and helps setting the text. Contains member functions for text formatting. Attributes ---------- actor : :class:`vtkTextActor` The text actor. message : str The initial text while building the actor. position : (float, float) (x, y) in pixels. color : (float, float, float) RGB: Values must be between 0-1. bg_color : (float, float, float) RGB: Values must be between 0-1. font_size : int Size of the text font. font_family : str Currently only supports Arial. justification : str left, right or center. vertical_justification : str bottom, middle or top. bold : bool Makes text bold. italic : bool Makes text italicised. shadow : bool Adds text shadow. """
[docs]def__init__(self,text="Text Block",font_size=18,font_family='Arial',justification='left',vertical_justification="bottom",bold=False,italic=False,shadow=False,color=(1,1,1),bg_color=None,position=(0,0)):""" Parameters ---------- text : str The initial text while building the actor. position : (float, float) (x, y) in pixels. color : (float, float, float) RGB: Values must be between 0-1. bg_color : (float, float, float) RGB: Values must be between 0-1. font_size : int Size of the text font. font_family : str Currently only supports Arial. justification : str left, right or center. vertical_justification : str bottom, middle or top. bold : bool Makes text bold. italic : bool Makes text italicised. shadow : bool Adds text shadow. """super(TextBlock2D,self).__init__(position=position)self.color=colorself.background_color=bg_colorself.font_size=font_sizeself.font_family=font_familyself.justification=justificationself.bold=boldself.italic=italicself.shadow=shadowself.vertical_justification=vertical_justificationself.message=text
def_setup(self):self.actor=vtk.vtkTextActor()self._background=None# For VTK < 7self.handle_events(self.actor)def_get_actors(self):""" Get the actors composing this UI component. """ifself._backgroundisnotNone:return[self.actor,self._background]return[self.actor]def_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """ifself._backgroundisnotNone:scene.add(self._background)scene.add(self.actor)@propertydefmessage(self):""" Gets message from the text. Returns ------- str The current text message. """returnself.actor.GetInput()@message.setterdefmessage(self,text):""" Sets the text message. Parameters ---------- text : str The message to be set. """self.actor.SetInput(text)@propertydeffont_size(self):""" Gets text font size. Returns ---------- int Text font size. """returnself.actor.GetTextProperty().GetFontSize()@font_size.setterdeffont_size(self,size):""" Sets font size. Parameters ---------- size : int Text font size. """self.actor.GetTextProperty().SetFontSize(size)@propertydeffont_family(self):""" Gets font family. Returns ---------- str Text font family. """returnself.actor.GetTextProperty().GetFontFamilyAsString()@font_family.setterdeffont_family(self,family='Arial'):""" Sets font family. Currently Arial and Courier are supported. Parameters ---------- family : str The font family. """iffamily=='Arial':self.actor.GetTextProperty().SetFontFamilyToArial()eliffamily=='Courier':self.actor.GetTextProperty().SetFontFamilyToCourier()else:raiseValueError("Font not supported yet: {}.".format(family))@propertydefjustification(self):""" Gets text justification. Returns ------- str Text justification. """justification=self.actor.GetTextProperty().GetJustificationAsString()ifjustification=='Left':return"left"elifjustification=='Centered':return"center"elifjustification=='Right':return"right"@justification.setterdefjustification(self,justification):""" Justifies text. Parameters ---------- justification : str Possible values are left, right, center. """text_property=self.actor.GetTextProperty()ifjustification=='left':text_property.SetJustificationToLeft()elifjustification=='center':text_property.SetJustificationToCentered()elifjustification=='right':text_property.SetJustificationToRight()else:msg="Text can only be justified left, right and center."raiseValueError(msg)@propertydefvertical_justification(self):""" Gets text vertical justification. Returns ------- str Text vertical justification. """text_property=self.actor.GetTextProperty()vjustification=text_property.GetVerticalJustificationAsString()ifvjustification=='Bottom':return"bottom"elifvjustification=='Centered':return"middle"elifvjustification=='Top':return"top"@vertical_justification.setterdefvertical_justification(self,vertical_justification):""" Justifies text vertically. Parameters ---------- vertical_justification : str Possible values are bottom, middle, top. """text_property=self.actor.GetTextProperty()ifvertical_justification=='bottom':text_property.SetVerticalJustificationToBottom()elifvertical_justification=='middle':text_property.SetVerticalJustificationToCentered()elifvertical_justification=='top':text_property.SetVerticalJustificationToTop()else:msg="Vertical justification must be: bottom, middle or top."raiseValueError(msg)@propertydefbold(self):""" Returns whether the text is bold. Returns ------- bool Text is bold if True. """returnself.actor.GetTextProperty().GetBold()@bold.setterdefbold(self,flag):""" Bolds/un-bolds text. Parameters ---------- flag : bool Sets text bold if True. """self.actor.GetTextProperty().SetBold(flag)@propertydefitalic(self):""" Returns whether the text is italicised. Returns ------- bool Text is italicised if True. """returnself.actor.GetTextProperty().GetItalic()@italic.setterdefitalic(self,flag):""" Italicises/un-italicises text. Parameters ---------- flag : bool Italicises text if True. """self.actor.GetTextProperty().SetItalic(flag)@propertydefshadow(self):""" Returns whether the text has shadow. Returns ------- bool Text is shadowed if True. """returnself.actor.GetTextProperty().GetShadow()@shadow.setterdefshadow(self,flag):""" Adds/removes text shadow. Parameters ---------- flag : bool Shadows text if True. """self.actor.GetTextProperty().SetShadow(flag)@propertydefcolor(self):""" Gets text color. Returns ------- (float, float, float) Returns text color in RGB. """returnself.actor.GetTextProperty().GetColor()@color.setterdefcolor(self,color=(1,0,0)):""" Set text color. Parameters ---------- color : (float, float, float) RGB: Values must be between 0-1. """self.actor.GetTextProperty().SetColor(*color)@propertydefbackground_color(self):""" Gets background color. Returns ------- (float, float, float) or None If None, there no background color. Otherwise, background color in RGB. """ifself.actor.GetTextProperty().GetBackgroundOpacity()==0:returnNonereturnself.actor.GetTextProperty().GetBackgroundColor()@background_color.setterdefbackground_color(self,color):""" Set text color. Parameters ---------- color : (float, float, float) or None If None, remove background. Otherwise, RGB values (must be between 0-1). """ifcolorisNone:# Remove background.self.actor.GetTextProperty().SetBackgroundOpacity(0.)else:self.actor.GetTextProperty().SetBackgroundColor(*color)self.actor.GetTextProperty().SetBackgroundOpacity(1.)def_set_position(self,position):""" Set text actor position. Parameters ---------- position : (float, float) The new position. (x, y) in pixels. """self.actor.SetPosition(*position)ifself._backgroundisnotNone:self._background.SetPosition(*self.actor.GetPosition())def_get_size(self):ifself._backgroundisnotNone:returnself._background.sizereturnself.font_size*1.2,self.font_size*1.2
[docs]classTextBox2D(UI):""" An editable 2D text box that behaves as a UI component. Currently supports: - Basic text editing. - Cursor movements. - Single and multi-line text boxes. - Pre text formatting (text needs to be formatted beforehand). Attributes ---------- text : str The current text state. actor : :class:`vtkActor2d` The text actor. width : int The number of characters in a single line of text. height : int The number of lines in the textbox. window_left : int Left limit of visible text in the textbox. window_right : int Right limit of visible text in the textbox. caret_pos : int Position of the caret in the text. init : bool Flag which says whether the textbox has just been initialized. """
[docs]def__init__(self,width,height,text="Enter Text",position=(100,10),color=(0,0,0),font_size=18,font_family='Arial',justification='left',bold=False,italic=False,shadow=False):""" Parameters ---------- width : int The number of characters in a single line of text. height : int The number of lines in the textbox. text : str The initial text while building the actor. position : (float, float) (x, y) in pixels. color : (float, float, float) RGB: Values must be between 0-1. font_size : int Size of the text font. font_family : str Currently only supports Arial. justification : str left, right or center. bold : bool Makes text bold. italic : bool Makes text italicised. shadow : bool Adds text shadow. """super(TextBox2D,self).__init__(position=position)self.message=textself.text.message=textself.text.font_size=font_sizeself.text.font_family=font_familyself.text.justification=justificationself.text.bold=boldself.text.italic=italicself.text.shadow=shadowself.text.color=colorself.text.background_color=(1,1,1)self.width=widthself.height=heightself.window_left=0self.window_right=0self.caret_pos=0self.init=True
def_setup(self):""" Setup this UI component. Create the TextBlock2D component used for the textbox. """self.text=TextBlock2D()# Add default events listener for this UI component.self.text.on_left_mouse_button_pressed=self.left_button_pressself.text.on_key_press=self.key_pressdef_get_actors(self):""" Get the actors composing this UI component. """returnself.text.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.text.add_to_scene(scene)def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.text.position=coordsdef_get_size(self):returnself.text.size
[docs]defset_message(self,message):""" Set custom text to textbox. Parameters ---------- message: str The custom message to be set. """self.message=messageself.text.message=messageself.init=Falseself.window_right=len(self.message)self.window_left=0self.caret_pos=self.window_right
[docs]defwidth_set_text(self,text):""" Adds newlines to text where necessary. This is needed for multi-line text boxes. Parameters ---------- text : str The final text to be formatted. Returns ------- str A multi line formatted text. """multi_line_text=""fori,tinenumerate(text):multi_line_text+=tif(i+1)%self.width==0:multi_line_text+="\n"returnmulti_line_text.rstrip("\n")
[docs]defhandle_character(self,character):""" Main driving function that handles button events. # TODO: Need to handle all kinds of characters like !, +, etc. Parameters ---------- character : str """ifcharacter.lower()=="return":self.render_text(False)returnTrueifcharacter.lower()=="backspace":self.remove_character()elifcharacter.lower()=="left":self.move_left()elifcharacter.lower()=="right":self.move_right()else:self.add_character(character)self.render_text()returnFalse
[docs]defmove_caret_right(self):""" Moves the caret towards right. """self.caret_pos=min(self.caret_pos+1,len(self.message))
[docs]defmove_caret_left(self):""" Moves the caret towards left. """self.caret_pos=max(self.caret_pos-1,0)
[docs]defright_move_right(self):""" Moves right boundary of the text window right-wards. """ifself.window_right<=len(self.message):self.window_right+=1
[docs]defright_move_left(self):""" Moves right boundary of the text window left-wards. """ifself.window_right>0:self.window_right-=1
[docs]defleft_move_right(self):""" Moves left boundary of the text window right-wards. """ifself.window_left<=len(self.message):self.window_left+=1
[docs]defleft_move_left(self):""" Moves left boundary of the text window left-wards. """ifself.window_left>0:self.window_left-=1
[docs]defadd_character(self,character):""" Inserts a character into the text and moves window and caret. Parameters ---------- character : str """iflen(character)>1andcharacter.lower()!="space":returnifcharacter.lower()=="space":character=" "self.message=(self.message[:self.caret_pos]+character+self.message[self.caret_pos:])self.move_caret_right()if(self.window_right-self.window_left==self.height*self.width-1):self.left_move_right()self.right_move_right()
[docs]defremove_character(self):""" Removes a character and moves window and caret accordingly. """ifself.caret_pos==0:returnself.message=(self.message[:self.caret_pos-1]+self.message[self.caret_pos:])self.move_caret_left()iflen(self.message)<self.height*self.width-1:self.right_move_left()if(self.window_right-self.window_left==self.height*self.width-1):ifself.window_left>0:self.left_move_left()self.right_move_left()
[docs]defmove_left(self):""" Handles left button press. """self.move_caret_left()ifself.caret_pos==self.window_left-1:if(self.window_right-self.window_left==self.height*self.width-1):self.left_move_left()self.right_move_left()
[docs]defmove_right(self):""" Handles right button press. """self.move_caret_right()ifself.caret_pos==self.window_right+1:if(self.window_right-self.window_left==self.height*self.width-1):self.left_move_right()self.right_move_right()
[docs]defshowable_text(self,show_caret):""" Chops out text to be shown on the screen. Parameters ---------- show_caret : bool Whether or not to show the caret. """ifshow_caret:ret_text=(self.message[:self.caret_pos]+"_"+self.message[self.caret_pos:])else:ret_text=self.messageret_text=ret_text[self.window_left:self.window_right+1]returnret_text
[docs]defrender_text(self,show_caret=True):""" Renders text after processing. Parameters ---------- show_caret : bool Whether or not to show the caret. """text=self.showable_text(show_caret)iftext=="":text="Enter Text"self.text.message=self.width_set_text(text)
[docs]defedit_mode(self):""" Turns on edit mode. """ifself.init:self.message=""self.init=Falseself.caret_pos=0self.render_text()
[docs]defleft_button_press(self,i_ren,_obj,_textbox_object):""" Left button press handler for textbox Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _textbox_object: :class:`TextBox2D` """i_ren.add_active_prop(self.text.actor)self.edit_mode()i_ren.force_render()
[docs]defkey_press(self,i_ren,_obj,_textbox_object):""" Key press handler for textbox Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _textbox_object: :class:`TextBox2D` """key=i_ren.event.keyis_done=self.handle_character(key)ifis_done:i_ren.remove_active_prop(self.text.actor)i_ren.force_render()
[docs]classLineSlider2D(UI):""" A 2D Line Slider. A sliding handle on a line with a percentage indicator. Attributes ---------- line_width : int Width of the line on which the disk will slide. length : int Length of the slider. track : :class:`Rectangle2D` The line on which the slider's handle moves. handle : :class:`Disk2D` The moving part of the slider. text : :class:`TextBlock2D` The text that shows percentage. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. default_color : (float, float, float) Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. """
[docs]def__init__(self,center=(0,0),initial_value=50,min_value=0,max_value=100,length=200,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,font_size=16,text_template="{value:.1f} ({ratio:.0%})",shape="disk"):""" Parameters ---------- center : (float, float) Center of the slider's center. initial_value : float Initial value of the slider. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. length : int Length of the slider. line_width : int Width of the line on which the disk will slide. inner_radius : int Inner radius of the handles (if disk). outer_radius : int Outer radius of the handles (if disk). handle_side : int Side length of the handles (if sqaure). font_size : int Size of the text to display alongside the slider (pt). text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`. If callable, this instance of `:class:LineSlider2D` will be passed as argument to the text template function. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.shape=shapeself.default_color=(1,1,1)self.active_color=(0,0,1)super(LineSlider2D,self).__init__()self.track.width=lengthself.track.height=line_widthifshape=="disk":self.handle.inner_radius=inner_radiusself.handle.outer_radius=outer_radiuselifshape=="square":self.handle.width=handle_sideself.handle.height=handle_sideself.center=centerself.min_value=min_valueself.max_value=max_valueself.text.font_size=font_sizeself.text_template=text_template# Offer some standard hooks to the user.self.on_change=lambdaui:Noneself.value=initial_valueself.update()
def_setup(self):""" Setup this UI component. Create the slider's track (Rectangle2D), the handle (Disk2D) and the text (TextBlock2D). """# Slider's trackself.track=Rectangle2D()self.track.color=(1,0,0)# Slider's handleifself.shape=="disk":self.handle=Disk2D(outer_radius=1)elifself.shape=="square":self.handle=Rectangle2D(size=(1,1))self.handle.color=self.default_color# Slider Textself.text=TextBlock2D(justification="center",vertical_justification="top")# Add default events listener for this UI component.self.track.on_left_mouse_button_pressed=self.track_click_callbackself.track.on_left_mouse_button_dragged=self.handle_move_callbackself.track.on_left_mouse_button_released= \
self.handle_release_callbackself.handle.on_left_mouse_button_dragged=self.handle_move_callbackself.handle.on_left_mouse_button_released= \
self.handle_release_callbackdef_get_actors(self):""" Get the actors composing this UI component. """returnself.track.actors+self.handle.actors+self.text.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handle.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):# Consider the handle's size when computing the slider's size.width=self.track.width+self.handle.size[0]height=max(self.track.height,self.handle.size[1])returnnp.array([width,height])def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# Offset the slider line by the handle's radius.track_position=coords+self.handle.size/2.# Offset the slider line height by half the slider line width.track_position[1]-=self.track.size[1]/2.self.track.position=track_positionself.handle.position=self.handle.position.astype('float64')self.handle.position+=coords-self.position# Position the text below the handle.self.text.position=(self.handle.center[0],self.handle.position[1]-10)@propertydefleft_x_position(self):returnself.track.position[0]@propertydefright_x_position(self):returnself.track.position[0]+self.track.size[0]
[docs]defset_position(self,position):""" Sets the disk's position. Parameters ---------- position : (float, float) The absolute position of the disk (x, y). """x_position=position[0]x_position=max(x_position,self.left_x_position)x_position=min(x_position,self.right_x_position)# Move slider disk.self.handle.center=(x_position,self.track.center[1])self.update()# Update information.
[docs]defformat_text(self):""" Returns formatted text to display along the slider. """ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(ratio=self.ratio,value=self.value)
[docs]defupdate(self):""" Updates the slider. """# Compute the ratio determined by the position of the slider disk.length=float(self.right_x_position-self.left_x_position)iflength!=self.track.width:raiseException("Disk position outside the slider line")disk_position_x=self.handle.center[0]self._ratio=(disk_position_x-self.left_x_position)/length# Compute the selected value considering min_value and max_value.value_range=self.max_value-self.min_valueself._value=self.min_value+self.ratio*value_range# Update text.text=self.format_text()self.text.message=text# Move the text below the slider's handle.self.text.position=(disk_position_x,self.text.position[1])self.on_change(self)
[docs]deftrack_click_callback(self,i_ren,_vtkactor,_slider):""" Update disk position and grab the focus. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineSlider2D` """position=i_ren.event.positionself.set_position(position)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_move_callback(self,i_ren,_vtkactor,_slider):""" Actual handle movement. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` """self.handle.color=self.active_colorposition=i_ren.event.positionself.set_position(position)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,_vtkactor,_slider):""" Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` """self.handle.color=self.default_colori_ren.force_render()
[docs]classLineDoubleSlider2D(UI):""" A 2D Line Slider with two sliding rings. Useful for setting min and max values for something. Currently supports: - Setting positions of both disks. Attributes ---------- line_width : int Width of the line on which the disk will slide. length : int Length of the slider. track : :class:`vtkActor` The line on which the handles move. handles : [:class:`vtkActor`, :class:`vtkActor`] The moving slider disks. text : [:class:`TextBlock2D`, :class:`TextBlock2D`] The texts that show the values of the disks. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. default_color : (float, float, float) Color of the handles when in unpressed state. active_color : (float, float, float) Color of the handles when they are pressed. """
[docs]def__init__(self,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,center=(450,300),length=200,initial_values=(0,100),min_value=0,max_value=100,font_size=16,text_template="{value:.1f}",shape="disk"):""" Parameters ---------- line_width : int Width of the line on which the disk will slide. inner_radius : int Inner radius of the handles (if disk). outer_radius : int Outer radius of the handles (if disk). handle_side : int Side length of the handles (if sqaure). center : (float, float) Center of the slider. length : int Length of the slider. initial_values : (float, float) Initial values of the two handles. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. font_size : int Size of the text to display alongside the slider (pt). text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`. If callable, this instance of `:class:LineDoubleSlider2D` will be passed as argument to the text template function. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.shape=shapeself.default_color=(1,1,1)self.active_color=(0,0,1)super(LineDoubleSlider2D,self).__init__()self.track.width=lengthself.track.height=line_widthself.center=centerifshape=="disk":self.handles[0].inner_radius=inner_radiusself.handles[0].outer_radius=outer_radiusself.handles[1].inner_radius=inner_radiusself.handles[1].outer_radius=outer_radiuselifshape=="square":self.handles[0].width=handle_sideself.handles[0].height=handle_sideself.handles[1].width=handle_sideself.handles[1].height=handle_sideself.min_value=min_valueself.max_value=max_valueself.text[0].font_size=font_sizeself.text[1].font_size=font_sizeself.text_template=text_template# Setting the handle positions will also update everything.self._values=[initial_values[0],initial_values[1]]self._ratio=[None,None]self.left_disk_value=initial_values[0]self.right_disk_value=initial_values[1]
def_setup(self):""" Setup this UI component. Create the slider's track (Rectangle2D), the handles (Disk2D) and the text (TextBlock2D). """# Slider's trackself.track=Rectangle2D()self.track.color=(1,0,0)# Handlesself.handles=[]ifself.shape=="disk":self.handles.append(Disk2D(outer_radius=1))self.handles.append(Disk2D(outer_radius=1))elifself.shape=="square":self.handles.append(Rectangle2D(size=(1,1)))self.handles.append(Rectangle2D(size=(1,1)))self.handles[0].color=self.default_colorself.handles[1].color=self.default_color# Slider Textself.text=[TextBlock2D(justification="center",vertical_justification="top"),TextBlock2D(justification="center",vertical_justification="top")]# Add default events listener for this UI component.self.track.on_left_mouse_button_dragged=self.handle_move_callbackself.handles[0].on_left_mouse_button_dragged= \
self.handle_move_callbackself.handles[1].on_left_mouse_button_dragged= \
self.handle_move_callbackself.handles[0].on_left_mouse_button_released= \
self.handle_release_callbackself.handles[1].on_left_mouse_button_released= \
self.handle_release_callbackdef_get_actors(self):""" Get the actors composing this UI component. """return(self.track.actors+self.handles[0].actors+self.handles[1].actors+self.text[0].actors+self.text[1].actors)def_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handles[0].add_to_scene(scene)self.handles[1].add_to_scene(scene)self.text[0].add_to_scene(scene)self.text[1].add_to_scene(scene)def_get_size(self):# Consider the handle's size when computing the slider's size.width=self.track.width+2*self.handles[0].size[0]height=max(self.track.height,self.handles[0].size[1])returnnp.array([width,height])def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# Offset the slider line by the handle's radius.track_position=coords+self.handles[0].size/2.# Offset the slider line height by half the slider line width.track_position[1]-=self.track.size[1]/2.self.track.position=track_positionself.handles[0].position=self.handles[0].position.astype('float64')self.handles[1].position=self.handles[1].position.astype('float64')self.handles[0].position+=coords-self.positionself.handles[1].position+=coords-self.position# Position the text below the handles.self.text[0].position=(self.handles[0].center[0],self.handles[0].position[1]-20)self.text[1].position=(self.handles[1].center[0],self.handles[1].position[1]-20)@propertydefleft_x_position(self):returnself.track.position[0]@propertydefright_x_position(self):returnself.track.position[0]+self.track.size[0]
[docs]defvalue_to_ratio(self,value):""" Converts the value of a disk to the ratio Parameters ---------- value : float """value_range=self.max_value-self.min_valuereturn(value-self.min_value)/value_range
[docs]defratio_to_coord(self,ratio):""" Converts the ratio to the absolute coordinate. Parameters ---------- ratio : float """returnself.left_x_position+ratio*self.track.width
[docs]defcoord_to_ratio(self,coord):""" Converts the x coordinate of a disk to the ratio Parameters ---------- coord : float """return(coord-self.left_x_position)/self.track.width
[docs]defratio_to_value(self,ratio):""" Converts the ratio to the value of the disk. Parameters ---------- ratio : float """value_range=self.max_value-self.min_valuereturnself.min_value+ratio*value_range
[docs]defset_position(self,position,disk_number):""" Sets the disk's position. Parameters ---------- position : (float, float) The absolute position of the disk (x, y). disk_number : int The index of disk being moved. """x_position=position[0]ifdisk_number==0andx_position>=self.handles[1].center[0]:x_position=self.ratio_to_coord(self.value_to_ratio(self._values[1]-1))ifdisk_number==1andx_position<=self.handles[0].center[0]:x_position=self.ratio_to_coord(self.value_to_ratio(self._values[0]+1))x_position=max(x_position,self.left_x_position)x_position=min(x_position,self.right_x_position)self.handles[disk_number].center=(x_position,self.track.center[1])self.update(disk_number)
@propertydefleft_disk_value(self):""" Returns the value of the left disk. """returnself._values[0]@left_disk_value.setterdefleft_disk_value(self,left_disk_value):""" Sets the value of the left disk. Parameters ---------- left_disk_value : New value for the left disk. """self.left_disk_ratio=self.value_to_ratio(left_disk_value)@propertydefright_disk_value(self):""" Returns the value of the right disk. """returnself._values[1]@right_disk_value.setterdefright_disk_value(self,right_disk_value):""" Sets the value of the right disk. Parameters ---------- right_disk_value : New value for the right disk. """self.right_disk_ratio=self.value_to_ratio(right_disk_value)@propertydefleft_disk_ratio(self):""" Returns the ratio of the left disk. """returnself._ratio[0]@left_disk_ratio.setterdefleft_disk_ratio(self,left_disk_ratio):""" Sets the ratio of the left disk. Parameters ---------- left_disk_ratio : New ratio for the left disk. """position_x=self.ratio_to_coord(left_disk_ratio)self.set_position((position_x,None),0)@propertydefright_disk_ratio(self):""" Returns the ratio of the right disk. """returnself._ratio[1]@right_disk_ratio.setterdefright_disk_ratio(self,right_disk_ratio):""" Sets the ratio of the right disk. Parameters ---------- right_disk_ratio : New ratio for the right disk. """position_x=self.ratio_to_coord(right_disk_ratio)self.set_position((position_x,None),1)
[docs]defformat_text(self,disk_number):""" Returns formatted text to display along the slider. Parameters ---------- disk_number : Index of the disk. """ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(value=self._values[disk_number])
[docs]defupdate(self,disk_number):""" Updates the slider. Parameters ---------- disk_number : Index of the disk to be updated. """# Compute the ratio determined by the position of the slider disk.self._ratio[disk_number]=self.coord_to_ratio(self.handles[disk_number].center[0])# Compute the selected value considering min_value and max_value.self._values[disk_number]=self.ratio_to_value(self._ratio[disk_number])# Update text.text=self.format_text(disk_number)self.text[disk_number].message=textself.text[disk_number].position=(self.handles[disk_number].center[0],self.text[disk_number].position[1])self.on_change(self)
[docs]defhandle_move_callback(self,i_ren,vtkactor,_slider):""" Actual handle movement. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineDoubleSlider2D` """position=i_ren.event.positionifvtkactor==self.handles[0].actors[0]:self.set_position(position,0)self.handles[0].color=self.active_colorelifvtkactor==self.handles[1].actors[0]:self.set_position(position,1)self.handles[1].color=self.active_colori_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,vtkactor,_slider):""" Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineDoubleSlider2D` """ifvtkactor==self.handles[0].actors[0]:self.handles[0].color=self.default_colorelifvtkactor==self.handles[1].actors[0]:self.handles[1].color=self.default_colori_ren.force_render()
[docs]classRingSlider2D(UI):""" A disk slider. A disk moves along the boundary of a ring. Goes from 0-360 degrees. Attributes ---------- mid_track_radius: float Distance from the center of the slider to the middle of the track. previous_value: float Value of Rotation of the actor before the current value. track : :class:`Disk2D` The circle on which the slider's handle moves. handle : :class:`Disk2D` The moving part of the slider. text : :class:`TextBlock2D` The text that shows percentage. default_color : (float, float, float) Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. """
[docs]def__init__(self,center=(0,0),initial_value=180,min_value=0,max_value=360,slider_inner_radius=40,slider_outer_radius=44,handle_inner_radius=0,handle_outer_radius=10,font_size=16,text_template="{ratio:.0%}"):""" Parameters ---------- center : (float, float) Position (x, y) of the slider's center. initial_value : float Initial value of the slider. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. slider_inner_radius : int Inner radius of the base disk. slider_outer_radius : int Outer radius of the base disk. handle_outer_radius : int Outer radius of the slider's handle. handle_inner_radius : int Inner radius of the slider's handle. font_size : int Size of the text to display alongside the slider (pt). text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`, `{angle:}`. If callable, this instance of `:class:RingSlider2D` will be passed as argument to the text template function. """self.default_color=(1,1,1)self.active_color=(0,0,1)super(RingSlider2D,self).__init__()self.track.inner_radius=slider_inner_radiusself.track.outer_radius=slider_outer_radiusself.handle.inner_radius=handle_inner_radiusself.handle.outer_radius=handle_outer_radiusself.center=centerself.min_value=min_valueself.max_value=max_valueself.text.font_size=font_sizeself.text_template=text_template# Offer some standard hooks to the user.self.on_change=lambdaui:Noneself._value=initial_valueself.value=initial_valueself._previous_value=initial_valueself._angle=0self._ratio=self.angle/TWO_PI
def_setup(self):""" Setup this UI component. Create the slider's circle (Disk2D), the handle (Disk2D) and the text (TextBlock2D). """# Slider's track.self.track=Disk2D(outer_radius=1)self.track.color=(1,0,0)# Slider's handle.self.handle=Disk2D(outer_radius=1)self.handle.color=self.default_color# Slider Textself.text=TextBlock2D(justification="center",vertical_justification="middle")# Add default events listener for this UI component.self.track.on_left_mouse_button_pressed=self.track_click_callbackself.track.on_left_mouse_button_dragged=self.handle_move_callbackself.track.on_left_mouse_button_released= \
self.handle_release_callbackself.handle.on_left_mouse_button_dragged=self.handle_move_callbackself.handle.on_left_mouse_button_released= \
self.handle_release_callbackdef_get_actors(self):""" Get the actors composing this UI component. """returnself.track.actors+self.handle.actors+self.text.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handle.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):returnself.track.size+self.handle.sizedef_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.track.position=coords+self.handle.size/2.self.handle.position+=coords-self.position# Position the text in the center of the slider's track.self.text.position=coords+self.size/2.@propertydefmid_track_radius(self):return(self.track.inner_radius+self.track.outer_radius)/2.@propertydefvalue(self):returnself._value@value.setterdefvalue(self,value):value_range=self.max_value-self.min_valueself.ratio=(value-self.min_value)/value_range@propertydefprevious_value(self):returnself._previous_value@propertydefratio(self):returnself._ratio@ratio.setterdefratio(self,ratio):self.angle=ratio*TWO_PI@propertydefangle(self):""" Angle (in rad) the handle makes with x-axis """returnself._angle@angle.setterdefangle(self,angle):self._angle=angle%TWO_PI# Wraparoundself.update()
[docs]defformat_text(self):""" Returns formatted text to display along the slider. """ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(ratio=self.ratio,value=self.value,angle=np.rad2deg(self.angle))
[docs]defupdate(self):""" Updates the slider. """# Compute the ratio determined by the position of the slider disk.self._ratio=self.angle/TWO_PI# Compute the selected value considering min_value and max_value.value_range=self.max_value-self.min_valueself._previous_value=self.valueself._value=self.min_value+self.ratio*value_range# Update text disk actor.x=self.mid_track_radius*np.cos(self.angle)+self.center[0]y=self.mid_track_radius*np.sin(self.angle)+self.center[1]self.handle.center=(x,y)# Update text.text=self.format_text()self.text.message=textself.on_change(self)# Call hook.
[docs]defmove_handle(self,click_position):"""Moves the slider's handle. Parameters ---------- click_position: (float, float) Position of the mouse click. """x,y=np.array(click_position)-self.centerangle=np.arctan2(y,x)ifangle<0:angle+=TWO_PIself.angle=angle
[docs]deftrack_click_callback(self,i_ren,_obj,_slider):""" Update disk position and grab the focus. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """click_position=i_ren.event.positionself.move_handle(click_position=click_position)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_move_callback(self,i_ren,_obj,_slider):""" Move the slider's handle. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """click_position=i_ren.event.positionself.handle.color=self.active_colorself.move_handle(click_position=click_position)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,_obj,_slider):""" Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """self.handle.color=self.default_colori_ren.force_render()
[docs]classRangeSlider(UI):""" A set of a LineSlider2D and a LineDoubleSlider2D. The double slider is used to set the min and max value for the LineSlider2D Attributes ---------- range_slider_center : (float, float) Center of the LineDoubleSlider2D object. value_slider_center : (float, float) Center of the LineSlider2D object. range_slider : :class:`LineDoubleSlider2D` The line slider which sets the min and max values value_slider : :class:`LineSlider2D` The line slider which sets the value """
[docs]def__init__(self,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,range_slider_center=(450,400),value_slider_center=(450,300),length=200,min_value=0,max_value=100,font_size=16,range_precision=1,value_precision=2,shape="disk"):""" Parameters ---------- line_width : int Width of the slider tracks inner_radius : int Inner radius of the handles. outer_radius : int Outer radius of the handles. handle_side : int Side length of the handles (if square). range_slider_center : (float, float) Center of the LineDoubleSlider2D object. value_slider_center : (float, float) Center of the LineSlider2D object. length : int Length of the sliders. min_value : float Minimum value of the double slider. max_value : float Maximum value of the double slider. font_size : int Size of the text to display alongside the sliders (pt). range_precision : int Number of decimal places to show the min and max values set. value_precision : int Number of decimal places to show the value set on slider. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.min_value=min_valueself.max_value=max_valueself.inner_radius=inner_radiusself.outer_radius=outer_radiusself.handle_side=handle_sideself.length=lengthself.line_width=line_widthself.font_size=font_sizeself.shape=shapeself.range_slider_text_template= \
"{value:."+str(range_precision)+"f}"self.value_slider_text_template= \
"{value:."+str(value_precision)+"f}"self.range_slider_center=range_slider_centerself.value_slider_center=value_slider_centersuper(RangeSlider,self).__init__()
def_setup(self):""" Setup this UI component. """self.range_slider= \
LineDoubleSlider2D(line_width=self.line_width,inner_radius=self.inner_radius,outer_radius=self.outer_radius,handle_side=self.handle_side,center=self.range_slider_center,length=self.length,min_value=self.min_value,max_value=self.max_value,initial_values=(self.min_value,self.max_value),font_size=self.font_size,shape=self.shape,text_template=self.range_slider_text_template)self.value_slider= \
LineSlider2D(line_width=self.line_width,length=self.length,inner_radius=self.inner_radius,outer_radius=self.outer_radius,handle_side=self.handle_side,center=self.value_slider_center,min_value=self.min_value,max_value=self.max_value,initial_value=(self.min_value+self.max_value)/2,font_size=self.font_size,shape=self.shape,text_template=self.value_slider_text_template)# Add default events listener for this UI component.self.range_slider.handles[0].on_left_mouse_button_dragged= \
self.range_slider_handle_move_callbackself.range_slider.handles[1].on_left_mouse_button_dragged= \
self.range_slider_handle_move_callbackdef_get_actors(self):""" Get the actors composing this UI component. """returnself.range_slider.actors+self.value_slider.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.range_slider.add_to_scene(scene)self.value_slider.add_to_scene(scene)def_get_size(self):returnself.range_slider.size+self.value_slider.sizedef_set_position(self,coords):pass
[docs]defrange_slider_handle_move_callback(self,i_ren,obj,_slider):""" Actual movement of range_slider's handles. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RangeSlider` """position=i_ren.event.positionifobj==self.range_slider.handles[0].actors[0]:self.range_slider.handles[0].color= \
self.range_slider.active_colorself.range_slider.set_position(position,0)self.value_slider.min_value=self.range_slider.left_disk_valueself.value_slider.update()elifobj==self.range_slider.handles[1].actors[0]:self.range_slider.handles[1].color= \
self.range_slider.active_colorself.range_slider.set_position(position,1)self.value_slider.max_value=self.range_slider.right_disk_valueself.value_slider.update()i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]classImageContainer2D(UI):""" A 2D container to hold an image. Currently Supports: - png and jpg/jpeg images Attributes ---------- size: (float, float) Image size (width, height) in pixels. img : vtkImageDataGeometryFilters The image loaded from the specified path. """
[docs]def__init__(self,img_path,position=(0,0),size=(100,100)):""" Parameters ---------- img_path : string Path of the image position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the image. size : (int, int), optional Width and height in pixels of the image. """super(ImageContainer2D,self).__init__(position)self.img=self._build_image(img_path)self.set_img(self.img)self.resize(size)
def_build_image(self,img_path):""" Converts image path to vtkImageDataGeometryFilters. A pre-processing step to prevent re-read of image during every state change. Parameters ---------- img_path : string Path of the image Returns ------- img : vtkImageDataGeometryFilters The corresponding image . """imgExt=img_path.split(".")[-1].lower()ifimgExt=="png":png=vtk.vtkPNGReader()png.SetFileName(img_path)png.Update()img=png.GetOutput()elifimgExtin["jpg","jpeg"]:jpeg=vtk.vtkJPEGReader()jpeg.SetFileName(img_path)jpeg.Update()img=jpeg.GetOutput()else:error_msg="This file format is not supported by the Image Holder"warn(Warning(error_msg))returnimgdef_get_size(self):lower_left_corner=self.texture_points.GetPoint(0)upper_right_corner=self.texture_points.GetPoint(2)size=np.array(upper_right_corner)-np.array(lower_left_corner)returnabs(size[:2])def_setup(self):""" Setup this UI Component. Return an image as a 2D actor with a specific position. Returns ------- :class:`vtkTexturedActor2D` """self.texture_polydata=vtk.vtkPolyData()self.texture_points=vtk.vtkPoints()self.texture_points.SetNumberOfPoints(4)polys=vtk.vtkCellArray()polys.InsertNextCell(4)polys.InsertCellPoint(0)polys.InsertCellPoint(1)polys.InsertCellPoint(2)polys.InsertCellPoint(3)self.texture_polydata.SetPolys(polys)tc=vtk.vtkFloatArray()tc.SetNumberOfComponents(2)tc.SetNumberOfTuples(4)tc.InsertComponent(0,0,0.0)tc.InsertComponent(0,1,0.0)tc.InsertComponent(1,0,1.0)tc.InsertComponent(1,1,0.0)tc.InsertComponent(2,0,1.0)tc.InsertComponent(2,1,1.0)tc.InsertComponent(3,0,0.0)tc.InsertComponent(3,1,1.0)self.texture_polydata.GetPointData().SetTCoords(tc)texture_mapper=vtk.vtkPolyDataMapper2D()texture_mapper=set_input(texture_mapper,self.texture_polydata)image=vtk.vtkTexturedActor2D()image.SetMapper(texture_mapper)self.texture=vtk.vtkTexture()image.SetTexture(self.texture)image_property=vtk.vtkProperty2D()image_property.SetOpacity(1.0)image.SetProperty(image_property)self.actor=image# Add default events listener to the VTK actor.self.handle_events(self.actor)def_get_actors(self):""" Returns the actors that compose this UI component. """return[self.actor]def_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """scene.add(self.actor)
[docs]defresize(self,size):""" Resize the image. Parameters ---------- size : (float, float) image size (width, height) in pixels. """# Update actor.self.texture_points.SetPoint(0,0,0,0.0)self.texture_points.SetPoint(1,size[0],0,0.0)self.texture_points.SetPoint(2,size[0],size[1],0.0)self.texture_points.SetPoint(3,0,size[1],0.0)self.texture_polydata.SetPoints(self.texture_points)
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.actor.SetPosition(*coords)
[docs]defscale(self,factor):""" Scales the image. Parameters ---------- factor : (float, float) Scaling factor (width, height) in pixels. """self.resize(self.size*factor)
[docs]defset_img(self,img):""" Modifies the image used by the vtkTexturedActor2D. Parameters ---------- img : imageDataGeometryFilter """self.texture=set_input(self.texture,img)
[docs]classOption(UI):""" A set of a Button2D and a TextBlock2D to act as a single option for checkboxes and radio buttons. Clicking the button toggles its checked/unchecked status. Attributes ---------- label : str The label for the option. font_size : int Font Size of the label. """
[docs]def__init__(self,label,position=(0,0),font_size=18):""" Parameters ---------- label : str Text to be displayed next to the option's button. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of the button of the option. font_size : int Font size of the label. """self.label=labelself.font_size=font_sizeself.checked=Falseself.button_size=(font_size*1.2,font_size*1.2)self.button_label_gap=10super(Option,self).__init__(position)# Offer some standard hooks to the user.self.on_change=lambdaobj:None
def_setup(self):""" Setup this UI component. """# Option's buttonself.button_icons=[]self.button_icons.append(('unchecked',read_viz_icons(fname="stop2.png")))self.button_icons.append(('checked',read_viz_icons(fname="checkmark.png")))self.button=Button2D(icon_fnames=self.button_icons,size=self.button_size)self.text=TextBlock2D(text=self.label,font_size=self.font_size)# Add callbacksself.button.on_left_mouse_button_clicked=self.toggleself.text.on_left_mouse_button_clicked=self.toggledef_get_actors(self):""" Get the actors composing this UI component. """returnself.button.actors+self.text.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.button.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):width=self.button.size[0]+self.button_label_gap+self.text.size[0]height=max(self.button.size[1],self.text.size[1])returnnp.array([width,height])def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """num_newlines=self.label.count('\n')self.button.position=coords+ \
(0,num_newlines*self.font_size*0.5)offset=(self.button.size[0]+self.button_label_gap,0)self.text.position=coords+offset
[docs]classCheckbox(UI):""" A 2D set of :class:'Option' objects. Multiple options can be selected. Attributes ---------- labels : list(string) List of labels of each option. options : list(Option) List of all the options in the checkbox set. padding : float Distance between two adjacent options """
[docs]def__init__(self,labels,padding=1,font_size=18,font_family='Arial',position=(0,0)):""" Parameters ---------- labels : list(string) List of labels of each option. padding : float The distance between two adjacent options font_size : int Size of the text font. font_family : str Currently only supports Arial. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of the button of the first option. """self.labels=list(reversed(labels))self._padding=paddingself._font_size=font_sizeself.font_family=font_familysuper(Checkbox,self).__init__(position)self.on_change=lambdacheckbox:Noneself.checked=[]
def_setup(self):""" Setup this UI component. """self.options=[]button_y=self.position[1]forlabelinself.labels:option=Option(label=label,font_size=self.font_size,position=(self.position[0],button_y))line_spacing=option.text.actor.GetTextProperty().GetLineSpacing()button_y=button_y+self.font_size* \
(label.count('\n')+1)*(line_spacing+0.1)+self.paddingself.options.append(option)# Set callbackoption.on_change=self._handle_option_changedef_get_actors(self):""" Get the actors composing this UI component. """actors=[]foroptioninself.options:actors=actors+option.actorsreturnactorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """foroptioninself.options:option.add_to_scene(scene)def_get_size(self):option_width,option_height=self.options[0].get_size()height=len(self.labels)*(option_height+self.padding) \
-self.paddingreturnnp.asarray([option_width,height])def_handle_option_change(self,option):""" Reacts whenever an option changes. Parameters ---------- option : :class:`Option` """ifoption.checked:self.checked.append(option.label)else:self.checked.remove(option.label)self.on_change(self)def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """button_y=coords[1]foroption_no,optioninenumerate(self.options):option.position=(coords[0],button_y)line_spacing=option.text.actor.GetTextProperty().GetLineSpacing()button_y=(button_y+self.font_size*(self.labels[option_no].count('\n')+1)*(line_spacing+0.1)+self.padding)@propertydeffont_size(self):""" Gets the font size of text. """returnself._font_size@propertydefpadding(self):""" Gets the padding between options. """returnself._padding
[docs]classRadioButton(Checkbox):""" A 2D set of :class:'Option' objects. Only one option can be selected. Attributes ---------- labels : list(string) List of labels of each option. options : list(Option) List of all the options in the checkbox set. padding : float Distance between two adjacent options """
[docs]def__init__(self,labels,padding=1,font_size=18,font_family='Arial',position=(0,0)):""" Parameters ---------- labels : list(string) List of labels of each option. padding : float The distance between two adjacent options font_size : int Size of the text font. font_family : str Currently only supports Arial. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of the button of the first option. """super(RadioButton,self).__init__(labels=labels,position=position,padding=padding,font_size=font_size,font_family=font_family)
[docs]classListBox2D(UI):""" UI component that allows the user to select items from a list. Attributes ---------- on_change: function Callback function for when the selected items have changed. """
[docs]def__init__(self,values,position=(0,0),size=(100,300),multiselection=True,reverse_scrolling=False,font_size=20,line_spacing=1.4):""" Parameters ---------- values: list of objects Values used to populate this listbox. Objects must be castable to string. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of this UI component. size : (int, int) Width and height in pixels of this UI component. multiselection: {True, False} Whether multiple values can be selected at once. reverse_scrolling: {True, False} If True, scrolling up will move the list of files down. font_size: int The font size in pixels. line_spacing: float Distance between listbox's items in pixels. """self.view_offset=0self.slots=[]self.selected=[]self.panel_size=sizeself.font_size=font_sizeself.line_spacing=line_spacingself.slot_height=int(self.font_size*self.line_spacing)# self.panel.resize(size)self.values=valuesself.multiselection=multiselectionself.last_selection_idx=0self.reverse_scrolling=reverse_scrollingsuper(ListBox2D,self).__init__()denom=len(self.values)-self.nb_slotsifnotdenom:denom+=1self.scroll_step_size=(self.slot_height*self.nb_slots-self.scroll_bar.height)/denomself.scroll_bar_active_color=(0.8,0,0)self.scroll_bar_inactive_color=(1,0,0)self.position=positionself.scroll_init_position=0self.update()# Offer some standard hooks to the user.self.on_change=lambda:None
def_setup(self):""" Setup this UI component. Create the ListBox (Panel2D) filled with empty slots (ListBoxItem2D). """self.margin=10size=self.panel_sizefont_size=self.font_size# Calculating the number of slots.self.nb_slots=int((size[1]-2*self.margin)//self.slot_height)# This panel facilitates adding slots at the right position.self.panel=Panel2D(size=size,color=(1,1,1))# Add a scroll barscroll_bar_height=self.nb_slots*(size[1]-2*self.margin) \
/len(self.values)self.scroll_bar=Rectangle2D(size=(int(size[0]/20),scroll_bar_height))self.scroll_bar.color=(1,0,0)iflen(self.values)<=self.nb_slots:self.scroll_bar.set_visibility(False)self.panel.add_element(self.scroll_bar,size-self.scroll_bar.size-self.margin)# Initialisation of empty text actorsslot_width=size[0]-self.scroll_bar.size[0]- \
2*self.margin-self.marginx=self.marginy=size[1]-self.marginfor_inrange(self.nb_slots):y-=self.slot_heightitem=ListBoxItem2D(list_box=self,size=(slot_width,self.slot_height))item.textblock.font_size=font_sizeitem.textblock.color=(0,0,0)self.slots.append(item)self.panel.add_element(item,(x,y+self.margin))# Add default events listener for this UI component.self.scroll_bar.on_left_mouse_button_pressed= \
self.scroll_click_callbackself.scroll_bar.on_left_mouse_button_released= \
self.scroll_release_callbackself.scroll_bar.on_left_mouse_button_dragged= \
self.scroll_drag_callback# Handle mouse wheel events on the panel.up_event="MouseWheelForwardEvent"down_event="MouseWheelBackwardEvent"ifself.reverse_scrolling:up_event,down_event=down_event,up_event# Swap eventsself.add_callback(self.panel.background.actor,up_event,self.up_button_callback)self.add_callback(self.panel.background.actor,down_event,self.down_button_callback)# Handle mouse wheel events on the slots.forslotinself.slots:self.add_callback(slot.background.actor,up_event,self.up_button_callback)self.add_callback(slot.background.actor,down_event,self.down_button_callback)self.add_callback(slot.textblock.actor,up_event,self.up_button_callback)self.add_callback(slot.textblock.actor,down_event,self.down_button_callback)
def_get_actors(self):""" Get the actors composing this UI component. """returnself.panel.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.panel.add_to_scene(scene)def_get_size(self):returnself.panel.sizedef_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.panel.position=coords
[docs]defup_button_callback(self,i_ren,_obj,_list_box):""" Pressing up button scrolls up in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box: :class:`ListBox2D` """ifself.view_offset>0:self.view_offset-=1self.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]+self.scroll_step_size)self.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position))i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defdown_button_callback(self,i_ren,_obj,_list_box):""" Pressing down button scrolls down in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box: :class:`ListBox2D` """view_end=self.view_offset+self.nb_slotsifview_end<len(self.values):self.view_offset+=1self.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]-self.scroll_step_size)self.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position))i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defscroll_click_callback(self,i_ren,_obj,_rect_obj):""" Callback to change the color of the bar when it is clicked. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _rect_obj: :class:`Rectangle2D` """self.scroll_bar.color=self.scroll_bar_active_colorself.scroll_init_position=i_ren.event.position[1]i_ren.force_render()i_ren.event.abort()
[docs]defscroll_release_callback(self,i_ren,_obj,_rect_obj):""" Callback to change the color of the bar when it is released. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor rect_obj: :class:`Rectangle2D` """self.scroll_bar.color=self.scroll_bar_inactive_colori_ren.force_render()
[docs]defscroll_drag_callback(self,i_ren,_obj,_rect_obj):""" Dragging scroll bar in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor rect_obj: :class:`Rectangle2D` """position=i_ren.event.positionoffset=int((position[1]-self.scroll_init_position)/self.scroll_step_size)ifoffset>0andself.view_offset>0:offset=min(offset,self.view_offset)elifoffset<0and(self.view_offset+self.nb_slots<len(self.values)):offset=min(-offset,len(self.values)-self.nb_slots-self.view_offset)offset=-offsetelse:returnself.view_offset-=offsetself.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]+offset*self.scroll_step_size)self.scroll_init_position+=offset*self.scroll_step_sizeself.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position))i_ren.force_render()i_ren.event.abort()
[docs]defupdate(self):""" Refresh listbox's content. """view_start=self.view_offsetview_end=view_start+self.nb_slotsvalues_to_show=self.values[view_start:view_end]# Populate slots according to the view.fori,choiceinenumerate(values_to_show):slot=self.slots[i]slot.element=choiceslot.set_visibility(True)ifslot.elementinself.selected:slot.select()else:slot.deselect()# Flush remaining slots.forslotinself.slots[len(values_to_show):]:slot.element=Noneslot.set_visibility(False)slot.deselect()
[docs]defupdate_scrollbar(self):""" Change the scroll-bar height when the values in the listbox change """self.scroll_bar.set_visibility(True)self.scroll_bar.height=self.nb_slots* \
(self.panel_size[1]-2*self.margin)/len(self.values)self.scroll_step_size=(self.slot_height*self.nb_slots-self.scroll_bar.height) \
/(len(self.values)-self.nb_slots)self.panel.update_element(self.scroll_bar,self.panel_size-self.scroll_bar.size-self.margin)iflen(self.values)<=self.nb_slots:self.scroll_bar.set_visibility(False)
[docs]defselect(self,item,multiselect=False,range_select=False):""" Select the item. Parameters ---------- item: ListBoxItem2D's object Item to select. multiselect: {True, False} If True and multiselection is allowed, the item is added to the selection. Otherwise, the selection will only contain the provided item unless range_select is True. range_select: {True, False} If True and multiselection is allowed, all items between the last selected item and the current one will be added to the selection. Otherwise, the selection will only contain the provided item unless multi_select is True. """selection_idx=self.values.index(item.element)ifself.multiselectionandrange_select:self.clear_selection()step=1ifselection_idx>=self.last_selection_idxelse-1foriinrange(self.last_selection_idx,selection_idx+step,step):self.selected.append(self.values[i])elifself.multiselectionandmultiselect:ifitem.elementinself.selected:self.selected.remove(item.element)else:self.selected.append(item.element)self.last_selection_idx=selection_idxelse:self.clear_selection()self.selected.append(item.element)self.last_selection_idx=selection_idxself.on_change()# Call hook.self.update()
[docs]classListBoxItem2D(UI):""" The text displayed in a listbox. """
[docs]def__init__(self,list_box,size):""" Parameters ---------- list_box: :class:`ListBox` The ListBox reference this text belongs to. size: int The size of the listbox item. """super(ListBoxItem2D,self).__init__()self._element=Noneself.list_box=list_boxself.background.resize(size)self.selected=Falseself.deselect()
def_setup(self):""" Setup this UI component. Create the ListBoxItem2D with its background (Rectangle2D) and its label (TextBlock2D). """self.background=Rectangle2D()self.textblock=TextBlock2D(justification="left",vertical_justification="middle")# Add default events listener for this UI component.self.add_callback(self.textblock.actor,"LeftButtonPressEvent",self.left_button_clicked)self.add_callback(self.background.actor,"LeftButtonPressEvent",self.left_button_clicked)def_get_actors(self):""" Get the actors composing this UI component. """returnself.background.actors+self.textblock.actorsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.background.add_to_scene(scene)self.textblock.add_to_scene(scene)def_get_size(self):returnself.background.sizedef_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.textblock.position=coords# Center background underneath the text.position=coordsself.background.position=(position[0],position[1]-self.background.size[1]/2.)
[docs]defleft_button_clicked(self,i_ren,_obj,_list_box_item):""" A callback to handle left click for this UI element. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box_item: :class:`ListBoxItem2D` """multiselect=i_ren.event.ctrl_keyrange_select=i_ren.event.shift_keyself.list_box.select(self,multiselect,range_select)i_ren.force_render()
[docs]classFileMenu2D(UI):""" A menu to select files in the current folder. Can go to new folder, previous folder and select multiple files. Attributes ---------- extensions: ['extension1', 'extension2', ....] To show all files, extensions=["*"] or [""] List of extensions to be shown as files. listbox : :class: 'ListBox2D' Container for the menu. """
[docs]def__init__(self,directory_path,extensions=None,position=(0,0),size=(100,300),multiselection=True,reverse_scrolling=False,font_size=20,line_spacing=1.4):""" Parameters ---------- extensions: list(string) List of extensions to be shown as files. directory_path: string Path of the directory where this dialog should open. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of this UI component. size : (int, int) Width and height in pixels of this UI component. multiselection: {True, False} Whether multiple values can be selected at once. reverse_scrolling: {True, False} If True, scrolling up will move the list of files down. font_size: int The font size in pixels. line_spacing: float Distance between listbox's items in pixels. """self.font_size=font_sizeself.multiselection=multiselectionself.reverse_scrolling=reverse_scrollingself.line_spacing=line_spacingself.extensions=extensionsor["*"]self.current_directory=directory_pathself.menu_size=sizesuper(FileMenu2D,self).__init__()self.position=positionself.set_slot_colors()
def_setup(self):""" Setup this UI component. Create the ListBox (Panel2D) filled with empty slots (ListBoxItem2D). """self.directory_contents=self.get_all_file_names()content_names=[x[0]forxinself.directory_contents]self.listbox=ListBox2D(values=content_names,multiselection=self.multiselection,font_size=self.font_size,line_spacing=self.line_spacing,reverse_scrolling=self.reverse_scrolling,size=self.menu_size)self.add_callback(self.listbox.scroll_bar.actor,"MouseMoveEvent",self.scroll_callback)# Handle mouse wheel events on the panel.up_event="MouseWheelForwardEvent"down_event="MouseWheelBackwardEvent"ifself.reverse_scrolling:up_event,down_event=down_event,up_event# Swap eventsself.add_callback(self.listbox.panel.background.actor,up_event,self.scroll_callback)self.add_callback(self.listbox.panel.background.actor,down_event,self.scroll_callback)# Handle mouse wheel events on the slots.forslotinself.listbox.slots:self.add_callback(slot.background.actor,up_event,self.scroll_callback)self.add_callback(slot.background.actor,down_event,self.scroll_callback)self.add_callback(slot.textblock.actor,up_event,self.scroll_callback)self.add_callback(slot.textblock.actor,down_event,self.scroll_callback)slot.add_callback(slot.textblock.actor,"LeftButtonPressEvent",self.directory_click_callback)slot.add_callback(slot.background.actor,"LeftButtonPressEvent",self.directory_click_callback)def_get_actors(self):""" Get the actors composing this UI component. """returnself.listbox.actors
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.listbox.position=coordsdef_add_to_scene(self,scene):""" Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.listbox.add_to_scene(scene)def_get_size(self):returnself.listbox.size
[docs]defget_all_file_names(self):""" Gets file and directory names. Returns ------- all_file_names: list((string, {"directory", "file"})) List of all file and directory names as string. """all_file_names=[]directory_names=self.get_directory_names()fordirectory_nameindirectory_names:all_file_names.append((directory_name,"directory"))file_names=self.get_file_names()forfile_nameinfile_names:all_file_names.append((file_name,"file"))returnall_file_names
[docs]defget_directory_names(self):""" Finds names of all directories in the current_directory Returns ------- directory_names: list(string) List of all directory names as string. """# A list of directory names in the current directorydirectory_names=[]for(_,dirnames,_)inos.walk(self.current_directory):directory_names+=dirnamesbreakdirectory_names.sort(key=lambdas:s.lower())directory_names.insert(0,"../")returndirectory_names
[docs]defget_file_names(self):""" Finds names of all files in the current_directory Returns ------- file_names: list(string) List of all file names as string. """# A list of file names with extension in the current directoryfor(_,_,files)inos.walk(self.current_directory):breakfile_names=[]if"*"inself.extensionsor""inself.extensions:file_names=fileselse:forextinself.extensions:forfileinfiles:iffile.endswith("."+ext):file_names.append(file)file_names.sort(key=lambdas:s.lower())returnfile_names
[docs]defset_slot_colors(self):""" Sets the text color of the slots based on the type of element they show. Blue for directories and green for files. """foridx,slotinenumerate(self.listbox.slots):list_idx=min(self.listbox.view_offset+idx,len(self.directory_contents)-1)ifself.directory_contents[list_idx][1]=="directory":slot.textblock.color=(0,0.6,0)elifself.directory_contents[list_idx][1]=="file":slot.textblock.color=(0,0,0.7)
[docs]defscroll_callback(self,i_ren,_obj,_filemenu_item):""" A callback to handle scroll and change the slot text colors. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _filemenu_item: :class:`FileMenu2D` """self.set_slot_colors()i_ren.force_render()i_ren.event.abort()
[docs]defdirectory_click_callback(self,i_ren,_obj,listboxitem):""" A callback to move into a directory if it has been clicked. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor listboxitem: :class:`ListBoxItem2D` """if(listboxitem.element,"directory")inself.directory_contents:new_directory_path=os.path.join(self.current_directory,listboxitem.element)ifos.access(new_directory_path,os.R_OK):self.current_directory=new_directory_pathself.directory_contents=self.get_all_file_names()content_names=[x[0]forxinself.directory_contents]self.listbox.clear_selection()self.listbox.values=content_namesself.listbox.view_offset=0self.listbox.update()self.listbox.update_scrollbar()self.set_slot_colors()i_ren.force_render()i_ren.event.abort()
[docs]classGridUI(UI):""" Add actors in a grid and interact with them individually. """
[docs]def__init__(self,actors,captions=None,caption_offset=(0,-100,0),cell_padding=0,cell_shape="rect",aspect_ratio=16/9.,dim=None,rotation_speed=1,rotation_axis=(0,1,0)):# TODO: add rotation axis None by defaultself.container=grid(actors,captions=captions,caption_offset=caption_offset,cell_padding=cell_padding,cell_shape=cell_shape,aspect_ratio=aspect_ratio,dim=dim)self._actors=[]self._actors_dict={}self.rotation_speed=rotation_speedself.rotation_axis=rotation_axisforiteminself.container._items:self._actors.append(item._items[0])self._actors_dict[item._items[0]]={'x':-np.inf,'y':-np.inf}super(GridUI,self).__init__(position=(0,0,0))
def_setup(self):"""Set up this UI component and the events of its actor """# Add default events listener to the VTK actor.foractorinself._actors:# self.handle_events(actor)ifself.rotation_axisisNone:self.add_callback(actor,"LeftButtonPressEvent",self.left_click_callback)self.add_callback(actor,"LeftButtonReleaseEvent",self.left_release_callback)self.add_callback(actor,"MouseMoveEvent",self.mouse_move_callback)else:self.add_callback(actor,"LeftButtonPressEvent",self.left_click_callback2)# TODO: possibly add this tooself.add_callback(actor,"LeftButtonReleaseEvent",self.left_release_callback2)self.add_callback(actor,"MouseMoveEvent",self.mouse_move_callback2)# TODO: this is currently not runningself.add_callback(actor,"KeyPressEvent",self.key_press_callback)# self.on_key_press = self.key_press_callback2def_get_actors(self):"""Get the actors composing this UI component."""returnself._actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.container.add_to_scene(scene)
[docs]defresize(self,size):"""Resize the button. Parameters ---------- size : (float, float) Button size (width, height) in pixels. """# Update actor.pass
def_set_position(self,coords):""" Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# coords = (0, 0, 0)pass