2
$\begingroup$

Primary

enter image description here

I would like to read in numeric data for multiple objects and create a presentation in Blender similar to a Bar Chart. I am requesting partial assistance, or more, in Python Scripts to automate this startling and critical task. The X scale of a bar should represent the data just as in any bar chart. A sample of the work is included above. I would like an efficient, simple and secure way to achieve my goal.

There are 30 objects each requiring 40 keyframes.

The animated objects move only in one axis (X) so X Scale can be keyframed.

every 20 frames (incrementing by 20, so 0,20,40,60,80 etc),

GIF of my first work https://i.imgur.com/koKg23t.gifv

Thanks in Advance.


Secondary

Manually the work is:

  • Repeat for 30 Objects.
    • Select object A
    • Repeat for 40 frame locations
      • Scale and Insert Keyframe

(around 40 times for 30 objects), and the risk of making errors is enormous.

$\endgroup$
16
  • $\begingroup$ Although your frames (seem) to increment by 20 ... you may want to clarify this, if it is a requirement. The proposed answer below can be changed to incrementing by 20 if necessary $\endgroup$ Commented Mar 20, 2018 at 8:26
  • $\begingroup$ Thanks atomicbezierslinger my English isn't perfect, yes frames incrementing by 20 starting from frame 0 so 0-20-40 etc $\endgroup$
    – itafan
    Commented Mar 20, 2018 at 9:40
  • $\begingroup$ Suggest only keyframing in the wins and driving the x scale of your bar and the x location of your shield with wins. $\endgroup$
    – batFINGER
    Commented Mar 20, 2018 at 13:12
  • $\begingroup$ @batFINGER wins? $\endgroup$
    – itafan
    Commented Mar 20, 2018 at 13:37
  • $\begingroup$ make a custom property named wins. $\endgroup$
    – batFINGER
    Commented Mar 20, 2018 at 13:51

4 Answers 4

2
$\begingroup$

Here's my approach. I usually find it hard to put the data into lists, so this script uses a text block to get it's input.

import bpy

text_source = 'input' # name of the textblock that contains the data
start_frame = 0 # if you need an offset
step = 20 # space in frames between keyframes
factor = 1.0 # scale all values by this amount

txt = bpy.data.texts[text_source] # getting the text

for a in txt.lines: # iterate through all text lines
    if len(a.body) < 2: # if a line is too short (empty lines)
        continue # just get the next line
    line_list = a.body.split(",") # turn the line into a list
    ob_name = line_list[0] # first item is the name
    ob_pos = line_list[1:] # sliced, everything BUT the first item
    ob = bpy.data.objects[ob_name] # get the object
    loc_count = start_frame # set the counter

    for single_pos in ob_pos: # iterate through all scale values
        ob.scale.x = float(single_pos) * factor # set the x scale for the object
        ob.keyframe_insert("scale", 0, loc_count) # add a keyframe, 0 = x, loc_count = which frame
        loc_count += step # increase, so the next key frame is set 'step' frames later

'input' has the data in csv style:

ob1,3,5,7,10,2,5,2,5,2,5
ob2,2,4,5,6
ob3,1,2,3,4.5,5,6,7,8,15.5

Daaaaaataaaaaaaa, there's an oil puddle in my room, why is that?

'factor' can be used to turn 1 meter into 10cm or 10m.

rollercoaster...WHEEEEE

$\endgroup$
4
  • $\begingroup$ Thanks! This is perfect, the way I can input the values is just perfect for what I have to do, just changed in line 19 ob.scale.x in ob.dimensions.x $\endgroup$
    – itafan
    Commented Mar 20, 2018 at 23:16
  • $\begingroup$ There is a way to decide in what unit of measure are the inputs processed? If the inputs are: (1,2,5) instead of (1m,2m,5m) it should be (10cm,20cm,50cm) so 1:10 $\endgroup$
    – itafan
    Commented Mar 20, 2018 at 23:28
  • $\begingroup$ @itafan I added two lines. $\endgroup$ Commented Mar 20, 2018 at 23:42
  • $\begingroup$ @itafan You're welcome. Just make sure you understand the script and not just use it ( : Python is super handy. $\endgroup$ Commented Mar 21, 2018 at 0:41
2
$\begingroup$

Here's how I would write this script for a single object. You could use a "for loop" to iterate through all of your objects.

import bpy

scene = bpy.context.scene
obj = scene.objects['Cube']

y_locations = [
    10,
    13,
    22,
]

current_frame = 0
for y_location in y_locations:
    obj.location[1] = y_location
    obj.keyframe_insert(
        data_path="location", frame=current_frame)

    current_frame += 20
$\endgroup$
1
  • $\begingroup$ Thanks, this works, my fault I made an error in the request.. $\endgroup$
    – itafan
    Commented Mar 20, 2018 at 22:47
2
$\begingroup$

Driving from a custom property.

Further to my comment I thought I would explain how to use one custom property, per club, in this case "points" to drive other objects. Consider this more an exercise in setting up and organizing.

I've used a script below to create the bars and badges, as I find that easier than using the UI.

Quite simply it's a cube as the bar, a sphere as the badge, with origins on their LHS, parented to an empty. The empty has a custom property "points". The scale of the bar, and the location of the badge are driven by the points property of empty. (self.parent["points"])

For convenience added a "League" empty to be able to transform the whole lot.

import bpy
from mathutils import Vector

context = bpy.context
scene = context.scene
def add_club(name, loc):
    bpy.ops.object.empty_add(radius=2, location=loc)
    mt = context.object
    mt.name = name
    bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
    bar = context.object
    bar.name = "%s_bar" % name
    bpy.ops.mesh.primitive_uv_sphere_add(location=(0, 0, 0))
    badge = context.object
    badge.name = "%s_badge" % name
    for o in [bar, badge]:
        o.parent = mt
        for v in o.data.vertices:
            v.co.x += 1

    mt["points"] = 1

    # add drivers to bar and badge
    d = bar.driver_add("scale", 0)
    d.driver.use_self = True
    d.driver.expression = 'self.parent["points"]'
    d = badge.driver_add("location", 0)
    d.driver.use_self = True
    d.driver.expression = '%4.2f * self.parent["points"]' % bar.dimensions.x
    return mt

clubs = {"Geelong" : [2, 4, 10],
        "Richmond" : [2, 3],
        "Collingwood" : [0],
        "Essendon" : [2, 4, 5],
        "Fremantle" : [1, 2],
        "Sydney" : [4],
        "Brisbane" : [2]}

loc = Vector()
bpy.ops.object.empty_add(radius=4)
handle = context.object
handle.name = "League"
for name, data in clubs.items():
    club = add_club(name, loc)
    club.parent = handle
    # keyframe data on points

    club["points"] = 0.0 # make int for steppy 
    club.keyframe_insert('["points"]', frame=0)
    frame_step = 20
    for i, pt in enumerate(data):
        club["points"] = float(pt)
        club.keyframe_insert('["points"]', frame=(i + 1) * frame_step)

    loc.z -= 2.2

scene.objects.active = handle
handle.select = True

enter image description here Result of running the script. A club can be selected simply from the outliner

$\endgroup$
1
  • $\begingroup$ Thanks, as soon as I can I'll try your script, unfortunately a parent's health problems kept me away from the web and blender. Your script seems very very interesting.. thanks again. $\endgroup$
    – itafan
    Commented Apr 1, 2018 at 14:26
1
$\begingroup$
  • Here is something small to get you started. This answer emphasizes scale keyframes.
  • Please Click image to see larger image which is easier to read.

enter image description here

  • For beginners in Python note the use of a container, list or tuple, which combines elegantly with [for]. 3 very nearly identical answers use [for].
  • Note the origins of each object are aligned on the [Y coordinate] side of cube so that scale works easily.
  • You could choose to make a manual selection for each object, that is not what we did here. But we can write a (2-3) lines to iterate on the current selection list
  • In this proposed answer the data names the object, this can serve as documentation about your decisions if necessary.
  • So above we iterate with (3) objects pictured. The OP will have (30) pieces of data.
  • Note the original question was changed to require a different answer.
  • Please inspect and verify code and comments
  • Bonne Chance!

OBSOLETE FOLLOWS

enter image description here

$\endgroup$
1
  • 7
    $\begingroup$ Please include any relevant code as text, instead of in an image. This will make it easier to read and allow people to copy the script easily. $\endgroup$ Commented Mar 20, 2018 at 13:41

You must log in to answer this question.

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