diff --git a/node_board/CMakeLists.txt b/node_board/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b7ba219bf484f39eafc33f74f30b63b8a774c56d
--- /dev/null
+++ b/node_board/CMakeLists.txt
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.13) # 3.13 is required for target_link_options
+project(NodeBoard CXX)
+
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Release")
+endif()
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+
+include(cmake/shared_settings.cmake)
+find_package(PNG REQUIRED)
+
+add_executable(generate_node_board
+    main.cpp
+    png_writer.cpp
+    png_writer.h
+)
+target_include_directories(generate_node_board PUBLIC ${PNG_INCLUDE_DIR})
+target_link_libraries(generate_node_board shared_settings ${PNG_LIBRARY})
+target_compile_features(generate_node_board PUBLIC cxx_std_17)
diff --git a/node_board/cmake/shared_settings.cmake b/node_board/cmake/shared_settings.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..19340ff1d74c3dd81346bb03ef2e0fa300b32068
--- /dev/null
+++ b/node_board/cmake/shared_settings.cmake
@@ -0,0 +1,35 @@
+# This file defines an interface library used to add common compile flags to all libraries and
+# executables in FunkyCT.
+
+add_library(shared_settings INTERFACE)
+
+# Warning flags
+target_compile_options(shared_settings INTERFACE
+    -Wall
+    -Wcast-align
+    -Wcast-qual
+    -Wextra
+    -Wundef
+    -Wuseless-cast
+    -Wzero-as-null-pointer-constant
+    -pedantic
+)
+
+# Speed flags
+target_compile_options(shared_settings INTERFACE -march=native -ffast-math)
+
+# Build type for profile generation
+target_compile_options(shared_settings INTERFACE $<$<CONFIG:ProfileGenerate>:
+    -fprofile-generate
+    -O3
+    -DNDEBUG
+>)
+target_link_options(shared_settings INTERFACE $<$<CONFIG:ProfileGenerate>:-fprofile-generate>)
+
+# Build type for profile use
+target_compile_options(shared_settings INTERFACE $<$<CONFIG:ProfileUse>:
+    -fprofile-use
+    -O3
+    -DNDEBUG
+>)
+target_link_options(shared_settings INTERFACE $<$<CONFIG:ProfileUse>:-fprofile-use>)
diff --git a/node_board/main.cpp b/node_board/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b6269331dd1fe2369573bd6cb79a5c2a0459de36
--- /dev/null
+++ b/node_board/main.cpp
@@ -0,0 +1,226 @@
+#include "png_writer.h"
+#include <iostream>
+
+//--------------------------------------------------------------------------------------------------
+struct Rectangle {
+    int32_t x_min;
+    int32_t x_max;
+    int32_t y_min;
+    int32_t y_max;
+};
+
+//--------------------------------------------------------------------------------------------------
+class Board {
+public:
+    Board(double width, double height, double pix_per_mm, double min_cut_thickness)
+    : width_(width), height_(height), pix_per_mm_(pix_per_mm),
+      min_cut_thickness_(min_cut_thickness), width_px_(to_px(width_)), height_px_(to_px(height_)),
+      min_cut_thickness_px_(to_px(min_cut_thickness_))
+    {
+        png_writer_.allocate(width_px_ + 4 * min_cut_thickness_px_,
+                             height_px_ + 4 * min_cut_thickness_px_);
+        png_writer_.set_all_pixels(255);
+        draw_int_rectangle(0, width_px_, 0, height_px_, 0);
+    }
+
+    int32_t to_px(double x) {
+        return static_cast<int32_t>(pix_per_mm_ * x);
+    }
+
+    void set_pixel(int32_t x, int32_t y, uint8_t value = 255) {
+        x = std::max(-2 * min_cut_thickness_px_, x);
+        x = std::min(width_px_ + 2 * min_cut_thickness_px_, x);
+        y = std::max(-2 * min_cut_thickness_px_, y);
+        y = std::min(height_px_ + 2 * min_cut_thickness_px_, y);
+        png_writer_.set_pixel(2 * min_cut_thickness_px_ + x,
+                              2 * min_cut_thickness_px_ + height_px_ - y - 1,
+                              value);
+    }
+
+    void draw_int_rectangle(
+        int32_t x_min, int32_t x_max, int32_t y_min, int32_t y_max, uint8_t value = 255
+    ) {
+        for (int32_t x = x_min; x < x_max; ++x) {
+            for (int32_t y = y_min; y < y_max; ++y) {
+                set_pixel(x, y, value);
+            }
+        }
+    }
+
+    void draw_rectangle(
+        double x_min, double x_max, double y_min, double y_max, uint8_t value = 255
+    ) {
+        draw_int_rectangle(to_px(x_min), to_px(x_max), to_px(y_min), to_px(y_max), value);
+    }
+
+    void draw_rectangle(Rectangle const& r, uint8_t value = 255) {
+        draw_int_rectangle(r.x_min, r.x_max, r.y_min, r.y_max, value);
+    }
+
+    void draw_pad(double x_min, double x_max, double y_min, double y_max) {
+        draw_rectangle(x_min - min_cut_thickness_, x_max + min_cut_thickness_,
+                       y_min - min_cut_thickness_, y_max + min_cut_thickness_,
+                       0);
+        draw_rectangle(x_min, x_max, y_min, y_max, 255);
+    }
+
+    void save(char const* filename) {
+        png_writer_.write(filename);
+    }
+
+    void save_outline(char const* filename) {
+        png_writer_.set_all_pixels_black();
+        draw_int_rectangle(0, width_px_, 0, height_px_);
+        save(filename);
+    }
+
+public:
+    PngWriter png_writer_;
+    double width_;
+    double height_;
+    double pix_per_mm_;
+    double min_cut_thickness_;
+    int32_t width_px_;
+    int32_t height_px_;
+    int32_t min_cut_thickness_px_;
+};
+
+//--------------------------------------------------------------------------------------------------
+// All length measurements are in mm.
+int main() {
+    // reused vars... dirty C style
+    double pad_x_min;
+    double pad_x_max;
+    double pad_y_min;
+    double pad_y_max;
+
+    // board params
+    // width is deduced later
+    double const height = 18;
+    double const ppmm = 50; // equivalent to 1270 ppi
+    double const min_cut_thickness = 0.38;
+    double const min_trace_thickness = 0.35;
+
+    // SOIC dims
+    double const pad_width = 0.6;
+    double const pad_height = 2.4;
+    double const soic_height = 7;
+    double const soic_pitch = 1.27;
+    double const soic_width = 3 * soic_pitch + pad_width;
+
+    // deduce width and make the board
+    double const width = soic_width + 3 * min_cut_thickness + 3 * min_trace_thickness;
+    std::cout << "Board width: " << width << "mm\n";
+    Board board(width, height, ppmm, min_cut_thickness);
+
+    // SOIC pads
+    double const soic_x_min = min_trace_thickness + min_cut_thickness;
+    double const soic_x_max = soic_x_min + 3 * soic_pitch + pad_width;
+    double const soic_btm_y = 0.5 * (height - soic_height);
+    double const soic_top_y = height - soic_btm_y;
+    for (int32_t i = 0; i < 4; ++i) {
+        pad_x_min = soic_x_min + i * soic_pitch;
+        pad_x_max = pad_x_min + pad_width;
+        pad_y_max = soic_btm_y + pad_height;
+        board.draw_rectangle(pad_x_min, pad_x_max, soic_btm_y, pad_y_max);
+        pad_y_min = soic_top_y - pad_height;
+        board.draw_rectangle(pad_x_min, pad_x_max, pad_y_min, soic_top_y);
+    }
+
+    // Ground pads and traces
+    double const cable_pad_width = (width - 2 * min_cut_thickness) / 3;
+    double const cable_pad_height = soic_btm_y - min_cut_thickness;
+    double const bridge_offset = 0.5 * (pad_width - min_trace_thickness);
+    board.draw_rectangle(0, cable_pad_width, 0, cable_pad_height);
+    board.draw_rectangle(0, min_trace_thickness, 0, height);
+    board.draw_rectangle(soic_x_min + bridge_offset,
+                         soic_x_min + bridge_offset + min_trace_thickness,
+                         cable_pad_height,
+                         cable_pad_height + min_cut_thickness);
+    board.draw_rectangle(0,
+                         cable_pad_width,
+                         soic_top_y + 2 * min_cut_thickness + min_trace_thickness,
+                         height);
+
+    // Data pads and traces
+    double const data_pad_x_min = cable_pad_width + min_cut_thickness;
+    double const data_pad_x_max = data_pad_x_min + cable_pad_width;
+    board.draw_rectangle(data_pad_x_min, data_pad_x_max, 0, cable_pad_height);
+    board.draw_rectangle(data_pad_x_max,
+                         soic_x_max + min_cut_thickness + min_trace_thickness,
+                         cable_pad_height - min_cut_thickness,
+                         cable_pad_height);
+    board.draw_rectangle(soic_x_max + min_cut_thickness,
+                         soic_x_max + min_cut_thickness + min_trace_thickness,
+                         cable_pad_height,
+                         soic_btm_y + pad_height + min_cut_thickness);
+    board.draw_rectangle(soic_x_min + bridge_offset,
+                         soic_x_max + min_cut_thickness + min_trace_thickness,
+                         soic_btm_y + pad_height + min_cut_thickness,
+                         soic_btm_y + pad_height + min_cut_thickness + min_trace_thickness);
+    board.draw_rectangle(soic_x_min + bridge_offset,
+                         soic_x_min + bridge_offset + min_trace_thickness,
+                         soic_btm_y + pad_height + min_cut_thickness,
+                         soic_top_y + min_cut_thickness);
+    board.draw_rectangle(soic_x_min + bridge_offset,
+                         data_pad_x_max,
+                         soic_top_y + min_cut_thickness,
+                         soic_top_y + min_cut_thickness + min_trace_thickness);
+    board.draw_rectangle(data_pad_x_min, data_pad_x_max, soic_top_y + min_cut_thickness, height);
+
+    // VCC pads and traces
+    double const vcc_pad_x_min = 2 * (cable_pad_width + min_cut_thickness);
+    // pad 1
+    board.draw_rectangle(vcc_pad_x_min,
+                         width,
+                         0,
+                         // Note the fudge factor... It makes mods happy.
+                         cable_pad_height - min_cut_thickness - min_trace_thickness - 0.025);
+    board.draw_rectangle(width - min_trace_thickness, width, 0, height);
+    // pad 2
+    board.draw_rectangle(vcc_pad_x_min,
+                         width,
+                         height - cable_pad_height,
+                         height);
+    // pad extension
+    board.draw_rectangle(soic_x_max + min_cut_thickness,
+                         width,
+                         soic_top_y - pad_height - min_cut_thickness,
+                         height - cable_pad_height);
+    double const vcc_pin_x = soic_x_min + soic_pitch + bridge_offset;
+    board.draw_rectangle(vcc_pin_x,
+                         width,
+                         soic_top_y - pad_height - min_cut_thickness - min_trace_thickness,
+                         soic_top_y - pad_height - min_cut_thickness);
+    board.draw_rectangle(vcc_pin_x,
+                         vcc_pin_x + min_trace_thickness,
+                         soic_top_y - pad_height - min_cut_thickness,
+                         soic_top_y);
+
+    // Extend pad divisions
+    board.draw_rectangle(cable_pad_width,
+                         cable_pad_width + min_cut_thickness,
+                         -min_cut_thickness,
+                         0,
+                         0);
+    board.draw_rectangle(cable_pad_width,
+                         cable_pad_width + min_cut_thickness,
+                         height,
+                         height + min_cut_thickness,
+                         0);
+    board.draw_rectangle(width - cable_pad_width - min_cut_thickness,
+                         width - cable_pad_width,
+                         -min_cut_thickness,
+                         0,
+                         0);
+    board.draw_rectangle(width - cable_pad_width - min_cut_thickness,
+                         width - cable_pad_width,
+                         height,
+                         height + min_cut_thickness,
+                         0);
+
+    board.save("node_board_traces.png");
+    board.save_outline("node_board_outline.png");
+
+    return 0;
+}
diff --git a/node_board/png_writer.cpp b/node_board/png_writer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ac2e4712e2f82f3023a54f71b9fe92aa7f5f5ed7
--- /dev/null
+++ b/node_board/png_writer.cpp
@@ -0,0 +1,126 @@
+#include "png_writer.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <cstring>
+
+#include <iostream>
+
+//..................................................................................................
+PngWriter::PngWriter(): width_(0), height_(0), row_pointers_(nullptr) {}
+
+//..................................................................................................
+PngWriter::~PngWriter() {
+    if (row_pointers_ != nullptr) {
+        free();
+    }
+}
+
+//..................................................................................................
+void PngWriter::free() {
+    for (int32_t y = 0; y < height_; y++) {
+        std::free(row_pointers_[y]);
+    }
+    std::free(row_pointers_);
+    width_ = 0;
+    height_ = 0;
+}
+
+//..................................................................................................
+void PngWriter::allocate(int32_t width, int32_t height) {
+    if (row_pointers_ != nullptr) {
+        free();
+    }
+    width_ = width;
+    height_ = height;
+    row_pointers_ = (png_bytep*)malloc(sizeof(png_bytep) * height_);
+    for (int y = 0; y < height_; y++) {
+        row_pointers_[y] = (png_bytep)malloc(row_size());
+    }
+}
+
+//..................................................................................................
+void PngWriter::set_pixel(int32_t x, int32_t y, uint8_t value) {
+    png_bytep row = row_pointers_[y];
+    png_bytep px = &(row[x * 3]);
+    px[0] = value;
+    px[1] = value;
+    px[2] = value;
+}
+
+//..................................................................................................
+void PngWriter::set_all_pixels(uint8_t value) {
+    for (int y = 0; y < height_; y++) {
+        png_bytep row = row_pointers_[y];
+        for (int x = 0; x < width_; x++) {
+            png_bytep px = &(row[x * 3]);
+            px[0] = value;
+            px[1] = value;
+            px[2] = value;
+        }
+    }
+}
+
+//..................................................................................................
+void PngWriter::set_all_pixels_black() {
+    for (int y = 0; y < height_; y++) {
+        std::memset(row_pointers_[y], 0, row_size());
+    }
+}
+
+//..................................................................................................
+void PngWriter::write(char const* filename) {
+    auto fp = fopen(filename, "wb");
+    if (!fp) {
+        std::cout << "Couldn't make file\n";
+        abort();
+    }
+
+    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+    if (!png) {
+        std::cout << "Couldn't make png_structp\n";
+        abort();
+    }
+
+    png_infop info = png_create_info_struct(png);
+    if (!info) {
+        std::cout << "Couldn't make png_structp\n";
+        abort();
+    }
+
+    if (setjmp(png_jmpbuf(png))) {
+        std::cout << "Couldn't set jump\n";
+        abort();
+    }
+
+    png_init_io(png, fp);
+    // Output is 8bit depth, RGB format.
+    png_set_IHDR(png,
+                 info,
+                 width_,
+                 height_,
+                 8,
+                 PNG_COLOR_TYPE_RGB,
+                 PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_DEFAULT,
+                 PNG_FILTER_TYPE_DEFAULT
+    );
+    png_write_info(png, info);
+
+    if (png_get_rowbytes(png, info) != row_size()) {
+        std::cout << "Allocated bad amount of memory\n";
+        abort();
+    }
+
+    // To remove the alpha channel for PNG_COLOR_TYPE_RGB format,
+    // Use png_set_filler().
+    //png_set_filler(png_, 0, PNG_FILLER_AFTER);
+
+    png_write_image(png, row_pointers_);
+    png_write_end(png, NULL);
+
+    if (png && info) {
+        png_destroy_write_struct(&png, &info);
+    }
+
+    fclose(fp);
+}
diff --git a/node_board/png_writer.h b/node_board/png_writer.h
new file mode 100644
index 0000000000000000000000000000000000000000..6f65af42c0c5f027b9340bf2988d2d3f6d13e7b4
--- /dev/null
+++ b/node_board/png_writer.h
@@ -0,0 +1,26 @@
+#include <png.h>
+#include <cstdint>
+
+//--------------------------------------------------------------------------------------------------
+// This class is derived from code by Guillaume Cottenceau, copyright 2002-2010 and distributed
+// under the X11 license. https://gist.github.com/niw/5963798
+class PngWriter {
+public:
+    PngWriter();
+    ~PngWriter();
+
+    void free();
+    void allocate(int32_t width, int32_t height);
+    png_bytep* row_pointers() { return row_pointers_; }
+    void set_pixel(int32_t x, int32_t y, uint8_t value = 255);
+    void set_all_pixels(uint8_t value);
+    void set_all_pixels_black();
+    void write(char const* filename);
+
+private:
+    uint32_t row_size() { return 3 * width_; }
+
+    int32_t width_;
+    int32_t height_;
+    png_bytep* row_pointers_;
+};