"""UI core module that describe UI abstract class."""importabcimportnumpyasnpfromfury.decoratorsimportwarn_on_args_to_kwargsfromfury.interactorimportCustomInteractorStylefromfury.ioimportload_imagefromfury.libimport(Actor2D,CellArray,DiskSource,FloatArray,Points,PolyData,PolyDataMapper2D,Polygon,Property2D,TextActor,Texture,TexturedActor2D,)fromfury.utilsimportset_input
[docs]classUI(object,metaclass=abc.ABCMeta):"""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_double_clicked: function Callback function for when left mouse button is double clicked (i.e pressed -> released -> 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_double_clicked: function Callback function for when right mouse button is double clicked (i.e pressed -> released -> 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_double_clicked: function Callback function for when middle mouse button is double clicked (i.e pressed -> released -> 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. """@warn_on_args_to_kwargs()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_left_mouse_double_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_double_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_double_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])iflen(callback)>3:iren.add_callback(*callback[:3],priority=callback[3],args=[self])
[docs]@warn_on_args_to_kwargs()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.0@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.0self.position=new_lower_left_corner
[docs]defset_visibility(self,visibility):"""Set visibility of this UI component."""foractorinself.actors:actor.SetVisibility(visibility)
[docs]classRectangle2D(UI):"""A 2D rectangle sub-classed from UI."""@warn_on_args_to_kwargs()def__init__(self,*,size=(0,0),position=(0,0),color=(1,1,1),opacity=1.0):"""Initialize 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=position)self.color=colorself.opacity=opacityself.resize(size)def_setup(self):"""Set up this UI component. Creating the polygon actor used internally. """# Setup four pointssize=(1,1)self._points=Points()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=Polygon()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=CellArray()polygons.InsertNextCell(polygon)# Create a PolyDataself._polygonPolyData=PolyData()self._polygonPolyData.SetPoints(self._points)self._polygonPolyData.SetPolys(polygons)# Create a mapper and actormapper=PolyDataMapper2D()mapper=set_input(mapper,self._polygonPolyData)self.actor=Actor2D()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):"""Set 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=PolyDataMapper2D()mapper=set_input(mapper,self._polygonPolyData)self.actor.SetMapper(mapper)
def_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.actor.SetPosition(*coords)@propertydefcolor(self):"""Get the rectangle's color."""color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):"""Set the rectangle's color. Parameters ---------- color : (float, float, float) RGB. Must take values in [0, 1]. """self.actor.GetProperty().SetColor(*color)@propertydefopacity(self):"""Get the rectangle's opacity."""returnself.actor.GetProperty().GetOpacity()@opacity.setterdefopacity(self,opacity):"""Set 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."""@warn_on_args_to_kwargs()def__init__(self,outer_radius,*,inner_radius=0,center=(0,0),color=(1,1,1),opacity=1.0,):"""Initialize a 2D Disk. 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=centerdef_setup(self):"""Setup this UI component. Creating the disk actor used internally. """# Setting up disk actor.self._disk=DiskSource()self._disk.SetRadialResolution(10)self._disk.SetCircumferentialResolution(50)self._disk.Update()# Mappermapper=PolyDataMapper2D()mapper=set_input(mapper,self._disk.GetOutputPort())# Actorself.actor=Actor2D()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):"""Set the lower-left corner position of this UI 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):"""Get the color of this UI component."""color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):"""Set the color of this UI component. Parameters ---------- color : (float, float, float) RGB. Must take values in [0, 1]. """self.actor.GetProperty().SetColor(*color)@propertydefopacity(self):"""Get the opacity of this UI component."""returnself.actor.GetProperty().GetOpacity()@opacity.setterdefopacity(self,opacity):"""Set the opacity of this UI component. 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]classTextBlock2D(UI):"""Wrap 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. size : (int, int) Size (width, height) in pixels of the text bounding box. auto_font_scale : bool Automatically scale font according to the text bounding box. dynamic_bbox : bool Automatically resize the bounding box according to the content. """@warn_on_args_to_kwargs()def__init__(self,*,text="Text Block",font_size=18,font_family="Arial",justification="left",vertical_justification="bottom",bold=False,italic=False,shadow=False,size=None,color=(1,1,1),bg_color=None,position=(0,0),auto_font_scale=False,dynamic_bbox=False,):"""Init class instance. 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. size : (int, int) Size (width, height) in pixels of the text bounding box. auto_font_scale : bool, optional Automatically scale font according to the text bounding box. dynamic_bbox : bool, optional Automatically resize the bounding box according to the content. """self.boundingbox=[0,0,0,0]super(TextBlock2D,self).__init__(position=position)self.scene=Noneself.have_bg=bool(bg_color)self.color=colorself.background_color=bg_colorself.font_family=font_familyself._justification=justificationself.bold=boldself.italic=italicself.shadow=shadowself._vertical_justification=vertical_justificationself._dynamic_bbox=dynamic_bboxself.auto_font_scale=auto_font_scaleself.message=textself.font_size=font_sizeifsizeisnotNone:self.resize(size)elifnotself.dynamic_bbox:# raise ValueError("TextBlock size is required as it is not dynamic.")self.resize((0,0))def_setup(self):self.actor=TextActor()self.actor.GetPosition2Coordinate().SetCoordinateSystemToViewport()self.background=Rectangle2D()self.handle_events(self.actor)
[docs]defresize(self,size):"""Resize TextBlock2D. Parameters ---------- size : (int, int) Text bounding box size(width, height) in pixels. """self.update_bounding_box(size=size)
def_get_actors(self):"""Get the actors composing this UI component."""return[self.actor]+self.background.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """scene.add(self.background,self.actor)@propertydefmessage(self):"""Get message from the text. Returns ------- str The current text message. """returnself.actor.GetInput()@message.setterdefmessage(self,text):"""Set the text message. Parameters ---------- text : str The message to be set. """self.actor.SetInput(text)ifself.dynamic_bbox:self.update_bounding_box()@propertydeffont_size(self):"""Get text font size. Returns ------- int Text font size. """returnself.actor.GetTextProperty().GetFontSize()@font_size.setterdeffont_size(self,size):"""Set font size. Parameters ---------- size : int Text font size. """ifnotself.auto_font_scale:self.actor.SetTextScaleModeToNone()self.actor.GetTextProperty().SetFontSize(size)ifself.dynamic_bbox:self.update_bounding_box()@propertydeffont_family(self):"""Get font family. Returns ------- str Text font family. """returnself.actor.GetTextProperty().GetFontFamilyAsString()@font_family.setterdeffont_family(self,family="Arial"):"""Set 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):"""Get text justification. Returns ------- str Text justification. """returnself._justification@justification.setterdefjustification(self,justification):"""Justify text. Parameters ---------- justification : str Possible values are left, right, center. """self._justification=justificationself.update_alignment()@propertydefvertical_justification(self):"""Get text vertical justification. Returns ------- str Text vertical justification. """returnself._vertical_justification@vertical_justification.setterdefvertical_justification(self,vertical_justification):"""Justify text vertically. Parameters ---------- vertical_justification : str Possible values are bottom, middle, top. """self._vertical_justification=vertical_justificationself.update_alignment()@propertydefbold(self):"""Return whether the text is bold. Returns ------- bool Text is bold if True. """returnself.actor.GetTextProperty().GetBold()@bold.setterdefbold(self,flag):"""Bold/un-bold text. Parameters ---------- flag : bool Sets text bold if True. """self.actor.GetTextProperty().SetBold(flag)@propertydefitalic(self):"""Return whether the text is italicised. Returns ------- bool Text is italicised if True. """returnself.actor.GetTextProperty().GetItalic()@italic.setterdefitalic(self,flag):"""Italicise/un-italicise text. Parameters ---------- flag : bool Italicises text if True. """self.actor.GetTextProperty().SetItalic(flag)@propertydefshadow(self):"""Return whether the text has shadow. Returns ------- bool Text is shadowed if True. """returnself.actor.GetTextProperty().GetShadow()@shadow.setterdefshadow(self,flag):"""Add/remove text shadow. Parameters ---------- flag : bool Shadows text if True. """self.actor.GetTextProperty().SetShadow(flag)@propertydefcolor(self):"""Get 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):"""Get background color. Returns ------- (float, float, float) or None If None, there no background color. Otherwise, background color in RGB. """ifnotself.have_bg:returnNonereturnself.background.color@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.have_bg=Falseself.background.set_visibility(False)else:self.have_bg=Trueself.background.set_visibility(True)self.background.color=color@propertydefauto_font_scale(self):"""Return whether text font is automatically scaled. Returns ------- bool Text is auto_font_scaled if True. """returnself._auto_font_scale@auto_font_scale.setterdefauto_font_scale(self,flag):"""Add/remove text auto_font_scale. Parameters ---------- flag : bool Automatically scales the text font if True. """self._auto_font_scale=flagifflag:self.actor.SetTextScaleModeToProp()self._justification="left"self.update_bounding_box(size=self.size)else:self.actor.SetTextScaleModeToNone()@propertydefdynamic_bbox(self):"""Automatically resize the bounding box according to the content. Returns ------- bool Bounding box is dynamic if True. """returnself._dynamic_bbox@dynamic_bbox.setterdefdynamic_bbox(self,flag):"""Add/remove dynamic_bbox. Parameters ---------- flag : bool The text bounding box is dynamic if True. """self._dynamic_bbox=flagifflag:self.update_bounding_box()
[docs]defupdate_alignment(self):"""Update Text Alignment."""text_property=self.actor.GetTextProperty()updated_text_position=[0,0]ifself.justification.lower()=="left":text_property.SetJustificationToLeft()updated_text_position[0]=self.boundingbox[0]elifself.justification.lower()=="center":text_property.SetJustificationToCentered()updated_text_position[0]=(self.boundingbox[0]+(self.boundingbox[2]-self.boundingbox[0])//2)elifself.justification.lower()=="right":text_property.SetJustificationToRight()updated_text_position[0]=self.boundingbox[2]else:msg="Text can only be justified left, right and center."raiseValueError(msg)ifself.vertical_justification.lower()=="bottom":text_property.SetVerticalJustificationToBottom()updated_text_position[1]=self.boundingbox[1]elifself.vertical_justification.lower()=="middle":text_property.SetVerticalJustificationToCentered()updated_text_position[1]=(self.boundingbox[1]+(self.boundingbox[3]-self.boundingbox[1])//2)elifself.vertical_justification.lower()=="top":text_property.SetVerticalJustificationToTop()updated_text_position[1]=self.boundingbox[3]else:msg="Vertical justification must be: bottom, middle or top."raiseValueError(msg)self.actor.SetPosition(updated_text_position)
[docs]defcal_size_from_message(self):"""Calculate size of background according to the message it contains."""lines=self.message.split("\n")max_length=max(len(line)forlineinlines)return[max_length*self.font_size,len(lines)*self.font_size]
[docs]@warn_on_args_to_kwargs()defupdate_bounding_box(self,*,size=None):"""Update Text Bounding Box. Parameters ---------- size : (int, int) or None If None, calculates bounding box. Otherwise, uses the given size. """ifsizeisNone:size=self.cal_size_from_message()self.boundingbox=[self.position[0],self.position[1],self.position[0]+size[0],self.position[1]+size[1],]self.background.resize(size)ifself.auto_font_scale:self.actor.SetPosition2(self.boundingbox[2]-self.boundingbox[0],self.boundingbox[3]-self.boundingbox[1],)else:self.update_alignment()
def_set_position(self,position):"""Set text actor position. Parameters ---------- position : (float, float) The new position. (x, y) in pixels. """self.actor.SetPosition(*position)self.background.position=positiondef_get_size(self):bb_size=(self.boundingbox[2]-self.boundingbox[0],self.boundingbox[3]-self.boundingbox[1],)ifself.dynamic_bboxorself.auto_font_scaleorsum(bb_size):returnbb_sizereturnself.cal_size_from_message()
[docs]classButton2D(UI):"""A 2D overlay button and is of type vtkTexturedActor2D. Currently supports:: - Multiple icons. - Switching between icons. """@warn_on_args_to_kwargs()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=position)self.icon_extents={}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 ImageData. 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 ImageData. """icons=[]foricon_name,icon_fnameinicon_fnames:icons.append((icon_name,load_image(icon_fname,as_vtktype=True)))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=PolyData()self.texture_points=Points()self.texture_points.SetNumberOfPoints(4)polys=CellArray()polys.InsertNextCell(4)polys.InsertCellPoint(0)polys.InsertCellPoint(1)polys.InsertCellPoint(2)polys.InsertCellPoint(3)self.texture_polydata.SetPolys(polys)tc=FloatArray()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=PolyDataMapper2D()texture_mapper=set_input(texture_mapper,self.texture_polydata)button=TexturedActor2D()button.SetMapper(texture_mapper)self.texture=Texture()button.SetTexture(self.texture)button_property=Property2D()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):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.actor.SetPosition(*coords)@propertydefcolor(self):"""Get the button's color."""color=self.actor.GetProperty().GetColor()returnnp.asarray(color)@color.setterdefcolor(self,color):"""Set 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):"""Scale 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):"""Modify the icon used by the vtkTexturedActor2D. Parameters ---------- icon : imageData """self.texture=set_input(self.texture,icon)
[docs]defnext_icon_id(self):"""Set 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):"""Increment the state of the Button. Also changes the icon. """self.next_icon_id()self.set_icon(self.icons[self.current_icon_id][1])