Creative pipeline
Skill Tibsfox/gsd-skill-creator/skills/physical-infrastructure/skills/creative-pipeline
Introduces a comprehensive agent-based framework for guided software development (GSD)
npx -y skills add Tibsfox/gsd-skill-creator --skill creative-pipelineAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
What its author says it does
Copied from the file, not written here
Generates Blender Python scripts for infrastructure visualization and ffmpeg commands for video production and social media export.
SKILL.md
31.9 KB, as published. Nobody here has run it
Creative 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.