Auto-generated project docs with 3D preview
For KiCad PCBs and STL files, made with mdBook
Published on May 05, 2024.A recurring problem I ran into with my projects is documentation. Especially with 3D printed parts or PCBs it's often required to generate preview images or PDFs so someone interested can take a look without having to clone the repo and install some development tools to open the original files. Doing this manually is time-consuming and repetitive. So it should be automated.
I've come across a couple of similar projects recently, like this or these. But they mostly spit out non-interactive images. So I've decided to go a slightly different and more interactive route.
Using GitHub Actions and GitHub Pages, I'm creating a static documentation website using mdBook. To visualize KiCad schematics and 2D board designs I'm exporting them as svg images and display them interactively using svg-pan-zoom. KiCad PCBs are also exported in 3D as VRML files and displayed using three.js.
To generate these files I'm mostly using shell scripts in the project repository.
generate_stls.sh
is creating STL files from OpenSCAD sourcesgenerate_fab.sh
is creating Gerber files to order PCBsgenerate_plot.sh
is creating the 2D and 3D files for KiCad projectsgenerate_docs.sh
is creating the static website using the output from the previous scripts
Then the workflows can be kept relatively short, just installing the required dependencies, running the proper script and putting the resulting files where they are needed. This way the coupling to the proprietary GitHub features is not that thight and can hopefully be ported to other providers with relative ease.
cmake.yml
is creating firmware archives (also usable for OTA updates)scad.yml
to create the STL fileskicad.yml
to generate board gerber filesdocs.yml
to create the static documentation pages
The whole workflow is relatively customizable. So for some projects I'm for example creating special board previews for etching PCBs at home.
I've implemented this workflow in a couple of projects now:
- OpenAutoLab (Repo)
- LARS (Repo)
- Dispensy (Repo)
- Tritonmischer (Repo)
Take a look there for the implementation details.
Example Implementation
Here are the basics to get you started.
1) mdBook dependency
Fetch the pre-built mdBook binary and place it in your PATH.
2) mdBook initialization
Add a docs directory to your project, either by copying it from one of my repos, or creating a new one and adapting the resulting book.toml
config.
mdbook init docs
3) svg-pan-zoom dependency
If you want to visualize SVG images for KiCad schematics and boards, add svg-pan-zoom as a subrepo.
cd docs
git submodule add https://github.com/bumbu/svg-pan-zoom
mkdir -p src/js
cd src/js
ln -s ../../svg-pan-zoom/dist/svg-pan-zoom.js svg-pan-zoom.js
ln -s ../../svg-pan-zoom/dist/svg-pan-zoom.min.js svg-pan-zoom.min.js
4) Generating docs
Add the docs/generate_docs.sh
script and adapt the config and calls to other script at the beginning, as needed for your project:
Here is an example generate_docs.sh
file.
#!/bin/bash # SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ INSCH="../pcb/drumkit.kicad_sch ../pcb2/lars2.kicad_sch" INPCB="../pcb/drumkit.kicad_pcb ../pcb2/lars2.kicad_pcb" cd "$(dirname "$0")" if [ "$1" = "build" ] ; then echo "Generating plots" ../pcb/generate_plot.sh echo echo "Generating fab" ../pcb/generate_fab.sh echo echo "Generating plots 2" ../pcb2/generate_plot.sh echo echo "Generating fab 2" ../pcb2/generate_fab.sh echo echo "Generating stls" ../3dprint/generate_stls.sh echo fi rm -rf src/plot cp -r ../pcb/plot src cp -r ../pcb2/plot/* src/plot/ cp ../pcb/fab.zip src/plot/fab_pcb.zip cp ../pcb2/fab.zip src/plot/fab_pcb2.zip rm -rf src/stl cp -r ../3dprint/stl src INSTL=`ls ../3dprint/stl/*.stl` for path in $INSCH do IN=`echo $path | sed "s:../pcb.\\?/::g"` o="src/inc_$IN.md" echo "Include for $IN at $o" rm -rf $o echo "<script src=\"js/svg-pan-zoom.js\" charset=\"UTF-8\"></script>" >> $o for f in `ls src/plot/$IN.svg/*.svg | sort -r`; do file=`echo $f | sed 's:src/:./:g'` name=`echo $f | sed "s:src/plot/$IN.svg/::g" | sed 's:.svg::g'` echo "Sheet at $name" echo "<h2>$name</h2>" >> $o echo "<div style=\"background-color: white; border: 1px solid black;\">" >> $o echo "<embed type=\"image/svg+xml\" src=\"$file\" id=\"pz_$name\" style=\"width:100%;\"/>" >> $o echo "<script>" >> $o echo "document.getElementById('pz_$name').addEventListener('load', function(){" >> $o echo "svgPanZoom(document.getElementById('pz_$name'), {controlIconsEnabled: true, minZoom: 1.0});" >> $o echo "})" >> $o echo "</script>" >> $o echo "</div>" >> $o echo >> $o echo "[Direct link to \`$name\`]($file)." >> $o echo >> $o done echo done plot_3d() { echo '<script type="importmap">' >> $1 echo ' {' >> $1 echo ' "imports": {' >> $1 echo ' "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",' >> $1 echo ' "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"' >> $1 echo ' }' >> $1 echo ' }' >> $1 echo '</script>' >> $1 echo "<p>Status: \"<span id=\"3d_info_$2\">Preparing 3D model...</span>\"</p>" >> $1 echo "<div id=\"3d_viewer_$2\" style=\"width: 100%; height: 100%; background-color: white; border: 1px solid black; position: relative;\"></div>" >> $1 echo '<script type="module">' >> $1 echo " var info = document.getElementById(\"3d_info_$2\");" >> $1 echo " var view = document.getElementById(\"3d_viewer_$2\");" >> $1 echo ' view.style.height = Math.floor(view.clientWidth * 0.707) + "px";' >> $1 echo ' import { init_3d } from "./js/3d.js";' >> $1 echo " init_3d(\"$3\", view, info, view.clientWidth, view.clientHeight);" >> $1 echo '</script>' >> $1 } for path in $INPCB do IN=`echo $path | sed "s:../pcb.\\?/::g"` o="src/inc_$IN.md" file="plot/$IN.wrl" name=`echo $file | sed "s:plot/::g" | sed 's:.wrl::g'` echo "Include for $IN at $o, $file, $name" rm -rf $o plot_3d $o $name $file done echo for IN in $INSTL do o=`echo $IN | sed "s:../3dprint/stl/:src/inc_:g" | sed "s:.stl:.stl.md:g"` file=`echo $IN | sed "s:../3dprint/::g"` name=`echo $file | sed "s:stl/::g" | sed 's:.stl::g'` echo "Include for $IN at $o, $file, $name" rm -rf $o plot_3d $o $name $file done echo echo "Generating docs" if [ "$1" = "serve" ] ; then mdbook serve --open elif [ "$1" = "build" ] ; then mdbook build else echo "Invalid command. 'build' or 'serve'." fi
5) Auto-generating and publishing docs
Add the .github/workflows/docs.yml
script:
Here is an example docs.yml
file.
# SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ name: Docs # only build single instance of docs for latest main branch on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest permissions: contents: write pages: write id-token: write steps: - name: Checkout repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Checkout repo submodules run: git submodule update --init - name: Install dependencies run: | sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases sudo apt update sudo apt install -y --install-recommends kicad pipx libfuse2 libegl1 poppler-utils openscad zip - name: Install latest mdbook run: | tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" mkdir mdbook curl -sSL $url | tar -xz --directory=./mdbook echo `pwd`/mdbook >> $GITHUB_PATH - name: Build Book run: docs/generate_docs.sh build - name: Setup Pages uses: actions/configure-pages@v2 - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: 'docs/book' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1
And don't forget to set the GitHub Pages source in the repo settings to GitHub Actions:
6) STL 3D visualization
If you want to visualize 3D print files, do the same with 3dprint/generate_stls.sh
and .github/workflows/scad.yml
:
Here is an example generate_stls.sh
file.
#!/bin/bash # ---------------------------------------------------------------------------- # Copyright (c) 2023 Kauzerei (openautolab@kauzerei.de) # Copyright (c) 2023 Thomas Buck (thomas@xythobuz.de) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # See <http://www.gnu.org/licenses/>. # ---------------------------------------------------------------------------- # space separated list of scad files (without extension) MODULES="actuator beam enclosure tamb_mount" OUTDIR="stl" # enter directory of script (case) cd "$(dirname "$0")" # Detect OS if [[ "$OSTYPE" == "darwin"* ]]; then echo "Mac OS X detected" SCAD="open -n -a OpenSCAD --args" else echo "Linux detected" SCAD="openscad" fi echo "deleting previous build output" rm -rf $OUTDIR mkdir -p $OUTDIR for MODULE in $MODULES do PARTS=$(grep -o "part.*//.*\[.*]" ${MODULE}.scad | sed 's/,/ /g' | sed 's/.*\[\([^]]*\)\].*/\1/g') echo "generating from ${MODULE}" for PART in ${PARTS} do if [[ "${PART}" != "OPT_"* ]]; then echo ${PART} FILENAME=$(echo $OUTDIR/${MODULE}_${PART}.stl | tr '[:upper:]' '[:lower:]') $SCAD $(pwd)/${MODULE}.scad --D part=\"${PART}\" --o $(pwd)/${FILENAME} fi done done
Here is an example scad.yml
file.
# SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ name: STLs # build for each push and pull request on: [push, pull_request] jobs: render: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Checkout repo submodules run: git submodule update --init - name: Install dependencies run: | sudo apt update sudo apt-get install -y openscad zip - name: Render STLs run: | ./3dprint/generate_stls.sh - name: Upload part files uses: actions/upload-artifact@v4.0.0 with: name: drumkit-stl path: 3dprint/stl if-no-files-found: error - name: Archive release files if: startsWith(github.ref, 'refs/tags/') run: | cd 3dprint zip -r drumkit-stl stl - name: Upload release files if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 with: files: 3dprint/drumkit-stl.zip
And then add something like this to the mdBook sources where you want the visualization to appear:
{{#include inc_enclosure_bottom.stl.md}}
[Direct link to this file](./stl/enclosure_bottom.stl).
7) PCB gerber files
If you want to generate gerber files from PCBs, do something similar with pcb/generate_fab.sh
and .github/workflows/kicad.yml
:
Here is an example generate_fab.sh
file.
#!/bin/bash # SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ cd "$(dirname "$0")" INFILE="lars2.kicad_pcb" INFILE_SCH="lars2.kicad_sch" OUTDIR="fabrication" OUTZIP="fab" echo "Creating output directory" rm -rf $OUTDIR mkdir -p $OUTDIR echo "Exporting drill files" #kicad-cli pcb export drill -o $OUTDIR/ --format excellon --generate-map --map-format pdf $INFILE kicad-cli pcb export drill -o $OUTDIR/ --format gerber --generate-map --map-format gerberx2 $INFILE echo "Exporting gerber files" #kicad-cli pcb export gerbers -o $OUTDIR/ $INFILE kicad-cli pcb export gerbers -o $OUTDIR/ -l F.Cu,B.Cu,F.Mask,B.Mask,F.Paste,B.Paste,F.Silkscreen,B.Silkscreen,Edge.Cuts $INFILE echo "Exporting BOM files" kicad-cli sch export python-bom -o $OUTDIR/bom.xml $INFILE_SCH # TODO convert BOM XML to proper format for JLCPCB echo "Compressing archive" rm -rf $OUTZIP.zip zip -r $OUTZIP fabrication
Here is an example kicad.yml
file.
# SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ name: PCB # build for each push and pull request on: [push, pull_request] jobs: fabrication: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repo uses: actions/checkout@v4 with: fetch-depth: 0 - name: Checkout repo submodules run: git submodule update --init - name: Install dependencies run: | sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases sudo apt update sudo apt install -y --install-recommends kicad sudo apt-get install -y zip - name: Generate fabrication files run: | ./pcb/generate_fab.sh ./pcb2/generate_fab.sh - name: Upload board files uses: actions/upload-artifact@v4.0.0 with: name: prototype-pcb path: pcb/fabrication - name: Upload board files uses: actions/upload-artifact@v4.0.0 with: name: v2-pcb path: pcb2/fabrication - name: Rename release files if: startsWith(github.ref, 'refs/tags/') run: | mv pcb/fab.zip fab_proto.zip mv pcb2/fab.zip fab_v2.zip - name: Upload release files if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 with: files: fab_proto.zip fab_v2.zip
8) PCB visualization
If you want to visualize KiCad schematics and PCBs in 2D and 3D, add pcb/generate_plot.sh
:
Here is an example generate_plot.sh
file.
#!/bin/bash # SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de> # SPDX-License-Identifier: CERN-OHL-S-2.0+ # # ------------------------------------------------------------------------------ # | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de> | # | | # | This source describes Open Hardware and is licensed under the CERN-OHL-S v2 | # | or any later version. | # | | # | You may redistribute and modify this source and make products using it under | # | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt) | # | or any later version. | # | | # | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, | # | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A | # | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version) | # | for applicable conditions. | # | | # | Source location: https://git.xythobuz.de/thomas/drumkit | # | | # | As per CERN-OHL-S v2 section 4, should You produce hardware based on this | # | source, You must where practicable maintain the Source Location visible | # | on the external case of the Gizmo or other products you make using this | # | source. | # ------------------------------------------------------------------------------ INSCH="lars2.kicad_sch" INPCB="lars2.kicad_pcb" OUTDIR="plot" LAYER_F="F.Cu,F.Mask,F.Paste,F.Silkscreen,Edge.Cuts,User.Drawings" LAYER_B="B.Cu,B.Mask,B.Paste,B.Silkscreen,Edge.Cuts,User.Drawings" LAYER_MANUAL="B.Cu,Edge.Cuts" cd "$(dirname "$0")" rm -rf $OUTDIR mkdir -p $OUTDIR # -------------- # | 2D Schematic | # -------------- for IN in $INSCH do echo "Exporting schematic $IN" for TYPE in pdf svg do echo "Exporting schematic $TYPE" rm -rf $OUTDIR/$IN.$TYPE kicad-cli sch export $TYPE \ -t "KiCad Default" \ -o $OUTDIR/$IN.$TYPE \ $IN echo done done for IN in $INPCB do echo "Exporting board $IN" # ----------- # | 2D Layout | # ----------- for TYPE in pdf svg do rm -rf $OUTDIR/$IN.$TYPE echo "Exporting board $TYPE" kicad-cli pcb export $TYPE \ -t "KiCad Classic" \ -l $LAYER_F,$LAYER_B \ -o $OUTDIR/$IN.$TYPE \ $IN echo echo "Exporting DIY board $VAR" rm -rf $OUTDIR/${IN}_diy.$TYPE kicad-cli pcb export $TYPE \ -t "KiCad Default" \ -l $LAYER_MANUAL \ --black-and-white \ --negative \ -o $OUTDIR/${IN}_diy.$TYPE \ $IN echo done # ----------- # | 3D Layout | # ----------- echo "Exporting board 3D" kicad-cli pcb export vrml \ -o $OUTDIR/$IN.wrl \ $IN echo done
Then include this where you want the 2D schematic visualization to appear:
You can also view the [schematics as PDF](./plot/lars2.kicad_sch.pdf).
{{#include inc_lars2.kicad_sch.md}}
And this where you want to visualize the 2D PCB layout:
You can also view the [2D PCB layout as PDF](./plot/lars2.kicad_pcb.pdf).
<script src="/js/svg-pan-zoom.js" charset="UTF-8"></script>
<div style="background-color: white; border: 1px solid black;">
<embed type="image/svg+xml" src="/plot/lars2.kicad_pcb.svg" id="pz_drumkit0" style="width: 100%;"/>
<script>
document.getElementById('pz_drumkit0').addEventListener('load', function(){
svgPanZoom(document.getElementById('pz_drumkit0'), {controlIconsEnabled: true, minZoom: 1.0});
})
</script>
</div>
[Direct link to this file](./plot/lars2.kicad_pcb.svg).
And add this where you want to visualize the 3D PCB layout:
{{#include inc_lars2.kicad_pcb.md}}
That should more or less be all that's required.
Of course, adapt paths as needed to match your project layout. And test locally by calling the scripts manually on your machine:
./pcb/generate_plot.sh
./3dprint/generate_stls.sh
./docs/generate_docs.sh serve
Hope this helps.