14
$\begingroup$

While gnuplot is serviceable and matplotlib is a popular choice for Python developers, I wanted to use Python for plotting some simple graphs (matplotlib had some issues installing on my system, with one of its dependencies).

While I could spend time to fix the issues with matplotlib, my needs are quite basic and something Blender could be made to do without much trouble.

Are there some existing solutions to setup Blender for real-time, graph plotting?
(which could be interesting to adjust input variables with real-time feedback for example).

$\endgroup$
  • $\begingroup$ While I admit that this question is within the scope of Blender Stack Exchange, I think the person most apt to be able to answer would be someone working in Mathematics, who might be more likely to be found on Mathematics stack Exchange, or perhaps on the Computer Graphics stack exchange site. Also, you might find help installing Pycairo on the stackoverflow site. $\endgroup$ – brasshat Feb 23 '16 at 9:16
9
$\begingroup$

Since asking this question, I've made a small (single script) plotting utility.

This could be made into an add-on however I find it useful as-is.

plot example

Usage:

  • Paste this script into a text-block named blend_plot.py and press "Run Script".
  • Running the script will create a blend_plot_func.py text-block containing an initial plotting template (if you don't have one already). eg:

    def plot(x, **kw):
        return x
    

    You could change this to, for example:

    def plot(x, **kw):
        return 1 - ((1 - x) ** 2)
    
  • Playback animation (a curve object will be automatically created if you don't have one active already).

  • You can duplicate the object and move it around, only visible objects are handled, so hiding or moving to disabled layers can avoid extra computations.
  • If you want to change blend_plot.py (curve resolution for eg). Simply edit the values and run the script again.
  • You may want to turn your scene's FPS down, anywhere from 5 to 15 FPS (to reduce CPU usage).

Some benefits of this method:

  • Easy to view and change parameters for multiple plots at once (overlay or side-by-side, objects can be moved around since the script only updates object-data).
  • Easy drag values with real-time updates as well as using Blender's animation system.

Other details can be found in the code header.

# All in one plotting utility.
#
# - Uses a frame-change handler
#  (play back animation for real-time updates as you type)
# - Expects module `blend_plot_func.py`, to have a function called `plot`
#   which takes and returns a float and keyword arguments,
#   which are taken from custom properties.
# - Will update all objects in the scene
#   (supports multiple plots at the same time).
# - Scene and Object custom properties,
#   are passed to the plot function as keyword arguments.
#   (Scene properties are like `global` variables,
#   Object properties can be different for every object).


# Curve resolution
RESOLUTION = 320
# Range to show
RANGE = -1.0, 2.0 
# RANGE = 0.0, 1.0

# Framing
MIN = 0.0, 0.0
MAX = 1.0, 1.0

PLOT_MODULE = "blend_plot_func"
PLOT_TEMPLATE = """
# Plot module template
from math import *

def plot(x, **kw):
    return x
"""

# End constants
# -------------


import bpy
from importlib import reload
from math import isfinite

def reinit_curve(context, curve):
    """ Initialize or re-initialize curve data.
    """
    curve.splines.clear()

    curve.fill_mode = 'NONE'

    # plot spline
    spline = curve.splines.new(type='POLY')
    spline.points.add(RESOLUTION)

    # frame spline
    spline = curve.splines.new(type='POLY')
    spline.points.add(3)
    spline.points[0].co.xy = MIN[0], MIN[1]
    spline.points[1].co.xy = MIN[0], MAX[1]
    spline.points[2].co.xy = MAX[0], MAX[1]
    spline.points[3].co.xy = MAX[0], MIN[1]
    spline.use_cyclic_u = True


def init_object(context):
    """ Call when we have no active plot-curve.
    """
    curve = bpy.data.curves.new(name="Plot Curve", type='CURVE')
    reinit_curve(context, curve)

    obj = bpy.data.objects.new(name="Plot Curve", object_data=curve)
    context.scene.objects.link(obj)
    context.scene.objects.active = obj
    obj.select = True

    return obj


def plot_callback_object(scene, obj, plot_cb, kw):
    from bpy import context

    curve = obj.data
    points = curve.splines[0].points if curve.splines else ()

    # re-initialize curve if we change settings
    if len(points) != RESOLUTION:
        reinit_curve(context, curve)
        points = curve.splines[0].points
    # print(kw)

    i_last = float(RESOLUTION - 1)

    span = RANGE[1] - RANGE[0]

    for i, p in enumerate(points):
        x = i / i_last
        x = (x * span + RANGE[0])
        try:
            y = plot_cb(x, **kw)
            if not isfinite(y):
                y = -1.0
        except Exception as ex:
            print(str(ex))
            y = -1.0

        p.co.xy = x, y


def plot_callback(scene):
    """ Runs on frame change, updates all objects in the scene.
    """
    try:
        plot_module = __import__(PLOT_MODULE)
    except ImportError as ex:
        # Create a new text block if this is the first execution.
        text_name = PLOT_MODULE + ".py"
        text = bpy.data.texts.get(text_name)
        if text is None:
            text = bpy.data.texts.new(name=text_name)
            text.from_string(PLOT_TEMPLATE)
        else:
            raise ex


    reload(plot_module)
    plot_cb = plot_module.plot

    from bpy import context
    obj = context.active_object
    if obj is None:
        obj = init_object(context)

    # use my keywords for all!
    kw_global = dict(scene.items())

    for obj in context.visible_objects:
        if obj.type == 'CURVE':
            kw_local = dict(obj.items())
            kw_copy = kw_global.copy()
            kw_copy.update(kw_local)
            plot_callback_object(context, obj, plot_cb, kw_copy)


bpy.app.handlers.frame_change_pre[:] = [plot_callback]
$\endgroup$
  • 4
    $\begingroup$ By rendering it in freestyle I think you could make very pretty graphs actually. $\endgroup$ – cybrbeast Feb 23 '16 at 14:21
  • $\begingroup$ Hi, how do you make this work in Blender 2.8? Run script doesn't do anything and trying to run a plot also does nothing? Thank you! $\endgroup$ – Richard Griffiths Dec 20 '18 at 8:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.