算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
blender-impl-animation【设计与创意】Use when implementing AEC-specific animations -- construction sequences, camera walkthroughs, solar studies, or phasing visualizations in Blender. Prevents the common mistake of keyframing visibility instead of using proper collection visibility for construction phases. Covers NLA workflow orchestration, batch keyframe operations, and time-based visu…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
ALWAYS set the property value BEFORE calling keyframe_insert() — keyframes record the current value at call time.
ALWAYS call fcurve.update() after manually adding or modifying keyframe points via keyframe_points.add() or direct .co assignment.
ALWAYS create animation_data before accessing .action or .nla_tracks — call obj.animation_data_create() if obj.animation_data is None.
NEVER use bone.layers or pose.bone_groups in Blender 4.0+ — removed. Use BoneCollection API.
NEVER insert keyframes on objects not linked to any collection — they will exist in bpy.data but have no scene presence.
ALWAYS call scene.frame_set(frame) when you need depsgraph evaluation at that frame (e.g., for visibility keyframes, constraint evaluation). Direct scene.frame_current = frame does NOT trigger depsgraph update.
ALWAYS use bpy.data.objects.get("Name") with None-check instead of direct key access bpy.data.objects["Name"] — prevents KeyError crashes.
What type of AEC animation do you need?
├─ Construction sequence (4D BIM)?
│ ├─ Objects appear/disappear over time?
│ │ └─ WORKFLOW A: Visibility Phasing
│ └─ Objects move into position (crane sim)?
│ └─ WORKFLOW B: Transform Sequencing
├─ Camera walkthrough / flythrough?
│ ├─ Fixed path (predefined route)?
│ │ └─ WORKFLOW C: Path-Based Camera
│ └─ Point-to-point with smooth transitions?
│ └─ WORKFLOW D: Keyframed Camera
├─ Solar / daylight study?
│ └─ WORKFLOW E: Sun Path Animation
├─ Phasing visualization (design options)?
│ └─ WORKFLOW F: Collection Phasing
└─ Multiple animation clips combined?
└─ WORKFLOW G: NLA Orchestration
| Feature | Blender 3.x | Blender 4.0+ | Blender 5.x |
|---|---|---|---|
| Visibility/NLA/Path/Constraints | Available | Available | Available |
BoneCollection | Not available | NEW | Available |
bgl for overlays | Available | Deprecated | REMOVED |
Use when objects must appear/disappear at specific frames to simulate construction phases.
import bpy
# Create phase collections under the scene collection
phase_names = ["Phase_Foundation", "Phase_Structure", "Phase_Envelope", "Phase_Interior"]
phases = {}
for name in phase_names:
col = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(col)
phases[name] = col
# Move objects to phase collections
# ALWAYS unlink from current collection first to avoid duplicates
def assign_to_phase(obj, phase_collection):
for col in obj.users_collection:
col.objects.unlink(obj)
phase_collection.objects.link(obj)
def animate_phase_visibility(obj, appear_frame, disappear_frame=None):
"""Keyframe object visibility for construction phasing."""
# Hidden before appear_frame
obj.hide_viewport = True
obj.hide_render = True
obj.keyframe_insert(data_path="hide_viewport", frame=appear_frame - 1)
obj.keyframe_insert(data_path="hide_render", frame=appear_frame - 1)
# Visible at appear_frame
obj.hide_viewport = False
obj.hide_render = False
obj.keyframe_insert(data_path="hide_viewport", frame=appear_frame)
obj.keyframe_insert(data_path="hide_render", frame=appear_frame)
if disappear_frame is not None:
obj.hide_viewport = False
obj.hide_render = False
obj.keyframe_insert(data_path="hide_viewport", frame=disappear_frame - 1)
obj.keyframe_insert(data_path="hide_render", frame=disappear_frame - 1)
obj.hide_viewport = True
obj.hide_render = True
obj.keyframe_insert(data_path="hide_viewport", frame=disappear_frame)
obj.keyframe_insert(data_path="hide_render", frame=disappear_frame)
# ALWAYS set CONSTANT interpolation for boolean visibility
if obj.animation_data and obj.animation_data.action:
for fc in obj.animation_data.action.fcurves:
if fc.data_path in ("hide_viewport", "hide_render"):
for kp in fc.keyframe_points:
kp.interpolation = 'CONSTANT'
# Define schedule: {phase_name: (start_frame, end_frame or None)}
schedule = {
"Phase_Foundation": (1, None),
"Phase_Structure": (60, None),
"Phase_Envelope": (120, None),
"Phase_Interior": (180, None),
}
for phase_name, (start, end) in schedule.items():
col = bpy.data.collections.get(phase_name)
if col is None:
continue
for obj in col.objects:
animate_phase_visibility(obj, start, end)
# Set timeline
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 250
Use when objects must animate from off-screen or staging positions to final positions.
import bpy
def animate_assembly(obj, staging_location, final_location,
start_frame, end_frame, interpolation='BEZIER'):
"""Animate object from staging to final position."""
obj.location = staging_location
obj.keyframe_insert(data_path="location", frame=start_frame)
obj.location = final_location
obj.keyframe_insert(data_path="location", frame=end_frame)
# Set interpolation
if obj.animation_data and obj.animation_data.action:
for fc in obj.animation_data.action.fcurves:
if fc.data_path == "location":
for kp in fc.keyframe_points:
kp.interpolation = interpolation
# Example: Lower beam from above
beam = bpy.data.objects.get("SteelBeam_01")
if beam:
animate_assembly(
beam,
staging_location=(beam.location.x, beam.location.y, beam.location.z + 20),
final_location=(beam.location.x, beam.location.y, beam.location.z),
start_frame=60,
end_frame=90
)
Use for architectural walkthroughs following a predefined curve path.
import bpy
from mathutils import Vector
# Create camera
cam_data = bpy.data.cameras.new("WalkthroughCam")
cam_data.lens = 24 # Wide angle for architecture
cam_obj = bpy.data.objects.new("WalkthroughCam", cam_data)
bpy.context.scene.collection.objects.link(cam_obj)
# Create path curve (or use existing)
curve_data = bpy.data.curves.new("CameraPath", type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('NURBS')
# Define waypoints
points = [(0, 0, 1.7), (5, 0, 1.7), (5, 10, 1.7), (10, 10, 1.7)]
spline.points.add(count=len(points) - 1)
for i, (x, y, z) in enumerate(points):
spline.points[i].co = (x, y, z, 1.0) # NURBS uses 4D (w=1.0)
spline.use_endpoint_u = True
spline.order_u = 3
path_obj = bpy.data.objects.new("CameraPath", curve_data)
bpy.context.scene.collection.objects.link(path_obj)
# Add Follow Path constraint to camera
follow = cam_obj.constraints.new('FOLLOW_PATH')
follow.target = path_obj
follow.use_curve_follow = True
follow.forward_axis = 'FORWARD_Y' # Camera looks along Y by default
follow.up_axis = 'UP_Z'
# Animate the path offset
follow.offset = 0.0
follow.keyframe_insert(data_path="offset", frame=1)
follow.offset = -100.0 # Full path traversal
follow.keyframe_insert(data_path="offset", frame=250)
# Set LINEAR interpolation for constant speed
cam_obj_action = cam_obj.animation_data.action
for fc in cam_obj_action.fcurves:
if "offset" in fc.data_path:
for kp in fc.keyframe_points:
kp.interpolation = 'LINEAR'
# Make camera look at a specific point during walkthrough
target = bpy.data.objects.new("CamTarget", None) # Empty
bpy.context.scene.collection.objects.link(target)
target.location = (5, 5, 3)
track = cam_obj.constraints.new('TRACK_TO')
track.target = target
track.track_axis = 'TRACK_NEGATIVE_Z'
track.up_axis = 'UP_Y'
track.influence = 0.5 # Blend between path direction and target
Use for controlled camera transitions between specific viewpoints.
import bpy
from math import radians
def keyframe_camera_viewpoint(cam_obj, location, rotation_euler,
frame, interpolation='BEZIER'):
"""Set camera to a viewpoint and keyframe it."""
cam_obj.location = location
cam_obj.rotation_euler = rotation_euler
cam_obj.keyframe_insert(data_path="location", frame=frame)
cam_obj.keyframe_insert(data_path="rotation_euler", frame=frame)
if cam_obj.animation_data and cam_obj.animation_data.action:
for fc in cam_obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
if kp.co[0] == frame:
kp.interpolation = interpolation
# Define viewpoints: (location, rotation_euler, frame)
viewpoints = [
((20, -20, 10), (radians(60), 0, radians(135)), 1),
((5, -15, 5), (radians(70), 0, radians(160)), 60),
((-5, 0, 2), (radians(85), 0, radians(200)), 120),
((0, 10, 15), (radians(30), 0, radians(250)), 180),
]
cam = bpy.data.objects.get("Camera")
if cam:
for loc, rot, frame in viewpoints:
keyframe_camera_viewpoint(cam, loc, rot, frame)
Use for daylight/shadow analysis across time-of-day or seasons.
import bpy
from math import radians, sin, cos
def animate_sun_arc(sun_obj, latitude, start_hour, end_hour,
frame_start, frame_end):
"""Animate sun lamp along simplified arc. See references/examples.md for full version."""
total_frames = frame_end - frame_start
total_hours = end_hour - start_hour
for i in range(total_frames + 1):
frame = frame_start + i
t = i / total_frames
hour = start_hour + t * total_hours
hour_angle = (hour - 12) * 15
altitude = max(0, min(90, 90 - abs(latitude) + 23.5 * sin(radians(hour_angle))))
azimuth = hour_angle
distance = 100
x = distance * cos(radians(altitude)) * sin(radians(azimuth))
y = distance * cos(radians(altitude)) * cos(radians(azimuth))
z = distance * sin(radians(altitude))
sun_obj.location = (x, y, z)
sun_obj.keyframe_insert(data_path="location", frame=frame)
# LINEAR interpolation for smooth arc
if sun_obj.animation_data and sun_obj.animation_data.action:
for fc in sun_obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'LINEAR'
# Setup: create Sun light + Track To constraint pointing at origin
sun_data = bpy.data.lights.new("SunStudy", type='SUN')
sun_data.energy = 5.0
sun_obj = bpy.data.objects.new("SunStudy", sun_data)
bpy.context.scene.collection.objects.link(sun_obj)
track = sun_obj.constraints.new('TRACK_TO')
target = bpy.data.objects.new("SunTarget", None)
bpy.context.scene.collection.objects.link(target)
target.location = (0, 0, 0)
track.target = target
track.track_axis = 'TRACK_NEGATIVE_Z'
track.up_axis = 'UP_Y'
animate_sun_arc(sun_obj, latitude=52.0, start_hour=6, end_hour=18,
frame_start=1, frame_end=120)
Use when toggling entire collections to show design alternatives or construction phases.
import bpy
def animate_collection_visibility(collection, visible_start, visible_end=None):
"""Animate collection visibility using exclude flag via view layer.
NOTE: Collection.hide_viewport can be keyframed directly.
"""
# Keyframe collection visibility
collection.hide_viewport = True
collection.keyframe_insert(data_path="hide_viewport", frame=1)
collection.hide_viewport = False
collection.keyframe_insert(data_path="hide_viewport", frame=visible_start)
if visible_end is not None:
collection.hide_viewport = False
collection.keyframe_insert(data_path="hide_viewport", frame=visible_end - 1)
collection.hide_viewport = True
collection.keyframe_insert(data_path="hide_viewport", frame=visible_end)
# CONSTANT interpolation for boolean toggle
if collection.animation_data and collection.animation_data.action:
for fc in collection.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = 'CONSTANT'
# Example: Phase schedule
phases = {
"Demolition": (1, 30),
"Foundation": (30, 90),
"Structure": (90, 150),
"Envelope": (150, 210),
"Finishes": (210, None),
}
for name, (start, end) in phases.items():
col = bpy.data.collections.get(name)
if col is not None:
animate_collection_visibility(col, start, end)
Use when combining multiple animation actions into a sequenced timeline. See references/examples.md for full NLA example.
import bpy
def create_visibility_action(action_name):
"""Create a self-contained visibility action."""
action = bpy.data.actions.new(name=action_name)
fc = action.fcurves.new(data_path="hide_viewport")
fc.keyframe_points.add(count=2)
fc.keyframe_points[0].co = (0.0, 1.0) # Hidden
fc.keyframe_points[1].co = (1.0, 0.0) # Visible
for kp in fc.keyframe_points:
kp.interpolation = 'CONSTANT'
fc.update()
return action
def sequence_on_nla(obj, actions_schedule):
"""Place actions on NLA tracks. actions_schedule: list of (action, start_frame, track_name)."""
anim_data = obj.animation_data if obj.animation_data else obj.animation_data_create()
for action, start_frame, track_name in actions_schedule:
track = anim_data.nla_tracks.new()
track.name = track_name
strip = track.strips.new(name=action.name, start=start_frame, action=action)
strip.blend_type = 'REPLACE'
strip.use_auto_blend = False
# Batch apply: staggered appearance via NLA
collection = bpy.data.collections.get("Structure")
if collection:
for i, obj in enumerate(collection.objects):
action = create_visibility_action(f"Appear_{obj.name}")
sequence_on_nla(obj, [(action, i * 10, f"Phase_{obj.name}")])
import bpy
def batch_keyframe_property(objects, data_path, frame, index=-1):
"""Insert keyframes for the current value on multiple objects."""
for obj in objects:
try:
obj.keyframe_insert(data_path=data_path, frame=frame, index=index)
except TypeError:
pass # Property not animatable on this object type
def batch_set_interpolation(objects, interpolation='LINEAR'):
"""Set interpolation type on all keyframes of given objects."""
for obj in objects:
if obj.animation_data and obj.animation_data.action:
for fc in obj.animation_data.action.fcurves:
for kp in fc.keyframe_points:
kp.interpolation = interpolation
def batch_clear_animation(objects):
"""Remove all animation data from objects."""
for obj in objects:
if obj.animation_data:
obj.animation_data_clear()
import bpy
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 250
scene.render.filepath = "//render/construction_" # // = blend-relative
scene.render.image_settings.file_format = 'PNG' # PNG sequence (resumable)
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.resolution_percentage = 100
scene.render.fps = 24
# For video: file_format='FFMPEG', ffmpeg.format='MPEG4', ffmpeg.codec='H264'
# bpy.ops.render.render(animation=True)