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$ – atomicbezierslinger Mar 20 '18 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 Mar 20 '18 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 Mar 20 '18 at 13:12
  • $\begingroup$ @batFINGER wins? $\endgroup$ – itafan Mar 20 '18 at 13:37
  • $\begingroup$ make a custom property named wins. $\endgroup$ – batFINGER Mar 20 '18 at 13:51
1
$\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 Mar 20 '18 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 Mar 20 '18 at 23:28
  • $\begingroup$ @itafan I added two lines. $\endgroup$ – Haunt_House Mar 20 '18 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$ – Haunt_House Mar 21 '18 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 Mar 20 '18 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 Apr 1 '18 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
  • 5
    $\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$ – Ray Mairlot Mar 20 '18 at 13:41

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.