3
$\begingroup$

I am visualizing data with python and Blender, and I have a graph that I build, and I am wondering how to approach ordering the bars in the graph, based on length of the bar, several times over the length of the animation.

Here is an example of this on youtube https://www.youtube.com/watch?v=F67NPWxBCto

To me it looks like there are several regular samples along the length of the animation, and then the bars start to move to the correct order/position for each "sample" a few seconds earlier...

ie. if there is a sample at every 100 frames, then 75 frames prior to that sample, the bars should begin to move to their positions, so they will be settled and correct by the time we get to the sample frame ?? ... and repeat, down the timeline.

Bars that are descending should move backwards in frame (positive Y direction) to do so, and ascending bars should move forward towards camera (negative Y direction ... to keep them from interpenetrating each other.

Here is an image of the graph, I have it all parented up individually, so each bar and text can be moved as a group simply.

Graph Drawn in Blender

$\endgroup$

1 Answer 1

2
$\begingroup$

enter image description here

enter image description here

enter image description here

Run script to get result

Warning: Need restart blender to remove the handler

import bpy, math

# select and del all object
if bpy.context.object:
    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.object.select_all(action = "SELECT")
    bpy.ops.object.delete(use_global=True, confirm=False)

# change viewport clip
for a in bpy.context.screen.areas:
    if a.type == 'VIEW_3D':
        for s in a.spaces:
            if s.type == 'VIEW_3D':
                s.clip_start = 0.1
                s.clip_end  = 10000

frame_start         = 0
frame_end           = 1800  # for 1 minute
anim_frame_offset   = 99

bpy.context.scene.render.fps    = 30
bpy.context.scene.frame_start   = frame_start
bpy.context.scene.frame_end     = frame_end
bpy.context.scene.frame_current = frame_start

w_max               = 1024
h_max               = 768
w_lim               = 900
block_size          = 100
block_distance      = 30
tx_name_offset_x    = - 150
tx_name_offset_y    = - 30
tx_data_offset_x    = 50
tx_data_offset_y    = - 30
font_size           = 100
font_size_data      = 70
z_offset            = 5

class BLOCK:
    def __init__(self, name, an_data):
        self.name       = name
        self.an_data    = an_data

        bpy.ops.mesh.primitive_plane_add(size=block_size, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
        self.mesh               = bpy.context.object
        mesh                    = self.mesh
        mesh.name               = name
        mesh.rotation_euler[1]  = - math.pi / 2
        md                      = mesh.modifiers.new(type="SOLIDIFY", name="solid")

        bpy.ops.object.text_add(enter_editmode=False, align='WORLD', location=(tx_name_offset_x, 0, 0), scale=(1, 1, 1))
        self.tx_name            = bpy.context.object
        text                    = self.tx_name
        text.data.body          = name
        text.data.size          = font_size

        bpy.ops.object.text_add(enter_editmode=False, align='WORLD', location=(tx_data_offset_x, 0, 0), scale=(1, 1, 1))
        self.tx_data            = bpy.context.object
        text                    = self.tx_data
        text.data.body          = name
        text.data.size          = font_size_data

data = []
#                frame:   0  100  200  ...
data.append(BLOCK("A", (1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9)))
data.append(BLOCK("B", (0.5, 0.7, 1.3, 1.4, 2.5, 1.7, 1.8, 1.9, 1.9, 2.1, 2.5, 2.6, 2.3, 2.9, 3.2, 3.3, 3.2, 3.5, 3.6)))
data.append(BLOCK("C", (1.7, 1.5, 1.8, 2.4, 2.1, 1.8, 2.0, 2.3, 1.9, 2.2, 2.3, 2.6, 2.5, 2.9, 3.5, 3.2, 3.5, 3.6, 3.7)))
data.append(BLOCK("D", (0.6, 0.4, 1.3, 1.4, 1.5, 1.5, 2.0, 2.0, 1.7, 1.3, 1.0, 0.9, 0.8, 0.6, 0.5, 0.4, 0.2, 0.1, 0.4)))
data.append(BLOCK("E", (3.2, 2.1, 1.3, 1.4, 1.5, 1.9, 1.7, 1.6, 1.5, 1.0, 0.8, 0.6, 0.5, 0.7, 0.2, 0.3, 0.1, 0.3, 0.5)))

len_data    = len(data)
sort_data   = [sorted([bl for bl in data], key=lambda x: x.an_data[i], reverse=True) for i in range(len(data[0].an_data))]

y = 0
z = 0
d = block_size + block_distance
f = w_lim / sort_data[0][0].an_data[0]

for da in sort_data[0]:
    mesh                    = da.mesh
    mesh.location.y         = y
    mesh.location.z         = z
    md                      = mesh.modifiers[0]
    md.thickness            = da.an_data[0] * f
    da.tx_name.location.y   = y + tx_name_offset_y
    da.tx_data.location.y   = y + tx_data_offset_y
    da.tx_data.location.x   = mesh.location.x + md.thickness + tx_data_offset_x
    da.tx_data.data.body    = str(da.an_data[0])

    mesh.keyframe_insert(data_path="location")
    mesh.keyframe_insert(data_path="modifiers[0].thickness")
    da.tx_name.keyframe_insert(data_path="location")
    da.tx_data.keyframe_insert(data_path="location")
    y -= d
    z += z_offset

i = 1
for r in range(frame_start + 100, frame_end + 1, 100):
    fr          = r - anim_frame_offset
    bpy.context.scene.frame_current = fr
    y           = 0

    for da in sort_data[i - 1]:
        da.mesh.keyframe_insert(data_path="location", frame=fr)
        da.mesh.keyframe_insert(data_path="modifiers[0].thickness", frame=fr)
        da.tx_name.keyframe_insert(data_path="location", frame=fr)
        da.tx_data.keyframe_insert(data_path="location", frame=fr)

        y -= d

    bpy.context.scene.frame_current = r
    y           = 0
    f           = w_lim / sort_data[i][0].an_data[i]
    for da in sort_data[i]:
        mesh            = da.mesh
        mesh.location.y = y
        mesh.keyframe_insert(data_path="location", frame=r)

        md                      = mesh.modifiers[0]
        md.thickness            = da.an_data[i] * f
        mesh.keyframe_insert(data_path="modifiers[0].thickness", frame=r)

        da.tx_name.location.y   = y + tx_name_offset_y
        da.tx_data.location.y   = y + tx_data_offset_y
        da.tx_data.location.x   = mesh.location.x + md.thickness + tx_data_offset_x
        da.tx_name.keyframe_insert(data_path="location", frame=r)
        da.tx_data.keyframe_insert(data_path="location", frame=r)

        y -= d


    i += 1


# data text part:
bpy.context.scene.frame_current = frame_start

def if_frame_change(dummy):
    i = min(max(0, bpy.context.scene.frame_current // 100 + 1), len(data[0].an_data) - 1)
    for bl in data:
        bl.tx_data.data.body = str(bl.an_data[i])

bpy.app.handlers.frame_change_post.append(if_frame_change)

enter image description here

$\endgroup$
1
  • 1
    $\begingroup$ Spot on, thanks. $\endgroup$ Apr 12, 2022 at 0:39

You must log in to answer this question.

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