RSS Amplifier

Mountain Of Code · Jan 11, 2026

📸 Rendering OpenSCAD Models To PNG

0
Sign in to vote or save

This page cannot be shown here. You can still read it on the original site — the toolbar below keeps your place in the directory.

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.…

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 magic comments at the end of the file. Each one is a snippet which defines how to render a part. Replacing //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] ?? null;
foreach ($scadFilePaths as $scadFilePath) {
    $baseFileName = basename($scadFilePath, '.scad');
    $fileName = basename($scadFilePath);
    $exitCode = null;
    $output = null;
    $includePreview = preg_match('#^(?!.*?//).*%#m', file_get_contents($scadFilePath)) === 1;
    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 . '... ';
            if ($exportFormat === null || $exportFormat === 'png') {
                echo '[PNG] ';
                renderScadToPng($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '.png');
                if ($includePreview) {
                    echo '[PNG--preview] ';
                    renderScadToPng($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '--preview.png', true);
                }
            }
            if ($exportFormat === null || $exportFormat === 'stl') {
                echo '[STL] ';
                renderScadToStl($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '.stl');
            }
            echo 'Done' . PHP_EOL;
        } else {
            foreach ($scadParts as $scadPart) {
                $scadPartFileName = str_replace(['(', ')', ';'], '', $scadPart);
                echo 'Rendering ' . $fileName . ' [' . $scadPartFileName . ']... ';
                if ($exportFormat === null || $exportFormat === 'png') {
                    echo '[PNG] ';
                    renderScadPartToPng($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '_' . $scadPartFileName . '.png', $scadPart);
                    if ($includePreview) {
                        echo '[PNG--preview] ';
                        renderScadPartToPng($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '_' . $scadPartFileName . '--preview.png', $scadPart, true);
                    }
                }
                if ($exportFormat === null || $exportFormat === 'stl') {
                    echo '[STL] ';
                    renderScadPartToStl($scadFilePath, dirname($scadFilePath) . '/' . $baseFileName . '_' . $scadPartFileName . '.stl', $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));
    }
}

Read on /rendering-openscad-to-image

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.