1
        if df['time'].max() > self.longest_time:
            self.longest_time = df['time'].max()
        for name,edf in df.groupby('objectnum'):
            print(name)
            n = f"Object {name}"
            edf = edf.sort_values('time')
            initialx = edf['x'].iat[0]
            initialy = edf['y'].iat[1]
            initialz = edf['z'].iat[2]
            bpy.ops.mesh.primitive_cube_add(location=(initialx,initialy,initialz))
            orig_cube =bpy.context.view_layer.objects.active;
            orig_cube.name = n
            orig_cube.scale = (0,0,0)

            for i,time in enumerate(edf['time']):
                orig_cube.location[0] = edf['x'].iat[i]
                orig_cube.location[1] = edf['y'].iat[i]
                orig_cube.location[2] = edf['z'].iat[i]
                if i == edf.shape[0]-1 or i == 0:
                    orig_cube.scale[0] = 0
                    orig_cube.scale[1] = 0
                    orig_cube.scale[2] = 0
                else:
                    orig_cube.scale[0] = 100000
                    orig_cube.scale[1] = 100000
                    orig_cube.scale[2] = 100000
                orig_cube.keyframe_insert(data_path="location", frame=time)
                orig_cube.keyframe_insert(data_path='scale',frame=time)

How can I just do this with something like this? I'm trying to eliminate the for loop that loops over all the data.

if ['time'].max() > self.longest_time:
    self.longest_time = df['time'].max()
for name,edf in df.groupby('onjectnum'):
    n = f"object {name}"
    edf = edf.sort_values('time')
    initialx = edf['x'].iat[0]
    initialy = edf['y'].iat[1]
    initialz = edf['z'].iat[2]
    bpy.ops.mesh.primitive_cube_add(location=(initialx,initialy,initialz))
    orig_cube =bpy.context.view_layer.objects.active;
    orig_cube.name = n
    orig_cube.scale = (0,0,0)
    #HERE IS WHERE I NEED THE MAGIC
#Creating a Nx3 array of locations [x,y,z]
locations = np.array([edf['x'],edf['y'],edf['z']]).T
orig_cube.ADD_KEYFRAMES(data_path='location',loc=locations,fame=edf['time'])
#Creating a Nx3 array of scales with first and last instances = 0,0,0
#With the rest being 1,1,1
scales = np.ones((edf.shape[0],3))*10000
scales[0,:] = 0
scales[-1,:] = 0
orig_cube.ADD_KEYFRAMES(data_path='scale',scale=scales,frame=edf['time'])

Carl
  • 23
  • 2

1 Answers1

2

The pattern for this is

# Create action to hold the fcurves (choose a name)
action = bpy.data.actions.new("CubeAction")

Action affects objects (vs shapekeys/materials/etc)

action.id_root = 'OBJECT'

Assign action to your new cube

orig_cube.animation_data_create() orig_cube.animation_data.action = action

For each X Y Z component

for i in range(0, 3): # Create FCurve affecting location[i] fc = action.fcurves.new(data_path="location", index=i, action_group="Location")

# coords is an array of length 2*(num keyframes) floats in the format
#   [ frame, value,   frame, value,   frame, value, ... ]
# values are the value of location[i].
# It can be a list, array.array, numpy array, ...
coords = ...

# Add enough keyframe points
fc.keyframe_points.add(len(coords) // 2)

# Set the keyframe point values
fc.keyframe_points.foreach_set("co", coords)

# Set all interpolations to LINEAR
ipo = (
    bpy.types.Keyframe.bl_rna.properties["interpolation"]
    .enum_items['LINEAR'].value
)
fc.keyframe_points.foreach_set("interpolation", [ipo] * len(fc.keyframe_points))


Scale is similar, just change the fcurves.new line...

The format for coords is two values for each keyframe. coords[::2] are the frames/times and coords[1::2] are the FCurve values at those frames. You'll have to figure out how to massage your data into that form on your own, but it shouldn't be too hard.

scurest
  • 10,349
  • 13
  • 31
  • Awesome. This seems to work for location [I haven't tried with scaling just yet.] But I notice that the interpolation between my keyframes is kinda wonky. Is there a way to set them all to LINEAR in a similar way without a loop? – Carl Sep 03 '21 at 23:37
  • Yeah, I added it in. – scurest Sep 04 '21 at 00:56