scene_functions.py 19.2 KB
Newer Older
1 2
import math

3 4 5
# Import Blender functions
filename = os.path.join(os.path.basename(bpy.data.filepath), "blender_functions.py")
exec(compile(open(filename).read(), filename, 'exec'))
6

7
def init(unique_id,transp_par,detectors,blender_path,bgshade):
8 9 10
    bcs = bpy.context.scene

    # Configure Environment
11
    bcs.world.light_settings.use_environment_light = False
12
    bcs.world.light_settings.environment_energy = 0.1
13
    bpy.context.scene.world.horizon_color = (bgshade,bgshade,bgshade)
14 15

    # Configure Stamp
16 17 18 19 20 21 22 23 24 25 26
    bcsr = bpy.context.scene.render
    bcsr.use_stamp = True
    bcsr.use_stamp_time = False
    bcsr.use_stamp_date = False
    bcsr.use_stamp_render_time = False
    bcsr.use_stamp_frame = False
    bcsr.use_stamp_scene = False
    bcsr.use_stamp_camera = False
    bcsr.use_stamp_filename = False
    bcsr.stamp_note_text = unique_id
    bcsr.use_stamp_note = True
27 28
    bcsr.stamp_font_size = 40

29 30 31 32

    # Cleanup
    bpy.data.objects.remove(bpy.data.objects['Cube'])
    bpy.data.objects.remove(bpy.data.objects['Camera'])
33
    bpy.data.objects.remove(bpy.data.objects['Lamp'])
34 35 36

    # Basic Objects
    addCameras() # Add cameras
37
    addLamps() # Add Lamps
38

39
    addALICE_Geometry(False,transp_par,detectors,blender_path)
40

41
def addALICE_Geometry(bright_colors=False, transp_par=1.0, detectors=[1,1,1,1,0], blender_path="/home/"):
42

43
    if bright_colors: # Defining sequence of RGB values to fill 'createMaterial' functions below
44
        rgb_v = [13,13,25,10] # Colors for brighter detector
45
    else:
46
        rgb_v = [0.5,0.9,1,0.2] # Colors for standard geometry
47

Breno Rilho Lemos's avatar
Breno Rilho Lemos committed
48

49
    if detectors[0]:
50 51 52 53 54 55 56 57 58 59
        addALICE_ITS(transp_par,rgb_v)
    if detectors[4]:
        importALICE_detailed_TPC(transp_par,blender_path)
    else:
        if detectors[1]:
            addALICE_TPC(transp_par,rgb_v)
    if detectors[2]:
        addALICE_TRD(transp_par,rgb_v)
    if detectors[3]:
        addALICE_EMCal(transp_par,rgb_v)
Breno Rilho Lemos's avatar
Breno Rilho Lemos committed
60

61
def addALICE_ITS(transp_par,rgb_v):
Breno Rilho Lemos's avatar
Breno Rilho Lemos committed
62

63
    # ADD ITS INNER BARREL
Breno Rilho Lemos's avatar
Breno Rilho Lemos committed
64

65
    # Material
66
    createMaterial("innerITS",R=rgb_v[2],G=0,B=rgb_v[2],shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*1.4,emit=0,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
Breno Rilho Lemos's avatar
Breno Rilho Lemos committed
67

68 69 70 71
    # Add Inner ITS
    bpy.ops.mesh.primitive_cylinder_add(radius=0.0421, depth=0.271, view_align=False, enter_editmode=False, location=(0, 0, 0))
    inner_TPC = bpy.context.object
    inner_TPC.name = "innerITS"
72

73 74 75
    # Set Material
    inner_TPC.data.materials.clear()
    inner_TPC.data.materials.append(bpy.data.materials["innerITS"])
76 77


78
    # ADD ITS OUTER BARREL
79

80
    # Material
81
    createMaterial("outerITS",R=rgb_v[3],G=0,B=rgb_v[3],shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.8,emit=0.8,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
82

83
    # ADD ITS MIDDLE LAYERS
84

85 86 87 88
    # Add "hole" to subtract from the middle
    bpy.ops.mesh.primitive_cylinder_add(radius=0.1944, depth=0.9, view_align=False, enter_editmode=False, location=(0, 0, 0)) #smaller cylinder
    middle_ITS_hole = bpy.context.object
    middle_ITS_hole.name = "Hole"
89

90 91 92 93
    # Add actual middle layer ITS part
    bpy.ops.mesh.primitive_cylinder_add(radius=0.247, depth=0.843, view_align=False, enter_editmode=False, location=(0, 0, 0)) #bigger cylinder
    middle_ITS = bpy.context.object
    middle_ITS.name = "middleITS"
94

95 96
    # Subtract hole from main TPC part
    subtract(middle_ITS_hole,middle_ITS)
97

98 99 100
    # Set material
    middle_ITS.data.materials.clear()
    middle_ITS.data.materials.append(bpy.data.materials["outerITS"])
101 102


103
    # ADD ITS OUTER LAYERS
104

105 106 107 108
    # Add "hole" to subtract from the middle
    bpy.ops.mesh.primitive_cylinder_add(radius=0.3423, depth=1.5, view_align=False, enter_editmode=False, location=(0, 0, 0)) #smaller cylinder
    outer_ITS_hole = bpy.context.object
    outer_ITS_hole.name = "Hole"
109

110 111 112 113
    # Add actual outer layer ITS part
    bpy.ops.mesh.primitive_cylinder_add(radius=0.3949, depth=1.475, view_align=False, enter_editmode=False, location=(0, 0, 0)) #bigger cylinder
    outer_ITS = bpy.context.object
    outer_ITS.name = "outerITS"
114

115 116 117 118 119 120
    # Subtract hole from main ITS part
    subtract(outer_ITS_hole,outer_ITS)

    # Set material
    outer_ITS.data.materials.clear()
    outer_ITS.data.materials.append(bpy.data.materials["outerITS"])
121

122 123 124 125
    # Make ITS middle and outer layers a single object
    joinObjects([middle_ITS,outer_ITS])
    Outer_ITS = bpy.context.object
    Outer_ITS.name = "OuterITS"
126

127
def addALICE_TPC(transp_par,rgb_v):
128

129
    # Material
130
    createMaterial("tpc",R=0,G=rgb_v[0],B=0,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.4,emit=0.3,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
131

132 133 134 135 136 137 138 139 140 141 142 143
    # Add TPC
    bpy.ops.mesh.primitive_cylinder_add(radius=2.461, depth=5.1, view_align=False, enter_editmode=False, location=(0, 0, 0)) #bigger cylinder
    TPC = bpy.context.object
    TPC.name = "TPC"

    # Set material
    TPC.data.materials.clear()
    TPC.data.materials.append(bpy.data.materials["tpc"])

def importALICE_detailed_TPC(transp_par,blender_path):

    # Materials
144 145 146 147
    createMaterial("tpc_part_1",R=0,G=1,B=0,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.4,emit=0.3,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
    createMaterial("tpc_part_2",R=1,G=1,B=0,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.4,emit=0.3,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
    createMaterial("tpc_part_3",R=1,G=0,B=0,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.4,emit=0.3,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
    createMaterial("tpc_part_4",R=0,G=1,B=1,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.4,emit=0.3,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
148 149

    # Import detailed TPC
150 151 152 153
    #
    # File was provided by CERN's researcher Stefan Rossegger
    # Email: stefan.rossegger@gmail.com
    #
154 155 156 157 158
    for i in range(1,5):
        bpy.ops.wm.append(filename="tpc_part"+str(i), directory=blender_path+"/Detailed_TPC.blend/Object/")
        bpy.context.scene.objects.active = bpy.data.objects["tpc_part"+str(i)]
        TPC_part = bpy.context.object
        TPC_part.name = "TPC_part_"+str(i)
159

160
        # Set material
161 162
        TPC_part.data.materials.clear()
        TPC_part.data.materials.append(bpy.data.materials["tpc_part_"+str(i)])
163

164
def addALICE_TRD(transp_par,rgb_v):
165

166
    # Material
167
    createMaterial("TRD",R=rgb_v[3],G=0,B=rgb_v[3],shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.3,emit=0.8,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
168

169 170 171 172
    # Add "hole" to subtract from the middle
    bpy.ops.mesh.primitive_cylinder_add(radius=2.9, depth=6, vertices=18, view_align=False, enter_editmode=False, location=(0, 0, 0)) #smaller cylinder
    TRD_hole = bpy.context.object
    TRD_hole.name = "Hole"
173

174 175 176 177
    # Add actual TRD part
    bpy.ops.mesh.primitive_cylinder_add(radius=3.7, depth=5.1, vertices=18, view_align=False, enter_editmode=False, location=(0, 0, 0)) #bigger cylinder
    TRD = bpy.context.object
    TRD.name = "TRD"
178

179 180
    # Subtract hole from main TRD part
    subtract(TRD_hole,TRD)
181

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    # Set material
    TRD.data.materials.clear()
    TRD.data.materials.append(bpy.data.materials["TRD"])

    # Add 'slices' to subtract from TRD structure
    bpy.ops.mesh.primitive_cube_add(radius=1, location=(2.855942,0.50358,0))
    slice = bpy.context.object
    slice.name = "slice"
    bpy.ops.transform.resize(value=(1,0.03,4))
    bpy.context.object.rotation_euler[2] = 0.174533
    subtract(slice,TRD)

    def rad(theta): # Convert degrees to radians
        return theta * math.pi / 180

    xn = 2.9 * math.cos(rad(10))
    yn = 2.9 * math.sin(rad(10))

    for n in range(1,18):

        dx = -2 * 2.9 * math.sin(rad(10)) * math.sin(rad(n * 20))
        xn += dx
204

205 206 207 208 209 210
        dy = 2 * 2.9 * math.sin(rad(10)) * math.cos(rad(n * 20))
        yn += dy

        rotat = rad(10 + n*20)

        bpy.ops.mesh.primitive_cube_add(radius=1, location=(xn,yn,0))
211 212 213
        slice = bpy.context.object
        slice.name = "slice"
        bpy.ops.transform.resize(value=(1,0.03,4))
214
        bpy.context.object.rotation_euler[2] = rotat
215

216
        subtract(slice,TRD)
217

218
def addALICE_EMCal(transp_par,rgb_v):
219

220
    # Material
221
    createMaterial("emcal",R=rgb_v[1],G=rgb_v[1],B=0,shadows=False,cast_shadows=False,transparency=True,alpha=transp_par*0.1,emit=1.5,specular_alpha=0,fresnel_factor=5,fresnel=0.3)
222

223 224 225 226
    # Add cylinder for EMCal
    bpy.ops.mesh.primitive_cylinder_add(radius=4.7, depth=5.1, vertices=19, view_align=False, enter_editmode=False, location=(0, 0, 0))
    EMCal = bpy.context.object
    EMCal.name = "EMCal"
227

228 229 230 231
    # Add cylinder to be removed from center
    bpy.ops.mesh.primitive_cylinder_add(radius=4.35, depth=5.2, vertices=19, view_align=False, enter_editmode=False, location=(0, 0, 0))
    emcal_hole = bpy.context.object
    emcal_hole.name = "Hole"
232

233
    subtract(emcal_hole,EMCal);
234

235 236 237 238 239
    # Adds rotated cube to be removed from EMCal so that there's a 7.3° angle with top y axis, clockwise
    bpy.ops.mesh.primitive_cube_add(location=(2.85,2.2,0), rotation=(0,0,-0.1274), radius=2.55)
    bpy.ops.transform.resize(value=(1.5,1.5,1.5), constraint_axis=(False,False,True))
    cube1 = bpy.context.object # first quadrant
    subtract(cube1,EMCal)
240

241 242 243 244 245
    # Adds rotated cube to be removed from EMCal so that there's a 9.7° angle with left x axis, anticlockwise
    bpy.ops.mesh.primitive_cube_add(location=(-2.08,-2.95,0), rotation=(0,0,0.1693), radius=2.55)
    bpy.ops.transform.resize(value=(1.5,1.5,1.5), constraint_axis=(False,False,True))
    cube3 = bpy.context.object # third quadrant
    subtract(cube3,EMCal)
246

247 248 249 250 251
    #Adds cube with right angle in fourth quadrant to be removed from EMCal
    bpy.ops.mesh.primitive_cube_add(location=(2.55,-2.55,0), radius=2.55)
    bpy.ops.transform.resize(value=(1.5,1.5,1.5), constraint_axis=(False,False,True))
    cube4 = bpy.context.object # fourth quadrant
    subtract(cube4,EMCal)
252

253 254 255
    # Set Material
    EMCal.data.materials.clear()
    EMCal.data.materials.append(bpy.data.materials["emcal"])
256

257

258
def addLamps():
259
    bpy.ops.object.lamp_add(type='POINT', location=(4,1,6))
260
    bpy.ops.object.lamp_add(type='POINT', location=(0,0,-8))
261

262 263
def addCameras():
    # ForwardCamera
264
    bpy.ops.object.camera_add(location = (0,0.5,20), rotation = (0, 0, 0))
265 266 267
    bpy.context.object.name = "ForwardCamera"
    camera_forward=bpy.data.objects['ForwardCamera']
    camera_forward.data.type = 'ORTHO'
268
    camera_forward.data.ortho_scale = 18
269 270

    # OverviewCamera
271
    bpy.ops.object.camera_add(location = (23.27182, 10.3968, 22.754), rotation = (-0.071558, 0.879645, 0.305433))
272 273 274 275 276 277 278
    bpy.context.object.name = "OverviewCamera"
    bpy.context.object.data.lens = 66.78

    # Barrel Camera
    bpy.ops.object.camera_add(location = (6, 0, 0), rotation = (0, 1.5708, 0))
    bpy.context.object.name = "BarrelCamera"

279 280 281 282 283
    # Side Camera
    bpy.ops.object.camera_add(location = (6, 0, 0), rotation = (0, 1.5708, 0))
    bpy.context.object.name = "SideCamera"
    bpy.context.object.data.lens = 9

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    # Moving Camera 1
    bpy.ops.object.camera_add()
    bpy.context.object.name = "Moving1Camera"
    bpy.context.object.data.lens = 26

    # Moving Camera 2
    bpy.ops.object.camera_add()
    bpy.context.object.name = "Moving2Camera"
    bpy.context.object.data.lens = 26

    # Moving Camera 3
    bpy.ops.object.camera_add()
    bpy.context.object.name = "Moving3Camera"
    bpy.context.object.data.lens = 26

    # Moving Camera 4
    bpy.ops.object.camera_add()
    bpy.context.object.name = "Moving4Camera"
302 303
    bpy.context.object.data.lens = 26

304 305
# Function that creates Blender Objects from input list of particles.
## Returns a list of blender objects
306
def createSceneParticles(particles, r_part=1, createTracks = False):
307 308 309 310 311 312 313
    # Associate particles and colors
    particle_types = ["Electron","Pion","Muon","Proton","Kaon","Unknown"]
    clRed = (1, 0, 0)
    clGreen = (0, 1, 0)
    clBlue = (0, 0, 1)
    clMagenta = (0.75, 0, 1)
    clYellow = (1, 1, 0)
314
    clWhite = (0.8, 0.8, 0.8)
315 316
    particle_colors = {"Electron":clRed, "Pion":clGreen, "Muon":clBlue, "Proton":clMagenta, "Kaon": clYellow, "Unknown": clWhite}

317
    # Create Materials
318 319 320
    for type in particle_types:
        bpy.data.materials.new(name=type)
        #bpy.context.object.active_material = (1, 0, 0)
321
        bpy.data.materials[type].emit = 0.05
322 323 324 325 326 327 328
        bpy.data.materials[type].diffuse_color = particle_colors[type]
        bpy.data.materials[type].use_shadows = False
        bpy.data.materials[type].use_cast_shadows = False

    # Create blender spheres (particles)
    blender_particles=[]
    n_particles=len(particles)
329 330 331 332 333 334 335

    # Define particle radius based on multiplicity
    if n_particles > 15000:
        r_part=0.01*r_part
    else:
        r_part=0.05-(0.04/15000)*n_particles*r_part

336 337
    for particle in particles:
        this_type=particle.p_type
338
        print("Adding Sphere - Particle " + str(len(blender_particles)+1)+" of "+str(n_particles)+" - "+this_type)
339
        bpy.ops.mesh.primitive_uv_sphere_add()
340
        bpy.ops.object.shade_smooth()
341 342 343 344 345 346 347 348 349 350 351 352
        this_particle = bpy.context.object
        this_particle.name = "part"+str(particle.iDx)
        this_particle.location = ((particle.x,particle.y,particle.z))
        this_particle.delta_scale = (r_part,r_part,r_part)
        this_particle.data.materials.clear()
        this_particle.data.materials.append(bpy.data.materials[this_type])
        blender_particles.append(this_particle)

    # Create blender curves (tracks)
    blender_tracks=[]
    if createTracks:
            for track in particles:
353 354
                this_type=track.p_type
                print("Adding Curve - Track " + str(len(blender_tracks)+1)+" of "+str(n_particles)+" - "+this_type)
355 356 357 358 359 360 361

                # create the Curve Datablock
                curveTrack = bpy.data.curves.new('CurveTrack', type='CURVE')
                curveTrack.dimensions = '3D'
                curveTrack.resolution_u = 2

                curveTrack.fill_mode = 'FULL'
362
                curveTrack.bevel_depth = 0.4*r_part # Tracks are 40% the thickness of particles
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
                curveTrack.bevel_resolution = 3


                # map coords to spline
                bcs = bpy.context.scene
                polyline = curveTrack.splines.new('NURBS')
                polyline.points.add(bcs.frame_end) # Add one point per frame
                for i in range(bcs.frame_end):
                    polyline.points[i].co = (particle.x,particle.y,particle.z, 1)

                # create Object
                trackOB = bpy.data.objects.new('Track', curveTrack)
                trackOB.data.materials.clear()
                trackOB.data.materials.append(bpy.data.materials[this_type])
                scn = bpy.context.scene
                scn.objects.link(trackOB)
                blender_tracks.append(trackOB)


    return blender_particles, blender_tracks

384 385 386
def animate_camera(driver):
    bcs = bpy.context.scene

387
    # Animate Moving Camera 1
388
    for f in range(bcs.frame_end):
389
        theta = f/bcs.frame_end*math.pi/2
390
        bcs.frame_current = f
391 392
        print("Configuring Moving1Camera in frame: "+str(f)+" of "+str(bcs.frame_end))
        bcs.objects.active=bpy.data.objects['Moving1Camera']
393 394 395 396 397 398 399 400 401 402 403
        x_cam=15*math.sin(theta)
        y_cam=7
        z_cam=15*math.cos(theta)
        x_rot_cam=-0.427606
        y_rot_cam=theta
        z_rot_cam=0
        bpy.context.object.location=(x_cam,y_cam,z_cam)
        bpy.context.object.keyframe_insert(data_path='location')
        bpy.context.object.rotation_euler=(x_rot_cam,y_rot_cam,z_rot_cam)
        bpy.context.object.keyframe_insert(data_path='rotation_euler')

404 405
    # Animate Moving Camera 2
    for f in range(bcs.frame_end):
406
        theta = f/bcs.frame_end*math.pi/2+math.pi/5
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
        bcs.frame_current = f
        print("Configuring Moving2Camerara in frame: "+str(f)+" of "+str(bcs.frame_end))
        bcs.objects.active=bpy.data.objects['Moving2Camera']
        x_cam=15*math.sin(theta)
        y_cam=7
        z_cam=-15*math.cos(theta)
        x_rot_cam=-0.427606
        y_rot_cam=math.pi-theta
        z_rot_cam=0
        bpy.context.object.location=(x_cam,y_cam,z_cam)
        bpy.context.object.keyframe_insert(data_path='location')
        bpy.context.object.rotation_euler=(x_rot_cam,y_rot_cam,z_rot_cam)
        bpy.context.object.keyframe_insert(data_path='rotation_euler')

    # Animate Moving Camera 3
    for f in range(bcs.frame_end):
        theta = f/bcs.frame_end*math.pi/2
        bcs.frame_current = f
425
        print("Configuring Moving3Camera in frame: "+str(f)+" of "+str(bcs.frame_end))
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
        bcs.objects.active=bpy.data.objects['Moving3Camera']
        x_cam=15*math.sin(theta)
        y_cam=15*math.cos(theta)
        z_cam=0
        x_rot_cam=-math.pi/2
        y_rot_cam=math.pi/2
        z_rot_cam=-theta
        bpy.context.object.location=(x_cam,y_cam,z_cam)
        bpy.context.object.keyframe_insert(data_path='location')
        bpy.context.object.rotation_euler=(x_rot_cam,y_rot_cam,z_rot_cam)
        bpy.context.object.keyframe_insert(data_path='rotation_euler')

    # Animate Moving Camera 4
    for f in range(bcs.frame_end):
        theta = f/bcs.frame_end*math.pi/2
        bcs.frame_current = f
442
        print("Configuring Moving4Camera in frame: "+str(f)+" of "+str(bcs.frame_end))
443 444 445 446 447 448 449 450 451 452 453
        bcs.objects.active=bpy.data.objects['Moving4Camera']
        x_cam=15*math.sin(theta)
        y_cam=-15*math.cos(theta)
        z_cam=0
        x_rot_cam=math.pi/2
        y_rot_cam=math.pi/2
        z_rot_cam=theta
        bpy.context.object.location=(x_cam,y_cam,z_cam)
        bpy.context.object.keyframe_insert(data_path='location')
        bpy.context.object.rotation_euler=(x_rot_cam,y_rot_cam,z_rot_cam)
        bpy.context.object.keyframe_insert(data_path='rotation_euler')
454

455 456 457 458 459
# Function that animates the scene using the particle propagator class
def animate(objects, particles, driver):
    bcs = bpy.context.scene

    #Animate particles
460
    for f in range(bcs.frame_end):
461 462 463 464 465 466 467 468 469 470 471 472 473
        t = driver.delta_t*f
        bcs.frame_current = f
        print("Configuring particles in frame: "+str(f)+" of "+str(bcs.frame_end))
        for i in range(0, len(objects)):
            bcs.objects.active=objects[i]
            objects[i].location=(particles[i].Propagate(t))
            objects[i].keyframe_insert(data_path='location')

# Function that animates particle tracks using the particle propagator class
def animate_tracks(tracks, particles, driver):
    bcs = bpy.context.scene

    #Animate tracks
474 475
    for f in range(bcs.frame_end):
        t = driver.delta_t*(f+1) # choosing (f+1) instead of (f) removes gap between track and particle
476 477
        bcs.frame_current = f
        print("Configuring tracks in frame: "+ str(f) +" of "+ str(bcs.frame_end))
478
        for point in range(f,bcs.frame_end+1):
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
            for i in range(0, len(particles)):
                #bcs.objects.active=tracks[i]
                tracks[i].data.splines[0].points[point].keyframe_insert(data_path="co", frame = f)
                x, y, z = particles[i].Propagate(t)
                tracks[i].data.splines[0].points[point].co = (x, y, z, 1)



                ##polyline = curveTrack.splines.new('NURBS')
                ##polyline.points.add(len(coords))
                ##for i, coord in enumerate(coords):
                ##    x,y,z = coord
                ##    polyline.points[i].co = (x, y, z, 1)

                #curve = bpy.data.objects["Track"]
                #curve.data.splines[0].points[1].co


                #point.keyframe_insert(data_path="co", frame = i)
                # https://blender.stackexchange.com/questions/73630/animate-curves-by-changing-spline-data-using-a-python-script