Skip to content

Merge branch 'main' of https://github.com/OpenSourceEcology/LifeTrac #40

Merge branch 'main' of https://github.com/OpenSourceEcology/LifeTrac

Merge branch 'main' of https://github.com/OpenSourceEcology/LifeTrac #40

name: Generate Animation GIF
on:
push:
branches: [ main, master ]
paths:
- 'LifeTrac-v25/mechanical_design/openscad/lifetrac_v25.scad'
- 'LifeTrac-v25/mechanical_design/openscad/**/*.scad'
pull_request:
branches: [ main, master ]
paths:
- 'LifeTrac-v25/mechanical_design/openscad/lifetrac_v25.scad'
- 'LifeTrac-v25/mechanical_design/openscad/**/*.scad'
workflow_dispatch:
jobs:
generate-animation-gif:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref || github.ref_name }}
fetch-depth: 0
- name: Install OpenSCAD and ImageMagick
run: |
sudo apt-get update
sudo apt-get install -y openscad xvfb x11-utils imagemagick
openscad --version
- name: Start Xvfb
run: |
Xvfb :99 -screen 0 1024x768x16 &
XVFB_PID=$!
echo "XVFB_PID=$XVFB_PID" >> $GITHUB_ENV
# Wait for Xvfb to be ready (max 10 seconds)
for i in {1..10}; do
if xdpyinfo -display :99 >/dev/null 2>&1; then
echo "Xvfb is ready on display :99"
break
fi
if [ $i -eq 10 ]; then
echo "Timeout waiting for Xvfb to start"
exit 1
fi
sleep 1
done
echo "DISPLAY=:99" >> $GITHUB_ENV
- name: Calculate camera position
run: |
cd LifeTrac-v25/mechanical_design
# Calculate camera position dynamically based on machine dimensions
# Extract parameters from OpenSCAD file
MACHINE_HEIGHT=$(grep -E "^MACHINE_HEIGHT = " openscad/lifetrac_v25.scad | grep -oE "[0-9]+\.?[0-9]*" || echo "1000")
WHEEL_BASE=$(grep -E "^WHEEL_BASE = " openscad/lifetrac_v25.scad | grep -oE "[0-9]+\.?[0-9]*" || echo "1400")
GROUND_CLEARANCE=$(grep -E "^GROUND_CLEARANCE = " openscad/lifetrac_v25.scad | grep -oE "[0-9]+\.?[0-9]*" || echo "150")
ARM_MAX_ANGLE=$(grep -E "^ARM_MAX_ANGLE = " openscad/lifetrac_v25.scad | grep -oE "[0-9]+\.?[0-9]*" || echo "60")
BUCKET_HEIGHT=$(grep -E "^BUCKET_HEIGHT = " openscad/lifetrac_v25.scad | grep -oE "[0-9]+\.?[0-9]*" || echo "450")
# Validate all extracted parameters
if [ -z "$MACHINE_HEIGHT" ] || [ -z "$WHEEL_BASE" ] || [ -z "$GROUND_CLEARANCE" ] || [ -z "$ARM_MAX_ANGLE" ] || [ -z "$BUCKET_HEIGHT" ]; then
echo "ERROR: Failed to extract required parameters from OpenSCAD file"
echo " MACHINE_HEIGHT: $MACHINE_HEIGHT"
echo " WHEEL_BASE: $WHEEL_BASE"
echo " GROUND_CLEARANCE: $GROUND_CLEARANCE"
echo " ARM_MAX_ANGLE: $ARM_MAX_ANGLE"
echo " BUCKET_HEIGHT: $BUCKET_HEIGHT"
exit 1
fi
# Calculate key dimensions
FRAME_Z_OFFSET=$GROUND_CLEARANCE
ARM_PIVOT_Z=$((FRAME_Z_OFFSET + MACHINE_HEIGHT - 50))
WHEEL_MIDPOINT_Y=$((WHEEL_BASE / 2))
# Calculate maximum height when arms are raised to ARM_MAX_ANGLE
ARM_LENGTH_APPROX=1600
# Max height = ARM_PIVOT_Z + ARM_LENGTH * sin(ARM_MAX_ANGLE) + BUCKET_HEIGHT
MAX_ARM_TIP_HEIGHT=$(awk "BEGIN {pi=3.14159; print int($ARM_PIVOT_Z + $ARM_LENGTH_APPROX * sin($ARM_MAX_ANGLE * pi / 180) + $BUCKET_HEIGHT)}")
# Calculate camera position to frame the scene properly
SCENE_HEIGHT=$MAX_ARM_TIP_HEIGHT
MARGIN_FACTOR="1.75"
# Center point: halfway between ground and max height, at wheel midpoint Y
CENTER_Y=$WHEEL_MIDPOINT_Y
CENTER_Z=$(awk "BEGIN {print int($SCENE_HEIGHT / 2)}")
# Camera distance
CAMERA_DISTANCE=$(awk "BEGIN {print int($SCENE_HEIGHT * $MARGIN_FACTOR * 1.414)}")
echo "=== Camera Calculation ==="
echo "Camera position: --camera=$CAMERA_DISTANCE,$CAMERA_DISTANCE,$CAMERA_DISTANCE,0,$CENTER_Y,$CENTER_Z"
echo "=========================="
# Export calculated values as environment variables for subsequent steps
echo "CAMERA_DISTANCE=$CAMERA_DISTANCE" >> $GITHUB_ENV
echo "CENTER_Y=$CENTER_Y" >> $GITHUB_ENV
echo "CENTER_Z=$CENTER_Z" >> $GITHUB_ENV
- name: Create output directories
run: |
cd LifeTrac-v25/mechanical_design
mkdir -p output/animations/frames
- name: Generate animation frames
run: |
cd LifeTrac-v25/mechanical_design
# Use camera parameters calculated in previous step (from environment variables)
echo "Generating animation frames with calculated camera position..."
echo "Camera: $CAMERA_DISTANCE,$CAMERA_DISTANCE,$CAMERA_DISTANCE,0,$CENTER_Y,$CENTER_Z"
# Generate 36 frames for animation (every 10 degrees)
# Using dynamically calculated camera position that adapts to machine dimensions
total_frames=36
for i in $(seq 0 $((total_frames-1))); do
# Use total_frames-1 so last frame lands exactly at t=1.0
t=$(awk "BEGIN {print $i/($total_frames-1)}")
printf "Frame %d (t=%.4f)\n" "$i" "$t"
openscad -o "output/animations/frames/frame_$(printf %03d $i).png" \
--camera=$CAMERA_DISTANCE,$CAMERA_DISTANCE,$CAMERA_DISTANCE,0,$CENTER_Y,$CENTER_Z \
--imgsize=720,720 \
--projection=p \
--colorscheme=Nature \
-D "animation_time=$t" \
-D "\$t=$t" \
openscad/lifetrac_v25.scad
done
echo "Animation frames generated"
- name: Create animation GIF
run: |
cd LifeTrac-v25/mechanical_design/output/animations
echo "Creating animation GIF of arm movement..."
convert -delay 10 -loop 0 frames/frame_*.png lifetrac_v25_animation.gif
# Copy to parent directory for easy access
cp lifetrac_v25_animation.gif ../../lifetrac_v25_animation.gif
echo "✓ Animation GIF created successfully"
ls -lh ../../lifetrac_v25_animation.gif
- name: Upload animation artifacts
uses: actions/upload-artifact@v4
with:
name: openscad-animation
path: |
LifeTrac-v25/mechanical_design/output/animations/
LifeTrac-v25/mechanical_design/lifetrac_v25_animation.gif
retention-days: 90
- name: Commit and push animation GIF
run: |
cd LifeTrac-v25/mechanical_design
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git add lifetrac_v25_animation.gif || true
if git diff --staged --quiet; then
echo "ℹ️ No changes to commit"
else
echo "📝 Committing changes..."
git commit -m "Update animation GIF [skip ci]"
# Fetch and rebase to incorporate any remote changes
BRANCH_NAME="${{ github.head_ref || github.ref_name }}"
echo "🔄 Fetching latest changes from branch: ${BRANCH_NAME}..."
if git fetch origin "${BRANCH_NAME}"; then
echo "🔄 Rebasing on latest changes..."
if ! git rebase "origin/${BRANCH_NAME}"; then
echo "❌ Failed to rebase. There may be conflicts."
echo "Aborting rebase and exiting..."
git rebase --abort
exit 1
fi
else
echo "⚠️ Could not fetch branch ${BRANCH_NAME} from origin. This may be a PR from a fork. Skipping fetch/rebase."
fi
echo "📤 Pushing changes with --force-with-lease..."
if git push --force-with-lease; then
echo "✅ Changes pushed successfully"
else
echo "⚠️ Push failed. This is expected for PRs from forks or if you do not have write permissions."
echo "The files are available as workflow artifacts."
fi
fi
- name: Cleanup Xvfb
if: always()
run: |
if [ -n "$XVFB_PID" ]; then
echo "Stopping Xvfb process (PID: $XVFB_PID)"
# Try graceful shutdown first
kill -TERM $XVFB_PID 2>/dev/null || true
# Wait up to 5 seconds for process to terminate
for i in {1..5}; do
if ! kill -0 $XVFB_PID 2>/dev/null; then
echo "Xvfb stopped gracefully"
break
fi
sleep 1
done
# Force kill if still running
kill -KILL $XVFB_PID 2>/dev/null || true
echo "Xvfb force stopped"
fi