Self-hosted 3D model gallery powered by FreeCAD, Three.js and GitHub Pages.
Platforms like MakerWorld, Thingiverse and Printables are great for sharing models, but you don't own your gallery. If the platform goes down or changes its terms, your portfolio disappears. CAD Gallery gives you a self-hosted, version-controlled 3D model gallery that lives in your GitHub repo and deploys automatically to GitHub Pages. Push a .FCStd file, get a gallery with interactive 3D previews -- no server needed.
All you need is a repo with .FCStd files and one workflow file. No config, no templates, no metadata required -- the action provides sensible defaults for everything.
Your repo structure:
my-cad-models/
Model_A.FCStd
Model_B.FCStd
.github/workflows/cad-gallery.yaml
The workflow file:
# .github/workflows/cad-gallery.yaml
name: CAD Gallery
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build Gallery
uses: schmiddim/freecad-action@v3
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: ./gallery
- id: deploy
uses: actions/deploy-pages@v5Prerequisites: Enable GitHub Pages in your repo settings (Settings > Pages > Source: GitHub Actions).
- Automatic STL export from FreeCAD
.FCStdfiles - Interactive 3D viewer with Three.js (OrbitControls)
- Metadata support: descriptions, tags, images, license, external links
- Tag-based filtering in the gallery view
- Download buttons for STL and FCStd files in the detail view
- Maker profile with About-page and links to GitHub, MakerWorld, Thingiverse, Printables
- GitHub link with icon in the navigation header
- Dark/light mode -- follows system preference, toggle button on every page
- Configurable gallery title via
cad-gallery.yaml - Discovery document at
gallery/discovery/cad-gallery.json(machine-readable index) - Optional aggregator ping on every build
- RSS and Atom feeds
- Fallback display for models without metadata
- Fully buildable and testable locally via Makefile + Docker
Create a YAML file in metadata/ matching your FCStd filename:
# metadata/my-model.yaml
title: "My Cool Model"
description: "A detailed description of the model."
tags:
- bracket
- 3d-print
images:
- filename: "photo1.jpg"
caption: "Printed version"
license: "CC-BY-SA-4.0"
links:
makerworld: "https://makerworld.com/en/models/..."
printables: "https://www.printables.com/model/..."Place additional images in metadata/images/{model_name}/.
Models without metadata are still displayed with a 3D preview and a hint showing which file to create.
Edit cad-gallery.yaml to configure paths and the gallery title:
# yaml-language-server: $schema=https://raw.githubusercontent.com/schmiddim/freecad-action/refs/tags/v3.0.0/schemas/cad-gallery.schema.json
title: "My 3D Models" # Optional, default: "CAD Gallery"
freecad_dir: "freecad-files" # Where your .FCStd files are
metadata_dir: "metadata" # Where metadata YAMLs and images are
output_dir: "gallery" # Where the HTML gallery is generated
exports_dir: "exports" # Where STL exports goIf your .FCStd files are in the repo root, set freecad_dir: ".".
Create a maker.yaml in your repo root to enable the About-page and add your profile links to the navigation:
# yaml-language-server: $schema=https://raw.githubusercontent.com/schmiddim/freecad-action/refs/tags/v3.0.0/schemas/maker.schema.json
name: "Your Name"
bio: "Short description about you."
links:
github: "https://github.com/..."
makerworld: "https://makerworld.com/en/@..."
thingiverse: "https://www.thingiverse.com/..."
printables: "https://www.printables.com/@..."When maker.yaml is present:
- An About page is generated at
gallery/about.html - An About link appears in the header navigation
- The GitHub link (with icon) appears in the header navigation
Every gallery build generates a machine-readable discovery document at:
gallery/discovery/cad-gallery.json
It contains the gallery metadata, all models (with STL/FCStd URLs, tags, license), the maker profile and the source repository URL. The schema is at schemas/discovery.schema.json.
By default, your gallery is standalone and does not communicate with any external service. If you opt in by setting send-ping: 'true', your gallery will be automatically listed on the FreeCAD Aggregator -- a public directory of CAD Gallery instances. This makes your models discoverable by other makers.
- name: Build Gallery
uses: schmiddim/freecad-action@v3
with:
send-ping: 'true'When enabled, the action sends a POST request after each successful build containing discovery_url, git_source_url and event: "push" to the aggregator at https://freecad-aggregator.fly.dev/. No API key or registration needed -- it just works.
What gets published: Your gallery title, model names, tags, license info, and links to your STL/FCStd files -- the same information that is already public on your GitHub Pages site. No private data is sent.
Note: The aggregator is currently work in progress and will be open-sourced soon.
| Input | Description | Default |
|---|---|---|
use-docker |
Use Docker for FreeCAD export (recommended) | true |
send-ping |
Publish your gallery to the FreeCAD Aggregator | false |
| Output | Description |
|---|---|
models-count |
Number of models exported and built |
uses: schmiddim/freecad-action@v3 # Latest 3.x (recommended)
uses: schmiddim/freecad-action@v3.0 # Latest 3.0.x
uses: schmiddim/freecad-action@v3.0.0 # Exact versionThe action ships with default HTML templates. To customize the gallery appearance, create a templates/ directory in your repo with gallery.html, detail.html and/or about.html. Your templates will take precedence over the defaults.
freecad-files/ # FreeCAD .FCStd source files (flat, no subdirs)
metadata/ # Model metadata YAML files
my-model.yaml # Must match FCStd filename (without extension)
images/ # Additional images per model
my-model/
photo1.jpg
schemas/ # JSON Schemas for validation
cad-gallery.schema.json # Schema for cad-gallery.yaml
maker.schema.json # Schema for maker.yaml
meta.schema.json # Schema for metadata/*.yaml
discovery.schema.json # Schema for discovery document
scripts/ # Build scripts
export.py # FreeCAD -> STL export
build_gallery.py # Generate HTML gallery from templates + metadata
validate.py # Validate YAML files against schemas
build.sh # Entrypoint used by Makefile and action.yml
templates/ # Jinja2 HTML templates + CSS
cad-gallery.yaml # Configuration (paths, gallery title)
maker.yaml # Maker profile (name, bio, links) -- optional
Makefile # Local build targets
Dockerfile # FreeCAD Docker image for export
pyproject.toml # Python dependencies
- Docker (for FreeCAD export)
- Python 3.10+ (for gallery build)
pip install -e ".[dev]"make help # Show all available targets
make docker-build # Build the FreeCAD Docker image
make export # Export STL from FCStd files (via Docker)
make gallery # Build the HTML gallery (no Docker needed)
make build # Full build: export + gallery
make serve # Build gallery and serve at http://localhost:8000
make validate # Validate metadata and profile YAML against schemas
make clean # Remove generated files (exports/ and gallery/)- Go to your repo Settings > Pages
- Set Source to GitHub Actions
- Push to the default branch -- the workflow will build and deploy automatically
If you see docker: command not found, make sure Docker is installed and running. On GitHub Actions (ubuntu-latest), Docker is pre-installed.
Check that freecad_dir in cad-gallery.yaml points to the correct directory. The default is . (repo root). Files must have the .FCStd extension.
- Verify Pages is enabled: Settings > Pages > Source: GitHub Actions
- Check that the workflow completed successfully in the Actions tab
- The gallery is deployed at
https://<user>.github.io/<repo>/
FreeCAD models must contain at least one visible Part::Feature or PartDesign::Body with a Shape. Sketches alone will not export.
The gallery respects prefers-color-scheme. The toggle button state is stored in localStorage. Clear your browser storage if it seems stuck.
| Workflow | Trigger | Purpose |
|---|---|---|
cad-gallery.yaml |
Push to master |
Build gallery + deploy to GitHub Pages |
docker-publish.yaml |
Push to master (Dockerfile changes) or Release |
Build + push Docker image to GHCR |
release.yaml |
Version tag (v*.*.*) |
Create GitHub Release + moving version tags |
dependabot-automerge.yml |
Dependabot PR | Auto-merge dependency updates |
The FreeCAD export container is hosted on GitHub Container Registry:
docker pull ghcr.io/schmiddim/freecad-action:latestSee CONTRIBUTING.md for guidelines.
