Create standalone build system for KiCad's core computation libraries that compiles without wxWidgets, targeting both native and WebAssembly. Key additions: - wx_shim.h: ~250 lines replacing wx utilities with standard C++ - wxString class with Format() method - wxFFile for file I/O - wxASSERT, wxCHECK, wxFAIL_MSG macros - wxLog stubs - Stub headers: wx/debug.h, wx/log.h, wx/string.h, wx/file.h, etc. - config.h, advanced_config.h: Platform and triangulation config - CMakeLists.txt: Builds kimath, sexpr, clipper2, rtree Build results: - Native: libkimath.a (1.4MB), libsexpr.a, test passes - WASM: test_kimath.wasm (867KB), runs in Node.js Test verifies: - VECTOR2I, SEG, SHAPE_POLY_SET geometry operations - S-expression parsing of .kicad_pcb format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
Phase 2: Core Library Extraction
Overview
Extract KiCad's core computation code into a standalone library that compiles to both native and WebAssembly. This enables running the native wxWidgets GUI while delegating computation to a WASM module.
Architecture: Native GUI + WASM Core
┌─────────────────────────────────────────────────────────┐
│ Native GUI (wxWidgets) │
│ - PCB Editor canvas, menus, dialogs │
│ - File dialogs, clipboard │
│ - User interaction │
└─────────────────────┬───────────────────────────────────┘
│ S-expression serialization
▼
┌─────────────────────────────────────────────────────────┐
│ WASM Bridge │
│ - Wasmtime/wasm3 runtime (future) │
│ - Serialize board → S-expr → WASM │
│ - Deserialize results ← S-expr ← WASM │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ kicad_core.wasm │
│ - libs/kimath (geometry) │
│ - libs/sexpr (parsing) │
│ - Board data model │
│ - DRC engine │
│ - Router (Push & Shove) │
└─────────────────────────────────────────────────────────┘
Why This Works: wxWidgets is NOT Required for Core
Important: The core libs don't use wxWidgets for GUI - they just use utility macros that happen to come from wx. We provide drop-in replacements via a ~50 line shim header:
| Library | wx Usage | Count | Shim Solution |
|---|---|---|---|
| libs/kimath | wxASSERT, wxLogTrace | ~34 | Macros → assert/no-op |
| libs/sexpr | wxFFile, wxString | ~4 | Classes → std equivalents |
| libs/core | wxString | ~17 | typedef → std::string |
wx_shim.h
// core/include/wx_shim.h
#ifndef KICAD_WX_SHIM_H
#define KICAD_WX_SHIM_H
#include <cassert>
#include <string>
#include <cstdio>
// Assertions - just use standard assert
#define wxASSERT(x) assert(x)
#define wxASSERT_MSG(x, msg) assert((x) && (msg))
#define wxCHECK(x, ret) do { if(!(x)) return ret; } while(0)
#define wxCHECK_MSG(x, ret, msg) do { if(!(x)) return ret; } while(0)
#define wxFAIL_MSG(msg) assert(false && (msg))
// Logging - no-op or stderr
#define wxLogTrace(...) ((void)0)
#define wxLogDebug(...) ((void)0)
#define wxLogWarning(...) fprintf(stderr, __VA_ARGS__)
// String - just use std::string
using wxString = std::string;
using wxChar = char;
// File I/O - use standard C++
#include <fstream>
class wxFFile {
std::ifstream m_file;
public:
bool Open(const std::string& name) { m_file.open(name); return m_file.is_open(); }
bool IsOpened() const { return m_file.is_open(); }
size_t Read(void* buf, size_t count) { m_file.read((char*)buf, count); return m_file.gcount(); }
bool Eof() const { return m_file.eof(); }
};
#endif
Directory Structure
kicad-wasm/
├── core/
│ ├── CMakeLists.txt # Standalone build without wxWidgets
│ ├── include/
│ │ ├── wx_shim.h # wx compatibility layer (~50 lines)
│ │ └── kicad_core_api.h # C API for WASM
│ ├── src/
│ │ └── api.cpp # API implementation
│ └── wasm/
│ └── CMakeLists.txt # Emscripten-specific settings
├── test/
│ ├── test_geometry.cpp # Test kimath without wx
│ ├── test_board_io.cpp # Test board load/save
│ └── test.kicad_pcb # Sample board file
Implementation Steps
Step 1: Foundation + Shim Layer
- Create
core/directory structure - Create
wx_shim.hwith standard C++ replacements - Create CMakeLists.txt that builds kimath, core, sexpr
- Test: Compiles without wxWidgets
Step 2: Board Model Extraction
- Identify minimal BOARD dependencies
- Create C API:
kicad_load_board(),kicad_save_board() - Handle S-expression serialization
- Test: Load a .kicad_pcb file via API
Step 3: DRC Engine Extraction
- Extract DRC_ENGINE and test providers
- Create C API:
kicad_drc_run()returns violations as JSON - Test: Run DRC on test board
Step 4: Router Extraction
- Extract PNS::ROUTER and supporting classes
- Create minimal ROUTER_IFACE implementation
- Create C API:
kicad_router_start(),_move(),_commit() - Test: Route traces via API
Step 5: Emscripten Build
- Set up emsdk toolchain
- Build
kicad_core.wasm - Create JavaScript bindings
- Test: Load board in Node.js, run DRC, route traces
Step 6: Browser Demo (Future)
- Simple HTML page with file upload
- Load .kicad_pcb, display stats
- Run DRC, show violations
C API Design
Board I/O
extern "C" {
// Load board from S-expression string
void* kicad_load_board(const char* sexpr_data, size_t len);
// Serialize board to S-expression
char* kicad_save_board(void* board);
// Free memory
void kicad_free_board(void* board);
void kicad_free_string(char* str);
// Query operations
int kicad_get_track_count(void* board);
int kicad_get_footprint_count(void* board);
}
DRC Engine
extern "C" {
// Initialize DRC with rules
void* kicad_drc_create(void* board, const char* rules_sexpr);
// Run DRC, returns JSON array of violations
char* kicad_drc_run(void* drc_engine);
// Query specific clearance
int kicad_drc_query_clearance(void* drc, int item_a, int item_b);
void kicad_drc_free(void* drc);
}
Router
extern "C" {
// Create router with board data
void* kicad_router_create(void* board);
// Start routing from point
int kicad_router_start(void* router, int x, int y, int layer);
// Move to point, returns preview geometry as S-expr
char* kicad_router_move(void* router, int x, int y);
// Commit route
char* kicad_router_commit(void* router);
void kicad_router_free(void* router);
}
Emscripten Build
source /path/to/emsdk/emsdk_env.sh
cd kicad-wasm
mkdir build-wasm && cd build-wasm
emcmake cmake ../core \
-DCMAKE_BUILD_TYPE=Release \
-DKICAD_WASM_BUILD=ON
emmake make
Emscripten CMake Settings
if(EMSCRIPTEN)
set_target_properties(kicad_core PROPERTIES
LINK_FLAGS "-s EXPORTED_FUNCTIONS='[_kicad_load_board,_kicad_save_board,...]' \
-s EXPORTED_RUNTIME_METHODS='[ccall,cwrap,UTF8ToString]' \
-s MODULARIZE=1 \
-s EXPORT_NAME='KicadCore' \
-s ALLOW_MEMORY_GROWTH=1"
)
endif()
Key KiCad Source Files
Libraries to include (via shim, no modification):
kicad/libs/kimath/src/**/*.cpp- 17.5k lines, geometrykicad/libs/core/*.cpp- 870 lines, utilitieskicad/libs/sexpr/*.cpp- 734 lines, parserkicad/pcbnew/board*.cpp- Board data modelkicad/pcbnew/pcb_io/kicad_sexpr/*.cpp- S-expr I/Okicad/pcbnew/drc/*.cpp- DRC enginekicad/pcbnew/router/pns_*.cpp- Router
Key headers:
kicad/libs/kimath/include/geometry/shape_poly_set.h- Polygon opskicad/pcbnew/board.h- BOARD class (1510 lines)kicad/pcbnew/drc/drc_engine.h- DRC entry pointkicad/pcbnew/router/pns_router.h- Router entry point
Progress
✅ Step 1: Foundation + Shim Layer (COMPLETE)
Date: 2025-11-26
Successfully compiled kimath standalone without wxWidgets:
core/
├── include/
│ ├── wx_shim.h # ~170 lines (more than expected, but still minimal)
│ ├── config.h # Platform configuration
│ ├── advanced_config.h # Default values for triangulation etc.
│ └── wx/ # Stub wx headers
│ ├── debug.h
│ ├── log.h
│ ├── string.h
│ └── confbase.h
├── src/
│ └── test_kimath.cpp
├── CMakeLists.txt
└── build/
├── libkimath.a # 1.4 MB static library
├── libclipper2.a
├── libkicad_core_utils.a
└── test_kimath # Working test executable
Key learnings:
- wx_shim.h needed to be ~170 lines, not ~50, due to:
wxString::Format()with varargs required a proper class with template Format methodFormatArg<T>template needed to convert string args toc_str()for snprintfwxLog::EnableLogging()used in polygon_triangulation.hwxString::RemoveLast()used for string manipulationADVANCED_CFGclass needed for triangulation settings
- C++20 required (not C++17) due to KiCad's use of concepts
- Build order: Our
core/include/must come FIRST in include paths
Test output:
Testing kimath standalone build...
Created VECTOR2I: (0,0) and (100,100)
SEG length: 141
Created polygon with 1 outline(s)
Polygon area: 1e+06
kimath standalone build: SUCCESS!
✅ Step 5 (partial): Emscripten Build (COMPLETE)
Date: 2025-11-26
Successfully compiled kimath to WebAssembly:
$ emcmake cmake .. && emmake make
$ node test_kimath.js
Testing kimath standalone build...
Created VECTOR2I: (0,0) and (100,100)
SEG length: 141
Created polygon with 1 outline(s)
Polygon area: 1e+06
kimath standalone build: SUCCESS!
Build artifacts:
build-wasm/
├── test_kimath.wasm # 845KB - WASM module
├── test_kimath.js # 154KB - JS glue code
├── libkimath.a # 5.1MB - Static WASM library
├── libclipper2.a # 1.2MB
└── libkicad_core_utils.a # 106KB
Key findings:
- No code changes needed between native and WASM builds
- Same wx_shim.h works for both targets
- WASM module runs identically to native in Node.js
✅ Step 2 (partial): S-expression Parser (COMPLETE)
Date: 2025-11-26
Added libs/sexpr to the standalone build:
Additional stubs needed:
wx/file.h,wx/ffile.h- file I/O stubswxFFileclass in wx_shim.h (~80 lines)string_utils.h- minimal stub forFrom_UTF8()
Test output (both native and WASM):
Testing S-expression parser...
Parsed S-expr with 4 elements
Root element: kicad_pcb
WASM sizes:
test_kimath.wasm - 867KB (geometry + sexpr parser)
libkimath.a - 5.1MB
libsexpr.a - 159KB
libclipper2.a - 1.2MB
Success Criteria
- wx_shim.h provides all needed wx replacements
- libs/kimath compiles with shim (no wxWidgets linked)
- kicad_core.wasm builds with Emscripten (kimath portion)
- libs/sexpr compiles with shim (added wxFFile, string_utils stubs)
- S-expression parser can parse .kicad_pcb format strings
- S-expression parser can load .kicad_pcb from string
- Board data model extracts cleanly
- C API wrapper builds as native static library
- kicad_core.wasm builds with Emscripten
- Node.js can load a .kicad_pcb file via WASM
- DRC runs in WASM, outputs violation list
- Router API works in WASM
First Concrete Step
Start with Step 1: Create core/ directory with wx_shim.h and attempt to compile just libs/kimath standalone. This proves the shim approach works before tackling the larger board model.
Dependencies
Must include in WASM build:
- Clipper2 library (polygon boolean operations) - pure C++
- RTree (spatial indexing) - header-only
Not needed:
- wxWidgets (replaced by shim)
- Boost (only header-only templates used)
- OpenCASCADE, ngspice, curl, libgit2 (already disabled in Phase 1)