Timeline Example

This example depicts how to create animations and integrate into a newly created timeline. To use this script, open a new database, create a plane and a cube, then execute the script : the script will create automatically kinematics parent for the cube so that it can be animated.

  1from os import path
  2from sys import stdout
  3from typing import List
  4
  5from p3dsdk import CurrentP3DSession, Database, P3DFile, Model
  6from p3dsdk import CameraBezierPathAnimation, Vector3, CameraBookmarkAnimation
  7from p3dsdk import Null, CameraBezierPathAnimationPositionType, CameraBezierPathAnimationTargetType
  8from p3dsdk import ChannelsSimpleAnimation, ChannelsCurveAnimation, Surface
  9
 10
 11def find_kinematic_object(db: Database, name: str) -> Null:
 12    for obj in db.list_models()[0].list_kinematic_objects():
 13        if obj.name == name:
 14            return obj
 15    raise Exception(f"No kinemtaics object named '{name}' found in the database")
 16
 17
 18def find_surface(db: Database, names: List[str]) -> Surface:
 19    geometry_layers = db.list_models()[0].list_geometry_layers()
 20    for layer in geometry_layers:
 21        for surface in layer.list_surfaces():
 22            for name in names:
 23                if surface.name.startswith(name):
 24                    return surface
 25    raise Exception(f"No surface starting by '{' or '.join(names)}' found in the database")
 26  
 27
 28def clean_empty_timelines(db: Database):
 29    timelines_to_remove = []
 30    for timeline in db.list_timelines():
 31        all_is_empty = timeline.product_animation_track().empty
 32        all_is_empty &= timeline.camera_animation_track().empty
 33        all_is_empty &= timeline.configuration_animation_track().empty
 34        all_is_empty &= timeline.empty_channels_track().empty
 35        all_is_empty &= timeline.empty_texture_track().empty
 36        for channel_track in timeline.channels_animation_tracks():
 37            all_is_empty &= channel_track.empty
 38        for texture_track in timeline.texture_animation_tracks():
 39            all_is_empty &= texture_track.empty
 40        if all_is_empty:
 41            timelines_to_remove.append(timeline)
 42    for timeline_to_remove in timelines_to_remove:
 43        timeline_to_remove.remove()
 44
 45
 46def create_bezier_path_camera_anim(db: Database) -> CameraBezierPathAnimation :
 47    anim = db.create_camera_bezier_path_animation('rotate_camera')
 48    anim.duration = 3
 49    anim.position_type = CameraBezierPathAnimationPositionType.FOLLOW_BEZIER_PATH
 50    bezier_path = db.list_models()[0].create_bezier_path('camera_bezier_path', [], [])
 51    bezier_path.translation = Vector3(0, 0.8, 0)
 52    anim.bezier_path_used_for_position = bezier_path
 53    anim.position_bezier_path_inverted = True
 54    anim.target_type = CameraBezierPathAnimationTargetType.FOLLOW_NULL
 55    anim.target_null = db.list_models()[0].create_null('camera_target_null')
 56    return anim
 57
 58
 59def create_bookmark_camera_anim(db: Database) -> CameraBookmarkAnimation :
 60    anim = db.create_camera_bookmark_animation('zoom_camera')
 61    group: CameraGroup = db.create_camera_group('CameraGroup For Animation')
 62    camera_1 = db.create_camera(group)
 63    camera_1.name = 'bookmarkCamera1'
 64    camera_1.view_from = Vector3(1,0.8,0)
 65    camera_2 = db.create_camera(group)
 66    camera_2.name = 'bookmarkCamera2'
 67    camera_2.view_from = Vector3(0.5, 0.4, 0.0)
 68    camera_1 = db.create_camera(group)
 69    camera_1.name = 'bookmarkCamera3'
 70    camera_1.view_from = Vector3(1, 0.8, 0)
 71    anim.add_camera_from_camera_group_to_animation(group=group)
 72    for camera_bookmark in anim.list_camera_bookmarks():
 73       camera_bookmark.duration = 1
 74    anim.list_camera_bookmarks()[-1].duration = 0
 75    return anim
 76
 77
 78def create_simple_channel_animation(db: Database) -> ChannelsSimpleAnimation :
 79    anim = db.create_channels_simple_animation('rotate_box')
 80    axis = find_kinematic_object(db, 'animated_axis')
 81    anim.set_axis_channel_animation(object=axis, start=0, end=360)
 82    anim.duration = 2
 83    return anim
 84
 85
 86def create_curve_channel_animation(db: Database) -> ChannelsCurveAnimation :
 87    anim = db.create_channels_curve_animation('translate_box')
 88    anim.duration = 3
 89
 90    null_obj = find_kinematic_object(db, 'animated_null')
 91    null_try_curve = anim.add_null_channel_animation(object=null_obj, channel='Translation/X')
 92    null_try_curve.insert_key_frame(1, 0.3)
 93    null_try_curve.insert_key_frame(2, 0.3)
 94    null_try_curve.insert_key_frame(3, 0.0)
 95    null_try_curve = anim.add_null_channel_animation(object=null_obj, channel='Translation/Z')
 96    null_try_curve.insert_key_frame(1, 0.3)
 97    null_try_curve.insert_key_frame(2, -0.3)
 98    null_try_curve.insert_key_frame(3, 0.0)
 99
100    return anim
101
102
103def prepare_product(db: Database):
104    product = db.list_models()[0].create_product('Product')
105    aspect_layer = product.create_aspect_layer("materials")
106    mat_group = db.create_material_group("gbuffer materials")
107
108    geometry_layers = db.list_models()[0].list_geometry_layers()
109    for layer in geometry_layers:
110        for surface in layer.list_surfaces():
111            color = surface.color
112            material = mat_group.create_standard_material("mat-" + surface.name)
113            material.diffuse_color = color;
114            aspect_layer.assign_material(surface, material) 
115
116
117def prepare_kinematics(db: Database):
118    null_obj = db.list_models()[0].create_null('animated_null')
119    axis = null_obj.create_axis('animated_axis')
120    axis.origin_point = (0, -0.35, 0)
121    axis.end_point = (0, 0.35, 0)
122    box = find_surface(db, ["Box","Cube"])
123    axis.add_surface(box)
124
125
126def timeline_example(db: Database):
127    prepare_product(db)
128    prepare_kinematics(db)
129
130    timeline = db.create_timeline('Timeline Example')
131    timeline.range_end = 10
132
133    start_time = 0
134    # create a curve channel animation and insert as a clip to the timeline in the empty track
135    advanced_channel_anim = create_curve_channel_animation(db)
136    timeline.empty_channels_track().insert_animation(start_time, advanced_channel_anim)
137
138    start_time += int(advanced_channel_anim.duration * 1000)
139    # create a simple channel animation and insert as a clip to the timeline
140    simple_channel_anim = create_simple_channel_animation(db)
141    timeline.empty_channels_track().insert_animation(start_time, simple_channel_anim)
142
143    start_time += int(simple_channel_anim.duration * 1000)
144    # create BezierPath Camera Animation and insert as a clip to the timeline at 0ms
145    bp_camera_anim = create_bezier_path_camera_anim(db)
146    timeline.camera_animation_track().insert_animation(start_time, bp_camera_anim)
147
148    start_time += int(bp_camera_anim.duration * 1000)
149    # create Camera Bookmark Animation and insert as a clip to the timeline after the previous animation
150    # N.B. the duration of the animation is in seconds and the insert clip method takes time in ms
151    bookmark_camera_anim = create_bookmark_camera_anim(db)
152    timeline.camera_animation_track().insert_animation(start_time, bookmark_camera_anim)
153
154    # create a product and insert a product key animation to the timeline
155    timeline.product_animation_track().insert_animation(0, db.list_product_key_animations()[0])
156
157    clean_empty_timelines(db)
158
159
160if __name__ == "__main__":
161    use_current_session = True
162    if use_current_session:
163        with CurrentP3DSession(fallback_port=33900) as p3d:
164            timeline_example(p3d.data)
165    else:
166        with P3DFile.create() as database:
167            timeline_example(database)
168            test_file_name = 'database_' + path.splitext(path.basename(__file__))[0] + '.p3d'
169            database.save_as(path.join(path.dirname(path.abspath(__file__)), test_file_name), True)

Download example

Executing script and playing the created timeline Created timeline

ChannelsCurveAnimation

A "ChannelsCurveAnimation" is created to animate the translation of the newly created kinematic object "Null". First, the animation is created using create_channels_curve_animation() and the total duration of the animation is set to 3 seconds using the duration attribute. Then, the channels to animate the translation along the X axis is added to the previously created animation using add_null_channel_animation() with the channel set as 'Translation/X'. Then the translated X position is defined for each keyframe using insert_key_frame(). Finally, the translation along the Y axis is created and defined similarly using add_null_channel_animation() with the channel set as 'Translation/Y' and the translated Y position for each keyframe. In this example, the null will translate in the following way : (X=0.3, Y=0.3) → (X=0.3, Y=-0.3) → (X=0.0, Y=0.0).

ChannelsSimpleAnimation

A "ChannelsSimpleAnimation" is created to animate the rotation of the newly created kinematic object "Axis". First, the animation is created using create_channels_simple_animation(). Then, the animation channel setting the start/end angle and duration for axis rotation is added to the previously created animation using set_axis_channel_animation(). In this example, the axis will rotate from 0° to 360° during 2 seconds.

CameraBezierPathAnimation

A "CameraBezierPathAnimation" is created to animate the "Camera" position and target. First, the animation is created using create_camera_bezier_path_animation() and the total duration of the animation is set to 3 seconds using the duration attribute. Then, in order to make the camera's position move along a "BezierPath", the position_type attribute is set to "CameraBezierPathAnimationPositionType.FOLLOW_BEZIER_PATH", the py:attr:bezier_path_used_for_position attribute is set to the desired "BezierPath" and the the py:attr:position_bezier_path_inverted attribute is set to True to make the camera move the inverted curve. Finally, to set the camera's target to the "Null" object, he target_type attribute is set to "CameraBezierPathAnimationTargetType.FOLLOW_NULL", the py:attr:target_null attribute is set to the desired "Null".

CameraBookmarkAnimation

A "CameraBookmarkAnimation" is created to animate the "Camera" position. First, the animation is created using create_camera_bookmark_animation(), here the total duration of the animation is set implicitely by computing the total sum of each "CameraBookmark"'s duration. In this example, the list of "CameraBookmark" is defined by creating a "CameraGroup" containing as much as "Camera" as the animation need bookmarks. Each camera's position will be used to define each bookmark's position. Then, by using add_camera_from_camera_group_to_animation() with the previously created "CameraGroup", an animation containing as many bookmarks as the cameras in the group is created. Finally, a for loop is used to define each bookmarj's duration, thus defining the total duration of the whole animation.

Insert to timeline

To insert the created animation into the timeline, the corresponding "TimelineTrack" needs to be found : "ChannelsCurveAnimation" and "ChannelsSimpleAnimation" will be inserted in empty_channels_track(), "CameraBezierPathAnimation" and "CameraBookmarkAnimation" will be inserted in camera_animation_track(), "ConfigurationKeyAnimation" will be inserted in configurationAnimationTrack(), "TextureAnimation" will be inserted in textureAnimationTracks(), "ProductKeyAnimation" will be inserted in list_product_key_animations().

Then the animation is inserted to the "TimelineTrack" as a clip by using insert_animation() with the starting time in milliseconds.

In the example a "Product" is inserted in py:meth:product_animation_track() to show how it can be done simply : the animation does not need to be created as list_product_key_animations() contains every existing product.

Clean empty Timelines

At the end of the script, timelines are looped through to check for empty tracks : is a timeline does not have a track with animation inserted, it is deleted.