5
$\begingroup$

How can I get a graph legend that automatically gets my maximum and minimum elevation from an object? Basically, I have an object that dinamically paints other with waves. I have a node setup that gives me color by relative height. I want to retrieve now the maximum and minimum absolute values of height that the waves are at current frame. I need to know how to build this http://lroc.sese.asu.edu/news/uploads/WAC_color_medium.4.colorbar.png that adapts height values from current time. this is my current situation: height_values the following blend file has my current code. Please fill free to change or adapt as you need/want to make it work.

$\endgroup$

1 Answer 1

6
+150
$\begingroup$

This script will generate a legend plane with text on the 2nd layer.

enter image description here

This script assumes the terrain object's name is "Plane". To change it, change the object's name in line 7:

obj = bpy.data.objects['Plane'] # Reference terrain object

The color ramp was generated according to the example in the question above (3 swatches with pure green, blue and red in that order). Anyway, the script takes the colors from the original terrain object's color ramp.

It also generates 7 rungs to the legend's ladder according to the provided example. To change this, find the rungs variable i the script and change its value.

import bpy
from math import radians

def update_legend( scene ):
    C = bpy.context

    ## Get global height coordinates of terrain vertices
    obj = bpy.data.objects['Plane'] # Reference terrain object
    m = obj.to_mesh( C.scene, True, 'RENDER' )

    offset  = 0.5 # The value placed in the math.add node
    heights = [ v.co.z + offset for v in m.vertices ]

    minZ = min( heights )
    maxZ = max( heights )

    ## Generate legend color bar

    legendPlane = None
    if 'legend' in bpy.data.objects:
        legendPlane = bpy.data.objects[ 'legend' ]
    else:
        # Add legened plane on layer 2
        bpy.ops.mesh.primitive_plane_add( layers = [ i == 1 for i in range(20) ] )

        legendPlane = bpy.data.objects[ C.object.name ]
        legendPlane.name = 'legend'
        legendPlane.dimensions.y = 20

        bpy.ops.object.transform_apply( scale = True ) # Apply scale (dim change)

        # Setup legend material
        cr = obj.active_material.node_tree.nodes['ColorRamp']

        legendMat = None
        if "legend" not in bpy.data.materials:
            legendMat = bpy.data.materials.new("legend")

            legendMat.use_nodes = True
            t = legendMat.node_tree

            t.nodes.remove( t.nodes['Diffuse BSDF'] )

            emit = t.nodes.new( 'ShaderNodeEmission' )
            mo   = t.nodes['Material Output']

            # Link Emission to Material Output
            t.links.new( emit.outputs['Emission'], mo.inputs['Surface'] )

            # Generate new color ramp and link to Emission node
            colorRamp = t.nodes.new( 'ShaderNodeValToRGB' )
            t.links.new( emit.inputs['Color'], colorRamp.outputs['Color'] )

            # Add another color swatch in the middle of the ramp
            colorRamp.color_ramp.elements.new( 0.5 )

            # Set color ramp colors
            originalRampColors = obj.active_material.node_tree.nodes['ColorRamp'].color_ramp.elements[:]
            for i, c in enumerate( originalRampColors ):
                color = c.color[:]
                colorRamp.color_ramp.elements[i].color = color

            # Add Texture coordiantes and Mapping nodes
            mapp  = t.nodes.new( 'ShaderNodeMapping'  )
            texco = t.nodes.new( 'ShaderNodeTexCoord' )

            # Set up texture coordiantes Y rotation
            mapp.rotation[1] = radians( 45 )

            # Set up links
            t.links.new( mapp.inputs['Vector'],   texco.outputs['Object'] )
            t.links.new( colorRamp.inputs['Fac'], mapp.outputs['Vector'] )

        else:
            legendMat = bpy.data.materials["legend"]

        legendPlane.active_material = legendMat

    ## Generate legend text

    legendTextMat = None
    if 'legendText' in bpy.data.materials:
        legendTextMat = bpy.data.materials["legendText"]
    else:
        # Generate a material for the text
        legendTextMat = bpy.data.materials.new( "legendText" )

        legendTextMat.use_nodes = True
        t = legendTextMat.node_tree
        if 'Diffuse BSDF' in t.nodes: 
            t.nodes.remove( t.nodes['Diffuse BSDF'] )

        emit = t.nodes.new( 'ShaderNodeEmission' )
        mo   = t.nodes['Material Output'] if 'Material Output' in t.nodes else t.nodes.new( 'ShaderNodeOutputMaterial' )
        t.links.new( emit.outputs['Emission'], mo.inputs['Surface'] )
        emit.inputs['Color'].default_value = (0,0,0,1) # Set color to black

    # Calculate text positions
    heightRange = maxZ - minZ
    rungs       = 7
    interval    = heightRange / rungs

    ladderText = [ str( round( minZ + i * interval - offset, 2 ) ) for i in range(2, rungs) ]
    ladderText = [ str( round( minZ - offset, 2 ) ) ] + ladderText + [ str( round( maxZ - offset, 2 ) ) ] # Add min and max Z values

    legendGlobCoo = [ legendPlane.matrix_world * v.co for v in legendPlane.data.vertices ]
    legendY = [ co.y for co in legendGlobCoo ]

    minY, maxY = ( min( legendY ), max( legendY ) )
    yInterval  = ( maxY - minY ) / rungs
    yPositions = [ minY + i * yInterval for i in range( rungs ) ]

    # Set X position as legend plane's maximum X + a space of 0.5 blender units
    xPos = max([ co.x for co in legendGlobCoo ]) + 0.5

    for i, yPos, text in zip( range(rungs), yPositions, ladderText ):
        name = "rung%s" % i

        o = None
        if name in bpy.data.objects:
            o = bpy.data.objects[ name ]
        else:
            bpy.ops.object.text_add( location = ( xPos, yPos, 0 ), layers = [ i == 1 for i in range(20) ] )
            o = bpy.data.objects[ bpy.context.object.name ]
            o.name = name
            o.location.y += i * ( o.dimensions.y / 2 )

        o.data.body = text

        o.active_material = legendTextMat

bpy.app.handlers.frame_change_pre.append( update_legend )

EDITED:

Fixed the issue that occurred when running this script with a modified mesh (in this case with dynamic paint), by converting the modified object to a mesh and using its coordinates. Here's an image of the legend generated at frame 110 of your simulation:

EDITED2:

Now updates values according to current frame via an app_handler.

EDITED3:

corrected line 128 > range(1,rungs) to range(2,rungs), so legend of scale bar is correct

$\endgroup$
14
  • $\begingroup$ I gave it a try, but when I press play, it doesn't give me the max height of the plane for the current frame. It has the same problem that I have with my script, only updates the info, if I apply the dynamic paint modifier. I'll vote +1 because I like the way that instantly makes the scale bar but I will not accept as final answer. $\endgroup$
    – MCunha
    Oct 27, 2016 at 9:53
  • 1
    $\begingroup$ Fixed the problem @MCunha, it required recalculating the modified mesh with the object.to_mesh() method (line 9). $\endgroup$
    – TLousky
    Oct 27, 2016 at 11:00
  • 1
    $\begingroup$ It doesn't update automatically, you need to re-run the script. To make it update automatically it should be possible to add this to an app handler (but that might not be a good idea). blender.stackexchange.com/questions/18463/… $\endgroup$
    – TLousky
    Oct 27, 2016 at 11:42
  • 1
    $\begingroup$ It works like a charm! thank you so much! You, now, are like a God to me! You don't know how I appreciate this! Many many thanks! $\endgroup$
    – MCunha
    Oct 27, 2016 at 11:50
  • 1
    $\begingroup$ Sure man :-D Nothing divine here, just some python :) $\endgroup$
    – TLousky
    Oct 27, 2016 at 11:52

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .