Source code for underworld.visualisation.objects

##~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~##
##                                                                                   ##
##  This file forms part of the Underworld geophysics modelling application.         ##
##                                                                                   ##
##  For full license and copyright information, please refer to the LICENSE.md file  ##
##  located at the project root, or contact the authors.                             ##
##                                                                                   ##
##~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~##

"""
This module provides different drawing objects for visualisation in Underworld. 

"""

import underworld as _underworld
import underworld._stgermain as _stgermain
import underworld.swarm as _swarmMod
import underworld.mesh as _uwmesh
from underworld.function import Function as _Function
import underworld.libUnderworld as _libUnderworld
import json as _json

#TODO: Drawing Objects to implement
# HistoricalSwarmTrajectory
#
# Maybe later...
# SwarmShapes, SwarmRGB, SwarmVectors
# EigenVectors, EigenVectorCrossSection

[docs]class ColourMap(_stgermain.StgCompoundComponent): """ The ColourMap class provides functionality for mapping colours to numerical values. Parameters ---------- colours: str, list List of colours to use for drawing object colour map. Provided as a string or as a list of strings. Example, "red blue", or ["red", "blue"] valueRange: tuple, list User defined value range to apply to colour map. Provided as a tuple of floats (minValue, maxValue). If none is provided, the value range will be determined automatically. logScale: bool Bool to determine if the colourMap should use a logarithmic scale. discrete: bool Bool to determine if a discrete colour map should be used. Discrete colour maps do not interpolate between colours and instead use nearest neighbour for colouring. """ _selfObjectName = "_cm" _objectsDict = { "_cm": "lucColourMap" } def __init__(self, colours="diverge", valueRange=None, logScale=False, discrete=False, **kwargs): if not hasattr(self, "properties"): self.properties = {} if not isinstance(colours,(str,list,tuple)): raise TypeError("'colours' object passed in must be of python type 'str', 'list' or 'tuple'") self.properties.update({"colours" : _json.dumps(colours)}) #User-defined props in kwargs self.properties.update(kwargs) dict((k.lower(), v) for k, v in self.properties.items()) if valueRange != None: # is valueRange correctly defined, ie list of length 2 made of numbers if not isinstance( valueRange, (list,tuple)): raise TypeError("'valueRange' must be of type 'list' or 'tuple'") if len(valueRange) != 2: raise ValueError("'valueRange' must have 2 real values") for item in valueRange: if not isinstance( item, (int, float) ): raise TypeError("'valueRange' must contain real numbers") if not valueRange[0] < valueRange[1]: raise ValueError("The first number of the valueRange list must be smaller than the second number") # valueRange arg is good self.properties.update({"range" : [valueRange[0], valueRange[1]]}) else: self.properties.update({"range" : [0.0, 0.0]}) # ignored if not isinstance(logScale, bool): raise TypeError("'logScale' parameter must be of 'bool' type.") self._logScale = logScale self.properties.update({"logscale" : logScale}) if not isinstance(discrete, bool): raise TypeError("'discrete' parameter must be of 'bool' type.") self.properties.update({"discrete" : discrete}) # build parent super(ColourMap,self).__init__() def _add_to_stg_dict(self,componentDictionary): # call parents method super(ColourMap,self)._add_to_stg_dict(componentDictionary) #dict methods def update(self, newdict): self.properties.update(newdict) def __getitem__(self, key): return self.properties[key] def __setitem__(self, key, item): self.properties[key] = item def _getProperties(self): #Convert properties to string return '\n'.join(['%s=%s' % (k,v) for k,v in self.properties.items()]);
[docs]class Drawing(_stgermain.StgCompoundComponent): """ This is the base class for all drawing objects but can also be instantiated as is for direct/custom drawing. Note that the defaults here are often overridden by the child objects. Parameters ---------- colours: str, list. See ColourMap class docstring for further information colourMap: visualisation.objects.ColourMap A ColourMap object for the object to use. This should not be specified if 'colours' is specified. opacity: float Opacity of object. If provided, must take values from 0. to 1. colourBar: bool Bool to determine if a colour bar should be rendered. valueRange: tuple, list See ColourMap class docstring for further information logScale: bool See ColourMap class docstring for further information discrete: bool See ColourMap class docstring for further information """ _selfObjectName = "_dr" _objectsDict = { "_dr": "lucDrawingObject" } # child should replace _dr with own derived type def __init__(self, name=None, colours=None, colourMap="", colourBar=False, valueRange=None, logScale=False, discrete=False, *args, **kwargs): if not hasattr(self, "properties"): self.properties = {} if colours and colourMap: raise RuntimeError("You should specify 'colours' or a 'colourMap', but not both.") if colourMap: self._colourMap = colourMap elif colours: self._colourMap = ColourMap(colours=colours, valueRange=valueRange, logScale=logScale) elif colourMap is not None: self._colourMap = ColourMap(valueRange=valueRange, logScale=logScale) else: self._colourMap = None if not isinstance(discrete, bool): raise TypeError("'discrete' parameter must be of 'bool' type.") if discrete and self._colourMap: self._colourMap["discrete"] = True #User-defined props in kwargs self.properties.update(kwargs) dict((k.lower(), v) for k, v in self.properties.items()) if not isinstance(colourBar, bool): raise TypeError("'colourBar' parameter must be of 'bool' type.") self._colourBar = None if colourBar and self._colourMap: #Create the associated colour bar self._colourBar = ColourBar(colourMap=self._colourMap) if name: self.properties["name"] = str(name) self.resetDrawing() # build parent super(Drawing,self).__init__(*args) def _add_to_stg_dict(self,componentDictionary): # call parents method super(Drawing,self)._add_to_stg_dict(componentDictionary) # add an empty(ish) drawing object. children should fill it out. componentDictionary[self._dr.name].update( { "properties" :self._getProperties(), "ColourMap" :self._colourMap._cm.name if self._colourMap else None } ) #dict methods def update(self, newdict): self.properties.update(newdict) def __getitem__(self, key): return self.properties[key] def __setitem__(self, key, item): self.properties[key] = item def _getProperties(self): #Convert properties to string return '\n'.join(['%s=%s' % (k,v) for k,v in self.properties.items()]); def render(self, viewer): #Place any custom geometry output in this method, called after database creation #General purpose plotting via LavaVu #Plot all custom data drawn on provided object try: obj = viewer.objects[self.properties["name"]] if not obj: raise KeyError("Object not found") except KeyError as e: print(self.properties["name"] + " Object lookup error: " + str(e)) return obj["renderer"] = self.geomType output = False if len(self.vertices): obj.vertices(self.vertices) output = True if len(self.vectors): obj.vectors(self.vectors) output = True if len(self.scalars): obj.values(self.scalars) output = True if len(self.labels): obj.labels(self.labels) output = True #Update the database if output: viewer.app.update(obj.ref, True) def resetDrawing(self): #Clear direct drawing data self.vertices = [] self.vectors = [] self.scalars = [] self.labels = [] self.geomType = None #Direct drawing methods
[docs] def label(self, text, pos=(0.,0.,0.), font="sans", scaling=1): """ Writes a label string Parameters ---------- text: str label text. pos: tuple X,Y,Z position to place the label. font : str label font (small/fixed/sans/serif/vector). scaling : float label font scaling (for "vector" font only). """ self.geomType = "labels" self.vertices.append(pos) self.labels.append(text) self.properties.update({"font" : font, "fontscale" : scaling}) #Merge
[docs] def point(self, pos=(0.,0.,0.)): """ Draws a point Parameters ---------- pos : tuple X,Y,Z position to place the point """ self.geomType = "points" self.vertices.append(pos)
[docs] def line(self, start=(0.,0.,0.), end=(0.,0.,0.)): """ Draws a line Parameters ---------- start : tuple X,Y,Z position to start line end : tuple X,Y,Z position to end line """ self.geomType = "lines" self.vertices.append(start) self.vertices.append(end)
[docs] def vector(self, position=(0.,0.,0.), vector=(0.,0.,0.)): """ Draws a vector Parameters ---------- position : tuple X,Y,Z position to centre vector on vector : tuple X,Y,Z vector value """ self.geomType = "vectors" self.vertices.append(position) self.vectors.append(vector)
@property def colourBar(self): """ colourBar (object): return colour bar of drawing object, create if doesn't yet exist. """ if not self._colourBar: self._colourBar = ColourBar(colourMap=self._colourMap) return self._colourBar @property def colourMap(self): """ colourMap (object): return colour map of drawing object """ return self._colourMap def getdata(self, viewer, typename): #Experimental: grabbing data from a LavaVu object alldata = [] obj = viewer.objects[self.properties["name"]] if obj: #Force viewer open to trigger surface optimisation etc viewer.app.resetViews() #Get data elements list dataset = obj.data() for geom in dataset: #Grab a copy of the data data = geom.copy(typename) alldata.append(data) return alldata
[docs]class ColourBar(Drawing): """ The ColourBar drawing object draws a colour bar for the provided colour map. Parameters ---------- colourMap: visualisation.objects.ColourMap Colour map for which the colour bar will be drawn. """ def __init__(self, colourMap, *args, **kwargs): #Default properties self.properties = {"colourbar" : 1} # build parent super(ColourBar,self).__init__(colourMap=colourMap, *args, **kwargs)
[docs]class CrossSection(Drawing): """ This drawing object class defines a cross-section plane, derived classes plot data over this cross section See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which cross section is rendered. fn : underworld.function.Function Function used to determine values to render. crossSection : str Cross Section definition, eg. z=0. resolution : list(unsigned) Surface sampling resolution. onMesh : boolean Sample the mesh nodes directly, as opposed to sampling across a regular grid. This flag should be used in particular where a mesh has been deformed. """ _objectsDict = { "_dr": "lucCrossSection" } def __init__(self, mesh, fn, crossSection="", resolution=[100,100,1], colourBar=True, offsetEdges=None, onMesh=False, *args, **kwargs): self._onMesh = onMesh #Check the mesh has a valid vertGridId, if not then we can't use onMesh #(Invalid should be -1, but is read as unsigned (4294967295) from python for some reason # valid value should be small, so just treat any large integer as invalid) if mesh._mesh.vertGridId > 1000 or mesh._mesh.vertGridId < 0: self._onMesh = False self._fn = _underworld.function.Function.convert(fn) if not isinstance(mesh,_uwmesh.FeMesh): raise TypeError("'mesh' object passed in must be of type 'FeMesh'") self._mesh = mesh if not isinstance(crossSection,str): raise ValueError("'crossSection' parameter must be of python type 'str'") self._crossSection = crossSection self._offsetEdges = offsetEdges if not isinstance( resolution, (list,tuple)): if isinstance(resolution,int): resolution = [resolution,resolution,1] else: raise TypeError("'resolution' passed in must be of type 'int', 'list' or 'tuple'") for el in resolution: if not isinstance(el,int): raise TypeError("'resolution' elements must be of python type 'int'") if el < 1: raise ValueError("'resolution' elements must be greater than zero") self._resolution = resolution # build parent super(CrossSection,self).__init__(colourBar=colourBar, *args, **kwargs) def _setup(self): _libUnderworld.gLucifer._lucCrossSection_SetFn( self._cself, self._fn._fncself ) if self._offsetEdges != None: self._dr.offsetEdges = self._offsetEdges def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # call parents method super(CrossSection,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name].update( { "Mesh": self._mesh._cself.name, "crossSection": self._crossSection, "resolutionA" : self._resolution[0], "resolutionB" : self._resolution[1], "onMesh" : self._onMesh } ) @property def crossSection(self): """ crossSection (str): Cross Section definition, eg;: z=0. """ return self._crossSection
[docs]class Surface(CrossSection): """ This drawing object class draws a surface using the provided scalar field. See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which cross section is rendered. fn : underworld.function.Function Function used to determine values to render. drawSides : str Sides (x,y,z,X,Y,Z) for which the surface should be drawn. For example, "xyzXYZ" would render the provided function across all surfaces of the domain in 3D. In 2D, this object always renders across the entire domain. """ _objectsDict = { "_dr" : "lucScalarField" } def __init__(self, mesh, fn, drawSides="xyzXYZ", colourBar=True, onMesh=None, *args, **kwargs): if onMesh is None: #Default onMesh=True for faster drawing and accuracy #(Will be disabled in CrossSection if mesh does not support it) onMesh = True #Default onMesh=False if less than 64 nodes #(Smooth/better output on interpolated mesh for low res meshes) if mesh.nodesGlobal < 64 or "resolution" in kwargs: onMesh = False if not isinstance(drawSides,str): raise ValueError("'drawSides' parameter must be of python type 'str'") self._drawSides = drawSides # build parent super(Surface,self).__init__( mesh=mesh, fn=fn, colourBar=colourBar, onMesh=onMesh, *args, **kwargs ) #Merge with default properties is3d = len(self._crossSection) == 0 defaults = {"cullface" : is3d, "lit" : is3d} defaults.update(self.properties) self.properties = defaults def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # append random string to provided name to ensure unique component names # call parents method super(Surface,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name]["drawSides"] = self._drawSides def _setup(self): _libUnderworld.gLucifer._lucCrossSection_SetFn( self._cself, self._fn._fncself ) def __del__(self): super(Surface,self).__del__()
[docs]class Contours(CrossSection): """ This drawing object class draws contour lines in a cross section using the provided scalar field. See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which cross section is rendered. fn : underworld.function.Function Function used to determine values to render. labelFormat: str Format string (printf style) used to print a contour label, eg: " %g K" unitScaling: Scaling factor to apply to value when printing labels interval: float Interval between contour lines limits: tuple, list User defined minimum and maximum limits for the contours. Provided as a tuple/list of floats (minValue, maxValue). If none is provided, the limits will be determined automatically. """ _objectsDict = { "_dr" : "lucContourCrossSection" } def __init__(self, mesh, fn, labelFormat="", unitScaling=1.0, interval=0.33, limits=(0.0, 0.0), *args, **kwargs): if not isinstance(labelFormat,str): raise ValueError("'labelFormat' parameter must be of python type 'str'") self._labelFormat = labelFormat if not isinstance( unitScaling, (int, float) ): raise TypeError("'unitScaling' must contain a number") self._unitScaling = unitScaling if not isinstance( interval, (int, float) ): raise TypeError("'interval' must contain a number") self._interval = interval # is limits correctly defined, ie list of length 2 made of numbers if not isinstance( limits, (list,tuple)): raise TypeError("'limits' must be of type 'list' or 'tuple'") if len(limits) != 2: raise ValueError("'limits' must have 2 real values") for item in limits: if not isinstance( item, (int, float) ): raise TypeError("'limits' must contain real numbers") if not limits[0] <= limits[1]: raise ValueError("The first number of the limits list must be smaller than the second number") self._limits = limits # build parent super(Contours,self).__init__( mesh=mesh, fn=fn, *args, **kwargs) #Default properties self.properties.update({"lit" : False}) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # append random string to provided name to ensure unique component names # call parents method super(Contours,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name]["unitScaling"] = self._unitScaling componentDictionary[self._dr.name][ "interval"] = self._interval componentDictionary[self._dr.name]["minIsovalue"] = self._limits[0] componentDictionary[self._dr.name]["maxIsovalue"] = self._limits[1] def _setup(self): #Override dictionary setting, it seems to left-trim strings self._dr.labelFormat = self._labelFormat _libUnderworld.gLucifer._lucCrossSection_SetFn( self._cself, self._fn._fncself ) def __del__(self): super(Contours,self).__del__()
[docs]class Points(Drawing): """ This drawing object class draws a swarm of points. See parent class for further parameter details. Also see property docstrings. Parameters --------- swarm : underworld.swarm.Swarm Swarm which provides locations for point rendering. fn_colour : underworld.function.Function Function used to determine colour to render particle. This function should return float/double values. fn_mask : underworld.function.Function Function used to determine if a particle should be rendered. This function should return bool values. fn_size : underworld.function.Function Function used to determine size to render particle. This function should return float/double values. """ _objectsDict = { "_dr": "lucSwarmViewer" } def __init__(self, swarm, fn_colour=None, fn_mask=None, fn_size=None, colourVariable=None, colourBar=True, *args, **kwargs): if not isinstance(swarm,_swarmMod.Swarm): raise TypeError("'swarm' object passed in must be of type 'Swarm'") self._swarm = swarm self._fn_colour = None if fn_colour != None: self._fn_colour = _underworld.function.Function.convert(fn_colour) else: colourBar = False self._fn_mask = None if fn_mask != None: self._fn_mask = _underworld.function.Function.convert(fn_mask) self._fn_size = None if fn_size != None: self._fn_size = _underworld.function.Function.convert(fn_size) # build parent super(Points,self).__init__(colourBar=colourBar, *args, **kwargs) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary super(Points,self)._add_to_stg_dict(componentDictionary) componentDictionary[ self._cself.name ][ "Swarm" ] = self._swarm._cself.name def _setup(self): fnc_ptr = None if self._fn_colour: fnc_ptr = self._fn_colour._fncself fnm_ptr = None if self._fn_mask: fnm_ptr = self._fn_mask._fncself fns_ptr = None if self._fn_size: fns_ptr = self._fn_size._fncself _libUnderworld.gLucifer._lucSwarmViewer_SetFn( self._cself, fnc_ptr, fnm_ptr, fns_ptr, None )
class _GridSampler3D(CrossSection): """ This drawing object class samples a regular grid in 3D. resolution : list(unsigned) Number of samples in the I,J,K directions. """ _objectsDict = { "_dr": None } #Abstract class, Set by child def __init__(self, resolution=[16,16,16], *args, **kwargs): self._resolution = resolution # build parent super(_GridSampler3D,self).__init__(resolution=resolution, *args, **kwargs) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # call parents method super(_GridSampler3D,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name].update( { "resolutionX": self._resolution[0], "resolutionY": self._resolution[1], "resolutionZ": self._resolution[2] } )
[docs]class VectorArrows(_GridSampler3D): """ This drawing object class draws vector arrows corresponding to the provided vector field. See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which vector arrows are rendered. fn : underworld.function.Function Function used to determine vectors to render. Function should return a vector of floats/doubles of appropriate dimensionality. arrowHead : float The size of the head of the arrow. If > 1.0 is ratio to arrow radius If in range [0.,1.] is ratio to arrow length scaling : float Scaling for entire arrow. autoscale : bool Scaling based on field min/max glyphs : int Type of glyph to render for vector arrow. 0: Line, 1 or more: 3d arrow, higher number => better quality. resolution : list(unsigned) Number of samples in the I,J,K directions. """ _objectsDict = { "_dr": "lucVectorArrows" } def __init__(self, mesh, fn, resolution=[16, 16, 16], autoscale=True, *args, **kwargs): self._autoscale = autoscale # build parent super(VectorArrows,self).__init__( mesh=mesh, fn=fn, resolution=resolution, colours=None, colourMap=None, colourBar=False, autoscale=autoscale, *args, **kwargs) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # call parents method super(VectorArrows,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name].update( { "dynamicRange": self._autoscale } )
[docs]class Volume(_GridSampler3D): """ This drawing object class draws a volume using the provided scalar field. See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which object is rendered. fn : underworld.function.Function Function used to determine colour values. Function should return a vector of floats/doubles of appropriate dimensionality. resolution : list(unsigned) Number of samples in the I,J,K directions. """ _objectsDict = { "_dr": "lucFieldSampler" } def __init__(self, mesh, fn, resolution=[64,64,64], colourBar=True, *args, **kwargs): # build parent if mesh.dim == 2: raise ValueError("Volume rendering requires a three dimensional mesh.") super(Volume,self).__init__( mesh=mesh, fn=fn, resolution=resolution, colourBar=colourBar, *args, **kwargs) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # call parents method super(Volume,self)._add_to_stg_dict(componentDictionary)
[docs]class Sampler(Drawing): """ The Sampler class provides functionality for sampling a field at a number of provided vertices. Parameters ---------- vertices: list,array List of vertices to sample the field at, either a list or numpy array mesh : underworld.mesh.FeMesh Mesh over which the values are sampled fn : underworld.function.Function Function used to get the sampled values. """ _objectsDict = { "_dr": "lucSampler" } def __init__(self, mesh, fn, *args, **kwargs): if not isinstance(mesh,_uwmesh.FeMesh): raise TypeError("'mesh' object passed in must be of type 'FeMesh'") self._mesh = mesh self._fn = None if fn != None: self._fn = _underworld.function.Function.convert(fn) # build parent super(Sampler,self).__init__(*args, **kwargs) def _add_to_stg_dict(self,componentDictionary): # call parents method super(Sampler,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name]["Mesh"] = self._mesh._cself.name def _setup(self): fnc_ptr = None if self._fn: fn_ptr = self._fn._fncself _libUnderworld.gLucifer._lucSampler_SetFn( self._cself, fn_ptr ) def sample(self, vertices): sz = len(vertices)/3*self._cself.fieldComponentCount import numpy values = numpy.zeros(sz, dtype='float32') _libUnderworld.gLucifer.lucSampler_SampleField( self._cself, vertices, values) return values
[docs]class IsoSurface(Volume): """ This drawing object class draws an isosurface using the provided scalar field. See parent class for further parameter details. Also see property docstrings. Parameters --------- mesh : underworld.mesh.FeMesh Mesh over which object is rendered. fn : underworld.function.Function Function used to determine surface position. Function should return a vector of floats/doubles. fn_colour : underworld.function.Function Function used to determine colour of surface. resolution : list(unsigned) Number of samples in the I,J,K directions. isovalue : float Isovalue to plot. isovalues : list of float List of multiple isovalues to plot. """ def __init__(self, mesh, fn, fn_colour=None, resolution=[64,64,64], colourBar=True, isovalue=None, *args, **kwargs): # build parent if mesh.dim == 2: raise ValueError("Isosurface requires a three dimensional mesh.") # Validate isovalue(s) params if not "isovalues" in kwargs: if isovalue is None: raise ValueError("Isosurface requires either 'isovalue' value or 'isovalues' list parameter.") kwargs["isovalues"] = [isovalue] self._sampler = None if fn_colour != None: self._sampler = Sampler(mesh, fn_colour) super(IsoSurface,self).__init__( mesh=mesh, fn=fn, resolution=resolution, colourBar=colourBar, *args, **kwargs) self.geomType = "triangles" def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # call parents method super(IsoSurface,self)._add_to_stg_dict(componentDictionary) def _setup(self): if self._sampler: self._sampler._setup() def render(self, viewer): # FieldSampler has exported a 3d volume to the database, # now we can use LavaVu to generate isosurface triangles isobj = viewer.objects[self.properties["name"]] if isobj: #Force viewer open to trigger surface optimisation viewer.app.resetViews() #Generate isosurface in same object, convert and delete volume, update db isobj.isosurface(name=None, convert=True, updatedb=True) else: print("Object not found: " + self.properties["name"]) def parallel_render(self, viewer, rank): #If this method defined, is run by all procs to process # any render output that must be done in parallel isobj = viewer.objects[self.properties["name"]] if isobj and self._sampler: #If coloured by another field, get the vertices, sample and load values #Clear existing values isobj.cleardata() #Get data elements list dataset = isobj.data() for geom in dataset: #Grab a view of the vertex data verts = geom.get("vertices") if len(verts): #Sample over tri vertices values = self._sampler.sample(verts) #Update data on root if rank == 0: #Update element with the sampled data values geom.set("sampledfield", values) #Write the colour data back to db on root if rank == 0: isobj.update("triangles")
[docs]class Mesh(Drawing): """ This drawing object class draws a mesh. See parent class for further parameter details. Also see property docstrings. Parameters ---------- mesh : underworld.mesh.FeMesh Mesh to render. nodeNumbers : bool Bool to determine whether global node numbers should be rendered. segmentsPerEdge : unsigned Number of segments to render per cell/element edge. For higher order mesh, more segments are useful to render mesh curvature correctly. """ _objectsDict = { "_dr": "lucMeshViewer" } def __init__( self, mesh, nodeNumbers=False, segmentsPerEdge=1, *args, **kwargs ): if not isinstance(mesh,_uwmesh.FeMesh): raise TypeError("'mesh' object passed in must be of type 'FeMesh'") self._mesh = mesh if not isinstance(nodeNumbers,bool): raise TypeError("'nodeNumbers' flag must be of type 'bool'") self._nodeNumbers = nodeNumbers if not isinstance(segmentsPerEdge,int) or segmentsPerEdge < 1: raise TypeError("'segmentsPerEdge' must be a positive 'int'") self._segmentsPerEdge = segmentsPerEdge #Default properties self.properties = {"lit" : False, "font" : "small", "fontscale" : 0.5, "pointsize" : 5 if self._nodeNumbers else 1, "pointtype" : 2 if self._nodeNumbers else 4} # build parent super(Mesh,self).__init__( colourMap=None, colourBar=False, *args, **kwargs ) def _add_to_stg_dict(self,componentDictionary): # lets build up component dictionary # append random string to provided name to ensure unique component names # call parents method super(Mesh,self)._add_to_stg_dict(componentDictionary) componentDictionary[self._dr.name][ "Mesh"] = self._mesh._cself.name componentDictionary[self._dr.name]["nodeNumbers"] = self._nodeNumbers componentDictionary[self._dr.name][ "segments"] = self._segmentsPerEdge