-
Notifications
You must be signed in to change notification settings - Fork 2
463 lines (389 loc) · 18.7 KB
/
openscad-render.yml
File metadata and controls
463 lines (389 loc) · 18.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
name: OpenSCAD Render and Export
on:
push:
paths:
- 'LifeTrac-v25/mechanical_design/**/*.scad'
- '.github/workflows/openscad-render.yml'
pull_request:
paths:
- 'LifeTrac-v25/mechanical_design/**/*.scad'
workflow_dispatch:
jobs:
render:
runs-on: ubuntu-latest
name: Generate Renders and Outputs
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install OpenSCAD
run: |
sudo apt-get update
sudo apt-get install -y openscad xvfb x11-utils
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: Create output directories
run: |
cd LifeTrac-v25/mechanical_design
mkdir -p output/{dxf,renders,animations}
- name: Generate assembly.png and cnclayout.svg
run: |
cd LifeTrac-v25/mechanical_design
# Generate assembly.png (1024x768 matching CEB-Press example)
echo "Generating assembly.png..."
openscad -o assembly.png \
--camera=3000,3000,2000,0,0,500 \
--imgsize=1024,768 \
--projection=p \
--colorscheme=Nature \
openscad/lifetrac_v25.scad
# Generate cnclayout.svg (2D projection for CNC cutting)
echo "Generating cnclayout.svg..."
openscad -o cnclayout.svg \
--projection=o \
--viewall \
cnclayout.scad
echo "✓ assembly.png and cnclayout.svg generated successfully"
ls -lh assembly.png cnclayout.svg
- name: Render preview images
run: |
cd LifeTrac-v25/mechanical_design
# Main assembly view
echo "Rendering main assembly..."
openscad -o output/renders/lifetrac_v25_main.png \
--camera=3000,3000,2000,0,0,500 \
--imgsize=1920,1080 \
--projection=p \
--colorscheme=Nature \
openscad/lifetrac_v25.scad
# Front view
echo "Rendering front view..."
openscad -o output/renders/lifetrac_v25_front.png \
--camera=0,3000,1000,0,0,500 \
--imgsize=1920,1080 \
--projection=p \
--colorscheme=Nature \
openscad/lifetrac_v25.scad
# Side view
echo "Rendering side view..."
openscad -o output/renders/lifetrac_v25_side.png \
--camera=3000,0,1000,0,0,500 \
--imgsize=1920,1080 \
--projection=p \
--colorscheme=Nature \
openscad/lifetrac_v25.scad
# Top view
echo "Rendering top view..."
openscad -o output/renders/lifetrac_v25_top.png \
--camera=0,0,3000,0,0,500 \
--imgsize=1920,1080 \
--projection=o \
--colorscheme=Nature \
openscad/lifetrac_v25.scad
echo "Preview images rendered successfully"
- name: Render module examples
run: |
cd LifeTrac-v25/mechanical_design
# Render each module's example
for module in modules/*.scad; do
filename=$(basename "$module" .scad)
echo "Rendering $filename examples..."
openscad -o "output/renders/${filename}_examples.png" \
--camera=500,500,500,0,0,0 \
--imgsize=1600,900 \
--projection=p \
--colorscheme=Nature \
"$module"
done
- name: Generate animation frames
run: |
cd LifeTrac-v25/mechanical_design
echo "Generating animation frames..."
mkdir -p output/animations/frames
# Generate 36 frames for animation (every 10 degrees)
for i in {0..35}; do
t=$(awk "BEGIN {print $i/36}")
printf "Frame %d (t=%.4f)\n" $i $t
openscad -o "output/animations/frames/frame_$(printf %03d $i).png" \
--camera=3000,3000,2000,0,0,500 \
--imgsize=1280,720 \
--projection=p \
--colorscheme=Nature \
-D "\$t=$t" \
openscad/lifetrac_v25.scad
done
echo "Animation frames generated"
- name: Create animation GIF
run: |
sudo apt-get install -y imagemagick
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: Generate summary report
run: |
cd LifeTrac-v25/mechanical_design
cat > output/RENDER_REPORT.md << 'EOF'
# OpenSCAD Render Report
**Generated:** $(date)
**Commit:** ${{ github.sha }}
**Branch:** ${{ github.ref_name }}
## Files Generated
### Preview Renders
- Main assembly view
- Front view
- Side view
- Top view
- Module examples (5 modules)
### Animations
- 36 animation frames (1280x720 PNG)
- Animated GIF showing arm movement (lifetrac_v25_animation.gif)
### CNC Files
- assembly.png (3D render of complete assembly)
- cnclayout.svg (2D layout for CNC cutting)
## Next Steps
1. Review rendered images in the artifacts
2. View lifetrac_v25_animation.gif to see arm movement
3. Use cnclayout.svg for CNC plasma cutting
4. Use animation frames to create videos
5. Export DXF files for individual parts using export_all_cnc_parts.sh
6. Check the Structural Analysis workflow for design validation results
---
Generated by GitHub Actions
EOF
- name: Upload render artifacts
uses: actions/upload-artifact@v4
with:
name: openscad-renders
path: |
LifeTrac-v25/mechanical_design/output/renders/
LifeTrac-v25/mechanical_design/output/RENDER_REPORT.md
retention-days: 90
- name: Upload animation artifacts
uses: actions/upload-artifact@v4
with:
name: openscad-animations
path: LifeTrac-v25/mechanical_design/output/animations/
retention-days: 90
- name: Extract specifications and update README
run: |
cd LifeTrac-v25
echo "Extracting specifications from OpenSCAD file..."
# Create Python script to extract specs
cat > /tmp/extract_specs.py << 'PYTHON_EOF'
import re
import sys
# Read the OpenSCAD file
try:
with open('mechanical_design/openscad/lifetrac_v25.scad', 'r') as f:
content = f.read()
except FileNotFoundError:
print("ERROR: OpenSCAD file not found", file=sys.stderr)
sys.exit(1)
# Extract key dimensions with error handling
def extract_value(pattern, content, name):
try:
match = re.search(pattern, content)
if match:
return float(match.group(1))
print(f"WARNING: Could not find {name} in OpenSCAD file", file=sys.stderr)
return None
except (ValueError, AttributeError) as e:
print(f"ERROR: Failed to extract {name}: {e}", file=sys.stderr)
return None
MACHINE_WIDTH = extract_value(r'MACHINE_WIDTH\s*=\s*(\d+)', content, 'MACHINE_WIDTH')
MACHINE_LENGTH = extract_value(r'MACHINE_LENGTH\s*=\s*(\d+)', content, 'MACHINE_LENGTH')
MACHINE_HEIGHT = extract_value(r'MACHINE_HEIGHT\s*=\s*(\d+)', content, 'MACHINE_HEIGHT')
WHEEL_DIAMETER = extract_value(r'WHEEL_DIAMETER\s*=\s*(\d+)', content, 'WHEEL_DIAMETER')
GROUND_CLEARANCE = extract_value(r'GROUND_CLEARANCE\s*=\s*(\d+)', content, 'GROUND_CLEARANCE')
HYDRAULIC_PRESSURE_PSI = extract_value(r'HYDRAULIC_PRESSURE_PSI\s*=\s*(\d+)', content, 'HYDRAULIC_PRESSURE_PSI')
LIFT_CYLINDER_BORE = extract_value(r'LIFT_CYLINDER_BORE\s*=\s*([\d.]+)', content, 'LIFT_CYLINDER_BORE')
# Validate that all required values were found
required_values = {
'MACHINE_WIDTH': MACHINE_WIDTH,
'MACHINE_LENGTH': MACHINE_LENGTH,
'MACHINE_HEIGHT': MACHINE_HEIGHT,
'WHEEL_DIAMETER': WHEEL_DIAMETER,
'GROUND_CLEARANCE': GROUND_CLEARANCE,
'HYDRAULIC_PRESSURE_PSI': HYDRAULIC_PRESSURE_PSI,
'LIFT_CYLINDER_BORE': LIFT_CYLINDER_BORE
}
missing_values = [k for k, v in required_values.items() if v is None]
if missing_values:
print(f"ERROR: Missing required values: {', '.join(missing_values)}", file=sys.stderr)
print("The following patterns failed to match:", file=sys.stderr)
for val in missing_values:
print(f" - {val}", file=sys.stderr)
print("Skipping README update to avoid breaking existing content", file=sys.stderr)
print("If OpenSCAD file structure changed, update the regex patterns in this workflow", file=sys.stderr)
sys.exit(0) # Exit gracefully without updating
# Conservative lift capacity estimate based on typical loader geometry
# NOTE: This is a simplified estimate. The actual lift capacity calculation in the
# OpenSCAD file uses complex lever arm geometry and moment calculations which would
# require additional extraction logic. For now, we use a conservative estimate.
# If design changes significantly, this value should be reviewed and updated.
ESTIMATED_LIFT_CAPACITY_KG = 1200
ESTIMATED_LIFT_CAPACITY_LBS = ESTIMATED_LIFT_CAPACITY_KG * 2.205
# Read current README
with open('README.md', 'r') as f:
readme = f.read()
# Build the specifications table
specs_table = f"""| Specification | Value (mm) | Value (inches) | Value (meters) |
|--------------|------------|----------------|----------------|
| **Overall Width** | {int(MACHINE_WIDTH):,} mm | {MACHINE_WIDTH/25.4:.1f}" | {MACHINE_WIDTH/1000:.2f} m |
| **Overall Length** | {int(MACHINE_LENGTH):,} mm | {MACHINE_LENGTH/25.4:.1f}" | {MACHINE_LENGTH/1000:.2f} m |
| **Height to Frame Top** | {int(MACHINE_HEIGHT):,} mm | {MACHINE_HEIGHT/25.4:.1f}" | {MACHINE_HEIGHT/1000:.2f} m |
| **Ground Clearance** | {int(GROUND_CLEARANCE)} mm | {GROUND_CLEARANCE/25.4:.1f}" | {GROUND_CLEARANCE/1000:.2f} m |
| **Wheel Diameter** | {int(WHEEL_DIAMETER)} mm | {WHEEL_DIAMETER/25.4:.1f}" | {WHEEL_DIAMETER/1000:.2f} m |"""
perf_table = f"""| Specification | Value |
|--------------|-------|
| **Hydraulic System Pressure** | {int(HYDRAULIC_PRESSURE_PSI):,} PSI |
| **Estimated Lift Capacity** | ~{int(ESTIMATED_LIFT_CAPACITY_KG):,} kg (~{int(ESTIMATED_LIFT_CAPACITY_LBS):,} lbs) |
| **Drive Configuration** | All-wheel drive (4 hydraulic motors) |
| **Cylinder Bore (Lift)** | {LIFT_CYLINDER_BORE} mm ({LIFT_CYLINDER_BORE/25.4:.1f}") |"""
# Update the specs in README using regex with validation
original_readme = readme
# Update technical specifications table
updated_readme = re.sub(
r'(\| Specification \| Value \(mm\) \| Value \(inches\) \| Value \(meters\) \|.*?\n)(\| \*\*Wheel Diameter\*\* \|[^\n]+)',
specs_table,
readme,
count=1,
flags=re.DOTALL
)
if updated_readme == readme:
print("WARNING: Technical specifications table pattern not found, trying alternative pattern", file=sys.stderr)
# Try simpler pattern that looks for the table header
pattern_start = readme.find('### Technical Specifications')
if pattern_start != -1:
# Find the end of the table
table_start = readme.find('| Specification | Value (mm)', pattern_start)
if table_start != -1:
# Find next section header or blank line after table
table_end = readme.find('\n\n###', table_start)
if table_end == -1:
table_end = readme.find('\n\n| Specification | Value |', table_start)
if table_end != -1:
# Extract table and replace
old_table = readme[table_start:readme.find('\n', readme.rfind('|', table_start, table_end))+1]
updated_readme = readme.replace(old_table, specs_table + '\n', 1)
readme = updated_readme
# Update performance specifications table
updated_readme = re.sub(
r'(### Performance Specifications\s*\n\n\| Specification \| Value \|.*?\n)(\| \*\*Cylinder Bore \(Lift\)\*\* \|[^\n]+)',
f'### Performance Specifications\n\n{perf_table}',
readme,
count=1,
flags=re.DOTALL
)
if updated_readme == readme:
print("WARNING: Performance specifications table pattern not found", file=sys.stderr)
else:
readme = updated_readme
# Only write if changes were made
if readme != original_readme:
try:
with open('README.md', 'w') as f:
f.write(readme)
print("✓ README specifications updated successfully")
except IOError as e:
print(f"ERROR: Failed to write README: {e}", file=sys.stderr)
sys.exit(1)
else:
print("INFO: No changes needed to README specifications")
print(f" Width: {int(MACHINE_WIDTH)} mm")
print(f" Length: {int(MACHINE_LENGTH)} mm")
print(f" Height: {int(MACHINE_HEIGHT)} mm")
print(f" Lift Capacity: ~{int(ESTIMATED_LIFT_CAPACITY_KG)} kg")
PYTHON_EOF
python3 /tmp/extract_specs.py
echo "README specifications updated"
- name: Commit and push rendered outputs
run: |
cd LifeTrac-v25
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
# Add the generated files (mechanical_design images and updated README)
git add mechanical_design/assembly.png mechanical_design/cnclayout.svg mechanical_design/lifetrac_v25_animation.gif README.md
# Check if there are changes
if ! git diff --staged --quiet; then
git commit -m "Update rendered outputs and specifications [skip ci]"
git push --force-with-lease origin HEAD || {
echo "INFO: git push failed. This is expected on PRs from forks."
echo "The files are available as workflow artifacts."
}
else
echo "No changes to rendered output files or README"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- 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
- name: Comment on PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const comment = `## OpenSCAD Render Results 🎨
✅ Rendering completed successfully!
### Generated Outputs
- 📸 Preview renders (main, front, side, top views)
- 🎬 Animation frames (36 frames)
- 🎞️ Animated GIF of arm movement (lifetrac_v25_animation.gif)
- 🔧 Module examples
- 🖼️ Assembly image and CNC layout (assembly.png, cnclayout.svg)
### Artifacts
Download the generated files from the workflow artifacts:
- \`openscad-renders\` - Preview images
- \`openscad-animations\` - Animation frames and GIF
### Committed Files
The following files have been committed to the repository:
- \`assembly.png\` - 3D assembly render
- \`cnclayout.svg\` - 2D CNC cutting layout
- \`lifetrac_v25_animation.gif\` - Animation showing arm movement
_Note: Check the **OpenSCAD Structural Analysis** workflow for design validation and structural analysis results._
**Workflow Run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
`;
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});