19
$\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$
1
  • $\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, 2016 at 9:16

1 Answer 1

12
$\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

(Updated, tested to work in versions 3.6 .. 4.0).

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)
    collection = context.scene.collection
    collection.objects.link(obj)
    obj.select_set(True)
    
    context.view_layer.objects.active = obj
    
    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 = bpy.data.texts[PLOT_MODULE + ".py"].as_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

    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$
6
  • 4
    $\begingroup$ By rendering it in freestyle I think you could make very pretty graphs actually. $\endgroup$
    – cybrbeast
    Feb 23, 2016 at 14:21
  • 1
    $\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$ Dec 20, 2018 at 8:34
  • $\begingroup$ @richard-griffiths updated for Blender 3.6. $\endgroup$
    – ideasman42
    Apr 14 at 9:24
  • $\begingroup$ @ideasman42 in case you find it useful I'm been working on a plotting library for blender called blender-plots, see my answer here for a 1d example blender.stackexchange.com/a/266920/150141 :) $\endgroup$
    – Linus
    Apr 26 at 19:20
  • $\begingroup$ I run this little guy in 3.2 like blender --python blend_plot.py or clip/paste as usage and nothing happens. Errors in console or stdout? Nope. Default cube, cam and point light. Nothing in dope sheet. I do fd -e py | grep -i plot around blender install and blend_plot.py path and see no template drop. I didn't study it. $\endgroup$ Sep 27 at 16:39

You must log in to answer this question.

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