Gsd-skill-creator creative-pipeline
Generates Blender Python scripts for infrastructure visualization and ffmpeg commands for video production and social media export.
git clone https://github.com/Tibsfox/gsd-skill-creator
T=$(mktemp -d) && git clone --depth=1 https://github.com/Tibsfox/gsd-skill-creator "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/physical-infrastructure/skills/creative-pipeline" ~/.claude/skills/tibsfox-gsd-skill-creator-creative-pipeline && rm -rf "$T"
skills/physical-infrastructure/skills/creative-pipeline/SKILL.mdCreative Pipeline Skill
At a Glance
Generate Blender Python (bpy) scripts for 3D infrastructure visualization and ffmpeg command sequences for video production and social media export — turning technical engineering designs into communication-ready visual deliverables.
Activation: After engineering design is verified and documented. InfrastructureRequest with outputFormat including 'render'. Stakeholder presentations, client deliverables, social media content creation.
Output types:
- Blender bpy scripts: Python scripts that run in Blender's Scripting workspace to create pipe geometry, equipment meshes, materials, lighting, camera paths, and animations.
- ffmpeg commands: Shell commands that assemble rendered frames into video and export to social media formats (YouTube, Instagram Reel, Twitter/X, thumbnail).
Prerequisites:
- Blender 3.6+ installed (free from blender.org)
- ffmpeg installed and in PATH (free from ffmpeg.org)
- Completed engineering design with dimensions and layout from blueprint engine
Quick-start — Blender script generation:
import bpy # Clear scene, set units to metric, configure Cycles renderer bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete() bpy.context.scene.unit_settings.system = 'METRIC' bpy.context.scene.render.engine = 'CYCLES'
Quick-start — ffmpeg social media export:
# Assemble rendered frames into video ffmpeg -framerate 30 -i render_%04d.png -c:v libx264 -pix_fmt yuv420p walkthrough.mp4 # Export for YouTube (1920x1080, 8 Mbps) ffmpeg -i walkthrough.mp4 -vf "scale=1920:1080" -b:v 8M youtube.mp4
Quick routing:
- Creating pipe geometry? See Pipe Routing as Bezier Curves
- Modeling equipment? See Equipment as Parametric Meshes
- Setting up materials? See Infrastructure Materials
- Camera animation? See Camera Paths
- Flow/thermal animation? See Infrastructure Animations
- Video production? See ffmpeg Assembly Pipeline
Blender Scene Generation (CREAT-01)
Scene Setup and Initialization
Every infrastructure visualization script begins with scene initialization:
import bpy import math # ── Clear default scene ────────────────────────────────────── bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete() # ── Set units to metric (millimeters for infrastructure) ───── bpy.context.scene.unit_settings.system = 'METRIC' bpy.context.scene.unit_settings.length_unit = 'MILLIMETERS' # ── Set render engine ──────────────────────────────────────── # EEVEE for fast preview, Cycles for photorealistic final render bpy.context.scene.render.engine = 'CYCLES' bpy.context.scene.cycles.samples = 128 # Preview: 128, Final: 256-512 # ── Set render resolution ──────────────────────────────────── bpy.context.scene.render.resolution_x = 1920 bpy.context.scene.render.resolution_y = 1080 bpy.context.scene.render.resolution_percentage = 100 # ── Set output format ──────────────────────────────────────── bpy.context.scene.render.image_settings.file_format = 'PNG' bpy.context.scene.render.filepath = '//renders/' # Relative to .blend file
Lighting Setup
Three standard lighting configurations for infrastructure visualization:
Studio lighting (3-point):
# Key light — main illumination at 45 degrees bpy.ops.object.light_add(type='AREA', location=(5, -5, 8)) key_light = bpy.context.active_object key_light.name = "Key_Light" key_light.data.energy = 1000 # Watts key_light.data.size = 3.0 # Soft shadow (larger = softer) # Fill light — reduce shadow harshness, opposite side at lower intensity bpy.ops.object.light_add(type='AREA', location=(-4, -3, 5)) fill_light = bpy.context.active_object fill_light.name = "Fill_Light" fill_light.data.energy = 400 # 40% of key light # Rim light — edge definition from behind bpy.ops.object.light_add(type='AREA', location=(0, 6, 6)) rim_light = bpy.context.active_object rim_light.name = "Rim_Light" rim_light.data.energy = 600
HDRI environment (realistic ambient):
# Load HDRI for realistic outdoor/sky lighting world = bpy.context.scene.world world.use_nodes = True nodes = world.node_tree.nodes links = world.node_tree.links nodes.clear() bg_node = nodes.new(type='ShaderNodeBackground') env_node = nodes.new(type='ShaderNodeTexEnvironment') output_node = nodes.new(type='ShaderNodeOutputWorld') # Load HDRI file (download from polyhaven.com — free CC0) env_node.image = bpy.data.images.load('//hdri/industrial_hall.hdr') bg_node.inputs['Strength'].default_value = 1.0 links.new(env_node.outputs['Color'], bg_node.inputs['Color']) links.new(bg_node.outputs['Background'], output_node.inputs['Surface'])
Volumetric (for coolant flow visualization with god rays):
# Add volumetric scatter to world for atmospheric depth vol_node = nodes.new(type='ShaderNodeVolumePrincipled') vol_node.inputs['Density'].default_value = 0.01 # Subtle atmosphere vol_node.inputs['Anisotropy'].default_value = 0.3 # Forward scattering links.new(vol_node.outputs['Volume'], output_node.inputs['Volume'])
Pipe Routing as Bezier Curves (CREAT-02)
Creating Pipe Geometry
Pipes are modeled as Bezier curves with a circular bevel profile. The bevel_depth sets the pipe outer radius.
# Create pipe as Bezier curve with circular cross-section bpy.ops.curve.primitive_bezier_curve_add() curve = bpy.context.active_object curve.name = "CoolingPipe_Supply" # Configure curve as 3D pipe curve_data = curve.data curve_data.dimensions = '3D' curve_data.bevel_depth = 0.0445 # Outer radius in meters (3" NPS = 88.9mm OD) curve_data.bevel_resolution = 8 # Smoothness of circular cross-section curve_data.fill_mode = 'FULL' # Set pipe route using spline control points spline = curve_data.splines[0] spline.bezier_points[0].co = (0, 0, 3.0) # Start point (meters) spline.bezier_points[1].co = (5.0, 0, 3.0) # End point (meters) # Handle positions control curve direction (tangent at each point) spline.bezier_points[0].handle_left = (-0.5, 0, 3.0) spline.bezier_points[0].handle_right = (1.0, 0, 3.0) spline.bezier_points[1].handle_left = (4.0, 0, 3.0) spline.bezier_points[1].handle_right = (5.5, 0, 3.0)
Adding Direction Changes (Elbows)
For pipe direction changes, add intermediate Bezier points with handle angles:
# Add a point for a 90-degree direction change (elbow) spline.bezier_points.add(1) # Now 3 points total # Route: horizontal run -> 90-deg down -> vertical drop spline.bezier_points[0].co = (0, 0, 3.0) # Horizontal start spline.bezier_points[1].co = (4.0, 0, 3.0) # Corner (elbow location) spline.bezier_points[2].co = (4.0, 0, 0.5) # Vertical end (drops to 0.5m) # Set handles at corner for sharp 90-degree turn spline.bezier_points[1].handle_left = (3.5, 0, 3.0) # Approach from left spline.bezier_points[1].handle_right = (4.0, 0, 2.5) # Exit downward # Handle type: AUTO for smooth curves, FREE for precise control for point in spline.bezier_points: point.handle_left_type = 'FREE' point.handle_right_type = 'FREE'
NPS to Blender bevel_depth Conversion Table
The bevel_depth parameter is the pipe outer radius in meters (OD / 2):
| NPS (inch) | OD (mm) | OD (m) | bevel_depth (m) | Common Use |
|---|---|---|---|---|
| 1/2" | 21.3 | 0.0213 | 0.0107 | Small branch lines |
| 3/4" | 26.7 | 0.0267 | 0.0134 | Residential supply |
| 1" | 33.4 | 0.0334 | 0.0167 | Branch distribution |
| 1-1/2" | 48.3 | 0.0483 | 0.0242 | Medium branch lines |
| 2" | 60.3 | 0.0603 | 0.0302 | CDU branch connections |
| 3" | 88.9 | 0.0889 | 0.0445 | Supply/return headers |
| 4" | 114.3 | 0.1143 | 0.0572 | Main distribution |
| 6" | 168.3 | 0.1683 | 0.0842 | Large distribution |
| 8" | 219.1 | 0.2191 | 0.1096 | Plant piping |
| 10" | 273.1 | 0.2731 | 0.1366 | Large plant piping |
| 12" | 323.8 | 0.3238 | 0.1619 | Municipal/plant mains |
Multi-Pipe Routing
For parallel supply/return headers, offset the second pipe by the pipe OD plus insulation thickness:
# Create return pipe offset from supply pipe offset_y = 0.20 # 200mm center-to-center (3" pipe + insulation + clearance) bpy.ops.curve.primitive_bezier_curve_add() return_pipe = bpy.context.active_object return_pipe.name = "CoolingPipe_Return" return_pipe.data.dimensions = '3D' return_pipe.data.bevel_depth = 0.0445 # Same size as supply spline_return = return_pipe.data.splines[0] spline_return.bezier_points[0].co = (0, offset_y, 3.0) spline_return.bezier_points[1].co = (5.0, offset_y, 3.0)
Equipment as Parametric Meshes (CREAT-02)
Creating Equipment from Dimensions
Equipment is modeled as scaled cube primitives positioned at their design locations:
# Create CDU enclosure (600mm x 800mm x 1800mm) bpy.ops.mesh.primitive_cube_add(location=(2.0, 0, 0.9)) # Center at half-height cdu = bpy.context.active_object cdu.name = "CDU_101" cdu.scale = (0.3, 0.4, 0.9) # Half-extents in meters (600/2, 800/2, 1800/2) bpy.ops.object.transform_apply(scale=True) # Apply scale to mesh data # Create server rack (42U: 600mm x 1000mm x 2000mm) bpy.ops.mesh.primitive_cube_add(location=(2.0, 2.0, 1.0)) rack = bpy.context.active_object rack.name = "Rack_A01" rack.scale = (0.3, 0.5, 1.0) bpy.ops.object.transform_apply(scale=True)
Standard Equipment Dimensions for Data Center
Use these dimensions for parametric mesh generation:
| Equipment | W x D x H (mm) | Blender scale (x, y, z) | Notes |
|---|---|---|---|
| Server rack (42U) | 600 x 1000 x 2000 | (0.3, 0.5, 1.0) | Standard EIA-310-E |
| Server rack (48U) | 600 x 1200 x 2200 | (0.3, 0.6, 1.1) | Deep rack for GPU servers |
| CDU (small) | 600 x 800 x 1800 | (0.3, 0.4, 0.9) | 30-60 kW range |
| CDU (large) | 800 x 1200 x 2000 | (0.4, 0.6, 1.0) | 100+ kW range |
| UPS (100 kVA) | 800 x 1000 x 1900 | (0.4, 0.5, 0.95) | Floor-standing |
| UPS (500 kVA) | 1200 x 1000 x 2000 | (0.6, 0.5, 1.0) | Large frame |
| Floor PDU | 600 x 800 x 1800 | (0.3, 0.4, 0.9) | With transformer |
| Pump (centrifugal) | 500 x 800 x 600 | (0.25, 0.4, 0.3) | Horizontal base-mounted |
| Panel board | 400 x 200 x 600 | (0.2, 0.1, 0.3) | Wall-mounted |
| Transformer (dry) | 1000 x 800 x 1200 | (0.5, 0.4, 0.6) | 500-1000 kVA |
| Cable tray (section) | 300 x 100 x 3000 | (0.15, 0.05, 1.5) | 12" ladder type |
Equipment Detail — Louvers and Airflow Indicators
Add visual detail to equipment faces to indicate airflow direction:
# Add edge loops to front face for louver appearance bpy.context.view_layer.objects.active = cdu bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.loopcut_slide( MESH_OT_loopcut={'number_cuts': 8, 'smoothness': 0}, TRANSFORM_OT_edge_slide={'value': 0} ) bpy.ops.object.mode_set(mode='OBJECT')
Infrastructure Materials (CREAT-02)
Material Overview
Seven PBR (Physically Based Rendering) materials cover common infrastructure components. All use the Principled BSDF shader node for physically correct rendering.
| Material | Base Color (RGB) | Metallic | Roughness | Special | Engineering Use |
|---|---|---|---|---|---|
| Copper pipe | (0.83, 0.48, 0.22) | 1.0 | 0.2 | — | ASTM B88 tubing, heat exchangers |
| PVC pipe | (0.9, 0.9, 0.88) | 0.0 | 0.85 | — | DWV piping, conduit |
| Brushed steel | (0.6, 0.6, 0.62) | 1.0 | 0.3 | — | ASTM A53 pipe, structural steel |
| Insulation | (0.72, 0.62, 0.35) | 0.0 | 1.0 | — | Fiberglass/mineral wool jacket |
| Coolant | (0.2, 0.5, 1.0) | 0.0 | 0.0 | IOR=1.33, transmission=0.8 | Chilled water, glycol visualization |
| Concrete | (0.5, 0.5, 0.5) | 0.0 | 0.95 | — | Slab, walls, raised floor |
| Cable tray | (0.75, 0.75, 0.72) | 0.8 | 0.4 | — | Galvanized steel per NEMA VE 1 |
Material Assignment
# Assign material to pipe object mat = bpy.data.materials.get("Infra_CopperPipe") if mat is None: # Create material if not already loaded from blender-materials.py mat = bpy.data.materials.new(name="Infra_CopperPipe") mat.use_nodes = True bsdf = mat.node_tree.nodes.get('Principled BSDF') bsdf.inputs['Base Color'].default_value = (0.83, 0.48, 0.22, 1.0) bsdf.inputs['Metallic'].default_value = 1.0 bsdf.inputs['Roughness'].default_value = 0.2 # Assign to object pipe_obj = bpy.data.objects['CoolingPipe_Supply'] if pipe_obj.data.materials: pipe_obj.data.materials[0] = mat else: pipe_obj.data.materials.append(mat)
Full material setup scripts: All 7 materials with complete Principled BSDF configuration, helper functions, and engineering context are available in
@references/blender-materials.py. Paste the entire script into Blender's Scripting workspace and run to create all materials at once.
Camera Paths (CREAT-03)
Orbital Overview Camera
Camera orbits the scene center on a circular path — best for showing overall system layout:
import math # Create camera bpy.ops.object.camera_add(location=(0, -10, 5)) camera = bpy.context.active_object camera.name = "Camera_Orbital" bpy.context.scene.camera = camera # Set as active camera # Create circular orbit path bpy.ops.curve.primitive_bezier_circle_add(radius=10, location=(0, 0, 3)) orbit_path = bpy.context.active_object orbit_path.name = "Camera_Orbit_Path" # Add Follow Path constraint to camera constraint = camera.constraints.new(type='FOLLOW_PATH') constraint.target = orbit_path constraint.use_fixed_location = False constraint.forward_axis = 'TRACK_NEGATIVE_Z' constraint.up_axis = 'UP_Y' # Add Track To constraint (camera always looks at scene center) track = camera.constraints.new(type='TRACK_TO') track.target = bpy.data.objects.get('CDU_101') # Or create an empty at center track.track_axis = 'TRACK_NEGATIVE_Z' track.up_axis = 'UP_Y' # Set animation timeline orbit_path.data.path_duration = 250 # 10 seconds at 25 fps bpy.context.scene.frame_start = 1 bpy.context.scene.frame_end = 250 # Animate path evaluation orbit_path.data.eval_time = 0 orbit_path.data.keyframe_insert('eval_time', frame=1) orbit_path.data.eval_time = 250 orbit_path.data.keyframe_insert('eval_time', frame=250)
Linear Walkthrough Camera
Camera moves along a straight path through the facility at eye level — best for showing hot/cold aisle layout:
# Create walkthrough path (straight line through data hall) bpy.ops.curve.primitive_bezier_curve_add() walk_path = bpy.context.active_object walk_path.name = "Camera_Walk_Path" walk_path.data.dimensions = '3D' spline = walk_path.data.splines[0] spline.bezier_points[0].co = (0, -8, 1.7) # Start: entrance at eye level (1.7m) spline.bezier_points[1].co = (0, 12, 1.7) # End: far side of data hall # Smooth handles for gentle camera motion spline.bezier_points[0].handle_right = (0, -4, 1.7) spline.bezier_points[1].handle_left = (0, 8, 1.7) # Create camera and attach to path bpy.ops.object.camera_add() walk_cam = bpy.context.active_object walk_cam.name = "Camera_Walkthrough" constraint = walk_cam.constraints.new(type='FOLLOW_PATH') constraint.target = walk_path constraint.forward_axis = 'FORWARD_Y' constraint.up_axis = 'UP_Z' # Animate over 15 seconds (375 frames at 25 fps) walk_path.data.path_duration = 375 bpy.context.scene.frame_end = 375 walk_path.data.eval_time = 0 walk_path.data.keyframe_insert('eval_time', frame=1) walk_path.data.eval_time = 375 walk_path.data.keyframe_insert('eval_time', frame=375)
Detail Zoom Camera
Camera starts at overview distance and animates to focus on a specific component — best for highlighting equipment:
# Create zoom path from far to close bpy.ops.curve.primitive_bezier_curve_add() zoom_path = bpy.context.active_object zoom_path.name = "Camera_Zoom_Path" zoom_path.data.dimensions = '3D' target_pos = (2.0, 0, 0.9) # CDU center position spline = zoom_path.data.splines[0] spline.bezier_points[0].co = (10, -8, 6) # Start: overview (far, high) spline.bezier_points[1].co = (3.0, -1.5, 1.2) # End: detail (close, eye level) # Create camera with Track To constraint aimed at equipment bpy.ops.object.camera_add() zoom_cam = bpy.context.active_object zoom_cam.name = "Camera_DetailZoom" path_constraint = zoom_cam.constraints.new(type='FOLLOW_PATH') path_constraint.target = zoom_path track_constraint = zoom_cam.constraints.new(type='TRACK_TO') target_empty = bpy.data.objects.new("Zoom_Target", None) bpy.context.collection.objects.link(target_empty) target_empty.location = target_pos track_constraint.target = target_empty track_constraint.track_axis = 'TRACK_NEGATIVE_Z' track_constraint.up_axis = 'UP_Y' # Animate over 5 seconds with ease-in/ease-out zoom_path.data.path_duration = 125 # 5 sec at 25 fps bpy.context.scene.frame_end = 125
Camera Path Summary
| Path Type | Use Case | Duration | Camera Height | Orbit Radius |
|---|---|---|---|---|
| Orbital overview | Full system layout | 8-12 sec | 3-5m above center | 10-20m (scene dependent) |
| Linear walkthrough | Hot/cold aisle tour | 12-20 sec | 1.7m (eye level) | N/A (straight line) |
| Detail zoom | Equipment spotlight | 4-8 sec | Starts high, ends eye-level | N/A (linear approach) |
Infrastructure Animations (CREAT-01, CREAT-03)
Coolant Flow Animation (Particle System)
Particles follow the pipe curve path to visualize fluid flow:
# Get the supply pipe curve object pipe_obj = bpy.data.objects['CoolingPipe_Supply'] # Convert curve to mesh temporarily for particle emission # (Particles emit from mesh faces, not curve surfaces) bpy.context.view_layer.objects.active = pipe_obj bpy.ops.object.convert(target='MESH', keep_original=True) pipe_mesh = bpy.context.active_object pipe_mesh.name = "CoolingPipe_Supply_Mesh" # Create small sphere for coolant droplet visualization bpy.ops.mesh.primitive_uv_sphere_add(radius=0.01, location=(0, 0, -10)) droplet = bpy.context.active_object droplet.name = "Coolant_Droplet" # Assign coolant material to droplet coolant_mat = bpy.data.materials.get("Infra_Coolant") if coolant_mat: droplet.data.materials.append(coolant_mat) # Add particle system to pipe mesh bpy.context.view_layer.objects.active = pipe_mesh bpy.ops.object.particle_system_add() ps = pipe_mesh.particle_systems[0] ps_settings = ps.settings ps_settings.count = 200 # Number of particles (more = denser flow) ps_settings.lifetime = 80 # Frames each particle lives ps_settings.emit_from = 'FACE' # Emit from pipe surface ps_settings.render_type = 'OBJECT' ps_settings.instance_object = droplet ps_settings.particle_size = 1.0 ps_settings.size_random = 0.2 # Slight size variation for realism # Set velocity along pipe normal (simulates flow direction) ps_settings.normal_factor = 0.5 # Speed along surface normal ps_settings.factor_random = 0.1 # Slight randomness
Electrical Current Animation (Emissive Pulse)
Animate emission strength along a conductor to simulate current flow:
# Create or get copper conductor material with emission node mat = bpy.data.materials.new(name="Copper_Conductor_Animated") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links nodes.clear() # Principled BSDF for base appearance output = nodes.new(type='ShaderNodeOutputMaterial') output.location = (600, 0) bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') bsdf.location = (0, 0) bsdf.inputs['Base Color'].default_value = (0.83, 0.48, 0.22, 1.0) bsdf.inputs['Metallic'].default_value = 1.0 bsdf.inputs['Roughness'].default_value = 0.2 # Add emission for glow effect bsdf.inputs['Emission Color'].default_value = (1.0, 0.8, 0.2, 1.0) # Warm yellow bsdf.inputs['Emission Strength'].default_value = 0 links.new(bsdf.outputs['BSDF'], output.inputs['Surface']) # Animate emission strength: off -> bright -> off (pulse effect) bpy.context.scene.frame_set(1) bsdf.inputs['Emission Strength'].default_value = 0 bsdf.inputs['Emission Strength'].keyframe_insert('default_value', frame=1) bpy.context.scene.frame_set(15) bsdf.inputs['Emission Strength'].default_value = 10 # Peak brightness bsdf.inputs['Emission Strength'].keyframe_insert('default_value', frame=15) bpy.context.scene.frame_set(30) bsdf.inputs['Emission Strength'].default_value = 0 bsdf.inputs['Emission Strength'].keyframe_insert('default_value', frame=30) # For repeating pulse: set keyframe extrapolation to cycle # (requires Graph Editor > Channel > Extrapolation Mode > Make Cyclic)
Thermal Mapping Animation (Color Ramp from Temperature Data)
Color-code surfaces by temperature using a custom attribute driven by calculation data:
# Create thermal visualization material mat_thermal = bpy.data.materials.new(name="Thermal_Mapping") mat_thermal.use_nodes = True nodes = mat_thermal.node_tree.nodes links = mat_thermal.node_tree.links nodes.clear() output = nodes.new(type='ShaderNodeOutputMaterial') output.location = (800, 0) bsdf = nodes.new(type='ShaderNodeBsdfPrincipled') bsdf.location = (400, 0) # Color ramp: blue (cold) -> green -> yellow -> red (hot) color_ramp = nodes.new(type='ShaderNodeValToRGB') color_ramp.location = (0, 0) color_ramp.color_ramp.elements[0].position = 0.0 color_ramp.color_ramp.elements[0].color = (0.0, 0.0, 1.0, 1.0) # Blue (cold) color_ramp.color_ramp.elements.new(0.33) color_ramp.color_ramp.elements[1].color = (0.0, 1.0, 0.0, 1.0) # Green color_ramp.color_ramp.elements.new(0.66) color_ramp.color_ramp.elements[2].color = (1.0, 1.0, 0.0, 1.0) # Yellow color_ramp.color_ramp.elements[3].position = 1.0 color_ramp.color_ramp.elements[3].color = (1.0, 0.0, 0.0, 1.0) # Red (hot) # Attribute node to read temperature values from mesh custom attribute attr_node = nodes.new(type='ShaderNodeAttribute') attr_node.location = (-200, 0) attr_node.attribute_name = "temperature" # Custom per-vertex attribute links.new(attr_node.outputs['Fac'], color_ramp.inputs['Fac']) links.new(color_ramp.outputs['Color'], bsdf.inputs['Base Color']) links.new(bsdf.outputs['BSDF'], output.inputs['Surface']) # To apply temperature data to vertices: # mesh.attributes.new(name='temperature', type='FLOAT', domain='POINT') # mesh.attributes['temperature'].data[vertex_index].value = normalized_temp (0-1)
Animation Summary
| Animation | Technique | Visual Effect | Frame Count |
|---|---|---|---|
| Coolant flow | Particle system on pipe mesh | Blue spheres flowing through pipes | 200-500 (8-20s) |
| Electrical current | Emissive keyframe animation | Glowing pulse along conductors | 60-150 (2-6s per cycle) |
| Thermal mapping | Color ramp + vertex attribute | Blue-to-red heat map on surfaces | Static or animated over time |
ffmpeg Assembly Pipeline (CREAT-04, CREAT-05)
Step 1: Assemble Rendered Frames into Base Video
After rendering animation frames from Blender (File > Render > Render Animation), assemble into video:
# Assemble PNG frames into H.264 video # -framerate: must match Blender render settings (25 or 30 fps) # -crf 18: visually lossless quality (lower = better, 0 = lossless, 23 = default) ffmpeg -framerate 30 -i render_%04d.png \ -c:v libx264 \ -preset slow \ -crf 18 \ -pix_fmt yuv420p \ walkthrough.mp4
Step 2: Add Narration Audio (Optional)
# Combine video with narration audio track ffmpeg -i walkthrough.mp4 -i narration.mp3 \ -c:v copy \ -c:a aac \ -b:a 192k \ -shortest \ final.mp4
Step 3: Social Media Exports
Four export presets for common platforms (full scripts in
@references/ffmpeg-presets.sh):
YouTube (16:9, 1920x1080, H.264):
ffmpeg -i final.mp4 \ -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \ -c:v libx264 -preset slow -crf 18 -b:v 8M -maxrate 10M -bufsize 20M \ -pix_fmt yuv420p -c:a aac -b:a 192k \ youtube_export.mp4
Instagram Reel (9:16 portrait, 1080x1920):
ffmpeg -i final.mp4 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black" \ -c:v libx264 -preset medium -crf 23 -b:v 3.5M \ -pix_fmt yuv420p -c:a aac -b:a 128k -t 60 \ instagram_reel.mp4
Twitter/X (16:9, 1280x720, max 2:20):
ffmpeg -i final.mp4 \ -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" \ -c:v libx264 -preset medium -crf 23 -b:v 5M \ -pix_fmt yuv420p -c:a aac -b:a 128k -t 140 \ twitter_export.mp4
Thumbnail (JPEG still frame, 1280x720):
# Extract a single frame at 5-second mark ffmpeg -i final.mp4 \ -ss 00:00:05 \ -frames:v 1 \ -vf "scale=1280:720" \ -q:v 2 \ thumbnail.jpg
Social Media Format Summary
| Platform | Resolution | Aspect Ratio | Max Duration | Bitrate | Codec |
|---|---|---|---|---|---|
| YouTube | 1920x1080 | 16:9 | Unlimited | 8 Mbps | H.264 |
| Instagram Reel | 1080x1920 | 9:16 | 60s (feed) / 90s (reel) | 3.5 Mbps | H.264 |
| Twitter/X | 1280x720 | 16:9 | 2:20 | 5 Mbps | H.264 |
| Thumbnail | 1280x720 | 16:9 | N/A (still) | N/A | JPEG |
| 1920x1080 | 16:9 | 10 min | 8 Mbps | H.264 |
Render Settings Recommendations
| Use Case | Render Engine | Samples | Resolution | Est. Time/Frame | Notes |
|---|---|---|---|---|---|
| Preview | EEVEE | N/A | 1280x720 | ~1s | Real-time preview, good for iteration |
| Draft | Cycles | 64 | 1920x1080 | ~15s | Noisy but compositionally correct |
| Final | Cycles | 256 | 1920x1080 | ~60s | Production quality |
| Thumbnail | Cycles | 512 | 2560x1440 | ~120s | High quality for downsampling |
Deep Reference
Complex Multi-Pipe Systems
For systems with many pipes (supply, return, condensate, makeup water), organize by system and color-code:
# System color convention (matching ASME A13.1 pipe identification) PIPE_SYSTEMS = { 'chilled_water_supply': {'color': (0.0, 0.5, 1.0, 1.0), 'label': 'CWS'}, 'chilled_water_return': {'color': (0.0, 0.8, 0.3, 1.0), 'label': 'CWR'}, 'condenser_supply': {'color': (1.0, 0.6, 0.0, 1.0), 'label': 'CS'}, 'condenser_return': {'color': (1.0, 0.3, 0.0, 1.0), 'label': 'CR'}, 'condensate_drain': {'color': (0.5, 0.5, 0.5, 1.0), 'label': 'CD'}, } def create_pipe_material(system_name): config = PIPE_SYSTEMS[system_name] mat = bpy.data.materials.new(name=f"Pipe_{config['label']}") mat.use_nodes = True bsdf = mat.node_tree.nodes.get('Principled BSDF') bsdf.inputs['Base Color'].default_value = config['color'] bsdf.inputs['Metallic'].default_value = 0.5 bsdf.inputs['Roughness'].default_value = 0.4 return mat
Advanced PBR Materials (Bump Maps and Displacement)
For photorealistic concrete and insulation surfaces, add procedural bump mapping:
# Concrete with procedural bump texture mat = bpy.data.materials.get("Infra_Concrete") nodes = mat.node_tree.nodes links = mat.node_tree.links # Add noise texture for surface irregularity noise = nodes.new(type='ShaderNodeTexNoise') noise.inputs['Scale'].default_value = 50.0 noise.inputs['Detail'].default_value = 6.0 noise.inputs['Roughness'].default_value = 0.6 # Add bump node to convert texture to normal perturbation bump = nodes.new(type='ShaderNodeBump') bump.inputs['Strength'].default_value = 0.3 # Subtle bump bump.inputs['Distance'].default_value = 0.001 bsdf = nodes.get('Principled BSDF') links.new(noise.outputs['Fac'], bump.inputs['Height']) links.new(bump.outputs['Normal'], bsdf.inputs['Normal'])
Camera Rig for Automated Pan-and-Scan
For large data halls requiring multiple camera angles:
# Define camera waypoints (x, y, z, look_at_x, look_at_y, look_at_z) WAYPOINTS = [ (0, -10, 5, 0, 0, 2), # Overview from entrance (0, 0, 1.7, 3, 0, 1), # Aisle view looking at rack row (3, 0, 1.2, 3, 0, 0.9), # CDU detail (0, 5, 1.7, -3, 5, 1), # Opposite aisle (0, 10, 5, 0, 5, 2), # Overview from far end ] # Calculate total frames (5 seconds per segment) fps = 25 seconds_per_segment = 5 total_frames = len(WAYPOINTS) * fps * seconds_per_segment
ACES Color Management
For physically correct HDR rendering with proper color space:
# Set color management to ACES (if available in Blender build) bpy.context.scene.display_settings.display_device = 'sRGB' bpy.context.scene.view_settings.view_transform = 'Filmic' # Or 'AgX' in Blender 4.x bpy.context.scene.view_settings.look = 'High Contrast' bpy.context.scene.view_settings.exposure = 0.0 bpy.context.scene.view_settings.gamma = 1.0
Geometry Nodes for Parametric Rack Arrays
For generating rows of identical racks automatically:
# Instance racks along a line using Array modifier rack_template = bpy.data.objects['Rack_A01'] array_mod = rack_template.modifiers.new(name="Row_Array", type='ARRAY') array_mod.count = 10 # 10 racks per row array_mod.relative_offset_displace = (0, 1.2, 0) # 1.2m pitch (600mm rack + 600mm aisle) array_mod.use_relative_offset = True
Batch Rendering Script
For rendering all camera angles and animation sequences:
# Render all camera paths sequentially cameras = ['Camera_Orbital', 'Camera_Walkthrough', 'Camera_DetailZoom'] for cam_name in cameras: camera = bpy.data.objects.get(cam_name) if camera: bpy.context.scene.camera = camera bpy.context.scene.render.filepath = f'//renders/{cam_name}/' bpy.ops.render.render(animation=True) print(f"Rendered: {cam_name}")
Output Passes for Compositing
Configure render passes for post-processing and compositing:
# Enable useful render passes view_layer = bpy.context.scene.view_layers[0] view_layer.use_pass_diffuse_color = True view_layer.use_pass_glossy_color = True view_layer.use_pass_emit = True view_layer.use_pass_ambient_occlusion = True view_layer.use_pass_z = True # Depth pass for DOF in compositing # Enable denoising (Cycles only) bpy.context.scene.cycles.use_denoising = True bpy.context.scene.cycles.denoiser = 'OPENIMAGEDENOISE'
Reference Files
| Reference | Purpose | Usage |
|---|---|---|
| @references/blender-materials.py | All 7 infrastructure materials as ready-to-paste bpy script | Paste into Blender Scripting workspace and run |
| @references/ffmpeg-presets.sh | All 4 social media export commands as bash functions | Source the file or copy individual commands |
Creative Pipeline Skill v1.0.0 — Physical Infrastructure Engineering Pack Phase 440-02 | References: Blender bpy API, ffmpeg, ASME A13.1 (pipe identification) This skill generates visualization scripts only — no engineering decisions are made.