Add parametric pivot mount assembly and welding jig #46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |