📸 Rendering OpenSCAD Models To PNG
I'm working on a project where I need to generate transparent images of OpenSCAD models. Easy, right? OpenSCAD can directly export PNGs. As always, the devil is in the detail. My approach is inspired by this post which is doing a lot more than I need.
Antialiasing

The native PNG export produces some pretty jagged edges. We can work around this by exporting a huge image and then shrinking it down.
openscad-nightly \
--hardwarnings \
--autocenter \
--viewall \
--imgsize=4096,4096 \
--render \
-o model.png \
model.scad
convert \
model.png \
-resize 700x700 \
model.png
Transparency

Making the background transparent was a lot easier than I expected. Image Magick can do a colour-to-transparency mask.
This replaces the background colour of #fafafa used in the Nature colour scheme. I also much prefer this colour
scheme.
openscad-nightly \
--hardwarnings \
--autocenter \
--viewall \
--imgsize=4096,4096 \
--colorscheme Nature \
--render \
-o model.png \
model.scad
convert \
model.png \
-transparent "#fafafa" \
-resize 700x700 \
model.png
Padding
The exported images contain a lot of whitespace around them, again we can lean on Image Magick to strip it back:
openscad-nightly \
--hardwarnings \
--autocenter \
--viewall \
--imgsize=4096,4096 \
--colorscheme Nature \
--render \
-o model.png \
model.scad
convert \
model.png \
-transparent "#fafafa" \
-trim \
-resize 650x650 \
-bordercolor none \
-border 25 \
model.png
Camera Position

The default camera position is usually fine, but sometimes you need to adjust the camera position. The --camera
argument does this but don't try to manually figure out the numbers to pass. Instead, position the model in the GUI and
copy the numbers from the toolbar:
![]()
openscad-nightly \
--hardwarnings \
--autocenter \
--viewall \
--imgsize=4096,4096 \
--colorscheme Nature \
--camera 3.12,-1.71,-2.69,73.20,0,32,192.04 \
--render \
-o model.png \
model.scad
Multiple Parts
It's not unusual for a design to consist of multiple parts, and I want each part to have its own image. The solution I
came up with was to add //part: with ! will render just that part.
module base() { /* ... */ } module nozzle() { /* ... */ } //part: base(); //part: nozzle();
Automation
Of course, I'm not doing all this manually. I whipped up the following script which processes all the models in a directory. It includes STL rendering too.
<?php declare(strict_types=1); $scadFilePaths = glob(__DIR__ . '/public/3d/*.scad'); $modelFilter = $argv[1] ?? null; $exportFormat = $argv[2] ?? 'update-existing'; foreach ($scadFilePaths as $scadFilePath) { $baseFileName = basename($scadFilePath, '.scad'); $fileName = basename($scadFilePath); $exitCode = null; $output = null; if ($modelFilter !== null && str_contains($fileName, $modelFilter) === false) { continue; } $scadParts = null; if (preg_match_all('@//\s*part:\s(.+?)(?=\n|$)@i', file_get_contents($scadFilePath), $scadParts) !== false) { $scadParts = $scadParts[1]; } try { if ($scadParts === []) { echo 'Rendering ' . $fileName . '... '; $pngPath = dirname($scadFilePath) . '/' . $baseFileName . '.png'; $stlPath = dirname($scadFilePath) . '/' . $baseFileName . '.stl'; if ($exportFormat === 'update-existing') { if (file_exists($pngPath)) { renderScadToPng($scadFilePath, $pngPath, true); } if (file_exists($stlPath)) { renderScadToStl($scadFilePath, $stlPath); } } else { match($exportFormat) { 'png' => renderScadToPng($scadFilePath, $pngPath, true), 'stl' => renderScadToStl($scadFilePath, $stlPath), }; } echo 'Done' . PHP_EOL; } else { foreach ($scadParts as $scadPart) { $scadPartFileName = str_replace(['(', ')', ';'], '', $scadPart); echo 'Rendering ' . $fileName . ' [' . $scadPartFileName . ']... '; $pngPath = dirname($scadFilePath) . '/' . $baseFileName . '_' . $scadPartFileName . '.png'; $stlPath = dirname($scadFilePath) . '/' . $baseFileName . '_' . $scadPartFileName . '.stl'; if ($exportFormat === 'update-existing') { if (file_exists($pngPath)) { renderScadPartToPng($scadFilePath, $pngPath, $scadPart, true); } if (file_exists($stlPath)) { renderScadPartToStl($scadFilePath, $stlPath, $scadPart); } } else { match($exportFormat) { 'png' => renderScadPartToPng($scadFilePath, $pngPath, $scadPart, true), 'stl', => renderScadPartToStl($scadFilePath, $stlPath, $scadPart), }; } echo 'Done' . PHP_EOL; } } } catch (Exception $e) { echo 'FAILED' . PHP_EOL . $e->getMessage() . PHP_EOL . PHP_EOL . PHP_EOL; continue; } } function renderScadPartToStl($scadFilePath, string $stlPath, string $part): void { $tempFilePath = $scadFilePath . '.tmp'; try { file_put_contents($tempFilePath, file_get_contents($scadFilePath) . "\n\n!" . $part . ";\n"); renderScadToStl($tempFilePath, $stlPath); } finally { unlink($tempFilePath); } } function renderScadToStl($scadFilePath, string $stlPath): void { exec( 'openscad-nightly \ --hardwarnings \ --autocenter \ --viewall \ -o ' . escapeshellarg($stlPath) . ' \ ' . escapeshellarg($scadFilePath) . ' 2>&1', $output, result_code: $exitCode, ); if ($exitCode !== 0) { throw new Exception(implode(PHP_EOL, $output)); } } function renderScadPartToPng($scadFilePath, string $pngPath, string $part, bool $preview = false): void { $tempFilePath = $scadFilePath . '.tmp'; try { file_put_contents($tempFilePath, file_get_contents($scadFilePath) . "\n\n!" . $part . ";\n"); renderScadToPng($tempFilePath, $pngPath, $preview); } finally { unlink($tempFilePath); } } function renderScadToPng($scadFilePath, string $pngPath, bool $preview = false): void { exec( 'openscad-nightly \ --hardwarnings \ --autocenter \ --viewall \ --imgsize=4096,4096 \ --colorscheme Nature \ ' . ($preview ? '--preview ' : '') . ' \ --render \ -o ' . escapeshellarg($pngPath) . ' \ ' . escapeshellarg($scadFilePath) . ' 2>&1', $output, result_code: $exitCode, ); if ($exitCode !== 0) { throw new Exception(implode(PHP_EOL, $output)); } $exitCode = null; $output = null; exec( 'convert \ ' . escapeshellarg($pngPath) . ' \ -transparent "#fafafa" \ -trim \ -resize 650x650 \ -bordercolor none \ -border 25 \ ' . escapeshellarg($pngPath) . ' 2>&1', $output, result_code: $exitCode, ); if ($exitCode !== 0) { throw new Exception(implode(PHP_EOL, $output)); } }