Reorganize project structure for clarity

- Rename tests/wasm-app/ to tests/apps/ for brevity
- Move lib/gl_immediate_shim.js to wasm/shims/ (consolidates WASM files)
- Delete docs/ directory (outdated Nov 2024 research docs)
- Delete patches/ directory (already applied to fork submodules)
- Delete wasm/patches/ and wasm/config/ (empty/unused)
- Update all file references in scripts, configs, and documentation
- Update .gitignore for new tests/apps/ paths

Verified both build workflows pass:
- wxWidgets tests: 255 passed
- Docker KiCad build: completed successfully

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-12-27 14:06:23 +01:00
commit a26b40c063
78 changed files with 38 additions and 128055 deletions

18
.gitignore vendored
View file

@ -39,15 +39,15 @@ wxwidgets-clean/
/tests/test-results/
/tests/.test-port
/test-results/
/tests/wasm-app/*.js
/tests/wasm-app/*.html
/tests/wasm-app/minimal_test.wasm
/tests/wasm-app/standalone/*/*.js
/tests/wasm-app/standalone/*/*.html
/tests/wasm-app/standalone/*/*.wasm
/tests/wasm-app/kicad/*.js
/tests/wasm-app/kicad/*.wasm
!tests/wasm-app/kicad/pcbnew.html
/tests/apps/*.js
/tests/apps/*.html
/tests/apps/minimal_test.wasm
/tests/apps/standalone/*/*.js
/tests/apps/standalone/*/*.html
/tests/apps/standalone/*/*.wasm
/tests/apps/kicad/*.js
/tests/apps/kicad/*.wasm
!tests/apps/kicad/pcbnew.html
/temp/
*.log
*.tmp

View file

@ -9,22 +9,25 @@ kicad-wasm/
├── kicad/ # KiCad source (git submodule)
├── wxwidgets/ # wxWidgets source (git submodule)
├── wasm/ # WASM compatibility layer
│ ├── bindings/ # Embind bindings for JavaScript
│ ├── cmake/ # CMake find modules
│ ├── kiplatform/ # Platform abstraction (app, UI, printing)
│ ├── libcontext/ # Coroutine/fiber implementation
│ ├── stubs/ # Stub implementations (libgit2, curl)
│ └── config/ # Build configuration headers
├── patches/ # KiCad source patches
│ ├── shims/ # Runtime JavaScript shims
│ └── stubs/ # Stub implementations (libgit2, curl)
├── scripts/ # Build scripts
│ ├── build-wxuniversal-wasm.sh # Build wxWidgets for WASM
│ ├── build-wasm-test.sh # Build wxWidgets test apps
│ ├── deps/ # Dependency build scripts
│ ├── kicad/ # KiCad build scripts
│ ├── common/ # Shared utilities and config
│ └── config/ # Build configuration
│ ├── common/ # Shared utilities
│ └── config/ # Build config wrappers
├── docker/ # Docker build environment
├── tests/ # Playwright E2E tests
├── output/ # Build output (pcbnew.js, pcbnew.wasm)
└── docs/ # Research documentation
│ ├── e2e/ # Test specs
│ └── apps/ # WASM test applications
├── tools/ # External tools (binaryen)
└── output/ # Build output (pcbnew.js, pcbnew.wasm)
```
## Two Build Workflows
@ -63,7 +66,7 @@ Build standalone wxWidgets test apps for feature testing:
cd tests && npm install && npm test
```
Output: `tests/wasm-app/standalone/`
Output: `tests/apps/standalone/`
## Prerequisites
@ -110,7 +113,6 @@ See [tests/README.md](tests/README.md) for test documentation.
- [Build System](build.md) - Docker build details
- [Docker README](docker/README.md) - Container setup
- [Tests README](tests/README.md) - Test infrastructure
- [Research Docs](docs/) - Original research notes
## License

View file

@ -1,335 +0,0 @@
# KiCad Wasm Port - Implementation Plan
## Goal
Run KiCad with native wxWidgets GUI, but with core logic (file parsing, geometry, DRC, routing) executing in a WebAssembly module. This validates the Wasm build before tackling the browser UI.
---
## Phase 1: Minimal Build with Stubs
**Objective**: Get KiCad compiling with optional deps disabled via preprocessor guards.
### 1.1 Create Project Structure
```
kicad-wasm/
├── kicad/ # Git submodule → upstream KiCad
├── patches/
│ └── 0001-optional-deps.patch
├── stubs/
│ └── include/
│ ├── git2.h # Minimal stub headers
│ └── curl/curl.h
├── cmake/
│ └── KicadWasmOptions.cmake
├── CMakeLists.txt
└── scripts/
├── prepare.sh # Apply patches
└── update-kicad.sh # Update to new KiCad version
```
### 1.2 Create the Patch
Add CMake options to `CMakeLists.txt` (after line 107):
```cmake
option( KICAD_USE_CURL "Enable network features" ON )
option( KICAD_USE_GIT "Enable git integration" ON )
option( KICAD_USE_OCC "Enable OpenCASCADE for STEP" ON )
option( KICAD_USE_NGSPICE "Enable SPICE simulation" ON )
option( KICAD_USE_DATABASE "Enable database libraries" ON )
```
Wrap `find_package` calls conditionally:
```cmake
if( KICAD_USE_CURL )
find_package( CURL REQUIRED )
add_compile_definitions( KICAD_USE_CURL )
else()
add_library( CURL::libcurl INTERFACE IMPORTED )
endif()
```
### 1.3 Create Stub Headers
Minimal headers that let code compile without the actual libraries:
```cpp
// stubs/include/git2.h
#pragma once
typedef struct git_repository git_repository;
// Stub functions return error codes
```
### 1.4 Add Preprocessor Guards to Source
Wrap implementations in `common/git/*.cpp`, `common/kicad_curl/*.cpp`:
```cpp
#ifdef KICAD_USE_GIT
// actual implementation
#else
// return error or throw "feature disabled"
#endif
```
### 1.5 Verify Native Build
```bash
cd kicad-wasm
./scripts/prepare.sh
mkdir build && cd build
cmake .. -DKICAD_USE_CURL=OFF -DKICAD_USE_GIT=OFF -DKICAD_USE_OCC=OFF
make -j$(nproc)
```
Confirm KiCad launches and can open/edit PCB files (without git/network/STEP features).
---
## Phase 2: Extract Core Library
**Objective**: Build core computation code as a standalone library that can be compiled to both native and Wasm.
### 2.1 Identify Core Components
Create `kicad-wasm/core/CMakeLists.txt` targeting:
| Component | Source Location | Notes |
|-----------|-----------------|-------|
| Math/Geometry | `libs/kimath/` | Pure C++, no deps |
| Core utilities | `libs/core/` | Pure C++ |
| KiCad file parser | `common/io/kicad/` | Needs minimal deps |
| S-expression parser | `libs/sexpr/` | Pure C++ |
| Board data model | `pcbnew/board*.cpp` | Extract carefully |
### 2.2 Define API Boundary
Create a C API for the core (easier Wasm interop than C++):
```cpp
// kicad-wasm/core/include/kicad_core_api.h
extern "C" {
// File operations
void* kicad_load_pcb(const char* data, size_t len);
void kicad_free_pcb(void* board);
char* kicad_serialize_pcb(void* board);
// Query
int kicad_get_track_count(void* board);
// Modification
void kicad_add_track(void* board, /* params */);
}
```
### 2.3 Build Core as Static Library
```cmake
# kicad-wasm/core/CMakeLists.txt
add_library(kicad_core STATIC
${KIMATH_SOURCES}
${SEXPR_SOURCES}
${IO_KICAD_SOURCES}
api/kicad_core_api.cpp
)
target_compile_definitions(kicad_core PRIVATE
KICAD_CORE_ONLY=1
)
```
---
## Phase 3: Compile Core to WebAssembly
**Objective**: Build `kicad_core.wasm` using Emscripten.
### 3.1 Emscripten Build
```bash
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
```
### 3.2 Export Functions
```cmake
# For Emscripten
if(EMSCRIPTEN)
set_target_properties(kicad_core PROPERTIES
LINK_FLAGS "-s EXPORTED_FUNCTIONS='[_kicad_load_pcb,_kicad_free_pcb,...]' \
-s EXPORTED_RUNTIME_METHODS='[ccall,cwrap]' \
-s MODULARIZE=1 \
-s EXPORT_NAME='KicadCore'"
)
endif()
```
### 3.3 Test Wasm Module
Create a simple test that loads a `.kicad_pcb` file:
```javascript
// test/test_core.mjs
import KicadCore from './kicad_core.js';
const core = await KicadCore();
const pcbData = fs.readFileSync('test.kicad_pcb', 'utf8');
const board = core.ccall('kicad_load_pcb', 'number', ['string', 'number'],
[pcbData, pcbData.length]);
console.log('Track count:', core.ccall('kicad_get_track_count', 'number', ['number'], [board]));
```
---
## Phase 4: Native App with Wasm Worker
**Objective**: Run native KiCad GUI but delegate core operations to the Wasm module.
### 4.1 Choose Wasm Runtime
Options for running Wasm in native app:
- **Wasmtime** - Rust-based, mature, good C API
- **wasm3** - Small, fast interpreter
- **WAMR** - WebAssembly Micro Runtime, lightweight
Recommend: **Wasmtime** for development (better debugging), **wasm3** for size.
### 4.2 Create Wasm Bridge
```cpp
// kicad-wasm/bridge/wasm_bridge.h
class WasmBridge {
public:
WasmBridge(const std::string& wasmPath);
BOARD* LoadPCB(const std::string& data);
std::string SerializePCB(BOARD* board);
void RunDRC(BOARD* board);
private:
wasmtime_instance_t* m_instance;
};
```
### 4.3 Integrate with KiCad
Modify KiCad to optionally use the Wasm bridge:
```cpp
// In pcbnew loading code
#ifdef USE_WASM_CORE
auto board = WasmBridge::Get().LoadPCB(fileContent);
#else
auto board = IO_KICAD::Load(filename);
#endif
```
### 4.4 Validate Correctness
1. Load same PCB file via native code and via Wasm bridge
2. Compare serialized output (should be identical)
3. Run DRC via both paths, compare results
4. Profile performance difference
---
## Phase 5: Incremental Migration
**Objective**: Move more functionality to Wasm core, validate stability.
### Priority Order
1. **File I/O** - parsing and serialization
2. **DRC engine** - computationally intensive, isolated
3. **Router** - Push & Shove algorithms
4. **ERC** - schematic checks
### Validation Strategy
For each migrated component:
1. Keep both native and Wasm implementations
2. Add flag to switch between them
3. Run test suite with both
4. Benchmark performance
5. Remove native implementation once confident
---
## Milestones
| Milestone | Deliverable | Validation |
|-----------|-------------|------------|
| M1 | KiCad builds with deps disabled | Opens PCB files, basic editing works |
| M2 | Core library extracts cleanly | Compiles as standalone static lib |
| M3 | Core compiles to Wasm | Passes unit tests via Node.js |
| M4 | Native app loads Wasm worker | Can load/save PCB via Wasm bridge |
| M5 | DRC runs in Wasm | Results match native DRC |
---
## Design Decisions
### Memory Management
**Decision**: Serialize/deserialize on every operation
- Proof of concept - simplicity over performance
- Clean interface between GUI and Wasm core
- Easy to debug
- Browser-friendly (no shared memory complexity)
- Can optimize later if needed
```
GUI Wasm Core
│ │
│──serialize(board)─────────────▶│
│ │──process
│◀─────────────serialize(board)──│
│ │
```
### Threading Model
**Decision**: Web Workers
- Target is browser, design for it from the start
- Each heavy operation (DRC, routing) runs in dedicated worker
- Clean message-passing interface
- No shared memory complexity
```javascript
// Main thread spawns workers for heavy operations
const drcWorker = new Worker('drc-worker.js');
drcWorker.postMessage({ board: serializedBoard });
drcWorker.onmessage = (e) => updateUI(e.data.violations);
```
### Incremental Updates
**Decision**: Re-serialize entire board
- Simple for proof of concept
- Board files typically <10MB, acceptable latency
- Can add delta operations later if performance requires it
---
## Next Steps
1. Fork KiCad repo, set up as submodule
2. Write the minimal patch for optional deps
3. Create stub headers
4. Verify native build with features disabled
5. Begin core library extraction

View file

@ -1,846 +0,0 @@
# KiCad WebAssembly Port - Complete Knowledge Base
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Dependencies Deep Dive](#dependencies-deep-dive)
3. [Graphics System (GAL)](#graphics-system-gal)
4. [File I/O System](#file-io-system)
5. [Python Scripting](#python-scripting)
6. [Build System](#build-system)
7. [Features to Disable](#features-to-disable)
8. [Code Extraction Candidates](#code-extraction-candidates)
9. [Technical Challenges](#technical-challenges)
10. [Design Decisions](#design-decisions)
11. [File Reference](#file-reference)
---
## Architecture Overview
### Main Applications
| Application | Directory | Purpose |
|-------------|-----------|---------|
| `kicad` | `kicad/` | Project manager, launcher |
| `pcbnew` | `pcbnew/` | PCB layout editor |
| `eeschema` | `eeschema/` | Schematic capture |
| `gerbview` | `gerbview/` | Gerber file viewer |
| `cvpcb` | `cvpcb/` | Component-footprint association |
| `bitmap2component` | `bitmap2component/` | Image to footprint converter |
| `pcb_calculator` | `pcb_calculator/` | Engineering calculators |
| `pagelayout_editor` | `pagelayout_editor/` | Drawing sheet editor |
| `kicad-cli` | `kicad/cli/` | Command-line interface |
### Shared Libraries
| Library | Location | Purpose |
|---------|----------|---------|
| `kicommon` | `common/` | Shared UI, I/O, utilities (SHARED lib) |
| `kigal` | `common/gal/` | Graphics Abstraction Layer (SHARED lib) |
| `kimath` | `libs/kimath/` | Geometry and math (STATIC lib) |
| `core` | `libs/core/` | Base utilities (STATIC lib) |
| `sexpr` | `libs/sexpr/` | S-expression parser (STATIC lib) |
| `kiplatform` | `libs/kiplatform/` | Platform abstraction |
### Plugin Architecture (KIFACE)
Each major application is built as both:
- Standalone executable
- KIFACE module (`.kiface` on Linux/Mac, `.dll` on Windows)
This allows:
- Standalone operation
- Project manager integration via dynamic loading
Key files:
- `include/kiway.h` - KIWAY system for inter-module communication
- `include/kiway_holder.h` - Mixin for frames that participate in KIWAY
- `include/kiway_player.h` - Frame base class (`KIWAY_PLAYER : public wxFrame`)
### Frame Hierarchy
```
wxFrame
└── KIWAY_PLAYER (include/kiway_player.h)
└── EDA_BASE_FRAME (include/eda_base_frame.h)
├── PCB_BASE_FRAME (pcbnew/)
│ └── PCB_EDIT_FRAME
├── SCH_BASE_FRAME (eeschema/)
│ └── SCH_EDIT_FRAME
└── ...other frames
```
---
## Dependencies Deep Dive
### wxWidgets (GUI Framework)
**Version**: 3.2.0+
**Components used**: `gl aui adv html core net base propgrid xml stc richtext webview`
**Location**: Found via `find_package(wxWidgets)` in `CMakeLists.txt:1089`
KiCad requires GTK3 port on Linux. All GUI code depends on wxWidgets.
Key wxWidgets classes used:
- `wxFrame`, `wxDialog`, `wxPanel` - windows
- `wxGLCanvas` - OpenGL context
- `wxFileDialog` - file selection
- `wxAuiManager` - dockable panes
- `wxPropertyGrid` - property editors
**Wasm strategy**: Keep native initially. Eventually replace with web framework (React/Vue/Svelte).
### OpenGL / GLEW
**Purpose**: Hardware-accelerated 2D rendering via GAL
**Location**: `common/gal/opengl/`
Uses OpenGL 2.1+ with shaders. GLEW handles extension loading.
**Wasm strategy**: Emscripten maps OpenGL ES to WebGL automatically. Need to:
- Use OpenGL ES subset
- Convert shaders to GLSL ES (remove `#version`, add `precision` qualifiers)
### Cairo
**Version**: 1.12+
**Purpose**: Software 2D rendering fallback, printing, PDF export
**Location**: `common/gal/cairo/`
**Wasm strategy**: Can compile Cairo with Emscripten. Or skip for MVP (OpenGL-only).
### OpenCASCADE (OCC/OCCT)
**Version**: 7.5.0+
**Purpose**:
- STEP file import/export (mechanical CAD interchange)
- 3D model loading for component visualization
- Boolean operations on 3D geometry
**Files using OCC**:
- `pcbnew/exporters/step/step_pcb_model.cpp` - STEP export
- `pcbnew/exporters/step/exporter_step.cpp` - Export orchestration
- `plugins/3d/oce/loadmodel.cpp` - 3D model loading
- `plugins/3d/oce/oce.cpp` - Plugin entry point
**Headers imported** (from `step_pcb_model.cpp`):
```cpp
#include <IGESCAFControl_Reader.hxx>
#include <STEPCAFControl_Reader.hxx>
#include <STEPCAFControl_Writer.hxx>
#include <TopoDS.hxx>
#include <XCAFDoc_ShapeTool.hxx>
// ... ~30 more OCC headers
```
**Wasm strategy**: Disable for MVP. OCC is huge (~40MB compiled). Alternative: [opencascade.js](https://github.com/nicholaseasmith/opencascade.js) exists but experimental.
### libcurl
**Purpose**: HTTP requests for:
1. Plugin Content Manager (PCM) - `kicad/pcm/pcm.cpp`
2. Update checker - `kicad/update_manager.cpp`
3. HTTP component libraries - `common/http_lib/http_lib_connection.cpp`
**Wrapper**: `common/kicad_curl/kicad_curl_easy.cpp` (~400 lines)
**Key class**: `KICAD_CURL_EASY`
```cpp
class KICAD_CURL_EASY {
void SetURL(const std::string& url);
void Perform();
std::string GetBuffer();
};
```
**Wasm strategy**: Stub out. Replace with Emscripten Fetch API or JavaScript fetch via embind.
### libgit2
**Version**: 1.5+
**Purpose**: Built-in version control for projects
**Files** (`common/git/`):
| File | Purpose |
|------|---------|
| `git_clone_handler.cpp` | Clone repositories |
| `git_commit_handler.cpp` | Create commits |
| `git_push_handler.cpp` | Push to remote |
| `git_pull_handler.cpp` | Pull from remote |
| `git_branch_handler.cpp` | Branch management |
| `git_status_handler.cpp` | Status display |
| `git_revert_handler.cpp` | Revert changes |
| `kicad_git_common.cpp` | Common utilities |
| `kigit_pcb_merge.cpp` | Custom PCB merge driver |
**UI integration**:
- Project tree shows git status icons
- Menus for git operations
- Conflict resolution dialogs
**Wasm strategy**: Stub out entirely. Optional feature. Could use isomorphic-git in browser later.
### ngspice
**Purpose**: SPICE circuit simulation in eeschema
**Location**: `eeschema/sim/`
**Wasm strategy**: Stub out. Simulation is a separate concern. Could compile ngspice to Wasm later.
### nanoodbc (ODBC)
**Purpose**: Database Libraries feature - fetch component data from SQL databases
**Location**: `common/database/database_connection.cpp`
**What it does**: Connects to external databases (MySQL, PostgreSQL, SQLite, SQL Server) to fetch component information instead of using local `.kicad_sym` files.
**Enterprise feature** - most users don't use this.
**Wasm strategy**: Stub out. Would need REST API backend in browser.
### Boost
**Version**: 1.71.0+
**Components**: `locale`, `unit_test_framework`
`boost::locale` is used by nanoodbc for Unicode handling.
**Wasm strategy**: Minimize. If we disable database libraries, we may not need boost::locale.
### Freetype / HarfBuzz / Fontconfig
**Purpose**: Text rendering with outline fonts
**Versions**: Freetype 2.11.1+, HarfBuzz (any), Fontconfig (any)
**Location**: `common/font/`
**Wasm strategy**: Compile with Emscripten. These work. May need to bundle fonts or use browser fonts.
### Protobuf
**Purpose**: IPC API for external tool integration
**Location**: `api/`
**Wasm strategy**: Can compile Protobuf to Wasm. Or stub out IPC API for MVP.
---
## Graphics System (GAL)
### Architecture
```
┌─────────────────────────────────────────┐
│ VIEW (common/view/) │
│ Manages what's visible, handles zoom │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ PAINTER (include/gal/painter.h) │
│ Converts board objects to draw calls │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ GAL (graphics_abstraction_layer) │
│ Abstract interface for drawing │
└─────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ OPENGL_GAL │ │ CAIRO_GAL │
│ (Hardware accel)│ │ (Software/print)│
└─────────────────┘ └─────────────────┘
```
### GAL Base Class
**File**: `include/gal/graphics_abstraction_layer.h`
```cpp
namespace KIGFX {
class GAL : public GAL_DISPLAY_OPTIONS_OBSERVER {
// Primitives
virtual void DrawLine(const VECTOR2D& start, const VECTOR2D& end);
virtual void DrawCircle(const VECTOR2D& center, double radius);
virtual void DrawArc(const VECTOR2D& center, double radius, ...);
virtual void DrawRectangle(const VECTOR2D& start, const VECTOR2D& end);
virtual void DrawPolygon(const std::deque<VECTOR2D>& points);
// State
virtual void SetFillColor(const COLOR4D& color);
virtual void SetStrokeColor(const COLOR4D& color);
virtual void SetLineWidth(float width);
// Transformations
virtual void Transform(const MATRIX3x3D& matrix);
virtual void Translate(const VECTOR2D& translation);
virtual void Scale(const VECTOR2D& scale);
virtual void Rotate(double angle);
// Layers
virtual void SetLayerDepth(double depth);
};
}
```
### OpenGL GAL
**Files**:
- `include/gal/opengl/opengl_gal.h`
- `common/gal/opengl/opengl_gal.cpp`
- `common/gal/opengl/shader.cpp` - GLSL shader management
- `common/gal/opengl/vertex_manager.cpp` - Vertex buffer management
- `common/gal/opengl/gpu_manager.cpp` - GPU memory management
- `common/gal/opengl/cached_container.cpp` - Geometry caching
**Canvas**: `HIDPI_GL_CANVAS` wraps `wxGLCanvas`
**Shader files** (`common/gal/shaders/`):
| File | Purpose |
|------|---------|
| `kicad_vert.glsl` | Main vertex shader |
| `kicad_frag.glsl` | Main fragment shader |
| `smaa_base.glsl` | SMAA antialiasing base |
| `smaa_pass_1_frag.glsl` | SMAA edge detection |
| `smaa_pass_2_frag.glsl` | SMAA blending weights |
| `smaa_pass_3_frag.glsl` | SMAA neighborhood blending |
Shaders are embedded as C strings at build time.
**For WebGL**: Need to convert shaders:
```glsl
// Before (desktop GLSL)
#version 120
varying vec4 color;
// After (WebGL/GLSL ES)
precision mediump float;
varying vec4 color;
```
### Cairo GAL
**Files**:
- `include/gal/cairo/cairo_gal.h`
- `common/gal/cairo/cairo_gal.cpp`
- `common/gal/cairo/cairo_compositor.cpp` - Layer compositing
- `common/gal/cairo/cairo_print.cpp` - Printing support
Used for:
- Software rendering fallback
- Printing
- PDF/SVG export
### Draw Panel
**File**: `include/class_draw_panel_gal.h`
`EDA_DRAW_PANEL_GAL` wraps GAL and handles:
- Mouse events
- Keyboard events
- Tool dispatching
- View management
---
## File I/O System
### Architecture
Plugin-based system with base class `IO_BASE`:
**File**: `include/io/io_base.h`
```cpp
class IO_BASE {
struct IO_FILE_DESC {
wxString m_Description;
std::vector<std::string> m_FileExtensions;
bool m_CanRead;
bool m_CanWrite;
};
virtual std::vector<IO_FILE_DESC> GetFileDescriptors();
virtual void SetReporter(REPORTER* reporter);
virtual void SetProgressReporter(PROGRESS_REPORTER* reporter);
};
```
### Format Plugins
**KiCad Native** (`common/io/kicad/`):
- S-expression based format
- `.kicad_pcb`, `.kicad_sch`, `.kicad_sym`, `.kicad_mod`
**Import Plugins** (`common/io/`):
| Plugin | Location | Formats |
|--------|----------|---------|
| Eagle | `common/io/eagle/` | `.brd`, `.sch` |
| Altium | `common/io/altium/` | `.PcbDoc`, `.SchDoc` |
| CADSTAR | `common/io/cadstar/` | `.cpa`, `.csa` |
| EasyEDA | `common/io/easyeda/` | `.json` |
| EasyEDA Pro | `common/io/easyedapro/` | `.epro` |
### S-Expression Parser
**Location**: `libs/sexpr/`
KiCad native files use S-expressions:
```lisp
(kicad_pcb (version 20221018)
(generator pcbnew)
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal))
(footprint "Package_SO:SOIC-8"
(at 100 100)
(pad "1" smd rect (at -1.905 -2.475) (size 0.6 1.5))))
```
Parser is pure C++, good candidate for Wasm.
### PCB Data Model
**Key classes** (in `pcbnew/`):
| Class | File | Purpose |
|-------|------|---------|
| `BOARD` | `board.h` | Top-level PCB container |
| `FOOTPRINT` | `footprint.h` | Component footprint |
| `PAD` | `pad.h` | Footprint pad |
| `PCB_TRACK` | `pcb_track.h` | Trace segment |
| `PCB_VIA` | `pcb_track.h` | Via |
| `ZONE` | `zone.h` | Copper pour |
| `PCB_SHAPE` | `pcb_shape.h` | Graphical shape |
| `PCB_TEXT` | `pcb_text.h` | Text |
**Hierarchy**:
```
BOARD
├── FOOTPRINT[]
│ ├── PAD[]
│ ├── PCB_SHAPE[]
│ └── PCB_TEXT[]
├── PCB_TRACK[]
├── PCB_VIA[]
├── ZONE[]
├── PCB_SHAPE[]
└── PCB_TEXT[]
```
---
## Python Scripting
### SWIG Bindings
**Location**: `scripting/`, `common/swig/`, `pcbnew/python/swig/`
**Interface files** (`.i`):
| File | Lines | Purpose |
|------|-------|---------|
| `kicadplugins.i` | 695 | Plugin framework |
| `wx.i` | 348 | wxWidgets types |
| `board.i` | 201 | BOARD class |
| `board_item.i` | 222 | Base item class |
| `footprint.i` | 200 | FOOTPRINT class |
| `pad.i` | 128 | PAD class |
| `pcbnew.i` | 148 | Main pcbnew module |
| Others | ~1600 | Various classes |
| **Total** | ~3500 | |
### How SWIG Works
1. SWIG reads `.i` interface files
2. Generates C++ wrapper code for CPython
3. Wrapper compiled to shared library (`_pcbnew.so`)
4. Python imports the module
```python
import pcbnew
board = pcbnew.GetBoard()
for track in board.GetTracks():
print(track.GetStart(), track.GetEnd())
```
### Plugin Types
| Type | Purpose | Interface |
|------|---------|-----------|
| FootprintWizard | Generate footprints programmatically | `FootprintWizardPlugin` |
| ActionPlugin | Custom toolbar actions | `ActionPlugin` |
| FilePlugin | Custom file formats | `FilePlugin` |
### Wasm Strategy
SWIG generates CPython-specific code. Options for Wasm:
1. **Emscripten embind**: Rewrite bindings (~3500 lines to port)
```cpp
#include <emscripten/bind.h>
EMSCRIPTEN_BINDINGS(pcbnew) {
class_<BOARD>("Board")
.function("GetTracks", &BOARD::Tracks);
}
```
2. **Pyodide**: If we want Python in browser, use Pyodide with custom FFI
3. **Skip for MVP**: Python scripting is optional for basic editing
---
## Build System
### Main CMakeLists.txt Structure
```
CMakeLists.txt
├── Project setup (lines 1-100)
├── Options (lines 110-290)
│ ├── KICAD_SPICE_QA
│ ├── KICAD_USE_SENTRY
│ ├── KICAD_BUILD_I18N
│ ├── KICAD_BUILD_QA_TESTS
│ ├── KICAD_SCRIPTING_WXPYTHON
│ ├── KICAD_UPDATE_CHECK
│ └── ... more options
├── Compiler setup (lines 300-800)
├── Dependencies (lines 800-1200)
│ ├── find_package(ZLIB)
│ ├── find_package(CURL) # Line 825 - REQUIRED
│ ├── find_package(libgit2) # Line 842 - REQUIRED
│ ├── find_package(ngspice) # Line 877 - REQUIRED
│ ├── find_package(OCC) # Line 880 - FATAL if not found
│ └── ... more deps
├── wxWidgets setup (lines 1080-1140)
└── Subdirectories (lines 1250+)
```
### Common Library Build
**File**: `common/CMakeLists.txt`
```cmake
# KICOMMON_SRCS includes:
# - git/*.cpp (lines 75-95)
# - kicad_curl/*.cpp (lines 140-141)
# - database/*.cpp
# - All UI code
target_link_libraries(kicommon
CURL::libcurl # Line 325
${LIBGIT2_LIBRARIES} # Line 330
# ...
)
```
### Adding CMake Options for Optional Deps
Need to add after line 107:
```cmake
option( KICAD_USE_CURL "Enable network features" ON )
option( KICAD_USE_GIT "Enable git integration" ON )
option( KICAD_USE_OCC "Enable STEP/3D via OpenCASCADE" ON )
option( KICAD_USE_NGSPICE "Enable SPICE simulation" ON )
option( KICAD_USE_DATABASE "Enable database libraries" ON )
```
Then wrap find_package calls:
```cmake
if( KICAD_USE_CURL )
find_package( CURL REQUIRED )
add_compile_definitions( KICAD_USE_CURL )
else()
add_library( CURL::libcurl INTERFACE IMPORTED )
endif()
```
---
## Features to Disable
### Git Integration
**Files to stub** (`common/git/`):
- `git_add_to_index_handler.cpp`
- `git_branch_handler.cpp`
- `git_clone_handler.cpp`
- `git_commit_handler.cpp`
- `git_config_handler.cpp`
- `git_compare_handler.cpp`
- `git_init_handler.cpp`
- `git_pull_handler.cpp`
- `git_push_handler.cpp`
- `git_remove_from_index_handler.cpp`
- `git_remove_vcs_handler.cpp`
- `git_resolve_conflict_handler.cpp`
- `git_revert_handler.cpp`
- `git_status_handler.cpp`
- `git_switch_branch_handler.cpp`
- `git_sync_handler.cpp`
- `kicad_git_common.cpp`
- `git_backend.cpp`
- `libgit_backend.cpp`
- `project_git_utils.cpp`
**Stub header needed**: `stubs/include/git2.h`
### Network Features (curl)
**Files to stub** (`common/kicad_curl/`):
- `kicad_curl.cpp`
- `kicad_curl_easy.cpp`
**Files to stub** (`common/http_lib/`):
- `http_lib_connection.cpp`
**Files affected** (`kicad/`):
- `pcm/pcm.cpp` - Plugin Content Manager
- `pcm/pcm_task_manager.cpp`
- `update_manager.cpp`
**Stub header needed**: `stubs/include/curl/curl.h`
### OpenCASCADE (STEP/3D)
**Files to exclude**:
- `pcbnew/exporters/step/*.cpp`
- `plugins/3d/oce/*.cpp`
**Approach**: Don't build these targets rather than stubbing.
### SPICE Simulation
**Files affected**: `eeschema/sim/`
**Approach**: Disable simulator UI, don't build sim targets.
### Database Libraries
**Files to stub** (`common/database/`):
- `database_connection.cpp`
- `database_cache.cpp`
---
## Code Extraction Candidates
### Tier 1: Pure Computation (No Dependencies)
| Component | Location | Lines (approx) | Notes |
|-----------|----------|----------------|-------|
| Math library | `libs/kimath/src/` | ~5000 | Vectors, matrices, geometry |
| Core utilities | `libs/core/src/` | ~2000 | String utils, exceptions |
| S-expr parser | `libs/sexpr/` | ~1500 | Pure parsing |
### Tier 2: File I/O (Minimal Dependencies)
| Component | Location | Notes |
|-----------|----------|-------|
| KiCad PCB parser | `common/io/kicad/` | Needs kimath, sexpr |
| Board data model | `pcbnew/*.cpp` | Core classes only |
| Schematic parser | `common/io/eeschema/` | Needs kimath |
### Tier 3: Algorithms (May Need Adaptation)
| Component | Location | Notes |
|-----------|----------|-------|
| DRC engine | `pcbnew/drc/` | May use threading |
| Router | `pcbnew/router/` | Push & Shove |
| ERC | `eeschema/erc/` | Electrical checks |
| Connectivity | `pcbnew/connectivity/` | Net analysis |
---
## Technical Challenges
### Threading
**Current usage**:
- DRC runs checks in parallel
- Router uses threading for optimization
- Background jobs system
**Wasm limitation**: Single-threaded by default.
**Solutions**:
1. **Web Workers**: Spawn separate Wasm instances
2. **Wasm threads** (experimental): SharedArrayBuffer + pthreads
3. **Sequential fallback**: Slower but works
**Decision**: Use Web Workers. Design for async message-passing.
### Memory Management
**Challenge**: Need to share board state between native GUI and Wasm core.
**Options**:
1. **Serialize/deserialize**: Simple, proof of concept
2. **Shared memory**: Complex, requires careful synchronization
3. **Authoritative Wasm copy**: GUI requests views
**Decision**: Serialize/deserialize. Proof of concept, simplicity over performance.
### File Access
**Native**: Direct filesystem, `wxFileDialog`
**Browser**: No filesystem access without user interaction
**Solutions**:
- Emscripten virtual filesystem (MEMFS, IDBFS)
- File System Access API (Chrome)
- IndexedDB for persistence
- Drag & drop / file picker
### Clipboard
**Native**: `wxClipboard`, platform integration
**Browser**: Async Clipboard API (permissions required)
### Fonts
**Native**: System fonts via Fontconfig
**Browser**: Bundle fonts or use CSS fonts
---
## Design Decisions
### Memory Management
**Decision**: Serialize/deserialize on every operation
**Rationale**:
- Proof of concept phase
- Simplicity over performance
- Clean interface between GUI and core
- Easy to debug
- Browser-friendly (no shared memory complexity)
**Implementation**:
```cpp
// GUI → Wasm: Send operation + serialized state
std::string boardJson = SerializeBoard(board);
wasmCore.ApplyOperation(boardJson, operation);
std::string newBoardJson = wasmCore.GetBoardState();
board = DeserializeBoard(newBoardJson);
```
### Threading Model
**Decision**: Web Workers
**Rationale**:
- Target is browser
- Clean message-passing interface
- Each worker is isolated Wasm instance
- No shared memory complexity
**Implementation**:
```javascript
// Main thread
const worker = new Worker('kicad-core-worker.js');
worker.postMessage({ type: 'runDRC', board: boardData });
worker.onmessage = (e) => { handleDRCResults(e.data); };
// Worker
importScripts('kicad_core.js');
onmessage = async (e) => {
const core = await KicadCore();
if (e.data.type === 'runDRC') {
const results = core.runDRC(e.data.board);
postMessage(results);
}
};
```
### Incremental Updates
**Decision**: Re-serialize entire board (for now)
**Rationale**:
- Simple implementation
- Board files are typically <10MB
- Performance acceptable for proof of concept
- Can optimize later with deltas if needed
---
## File Reference
### Core Headers
| Purpose | File |
|---------|------|
| GAL interface | `include/gal/graphics_abstraction_layer.h` |
| OpenGL GAL | `include/gal/opengl/opengl_gal.h` |
| Cairo GAL | `include/gal/cairo/cairo_gal.h` |
| Draw panel | `include/class_draw_panel_gal.h` |
| Base frame | `include/eda_base_frame.h` |
| KIWAY | `include/kiway.h` |
| KIWAY holder | `include/kiway_holder.h` |
| I/O base | `include/io/io_base.h` |
| Board | `pcbnew/board.h` |
| Footprint | `pcbnew/footprint.h` |
| Track | `pcbnew/pcb_track.h` |
### Build Files
| Purpose | File |
|---------|------|
| Main build | `CMakeLists.txt` |
| Common lib | `common/CMakeLists.txt` |
| GAL lib | `common/gal/CMakeLists.txt` |
| PCBnew | `pcbnew/CMakeLists.txt` |
| Eeschema | `eeschema/CMakeLists.txt` |
| 3D viewer | `3d-viewer/CMakeLists.txt` |
### SWIG Bindings
| Purpose | File |
|---------|------|
| Main entry | `scripting/kicadplugins.i` |
| Common | `common/swig/kicad.i` |
| wxWidgets | `common/swig/wx.i` |
| Math | `common/swig/math.i` |
| Shapes | `common/swig/shape.i` |
| Board | `pcbnew/python/swig/board.i` |
| PCBnew main | `pcbnew/python/swig/pcbnew.i` |
### Git Integration
| Purpose | File |
|---------|------|
| Common class | `common/git/kicad_git_common.cpp` |
| Clone | `common/git/git_clone_handler.cpp` |
| Commit | `common/git/git_commit_handler.cpp` |
| Push | `common/git/git_push_handler.cpp` |
| Pull | `common/git/git_pull_handler.cpp` |
| Status | `common/git/git_status_handler.cpp` |
| PCB merge | `pcbnew/git/kigit_pcb_merge.cpp` |
### Curl/Network
| Purpose | File |
|---------|------|
| Curl wrapper | `common/kicad_curl/kicad_curl_easy.cpp` |
| Curl init | `common/kicad_curl/kicad_curl.cpp` |
| HTTP lib | `common/http_lib/http_lib_connection.cpp` |
| PCM | `kicad/pcm/pcm.cpp` |
| Updates | `kicad/update_manager.cpp` |
### OpenCASCADE/STEP
| Purpose | File |
|---------|------|
| STEP model | `pcbnew/exporters/step/step_pcb_model.cpp` |
| STEP export | `pcbnew/exporters/step/exporter_step.cpp` |
| OCC plugin | `plugins/3d/oce/oce.cpp` |
| Model loader | `plugins/3d/oce/loadmodel.cpp` |

View file

@ -1,136 +0,0 @@
# KiCad WebAssembly Port - Knowledge Base
## Architecture Overview
KiCad is a modular EDA suite with these main components:
- **Project Manager** (`kicad/`) - launches other tools
- **PCB Editor** (`pcbnew/`) - board layout
- **Schematic Editor** (`eeschema/`) - circuit design
- **3D Viewer** (`3d-viewer/`) - 3D visualization
Shared code lives in:
- `common/` - shared library (kicommon), includes GUI, I/O, git, curl
- `libs/core/` - utilities
- `libs/kimath/` - geometry and math (pure C++, no deps)
## Key Dependencies
| Dependency | Purpose | Wasm Strategy |
|------------|---------|---------------|
| **wxWidgets** | All GUI | Keep native initially; replace with web UI later |
| **OpenGL** | 2D rendering via GAL | WebGL (Emscripten handles this) |
| **Cairo** | Fallback 2D rendering | Compile with Emscripten or skip |
| **OpenCASCADE** | STEP import/export, 3D | Disable for MVP; huge (~40MB) |
| **libcurl** | PCM, update check, HTTP libs | Stub out; replace with Fetch API |
| **libgit2** | Version control integration | Stub out; optional feature |
| **ngspice** | Circuit simulation | Stub out; separate concern |
| **nanoodbc** | Database libraries | Stub out; enterprise feature |
| **Freetype/HarfBuzz** | Font rendering | Compile with Emscripten (works) |
| **Boost** | Locale, unit tests | Minimize; locale needed for nanoodbc only |
## Graphics Abstraction Layer (GAL)
Location: `common/gal/`, `include/gal/`
KiCad abstracts rendering through GAL with two backends:
- `OPENGL_GAL` (`common/gal/opengl/`) - primary, uses GLSL shaders
- `CAIRO_GAL` (`common/gal/cairo/`) - fallback, vector graphics
Key files:
- `include/gal/graphics_abstraction_layer.h` - base interface
- `common/gal/opengl/opengl_gal.cpp` - OpenGL implementation
- `common/gal/shaders/` - GLSL shaders (need ES conversion for WebGL)
For Wasm: OpenGL ES subset via Emscripten maps to WebGL. Shaders need `#version` removal and precision qualifiers.
## File I/O System
Location: `common/io/`, `include/io/`
Plugin-based architecture supporting multiple formats:
- KiCad native (`.kicad_pcb`, `.kicad_sch`)
- Eagle, Altium, CADSTAR, EasyEDA imports
Key class: `IO_BASE` in `include/io/io_base.h`
Parsers are mostly pure C++ - good candidates for Wasm core.
## Python Scripting
Location: `scripting/`, `pcbnew/python/`
Uses SWIG to generate CPython bindings (~3,500 lines of `.i` files).
For Wasm: SWIG bindings won't work. Options:
1. Use Emscripten's `embind` instead
2. Use Pyodide with custom FFI
3. Skip Python for MVP
## Optional Features to Disable
These have minimal impact on core editing functionality:
| Feature | CMake Area | Files |
|---------|-----------|-------|
| Git integration | `common/git/` | 15 handler files |
| Network (PCM, updates) | `common/kicad_curl/`, `common/http_lib/` | ~5 files |
| Database libraries | `common/database/` | 2-3 files |
| STEP/3D export | `pcbnew/exporters/step/`, `plugins/3d/oce/` | Isolated |
| SPICE simulation | `eeschema/sim/` | Isolated subsystem |
## Build System Notes
Main CMake: `CMakeLists.txt`
Currently all deps are REQUIRED (lines 820-892). No options exist to disable curl/git/OCC.
Libraries link in `common/CMakeLists.txt:316-343`:
```cmake
target_link_libraries( kicommon
CURL::libcurl
${LIBGIT2_LIBRARIES}
...
)
```
## IPC/Communication
`KIWAY` system (`include/kiway.h`, `include/kiway_holder.h`) handles inter-frame communication. Frames inherit from `KIWAY_PLAYER`.
For Wasm worker architecture: This could be adapted for message-passing between native GUI and Wasm worker.
## Potential Problem Areas
1. **Threading**: KiCad uses threads for DRC, rendering. Wasm has Web Workers but different threading model.
2. **File dialogs**: `wxFileDialog` throughout - needs abstraction for browser File API.
3. **Memory**: Large boards can use 1GB+. Wasm has 4GB limit, but browser tabs may have lower practical limits.
4. **Clipboard**: Native clipboard integration in multiple places.
5. **Printing**: Cairo-based printing system won't work in browser.
## Files of Interest for Core Extraction
Pure computation, no GUI deps - good Wasm candidates:
- `libs/kimath/` - all geometry code
- `libs/core/` - utilities
- `common/io/kicad/` - native format parser
- `pcbnew/router/` - Push & Shove routing algorithms
- `pcbnew/drc/` - Design Rule Check engine
- `eeschema/erc/` - Electrical Rule Check
## Reference Paths
| Component | Path |
|-----------|------|
| Main CMake | `CMakeLists.txt` |
| Common library | `common/CMakeLists.txt` |
| GAL system | `common/gal/`, `include/gal/` |
| File I/O | `common/io/` |
| PCB data model | `pcbnew/board.h`, `pcbnew/footprint.h` |
| Git integration | `common/git/` |
| Curl wrapper | `common/kicad_curl/` |
| Python bindings | `scripting/`, `pcbnew/python/swig/` |
| 3D/STEP | `pcbnew/exporters/step/`, `3d-viewer/` |

View file

@ -1,395 +0,0 @@
# 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
```cpp
// 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.h` with 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
```cpp
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
```cpp
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
```cpp
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
```bash
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
```cmake
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, geometry
- `kicad/libs/core/*.cpp` - 870 lines, utilities
- `kicad/libs/sexpr/*.cpp` - 734 lines, parser
- `kicad/pcbnew/board*.cpp` - Board data model
- `kicad/pcbnew/pcb_io/kicad_sexpr/*.cpp` - S-expr I/O
- `kicad/pcbnew/drc/*.cpp` - DRC engine
- `kicad/pcbnew/router/pns_*.cpp` - Router
**Key headers:**
- `kicad/libs/kimath/include/geometry/shape_poly_set.h` - Polygon ops
- `kicad/pcbnew/board.h` - BOARD class (1510 lines)
- `kicad/pcbnew/drc/drc_engine.h` - DRC entry point
- `kicad/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 method
- `FormatArg<T>` template needed to convert string args to `c_str()` for snprintf
- `wxLog::EnableLogging()` used in polygon_triangulation.h
- `wxString::RemoveLast()` used for string manipulation
- `ADVANCED_CFG` class 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:
```bash
$ 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 stubs
- `wxFFile` class in wx_shim.h (~80 lines)
- `string_utils.h` - minimal stub for `From_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
- [x] wx_shim.h provides all needed wx replacements
- [x] libs/kimath compiles with shim (no wxWidgets linked)
- [x] kicad_core.wasm builds with Emscripten (kimath portion)
- [x] libs/sexpr compiles with shim (added wxFFile, string_utils stubs)
- [x] 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)

View file

@ -1,597 +0,0 @@
# Phase 3: wxWidgets wxUniversal for WebAssembly
## Overview
Port wxWidgets with wxUniversal backend to WebAssembly, enabling the full KiCad GUI to run in browsers. This builds on the core library already compiled to WASM (kimath, sexpr).
## Current State (from Phase 2)
Already working in WASM:
- `libkimath.a` - Geometry library (5.1MB)
- `libsexpr.a` - S-expression parser (159KB)
- `libclipper2.a` - Polygon operations (1.2MB)
- `libkicad_core_utils.a` - Core utilities (106KB)
- wxBase (non-GUI utilities) - via existing build script
## Goal
Build wxWidgets with **wxUniversal** backend for Emscripten, enabling:
- Full wxWidgets GUI rendered to HTML5 canvas
- wxGLCanvas for KiCad's GAL (Graphics Abstraction Layer)
- Event handling (mouse, keyboard, touch)
**First Milestone**: Any .kicad_pcb file renders in browser
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ HTML5 Canvas│ │ WebGL │ │ DOM Events │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ wxWidgets (wxUniversal backend) │
│ - Draws all widgets to canvas (no native widgets) │
│ - wxGLCanvas → WebGL context │
│ - Event translation (browser → wx events) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ KiCad Application │
│ - GAL (Graphics Abstraction Layer) → uses wxGLCanvas │
│ - Board data model (already in WASM) │
│ - PCB Painter │
└─────────────────────────────────────────────────────────────┘
```
---
## Strategy: Create Your Own wxWidgets Fork
Rather than using the abandoned wxWidgets-wasm fork (based on old wx 3.0.x), create a fresh fork from modern wxWidgets 3.2.6 and apply the ~10 required patches.
### Reference: wxWidgets-wasm Commits
From [ahilss/wxWidgets-wasm](https://github.com/ahilss/wxWidgets-wasm):
| Commit | Purpose | Likely Files |
|--------|---------|--------------|
| Suppress locale warnings | Browser env | `src/common/intl.cpp` |
| Touch → mouse events | Web input | `src/univ/topluniv.cpp` |
| Font size in pixels | Web rendering | `src/univ/themes/*.cpp` |
| Size top window before run | WASM init | `src/univ/topluniv.cpp` |
| Mouse window crash fix | Event stability | `src/common/wincmn.cpp` |
### Configure Flags (from wxWidgets-wasm)
```bash
--host=emscripten \
--with-cxx=17 \
--enable-utf8 \
--enable-universal \ # Key: Use wxUniversal backend
--disable-shared \
--disable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-opengl # Enable wxGLCanvas for WebGL
```
---
## Implementation Steps
### Step 1: Fork wxWidgets
1. Fork wxWidgets on GitHub (e.g., `VV-EE/wxWidgets`)
2. Clone locally
3. Checkout tag `v3.2.6`
4. Create branch `wasm-port`
```bash
git clone git@github.com:VV-EE/wxWidgets.git
cd wxWidgets
git checkout v3.2.6
git checkout -b wasm-port
```
### Step 2: Study wxWidgets-wasm Patches
Clone wxWidgets-wasm and identify the specific changes:
```bash
git clone https://github.com/ahilss/wxWidgets-wasm.git wxwidgets-wasm-ref
cd wxwidgets-wasm-ref
# Find commits that differ from upstream
git log --oneline | head -20
```
For each WASM-specific commit:
1. Identify changed files
2. Understand the change
3. Create equivalent patch for 3.2.6
### Step 3: Apply Patches to Your Fork
Create patch files and apply:
```bash
# In your wxWidgets fork
git apply ../patches/0001-suppress-locale-warnings.patch
git apply ../patches/0002-touch-to-mouse-events.patch
# etc.
git commit -m "Add Emscripten/WASM support"
```
### Step 4: Submodule Setup After Cloning
The wxwidgets submodule is a fork (`VV-EE/wxWidgets`) with all WASM changes already committed. However, wxWidgets has **nested submodules** (pcre, expat, jpeg, png, tiff, zlib) that need config.sub modifications for Emscripten support. These nested submodules are separate repositories, so their changes are NOT tracked by the wxwidgets fork.
**After cloning kicad-wasm, you must:**
```bash
git clone <kicad-wasm-repo>
cd kicad-wasm
# 1. Initialize wxwidgets submodule
git submodule update --init wxwidgets
# 2. Initialize wxwidgets' nested submodules
cd wxwidgets
git submodule update --init --recursive
# 3. Copy config.sub to nested submodules (required for Emscripten)
# The main wxwidgets/config.sub already has emscripten/wasm32 support.
# Copy it to all nested submodule locations:
cp config.sub 3rdparty/pcre/config.sub
cp config.sub src/expat/expat/conftools/config.sub
cp config.sub src/jpeg/config.sub
cp config.sub src/png/config.sub
cp config.sub src/tiff/config/config.sub
cd ..
```
**Why is this needed?**
- wxWidgets bundles libraries (pcre, expat, jpeg, png, tiff) as git submodules
- Each submodule has its own `config.sub` that must recognize `emscripten` and `wasm32` hosts
- These submodules point to upstream repos, so we can't commit changes to them
- The same modified config.sub must be copied to all 5 locations after every fresh clone
**Reproducibility Testing** (optional):
A patch-based build system exists for validation:
```bash
# Generate patches from current wxwidgets state
./scripts/generate-wxwidgets-patches.sh
# Build from clean clone + patches (validates reproducibility)
./scripts/build-wxwidgets-wasm-clean.sh --clean
```
### Step 5: Create Build Script
Create `scripts/build-wxuniversal-wasm.sh`:
```bash
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
WX_SOURCE="$PROJECT_ROOT/wxwidgets"
# Environment setup
export CFLAGS="-I$EMSDK/upstream/emscripten/system/local/include"
export CXXFLAGS="-I$EMSDK/upstream/emscripten/system/local/include"
export LDFLAGS="-L$EMSDK/upstream/emscripten/system/local/lib -sERROR_ON_UNDEFINED_SYMBOLS=0"
CONFIGURE_ARGS="--host=emscripten \
--with-cxx=17 \
--enable-utf8 \
--enable-universal \
--disable-shared \
--disable-exceptions \
--disable-richtext \
--without-libtiff \
--disable-xlocale \
--with-opengl"
if [ "$1" = "--clean" ]; then
rm -rf "$BUILD_DIR"
fi
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
# Initialize submodules if needed
cd "$WX_SOURCE"
git submodule update --init src/jpeg 2>/dev/null || true
git submodule update --init 3rdparty/catch 2>/dev/null || true
cd "$BUILD_DIR"
# Configure and build
emconfigure "$WX_SOURCE/configure" $CONFIGURE_ARGS
emmake make -j$(sysctl -n hw.ncpu 2>/dev/null || nproc)
echo ""
echo "=== Build complete ==="
ls -lh "$BUILD_DIR"/lib/*.a 2>/dev/null || echo "Libraries in $BUILD_DIR/lib/"
```
### Step 5: Verify wxUniversal Build
Expected output libraries:
```
lib/
├── libwx_baseu-3.2.a # Base utilities (strings, files, etc.)
├── libwx_coreu-3.2.a # wxUniversal core (widgets, events)
├── libwx_glu-3.2.a # OpenGL/WebGL support
└── libwxregexu-3.2.a # Regex support
```
Verify `setup.h` contains:
```c
#define wxUSE_UNIVERSAL 1
#define __WXUNIVERSAL__ 1
#define wxUSE_GLCANVAS 1
```
---
## Phase 3.1: Minimal wxApp Test
### Test Application
Create `test/wx_minimal/main.cpp`:
```cpp
#include <wx/wx.h>
class MinimalApp : public wxApp {
public:
bool OnInit() override {
wxFrame* frame = new wxFrame(nullptr, wxID_ANY,
"KiCad WASM Test", wxDefaultPosition, wxSize(800, 600));
// Add a simple panel with text
wxPanel* panel = new wxPanel(frame);
new wxStaticText(panel, wxID_ANY, "wxWidgets in WebAssembly!",
wxPoint(10, 10));
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(MinimalApp);
```
### Build Command
```bash
emcc main.cpp \
-I$WX_BUILD/lib/wx/include/emscripten-unicode-static-3.2 \
-I$WX_SOURCE/include \
-L$WX_BUILD/lib \
-lwx_baseu-3.2 \
-lwx_coreu-3.2 \
-o minimal.html \
-s ASYNCIFY=1 \
-s ALLOW_MEMORY_GROWTH=1
```
### Verification
- [ ] HTML file loads in browser
- [ ] wxFrame window appears (rendered to canvas)
- [ ] Text displays
- [ ] No JavaScript errors in console
---
## Phase 3.2: wxGLCanvas + WebGL
### Test Application
Create `test/wx_glcanvas/main.cpp`:
```cpp
#include <wx/wx.h>
#include <wx/glcanvas.h>
#ifdef __EMSCRIPTEN__
#include <GLES2/gl2.h>
#else
#include <GL/gl.h>
#endif
class GLFrame : public wxFrame {
wxGLCanvas* m_canvas;
wxGLContext* m_context;
public:
GLFrame() : wxFrame(nullptr, wxID_ANY, "WebGL Test",
wxDefaultPosition, wxSize(800, 600)) {
wxGLAttributes attrs;
attrs.RGBA().DoubleBuffer().Depth(16).EndList();
m_canvas = new wxGLCanvas(this, attrs);
m_context = new wxGLContext(m_canvas);
m_canvas->Bind(wxEVT_PAINT, &GLFrame::OnPaint, this);
m_canvas->Bind(wxEVT_SIZE, &GLFrame::OnSize, this);
}
void OnPaint(wxPaintEvent& evt) {
wxPaintDC dc(m_canvas);
m_context->SetCurrent(*m_canvas);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw a simple triangle
// (shader code omitted for brevity)
m_canvas->SwapBuffers();
}
void OnSize(wxSizeEvent& evt) {
m_canvas->Refresh();
}
};
class GLApp : public wxApp {
public:
bool OnInit() override {
GLFrame* frame = new GLFrame();
frame->Show(true);
return true;
}
};
wxIMPLEMENT_APP(GLApp);
```
### Build Command (with WebGL)
```bash
emcc main.cpp \
-I$WX_BUILD/lib/wx/include/emscripten-unicode-static-3.2 \
-I$WX_SOURCE/include \
-L$WX_BUILD/lib \
-lwx_baseu-3.2 \
-lwx_coreu-3.2 \
-lwx_glu-3.2 \
-o glcanvas.html \
-s ASYNCIFY=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s USE_WEBGL2=1 \
-s FULL_ES3=1
```
### Verification
- [ ] WebGL context created successfully
- [ ] Canvas clears to specified color
- [ ] No WebGL errors in console
- [ ] Window resizing works
---
## Phase 3.3: KiCad GAL Integration
### Shader Conversion (GLSL 1.2 → GLSL ES 3.0)
KiCad shaders need conversion for WebGL 2.0:
**Vertex Shader:**
```glsl
// Before (GLSL 1.2)
#version 120
attribute vec4 a_position;
varying vec4 v_color;
// After (GLSL ES 3.0)
#version 300 es
precision highp float;
in vec4 a_position;
out vec4 v_color;
```
**Fragment Shader:**
```glsl
// Before (GLSL 1.2)
#version 120
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
// After (GLSL ES 3.0)
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}
```
### Files to Convert
- `kicad/common/gal/shaders/kicad_frag.glsl`
- `kicad/common/gal/shaders/kicad_vert.glsl`
- `kicad/common/gal/shaders/smaa_*.glsl` (anti-aliasing, optional)
### WebGL GAL Adapter
Create `core/gal-wasm/webgl_gal.h`:
```cpp
// Adapter for OPENGL_GAL to work with Emscripten WebGL
class WEBGL_GAL : public OPENGL_GAL {
public:
WEBGL_GAL(...);
// Override shader loading to use GLSL ES
void loadShaders() override;
// Skip GLEW initialization (not needed in Emscripten)
void initGLEW() override { /* no-op */ }
};
```
---
## Phase 3.4: PCB Viewer
### Application Structure
```
viewer/
├── main.cpp # wxApp entry point
├── pcb_view_frame.cpp # Main frame with canvas
├── pcb_view_canvas.cpp # wxGLCanvas + GAL + VIEW
├── index.html # HTML shell
└── CMakeLists.txt
```
### PCB View Canvas
```cpp
class PCB_VIEW_CANVAS : public wxGLCanvas {
std::unique_ptr<WEBGL_GAL> m_gal;
std::unique_ptr<KIGFX::VIEW> m_view;
std::unique_ptr<KIGFX::PCB_PAINTER> m_painter;
BOARD* m_board = nullptr;
public:
PCB_VIEW_CANVAS(wxWindow* parent);
void LoadBoard(const std::string& sexpr);
protected:
void OnPaint(wxPaintEvent& evt);
void OnMouseWheel(wxMouseEvent& evt); // Zoom
void OnMouseMove(wxMouseEvent& evt); // Pan
};
```
### JavaScript Integration
```javascript
// Load PCB file via fetch and pass to WASM
async function loadPCB(url) {
const response = await fetch(url);
const content = await response.text();
// Call WASM function
Module.ccall('loadPCBContent', 'void', ['string'], [content]);
}
// File input handler
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
const content = await file.text();
Module.ccall('loadPCBContent', 'void', ['string'], [content]);
});
```
---
## Directory Structure
```
kicad-wasm/
├── wxwidgets/ # Your wxWidgets fork (with WASM patches)
├── kicad/ # KiCad source (submodule)
├── core/
│ ├── CMakeLists.txt # Existing: kimath, sexpr, etc.
│ ├── wasm-config/ # WASM config.h
│ └── gal-wasm/ # NEW: WebGL GAL
│ ├── webgl_gal.h
│ ├── webgl_gal.cpp
│ └── shaders/
│ ├── kicad_frag_es.glsl
│ └── kicad_vert_es.glsl
├── viewer/ # NEW: PCB Viewer app
│ ├── CMakeLists.txt
│ ├── main.cpp
│ ├── pcb_view_frame.cpp
│ ├── pcb_view_canvas.cpp
│ └── index.html
├── scripts/
│ ├── build-wxbase-wasm.sh # Existing
│ ├── build-wxuniversal-wasm.sh # NEW
│ ├── build-core-wasm.sh # Existing
│ └── build-viewer.sh # NEW
├── test/
│ ├── wx_minimal/ # NEW: Test wxApp
│ └── wx_glcanvas/ # NEW: Test WebGL
└── docs/
├── 00-OVERVIEW.md
├── 01-KNOWLEDGE-BASE.md
├── 02-PHASE2-CORE-EXTRACTION.md
└── 03-PHASE3-WXWIDGETS-WASM.md # This file
```
---
## Success Criteria
### Phase 3.1: wxUniversal Build
- [ ] wxWidgets fork created with WASM patches
- [ ] `./configure` completes with `--enable-universal`
- [ ] `libwx_baseu-3.2.a`, `libwx_coreu-3.2.a`, `libwx_glu-3.2.a` built
### Phase 3.2: Minimal wxApp
- [ ] wxFrame renders in browser
- [ ] Text/widgets display correctly
- [ ] Events (click, resize) work
### Phase 3.3: wxGLCanvas
- [ ] WebGL context created
- [ ] Basic OpenGL rendering works
- [ ] Canvas clears and draws
### Phase 3.4: KiCad GAL
- [ ] Shaders converted to GLSL ES
- [ ] WEBGL_GAL draws basic shapes
- [ ] Lines, circles, polygons render
### Phase 3.5: PCB Viewer (First Milestone!)
- [ ] .kicad_pcb file loads
- [ ] Board outline visible
- [ ] Tracks visible
- [ ] Pads visible
- [ ] Pan/zoom works
---
## Risk Mitigation
| Risk | Impact | Mitigation |
|------|--------|------------|
| wxWidgets 3.2 vs old wx patches | High | Patches are small (~10 commits), review carefully |
| GLSL shader incompatibility | Medium | Use WebGL 2.0 (GLSL ES 3.0), test incrementally |
| Performance with large boards | Medium | Accept slow initially, optimize later |
| Event handling differences | Medium | Test thoroughly, refer to wxWidgets-wasm solutions |
---
## References
- [wxWidgets-wasm Repository](https://github.com/ahilss/wxWidgets-wasm)
- [Wavacity (Audacity WASM port)](https://github.com/ahilss/wavacity)
- [wxWidgets WebAssembly Discussion](https://forums.wxwidgets.org/viewtopic.php?t=51463)
- [Emscripten wxWidgets Issue](https://github.com/emscripten-core/emscripten/issues/13983)

View file

@ -1,347 +0,0 @@
# Phase 4: Comprehensive wxWidgets WASM Test Application
## Overview
Create a multi-widget showcase test application for the wxWidgets WASM port that thoroughly exercises wxUniversal controls and event handling. This validates the WASM port before integrating KiCad components.
## Goal
1. Display various wxUniversal controls in a tabbed interface
2. Support full interaction: mouse clicks, keyboard input, mouse drag/draw, hover effects
3. Show an on-screen event log panel for visual verification
4. Have corresponding Playwright tests to verify all interactions work
---
## Application Architecture
```
+-------------------------------------------------------+
| Menu: File | Edit | Help |
+-------------------------------------------------------+
| [Controls Tab] [Text Tab] [Drawing Tab] [Lists Tab] |
+-------------------------------------------------------+
| |
| Tab Content Area |
| (varies by tab) |
| |
+-------------------------------------------------------+
| Event Log Panel (wxListBox) |
| - "Button 'Test' clicked" |
| - "Checkbox toggled: checked" |
| - "Mouse moved to (123, 456)" |
+-------------------------------------------------------+
| Status Bar: Ready |
+-------------------------------------------------------+
```
---
## Files to Modify
| File | Action | Description |
|------|--------|-------------|
| `tests/wasm-app/minimal_test.cpp` | Replace | Comprehensive widget showcase |
| `tests/e2e/wxwidgets.spec.ts` | Update | Add interaction tests |
---
## Test Application Structure
### Control IDs
```cpp
enum {
ID_BTN_TEST = wxID_HIGHEST + 1,
ID_BTN_TOGGLE,
ID_CHK_FEATURE,
ID_RADIO_OPTIONS,
ID_SLIDER,
ID_GAUGE,
ID_TEXT_SINGLE,
ID_TEXT_MULTI,
ID_TEXT_PASSWORD,
ID_COMBO,
ID_LISTBOX,
ID_CHOICE,
ID_BTN_CLEAR,
ID_EVENT_LOG,
ID_DRAWING_PANEL
};
```
### Main Classes
```cpp
class TestApp : public wxApp; // Application entry point
class TestFrame : public wxFrame; // Main frame with notebook and event log
class ControlsPage : public wxPanel; // Tab 1: Buttons, checkboxes, sliders
class TextPage : public wxPanel; // Tab 2: Text controls
class DrawingPage : public wxPanel; // Tab 3: Mouse drawing canvas
class ListsPage : public wxPanel; // Tab 4: Lists and dropdowns
class DrawingPanel : public wxPanel; // Custom drawing canvas
```
---
## Tab Contents
### Tab 1: Controls
| Control | ID | Purpose |
|---------|-----|---------|
| wxButton ("Click Me") | `ID_BTN_TEST` | Basic click event |
| wxToggleButton ("Toggle") | `ID_BTN_TOGGLE` | Two-state button |
| wxCheckBox ("Enable feature") | `ID_CHK_FEATURE` | Boolean toggle |
| wxRadioBox ("Options": A/B/C) | `ID_RADIO_OPTIONS` | Exclusive selection |
| wxSlider (0-100) | `ID_SLIDER` | Range selection |
| wxGauge | `ID_GAUGE` | Shows slider value |
### Tab 2: Text Input
| Control | ID | Purpose |
|---------|-----|---------|
| wxTextCtrl (single-line) | `ID_TEXT_SINGLE` | Basic text input |
| wxTextCtrl (multi-line) | `ID_TEXT_MULTI` | Multi-line text |
| wxTextCtrl (password) | `ID_TEXT_PASSWORD` | Masked input |
| wxComboBox | `ID_COMBO` | Dropdown with text |
### Tab 3: Drawing Canvas
| Control | ID | Purpose |
|---------|-----|---------|
| DrawingPanel | `ID_DRAWING_PANEL` | Custom painting |
| wxButton ("Clear") | `ID_BTN_CLEAR` | Reset canvas |
Mouse interactions:
- `wxEVT_LEFT_DOWN` - Start drawing
- `wxEVT_MOTION` - Draw line segments
- `wxEVT_LEFT_UP` - End drawing
### Tab 4: Lists
| Control | ID | Purpose |
|---------|-----|---------|
| wxListBox | `ID_LISTBOX` | Multi-item selection |
| wxChoice | `ID_CHOICE` | Dropdown selection |
| Add/Remove buttons | - | Modify list contents |
---
## Event Log Panel
- `wxListBox` at bottom of frame
- ID: `ID_EVENT_LOG`
- Max 100 entries (older entries removed)
- Auto-scrolls to bottom
- Format: `"[HH:MM:SS] Event description"`
Events also logged to browser console for Playwright verification.
---
## Event Types to Test
| Event | Control | Test Method |
|-------|---------|-------------|
| `wxEVT_BUTTON` | Button | Click canvas coordinates |
| `wxEVT_TOGGLEBUTTON` | ToggleButton | Click and verify state |
| `wxEVT_CHECKBOX` | CheckBox | Click and verify checked |
| `wxEVT_RADIOBOX` | RadioBox | Click option and verify |
| `wxEVT_SLIDER` | Slider | Drag and verify value |
| `wxEVT_TEXT` | TextCtrl | Type and verify content |
| `wxEVT_TEXT_ENTER` | TextCtrl | Press Enter |
| `wxEVT_LISTBOX` | ListBox | Click item and verify |
| `wxEVT_CHOICE` | Choice | Click and select |
| `wxEVT_LEFT_DOWN` | Panel | Mouse down |
| `wxEVT_MOTION` | Panel | Mouse move |
| `wxEVT_LEFT_UP` | Panel | Mouse up |
| `wxEVT_PAINT` | Panel | Automatic on invalidate |
---
## Playwright Test Structure
```typescript
test.describe('wxWidgets WASM Comprehensive', () => {
// Existing loading tests...
test.describe('Controls Tab', () => {
test('button click logs event');
test('checkbox toggle works');
test('slider interaction');
test('radio button selection');
});
test.describe('Text Input Tab', () => {
test('text input accepts keyboard');
test('multiline text input');
test('password field masks input');
});
test.describe('Drawing Tab', () => {
test('mouse drag draws on canvas');
test('clear button resets canvas');
});
test.describe('Lists Tab', () => {
test('listbox selection');
test('choice dropdown');
});
test.describe('Event Log', () => {
test('events are logged visually');
});
});
```
### Test Helpers
```typescript
// Wait for app to be ready
async function waitForApp(page: Page) {
await page.waitForSelector('canvas', { state: 'visible', timeout: 30000 });
await page.waitForTimeout(500); // Let UI settle
}
// Simulate click at canvas coordinates
async function clickAt(page: Page, x: number, y: number) {
const canvas = page.locator('canvas');
await canvas.click({ position: { x, y } });
}
// Check console for event log entries
async function getConsoleEvents(page: Page): Promise<string[]> {
// Capture console.log messages with [EVENT] prefix
}
```
---
## Testing Challenges & Solutions
### Challenge 1: Canvas Coordinate Mapping
wxUniversal renders all widgets to a single HTML5 canvas. Tests need to know widget positions.
**Solution**: Use consistent layout with predictable positions. Log control bounds to console on startup.
### Challenge 2: Verifying Event Log Content
The event log is inside the WASM app, not directly accessible from JavaScript.
**Solution**: Log events to browser console with `[EVENT]` prefix. Playwright captures via `page.on('console')`.
### Challenge 3: Keyboard Input
Need to route keyboard events to the correct wxWidgets control.
**Solution**: Focus canvas first, then use `page.keyboard.type()`. wxWidgets handles focus internally.
---
## wxUniversal Controls Reference
Available controls in wxUniversal (verified in `wxwidgets/include/wx/univ/`):
**Buttons:**
- wxButton, wxBitmapButton, wxToggleButton
**Input Controls:**
- wxCheckBox, wxRadioButton, wxRadioBox
- wxTextCtrl (single/multi-line)
- wxComboBox, wxChoice
**Lists:**
- wxListBox, wxCheckListBox
**Range Controls:**
- wxSlider, wxGauge, wxSpinButton
**Containers:**
- wxNotebook (tabbed container)
- wxPanel, wxScrolledWindow
**Static Controls:**
- wxStaticText, wxStaticBox, wxStaticLine
**Frame Elements:**
- wxMenu, wxMenuBar, wxStatusBar
---
## Implementation Steps
### Step 1: Update minimal_test.cpp
Replace current minimal test with comprehensive widget showcase.
Key components:
1. `TestFrame` constructor creates notebook, tabs, and event log
2. Each tab page creates its controls with appropriate sizers
3. Event handlers call `LogEvent()` to record actions
4. `LogEvent()` adds to wxListBox AND prints to console
### Step 2: Update Playwright Tests
Extend `tests/e2e/wxwidgets.spec.ts`:
1. Keep existing loading tests
2. Add test groups for each tab
3. Add console event capture
4. Add canvas interaction helpers
### Step 3: Build and Test
```bash
# Rebuild test app
./scripts/build-wasm-test.sh
# Run tests
cd tests && npm test
# Visual debugging
npm run test:ui
# or
cd wasm-app && npx serve .
```
---
## Success Criteria
### Visual Verification
- [ ] App displays with menu bar
- [ ] Four tabs visible and switchable
- [ ] Controls visible on each tab
- [ ] Event log panel at bottom
### Interaction Tests
- [ ] Button click logs "Button clicked"
- [ ] Checkbox toggle logs state change
- [ ] Slider drag updates gauge and logs value
- [ ] Text input shows typed text and logs changes
- [ ] Drawing canvas responds to mouse drag
- [ ] ListBox/Choice selection works
### Playwright Tests
- [ ] All existing loading tests pass
- [ ] Button interaction test passes
- [ ] Checkbox interaction test passes
- [ ] Text input test passes
- [ ] Drawing interaction test passes
---
## Dependencies
Before implementation:
- wxWidgets WASM build complete (`./scripts/build-wxuniversal-wasm.sh`)
- Existing Playwright tests passing (`cd tests && npm test`)
---
## References
- wxWidgets Samples: `wxwidgets/samples/widgets/` - Multi-widget showcase pattern
- wxWidgets Samples: `wxwidgets/samples/drawing/` - Custom painting pattern
- wxUniversal Headers: `wxwidgets/include/wx/univ/` - Available controls

View file

@ -1,324 +0,0 @@
# 05 - wxGLCanvas WASM Implementation
This document outlines the plan to implement wxGLCanvas (OpenGL/WebGL support) for the wxWidgets WASM port, enabling KiCad's OpenGL-based rendering in the browser.
## Current Status: Completed ✅
**wxGLCanvas implementation is complete.** The GL library builds successfully:
```
libwx_wasmunivu_gl-3.2-emscripten.a
```
### Build Command
```bash
./scripts/build-wxuniversal-wasm.sh # Incremental build
./scripts/build-wxuniversal-wasm.sh --clean # Clean build
```
### Implementation Summary
- Created `wxwidgets/include/wx/wasm/glcanvas.h`
- Created `wxwidgets/src/wasm/glcanvas.cpp`
- Updated `wxwidgets/configure.in` for WASM OpenGL support
- Updated `wxwidgets/build/bakefiles/files.bkl`
- Added empty stubs for legacy wxGLAPI functions in `glcmn.cpp` (KiCad doesn't use these)
---
## Phase 1: Fix wxWidgets Build (PCRE2 Issue) ✅ Completed
### Problem
```
fatal error: 'pcre2.h' file not found
```
### Root Cause
Build race condition during parallel make. With `-j` parallel builds, `regex.cpp` can compile before PCRE generates its headers.
### Solution
Modify `scripts/build-wxuniversal-wasm.sh` to build PCRE first:
```bash
# After configure section, add:
echo ""
echo "=== Building PCRE first (dependency) ==="
emmake make -C 3rdparty/pcre
# Then existing make:
echo ""
echo "=== Building ==="
emmake make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
```
---
## Phase 2: Implement wxGLCanvas for WASM ✅ Completed
### Estimated Effort: 18-27 hours (Actual: ~8 hours)
| Component | Files | Effort |
|-----------|-------|--------|
| Header file | `include/wx/wasm/glcanvas.h` | 2-3 hours |
| Implementation | `src/wasm/glcanvas.cpp` | 8-12 hours |
| Configure updates | `configure.in` | 1-2 hours |
| Build system | `build/files.bkl` | 1 hour |
| Testing & debug | - | 6-10 hours |
### Implementation Approach: Direct WebGL via Emscripten
Use Emscripten's HTML5 API (`emscripten/html5.h`) for WebGL context management.
#### 1. Create `include/wx/wasm/glcanvas.h` (~100-150 lines)
```cpp
#ifndef _WX_WASM_GLCANVAS_H_
#define _WX_WASM_GLCANVAS_H_
#include "wx/glcanvas.h"
#include <emscripten/html5.h>
class WXDLLIMPEXP_GL wxGLContext : public wxGLContextBase
{
public:
wxGLContext(wxGLCanvas *win, const wxGLContext *other = NULL,
const wxGLContextAttrs *ctxAttrs = NULL);
virtual ~wxGLContext();
virtual bool SetCurrent(const wxGLCanvas& win) const wxOVERRIDE;
private:
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context;
wxDECLARE_CLASS(wxGLContext);
};
class WXDLLIMPEXP_GL wxGLCanvas : public wxGLCanvasBase
{
public:
wxGLCanvas(wxWindow *parent,
const wxGLAttributes& dispAttrs,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
const wxString& name = wxGLCanvasName,
const wxPalette& palette = wxNullPalette);
virtual ~wxGLCanvas();
virtual bool SwapBuffers() wxOVERRIDE;
// Get the canvas element ID for Emscripten
const char* GetCanvasId() const { return m_canvasId.c_str(); }
private:
bool CreateWindow(wxWindow *parent, const wxGLAttributes& dispAttrs,
wxWindowID id, const wxPoint& pos, const wxSize& size,
long style, const wxString& name);
std::string m_canvasId;
wxDECLARE_CLASS(wxGLCanvas);
};
#endif // _WX_WASM_GLCANVAS_H_
```
#### 2. Create `src/wasm/glcanvas.cpp` (~500-700 lines)
Key implementation tasks:
- Parse wxGL_* attributes → WebGL context attributes
- Create WebGL context using `emscripten_webgl_create_context()`
- Implement `SetCurrent()` using `emscripten_webgl_make_context_current()`
- Handle canvas resize events
- `SwapBuffers()` - typically no-op for WebGL (auto-swaps)
#### 3. Update `configure.in` (line ~3890)
```autoconf
elif test "$wxUSE_WASM" = 1; then
dnl WASM uses WebGL through Emscripten
OPENGL_LIBS=""
wxUSE_OPENGL="yes"
```
#### 4. Update `build/files.bkl`
Add `glcanvas.cpp` to WASM sources list.
### Reference Files
- `src/unix/glegl.cpp` - EGL implementation (922 lines)
- `src/gtk/glcanvas.cpp` - GTK implementation (303 lines)
- `src/common/glcmn.cpp` - Base implementation shared by all ports
---
## Phase 3: Legacy OpenGL Emulation for KiCad
### The Problem
**KiCad uses legacy OpenGL immediate mode** which is **NOT supported in WebGL**. KiCad does NOT use wxGLAPI (wxWidgets' wrapper), but calls raw OpenGL directly:
| Function Category | Count | Examples |
|---|---|---|
| glBegin/glEnd | 70 | Immediate mode drawing |
| glVertex2f/3f/2d/3d | 112 | Vertex specification |
| glMatrixMode/glPushMatrix/glPopMatrix | 88 | Matrix stack operations |
| glEnableClientState/glDisableClientState | 43 | Legacy vertex arrays |
| glColor3f/4f/3d/4d | 34 | Per-vertex colors |
| glTranslatef/glRotatef/glScalef | 26 | Matrix transforms |
| glTexCoord2f/2d | 19 | Texture coordinates |
| glVertexPointer/glColorPointer | 18 | Legacy vertex arrays |
| glNormal3f/3d | 12 | Normal vectors |
| **TOTAL** | **423+** | **Must be emulated** |
### Most Affected KiCad Files
**3D Viewer (heaviest usage):**
- `opengl_utils.cpp` - 88+ calls (bounding boxes, debug geometry)
- `render_3d_opengl.cpp` - 50+ calls (core 3D board rendering)
- `3d_spheres_gizmo.cpp` - 48+ calls (interactive gizmos)
- `layer_triangles.cpp` - 34 calls (layer mesh rendering)
**2D PCB Viewer:**
- `opengl_gal.cpp` - 39+ calls (bitmap text, cursor)
- `antialiasing.cpp` - 37+ calls (fullscreen AA post-processing)
- `opengl_compositor.cpp` - 19+ calls (screen compositing)
### Solution: Emscripten Legacy GL Emulation
**Link flag:** `-sLEGACY_GL_EMULATION`
This built-in Emscripten feature emulates legacy OpenGL (immediate mode, matrix stack, etc.) on top of WebGL. It was used to port the Sauerbraten 3D game (BananaBread).
**Optional performance flags:**
- `-sGL_UNSAFE_OPTS` - Skip redundant GL work
- `-sGL_FFP_ONLY` - Disable programmable pipeline detection
**Alternatives (if needed later):**
- [gl4es](https://github.com/ptitSeb/gl4es) - OpenGL 2.1 → GLES 2.0 translation
- [Regal](https://github.com/emscripten-ports/regal) - Used by D3Wasm (Doom 3 port)
---
## Phase 4: Test Graphics
### Goal
Create tests that exercise **all GL functions KiCad uses** to verify emulation works.
### GL Functions to Test (Based on KiCad Usage)
#### Immediate Mode Drawing
```cpp
glBegin(GL_TRIANGLES);
glBegin(GL_QUADS);
glBegin(GL_LINE_STRIP);
glBegin(GL_LINE_LOOP);
glBegin(GL_LINES);
glEnd();
```
#### Vertex Specification
```cpp
glVertex2f(x, y);
glVertex3f(x, y, z);
glVertex2d(x, y);
glVertex3d(x, y, z);
```
#### Color Specification
```cpp
glColor3f(r, g, b);
glColor4f(r, g, b, a);
glColor3ub(r, g, b);
glColor4ub(r, g, b, a);
```
#### Matrix Operations
```cpp
glMatrixMode(GL_PROJECTION);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glPushMatrix();
glPopMatrix();
glTranslatef(x, y, z);
glRotatef(angle, x, y, z);
glScalef(x, y, z);
glOrtho(...);
gluPerspective(...);
```
#### Texture Coordinates
```cpp
glTexCoord2f(s, t);
glTexCoord2d(s, t);
```
#### Normal Vectors
```cpp
glNormal3f(nx, ny, nz);
glNormal3d(nx, ny, nz);
```
#### Legacy Vertex Arrays
```cpp
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDisableClientState(...);
glVertexPointer(...);
glColorPointer(...);
glDrawArrays(...);
```
#### State Management
```cpp
glEnable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glDisable(...);
glBlendFunc(...);
```
### Test File: `tests/wasm-app/gl_test.cpp`
Create test application that:
1. Creates wxGLCanvas with WebGL context
2. Tests each category of GL functions
3. Renders a known pattern (e.g., colored shapes)
4. Logs results to console
### Playwright Tests: `tests/e2e/gl.spec.ts`
Automated tests:
- WebGL context creation succeeds
- Each GL function category executes without errors
- Visual verification of rendered output (screenshot comparison)
---
## Files to Create/Modify
| File | Action |
|------|--------|
| `scripts/build-wxuniversal-wasm.sh` | Modify - fix PCRE build order |
| `wxwidgets/include/wx/wasm/glcanvas.h` | Create - wxGLCanvas header |
| `wxwidgets/src/wasm/glcanvas.cpp` | Create - wxGLCanvas implementation |
| `wxwidgets/configure.in` | Modify - add WASM OpenGL support |
| `wxwidgets/build/files.bkl` | Modify - add source file |
| `tests/wasm-app/gl_test.cpp` | Create - test application |
| `tests/e2e/gl.spec.ts` | Create - Playwright tests |
---
## Why This Matters for KiCad
KiCad uses a Graphics Abstraction Layer (GAL) with two backends:
- **Cairo backend**: Uses wxDC (2D) - works with existing wxUniversal WASM
- **OpenGL backend**: Uses wxGLCanvas - **requires this implementation**
KiCad's OpenGL GAL provides:
- Hardware-accelerated rendering
- Better performance for complex PCBs
- 3D viewer support
Without wxGLCanvas, KiCad would be limited to Cairo rendering only.

View file

@ -1,146 +0,0 @@
# 06 - wxGLCanvas WASM Canvas Element Integration
This document describes how to fix wxGLCanvas to render visibly in WASM by creating its own canvas element.
## Problem
wxGLCanvas currently hardcodes `m_canvasTarget = "#canvas"` which creates WebGL context on the main wxUniversal 2D canvas, causing a conflict. The GL rendering doesn't appear because it's overwritten by 2D UI rendering.
## Key Discovery
The WASM port already supports **multiple canvas elements per window**:
- Main window uses `#canvas` for 2D rendering
- Child windows can each get their own canvas element via `createWindow(id, needsCanvas=true)`
- Windows are positioned absolutely with z-index layering
- JavaScript (wx.js) manages canvas creation, positioning, and context stacking
## How KiCad Uses wxGLCanvas
```
wxFrame (EDA_DRAW_FRAME)
└── EDA_DRAW_PANEL_GAL (wxScrolledCanvas)
└── OPENGL_GAL (inherits from wxGLCanvas)
- Sized to match parent
- Child window positioned within parent
- Has its own GL context
```
Key KiCad files:
- `kicad/common/gal/opengl/opengl_gal.cpp` - GL rendering implementation
- `kicad/common/draw_panel_gal.cpp` - Creates OPENGL_GAL as child: `new OPENGL_GAL(..., this, ...)`
- `kicad/include/gal/hidpi_gl_canvas.h` - wxGLCanvas wrapper class
---
## Solution
wxGLCanvas should integrate with the existing WASM window system:
1. **Create its own canvas element** - Use `createWindow(id, needsCanvas=true)` in JavaScript
2. **Create WebGL context on that canvas** - Use selector `#window-{id} canvas`
3. **Handle positioning via window system** - `setWindowRect()` positions the canvas
### Key JavaScript Functions (already exist in wx.js)
```javascript
createWindow(id, needsCanvas, isVisible, classList)
setWindowRect(id, x, y, width, height)
setWindowVisibility(id, isVisible)
destroyWindow(id)
```
---
## Implementation
### Step 1: Modify Create() to create canvas element
In `wxwidgets/src/wasm/glcanvas.cpp`:
```cpp
bool wxGLCanvas::Create(wxWindow *parent, ...)
{
if ( !wxWindow::Create(parent, id, pos, size, style, name) )
return false;
// Create a window with canvas element in JavaScript
int cssId = GetCSSId();
EM_ASM({
createWindow($0, true, true, "glcanvas");
}, cssId);
// Position the canvas
wxPoint screenPos = GetScreenPosition();
wxSize clientSize = GetClientSize();
EM_ASM({
setWindowRect($0, $1, $2, $3, $4);
}, cssId, screenPos.x, screenPos.y, clientSize.GetWidth(), clientSize.GetHeight());
// Set dynamic canvas selector
m_canvasTarget = wxString::Format("#window-%d canvas", cssId).ToStdString();
return CreateWebGLContext(dispAttrs);
}
```
### Step 2: Override DoSetSize for resize handling
```cpp
void wxGLCanvas::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
wxPoint screenPos = GetScreenPosition();
wxSize clientSize = GetClientSize();
EM_ASM({
setWindowRect($0, $1, $2, $3, $4);
}, GetCSSId(), screenPos.x, screenPos.y, clientSize.GetWidth(), clientSize.GetHeight());
}
```
### Step 3: Handle visibility
```cpp
void wxGLCanvas::DoShow(bool show)
{
wxWindow::DoShow(show);
EM_ASM({
setWindowVisibility($0, $1);
}, GetCSSId(), show);
}
```
### Step 4: Clean up in destructor
```cpp
wxGLCanvas::~wxGLCanvas()
{
if ( m_webglContext > 0 )
{
emscripten_webgl_destroy_context(m_webglContext);
m_webglContext = 0;
}
EM_ASM({
destroyWindow($0);
}, GetCSSId());
}
```
---
## Files to Modify
| File | Changes |
|------|---------|
| `wxwidgets/src/wasm/glcanvas.cpp` | Main implementation - create own canvas, dynamic selector, resize/visibility handling |
| `wxwidgets/include/wx/wasm/glcanvas.h` | Add DoSetSize, DoShow declarations if needed |
---
## Testing
The existing test app in `tests/wasm-app/` with the OpenGL tab will verify:
1. Canvas element is created in DOM
2. WebGL context is on the correct canvas
3. GL rendering appears in the canvas area
4. Legacy GL emulation works (via `-sLEGACY_GL_EMULATION`)

View file

@ -1,546 +0,0 @@
# KiCad PCBnew WASM Build Plan
## Goal
Build the full KiCad PCBnew application for WebAssembly with all major features:
- **Target**: PCBnew (PCB Editor)
- **3D/STEP**: OpenCASCADE ported to WASM
- **Simulation**: ngspice ported to WASM
- **Threading**: Emscripten pthreads (true parallelism)
- **Coroutines**: Emscripten Asyncify fibers for libcontext
- **Stub only**: nanodbc (ODBC not available in browser)
## Core Principles
### 1. No Source Modifications to KiCad or wxWidgets
**CRITICAL**: All WASM-specific code must go into compatibility layers, NOT into the KiCad or wxWidgets source trees.
```
kicad-wasm/
├── kicad/ # Git submodule - DO NOT MODIFY
├── wxwidgets/ # Git submodule - DO NOT MODIFY (except WASM platform)
├── wasm/ # NEW: All WASM compatibility layers
│ ├── kiplatform/ # Platform layer implementations
│ ├── libcontext/ # Fiber implementation
│ ├── shims/ # Header shims and wrappers
│ └── stubs/ # Feature stubs
├── stubs/ # (existing) Stub headers/implementations
├── cmake/ # (existing) CMake find modules
└── patches/ # Minimal patches ONLY if absolutely necessary
```
### 2. Compatibility Layer Strategy
Instead of patching KiCad source, we:
1. **Override include paths** - Put our headers first in include path
2. **Provide stub libraries** - Link our stubs instead of real libraries
3. **CMake module overrides** - Replace find_package results with our targets
4. **Platform implementations** - Provide WASM versions of platform-specific code
### 3. Dependencies First Approach
Build all dependencies (including OpenCASCADE and ngspice) BEFORE building KiCad to ensure a clean build.
---
## KiCad Submodule Version
```
Commit: 4bfed3f1746e8cc0a7d942767770f56fa28b393c
Version: 8.99 (development)
```
---
## Exact Dependency Versions
These versions are from KiCad's `CMakeLists.txt` and `vcpkg.json`:
### Required Dependencies
| Dependency | Min Version | Pinned Version | Source |
|------------|-------------|----------------|--------|
| wxWidgets | 3.2.0 | 3.3.1 | vcpkg override |
| GLM | 0.9.8 | 0.9.9.8 | vcpkg override |
| Boost | 1.71.0 | latest | CMakeLists.txt |
| FreeType | 2.11.1 | latest | CMakeLists.txt |
| HarfBuzz | - | latest | CMakeLists.txt |
| Fontconfig | - | latest | CMakeLists.txt |
| Cairo | 1.12 | latest | CMakeLists.txt |
| Pixman | 0.30 | latest | CMakeLists.txt |
| zlib | - | latest | CMakeLists.txt |
| Zstd | - | latest | CMakeLists.txt |
| OpenCASCADE | 7.5.0+ | 7.8.0+ preferred | CMakeLists.txt |
| ngspice | - | 45.2 | vcpkg override |
| Protobuf | 3.21.12 | 3.21.12 | vcpkg override |
| libgit2 | 1.5 | latest | CMakeLists.txt |
| CURL | - | latest | CMakeLists.txt |
| Python | 3.6+ | 3.11.5 | vcpkg override |
### What We Build vs Stub
| Dependency | Action | Reason |
|------------|--------|--------|
| wxWidgets | Already ported | WASM platform in wxwidgets submodule |
| GLM | Header-only | Just include |
| Boost | Header-only subset | Only need headers for most parts |
| FreeType | Emscripten port | `-sUSE_FREETYPE=1` |
| HarfBuzz | Build for WASM | Text shaping needed |
| zlib | Emscripten port | `-sUSE_ZLIB=1` |
| Zstd | Build for WASM | Compression needed |
| OpenCASCADE | Build for WASM | 3D/STEP support |
| ngspice | Build for WASM | Simulation support |
| Cairo | Build for WASM | 2D rendering fallback |
| Pixman | Build for WASM | Cairo dependency |
| libgit2 | **STUB** | No git in browser |
| CURL | **STUB** | Use fetch API instead |
| nanodbc | **STUB** | No ODBC in browser |
| Python/SWIG | **DISABLE** | No Python scripting |
| nng | **STUB** | No IPC in browser |
| SPNAV | **STUB** | No 3D mouse in browser |
---
## Compatibility Layer Structure
### Directory Layout
```
wasm/
├── CMakeLists.txt # Master WASM compat build
├── kiplatform/ # Platform layer for WASM
│ ├── CMakeLists.txt
│ ├── app.cpp # App lifecycle
│ ├── drivers.cpp # GPU detection ("WebGL")
│ ├── environment.cpp # Env vars via localStorage
│ ├── io.cpp # Virtual filesystem
│ ├── policy.cpp # Permissions (always allow)
│ ├── secrets.cpp # Credentials (localStorage)
│ ├── sysinfo.cpp # System info
│ └── printing.cpp # Browser print()
├── libcontext/ # Coroutine implementation
│ ├── CMakeLists.txt
│ └── fcontext_wasm.cpp # Asyncify fiber impl
├── shims/ # Header overrides
│ ├── CMakeLists.txt
│ ├── kiplatform_redirect.h # Redirect to our impl
│ └── libcontext_redirect.h # Redirect to our impl
└── config/ # Build configuration
├── kicad_wasm_config.h # Version/feature config
└── setup.h # Platform setup
```
### Existing Stubs (Already Done)
```
stubs/
├── include/
│ ├── curl/curl.h, easy.h # CURL stubs
│ ├── git2.h # libgit2 stub
│ ├── git2/sys/errors.h, merge.h # libgit2 internals
│ ├── ngspice/sharedspice.h # ngspice header (for stub build)
│ └── Standard_Version.hxx # OCC version stub
└── src/
├── disabled_features_stubs.cpp # CURL/git function stubs
├── kicad_git_stubs.cpp # Git feature stubs
├── kicad_git_all_stubs.cpp # Complete git stubs
├── occ_stubs.cpp # OpenCASCADE stubs
└── panel_git_repos_stub.cpp # Git UI stubs
```
### CMake Overrides (Already Done)
```
cmake/
├── FindCURL.cmake # Returns stub target
├── FindOCC.cmake # Configurable real/stub
├── Findlibgit2.cmake # Returns stub target
├── Findngspice.cmake # Configurable real/stub
└── KicadWasmOptions.cmake # Feature flags
```
---
## Build Phases
### Phase 1: Build Infrastructure
Create common utilities:
```bash
scripts/
├── common/
│ ├── env.sh # Emscripten environment, paths
│ ├── functions.sh # Error handling, logging
│ └── versions.sh # Dependency versions (from above table)
├── build-kicad-wasm.sh # Master orchestrator
└── build-deps/ # Per-dependency scripts
```
**versions.sh** - Pin to KiCad's required versions:
```bash
#!/bin/bash
# Versions matching KiCad 8.99 requirements
export KICAD_COMMIT="4bfed3f1746e8cc0a7d942767770f56fa28b393c"
# From vcpkg.json overrides
export GLM_VERSION="0.9.9.8"
export NGSPICE_VERSION="45.2"
export PROTOBUF_VERSION="3.21.12"
# From CMakeLists.txt minimums
export WXWIDGETS_MIN="3.2.0"
export GLM_MIN="0.9.8"
export BOOST_MIN="1.71.0"
export FREETYPE_MIN="2.11.1"
export CAIRO_MIN="1.12"
export PIXMAN_MIN="0.30"
export LIBGIT2_MIN="1.5"
export OCC_MIN="7.5.0"
# Recommended versions for WASM build
export OCC_VERSION="7.8.0"
export ZSTD_VERSION="1.5.5"
export HARFBUZZ_VERSION="8.3.0"
```
### Phase 2: Dependencies (In Order)
Build these BEFORE KiCad:
| Order | Dependency | Script | Notes |
|-------|------------|--------|-------|
| 1 | zlib | Emscripten port | `-sUSE_ZLIB=1` |
| 2 | Zstd | `build-zstd-wasm.sh` | Compression |
| 3 | FreeType | Emscripten port | `-sUSE_FREETYPE=1` |
| 4 | HarfBuzz | `build-harfbuzz-wasm.sh` | Text shaping |
| 5 | Pixman | `build-pixman-wasm.sh` | Cairo dep |
| 6 | Cairo | `build-cairo-wasm.sh` | 2D rendering |
| 7 | Boost | Headers only | Copy headers |
| 8 | Protobuf | `build-protobuf-wasm.sh` | If IPC needed |
| 9 | OpenCASCADE | `build-occ-wasm.sh` | 3D/STEP (large) |
| 10 | ngspice | `build-ngspice-wasm.sh` | Simulation |
| 11 | wxWidgets | Already done | `build-wxuniversal-wasm.sh` |
### Phase 3: WASM Compatibility Layer
Create `wasm/` directory with platform implementations.
#### kiplatform WASM (wasm/kiplatform/)
These files provide WASM implementations of KiCad's platform abstraction:
```cpp
// wasm/kiplatform/app.cpp
#include <kiplatform/app.h>
namespace KIPLATFORM::APP {
bool Init() { return true; }
wxString GetUserConfigPath() { return "/home/kicad"; }
wxString GetUserDataPath() { return "/home/kicad"; }
// ... etc
}
```
```cpp
// wasm/kiplatform/environment.cpp
#include <kiplatform/environment.h>
#include <emscripten.h>
namespace KIPLATFORM::ENV {
wxString GetEnv(const wxString& var) {
// Use localStorage via JS
char* val = (char*)EM_ASM_PTR({
var key = UTF8ToString($0);
var val = localStorage.getItem('env_' + key) || '';
return stringToNewUTF8(val);
}, var.c_str());
wxString result(val);
free(val);
return result;
}
}
```
#### libcontext Asyncify Fibers (wasm/libcontext/)
```cpp
// wasm/libcontext/fcontext_wasm.cpp
#ifdef __EMSCRIPTEN__
#include <emscripten/fiber.h>
// Provide same interface as libcontext but using Emscripten fibers
struct fcontext_transfer {
void* fctx;
void* data;
};
static emscripten_fiber_t main_fiber;
static bool main_fiber_initialized = false;
extern "C" {
fcontext_transfer jump_fcontext(void* to, void* vp);
void* make_fcontext(void* sp, size_t size, void (*fn)(fcontext_transfer));
}
// Implementation using emscripten_fiber_* APIs
#endif
```
### Phase 4: KiCad Build
**Script**: `scripts/build-pcbnew-wasm.sh`
```bash
#!/bin/bash
set -e
source "$(dirname "$0")/common/env.sh"
# Key: Override include paths to use our compatibility layers FIRST
WASM_INCLUDES="-I$PROJECT_ROOT/wasm/kiplatform"
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/wasm/libcontext"
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/wasm/shims"
WASM_INCLUDES="$WASM_INCLUDES -I$PROJECT_ROOT/stubs/include"
emcmake cmake ../kicad \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="$WASM_INCLUDES" \
\
# Use our CMake modules for stubs
-DCMAKE_MODULE_PATH="$PROJECT_ROOT/cmake" \
\
# Feature flags
-DKICAD_USE_OCC=ON \
-DKICAD_USE_NGSPICE=ON \
-DKICAD_USE_GIT=OFF \
-DKICAD_USE_CURL=OFF \
-DKICAD_SCRIPTING_WXPYTHON=OFF \
-DKICAD_IPC_API=OFF \
-DKICAD_BUILD_QA_TESTS=OFF \
-DKICAD_BUILD_I18N=OFF \
\
# Point to our builds
-DwxWidgets_CONFIG_EXECUTABLE="$WX_BUILD/wx-config" \
-DOCC_INCLUDE_DIR="$SYSROOT/include/opencascade" \
-DNGSPICE_LIBRARY="$SYSROOT/lib/libngspice.a"
emmake make pcbnew -j$(nproc)
```
### Phase 5: Testing
Follow existing patterns in `tests/`:
```
tests/wasm-app/standalone/pcbnew/
├── pcbnew_test.cpp # Minimal PCBnew test app
├── pcbnew_test.html # Generated
├── pcbnew_test.js # Generated
└── pcbnew_test.wasm # Generated
tests/e2e/
└── pcbnew.spec.ts # Playwright E2E tests
```
---
## Link Flags
```bash
# Core flags
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=256MB
-sSTACK_SIZE=5MB
# Async/modal support
-sASYNCIFY=1
-sASYNCIFY_STACK_SIZE=16384
# OpenGL/WebGL
-sLEGACY_GL_EMULATION
-sMAX_WEBGL_VERSION=2
# Threading
-pthread
-sPROXY_TO_PTHREAD=1
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency
-sOFFSCREENCANVAS_SUPPORT=1
```
Server headers for pthreads:
```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
---
## File Organization Summary
### What Goes Where
| Code Type | Location | Reason |
|-----------|----------|--------|
| WASM platform impl | `wasm/kiplatform/` | Don't touch kicad/ |
| Fiber implementation | `wasm/libcontext/` | Don't touch kicad/ |
| Header overrides | `wasm/shims/` | Include path override |
| Stub headers | `stubs/include/` | Already exists |
| Stub implementations | `stubs/src/` | Already exists |
| CMake finders | `cmake/` | Already exists |
| Build scripts | `scripts/` | Reproducible |
| Test apps | `tests/wasm-app/` | Existing pattern |
| E2E tests | `tests/e2e/` | Existing pattern |
### Files to Create
```
NEW:
├── wasm/
│ ├── CMakeLists.txt
│ ├── kiplatform/*.cpp (8 files)
│ ├── libcontext/fcontext_wasm.cpp
│ ├── shims/*.h
│ └── config/*.h
├── scripts/
│ ├── common/{env,functions,versions}.sh
│ ├── build-kicad-wasm.sh
│ ├── build-pcbnew-wasm.sh
│ └── build-deps/*.sh
├── stubs/src/nanodbc_stub.cpp
└── tests/
├── wasm-app/standalone/pcbnew/*
└── e2e/pcbnew.spec.ts
```
### Files NOT to Modify
```
DO NOT MODIFY:
├── kicad/ # Git submodule - use compatibility layers instead
└── wxwidgets/ # Git submodule - WASM platform already added
```
---
## Patches (Only If Absolutely Necessary)
If patches are unavoidable, they go in `patches/` with clear documentation:
```
patches/
├── kicad/
│ ├── 0001-*.patch
│ ├── checksums.sha256
│ └── README.md # Explain WHY each patch is needed
├── opencascade/
│ └── *.patch # OCC WASM compatibility
└── ngspice/
└── *.patch # Remove fork/exec
```
**Rule**: Before creating a patch, ask "Can this be done with a compatibility layer instead?"
---
## Success Criteria
### MVP (Milestone 1)
- [ ] All dependencies built for WASM
- [ ] PCBnew window opens in browser
- [ ] Menu bar and toolbars visible
- [ ] Can load .kicad_pcb file
- [ ] Board renders in WebGL
- [ ] Pan/zoom works
### Full Features (Milestone 2)
- [ ] All editing tools work
- [ ] Interactive router works (via Asyncify fibers)
- [ ] Zone filling works (via pthreads)
- [ ] DRC runs
- [ ] Save/export works
- [ ] 3D viewer works (OCC)
- [ ] Simulation works (ngspice)
---
## Quick Start
```bash
# 1. Set up Emscripten (already available via homebrew)
# Emscripten is at /opt/homebrew/bin/emcc
# 2. Build wxWidgets (if not already done)
./scripts/build-wxuniversal-wasm.sh
# 3. Build all dependencies
./scripts/deps/build-all-deps.sh --all
# 4. Build PCBnew for WASM
./scripts/build-pcbnew-wasm.sh
# 5. Test
cd tests && npm test
# 6. Serve (with COOP/COEP headers for SharedArrayBuffer)
cd build-wasm && npx serve -p 8080
```
---
## Implementation Status
### Created Files
#### Build Infrastructure (`scripts/common/`)
- `env.sh` - Environment setup (paths, Emscripten config)
- `functions.sh` - Utility functions (logging, downloads, stamps)
- `versions.sh` - Pinned dependency versions from KiCad
#### Dependency Build Scripts (`scripts/deps/`)
- `build-all-deps.sh` - Master dependency builder
- `build-zstd.sh` - Compression library
- `build-freetype.sh` - Font rendering
- `build-harfbuzz.sh` - Text shaping
- `build-pixman.sh` - Pixel manipulation
- `build-cairo.sh` - 2D graphics
- `build-glm.sh` - Math library (header-only)
- `build-protobuf.sh` - Protocol buffers
- `build-opencascade.sh` - 3D geometry/STEP
- `build-ngspice.sh` - SPICE simulation
#### WASM Compatibility Layer (`wasm/`)
- `CMakeLists.txt` - Main CMake configuration
- `README.md` - Documentation
- `kiplatform/CMakeLists.txt`
- `kiplatform/app.cpp` - Application lifecycle
- `kiplatform/drivers.cpp` - 3D mouse (stub)
- `kiplatform/environment.cpp` - Environment/paths with localStorage
- `kiplatform/io.cpp` - File I/O for virtual filesystem
- `kiplatform/policy.cpp` - Enterprise policies (stub)
- `kiplatform/secrets.cpp` - Credential storage via localStorage
- `kiplatform/sysinfo.cpp` - System info via WebGL/navigator
- `kiplatform/ui.cpp` - UI utilities (theme detection, etc.)
- `libcontext/CMakeLists.txt`
- `libcontext/libcontext_wasm.h` - Asyncify fiber header
- `libcontext/libcontext_wasm.cpp` - Asyncify fiber implementation
- `cmake/KiCadWASMConfig.cmake` - CMake config for WASM
- `cmake/FindKiplatformWASM.cmake` - Find module for kiplatform
- `cmake/FindLibcontextWASM.cmake` - Find module for libcontext
#### PCBnew Build
- `scripts/build-pcbnew-wasm.sh` - Main PCBnew build script
#### Tests (`tests/kicad/`)
- `pcbnew.html` - Test app HTML
- `pcbnew.spec.ts` - Playwright E2E tests
### Known Issues
1. **CMake Policy**: Zstd and some older libraries need `-DCMAKE_POLICY_VERSION_MINIMUM=3.5`
to work with modern CMake
2. **Shell Environment**: When sourcing scripts, use a fresh bash (`bash -c '...'`) to avoid
conflicts with existing environment variables

View file

@ -1,198 +0,0 @@
# KiCad WASM Build State
## Build Output
| File | Size | Description |
|------|------|-------------|
| `pcbnew.wasm` | 15MB | WebAssembly binary |
| `pcbnew.js` | 146KB | JS loader/glue |
Location: `build-wasm/kicad-pcbnew/pcbnew/`
## Build Command
```bash
# Full build (clean)
./scripts/kicad/build-pcbnew.sh
# Incremental build
./scripts/kicad/build-pcbnew.sh --no-clean --skip-deps
# Inside Docker
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
/workspace/scripts/kicad/build-pcbnew.sh
```
## Build Configuration
```
Memory: 256MB initial, 4GB max (ALLOW_MEMORY_GROWTH)
Threads: pthreads with pool of 4
Async: ASYNCIFY enabled (64KB stack)
Graphics: wxUniversal + Cairo GAL
```
---
## KiCad Fork Modifications (`/kicad`)
68 files modified. Grouped by category:
### CMake Find Modules (`cmake/Find*.cmake`)
Cross-compilation support - `find_library()` fails in WASM, need direct paths.
| File | Change |
|------|--------|
| `FindCairo.cmake` | Direct path lookup for WASM |
| `FindFontconfig.cmake` | Mark unavailable (no system fonts in browser) |
| `FindGLEW.cmake` | Mark not needed (WebGL handles extensions) |
| `FindGLM.cmake` | Direct path lookup |
| `FindOCC.cmake` | OpenCASCADE direct path |
| `FindPixman.cmake` | Direct path lookup |
| `FindSPNAV.cmake` | 3D mouse not available in browser |
| `FindZSTD.cmake` | Direct path for cross-compilation |
| `Findlibgit2.cmake` | Headers only (functions stubbed) |
| `Findngspice.cmake` | SPICE not available |
| `Findnng.cmake` | IPC not supported in browser |
| `FindwxWidgets.cmake` | Skip library existence checks for WASM |
### Main CMakeLists.txt
- WASM port detection (`if(EMSCRIPTEN)`)
- `KICAD_SCRIPTING` OFF by default for EMSCRIPTEN
- `KICAD_IPC_API` OFF by default for EMSCRIPTEN
- Fontconfig disabled, sets `KICAD_USE_FONTCONFIG=0`
- Python/SWIG only when `KICAD_SCRIPTING` enabled
- 3D viewer subdirectory excluded for WASM
### IPC API Guards (`#ifdef KICAD_IPC_API`)
Protobuf serialization code disabled when IPC API off.
**Headers:**
- `include/api/api_utils.h`
- `pcbnew/api/api_pcb_utils.h`
**Common:**
- `common/eda_shape.cpp` - Serialize/Deserialize methods
- `common/eda_text.cpp` - Serialize/Deserialize methods
- `common/netclass.cpp` - Protobuf includes
**PCBnew objects:**
- `pcbnew/board_connected_item.cpp`
- `pcbnew/board_item.cpp`
- `pcbnew/board_stackup_manager/board_stackup.cpp`
- `pcbnew/footprint.cpp`
- `pcbnew/pad.cpp`
- `pcbnew/padstack.cpp`
- `pcbnew/pcb_dimension.cpp`
- `pcbnew/pcb_field.cpp`
- `pcbnew/pcb_group.cpp`
- `pcbnew/pcb_shape.cpp`
- `pcbnew/pcb_text.cpp`
- `pcbnew/pcb_textbox.cpp`
- `pcbnew/pcb_track.cpp`
- `pcbnew/zone.cpp`
### Python Scripting Guards (`#ifdef KICAD_SCRIPTING`)
Python.h and pybind11 not available in WASM.
- `pcbnew/pcbnew.cpp` - PyInit, KIFACE_SCRIPTING_LEGACY
- `pcbnew/pcbnew_settings.cpp` - pybind11 include
- `pcbnew/pcb_edit_frame.cpp` - Python sync functions, scripting includes
- `pcbnew/menubar_pcb_editor.cpp` - Scripting menu items
- `pcbnew/toolbars_pcb_editor.cpp` - Scripting toolbar
### Altium Plugin Disabled (`#ifndef __EMSCRIPTEN__`)
`char_traits<unsigned char>` specialization issue in Emscripten.
- `pcbnew/CMakeLists.txt` - `add_subdirectory(pcb_io/altium)` conditional
- `pcbnew/pcb_io/pcb_io_mgr.cpp` - Altium includes and plugin registration
### 3D Viewer Disabled
Requires GLU and OpenGL fixed-function pipeline (not in WebGL).
- `CMakeLists.txt` - `add_subdirectory(3d-viewer)` conditional
- `pcbnew/CMakeLists.txt` - `3d-viewer` library link conditional
### Fontconfig
No system font access in browser.
- `common/font/fontconfig.cpp` - Wrapped with `#if KICAD_USE_FONTCONFIG`
- `include/font/fontconfig.h` - Conditional compilation
### Third-party Libraries
- `thirdparty/libcontext/libcontext.cpp` - WASM stub for coroutines (returns nullptr)
- `thirdparty/libcontext/libcontext.h` - `LIBCONTEXT_PLATFORM_wasm32` detection
- `thirdparty/lemon/CMakeLists.txt` - Build fixes
### Other Fixes
- `pcbnew/dialogs/panel_setup_layers.cpp` - `GetCurrentSelection()``GetSelection()` (wxUniversal)
- `include/gal/opengl/kiglew.h` - GLEW version stub for WASM
- `common/gal/CMakeLists.txt` - GAL library adjustments
- `common/CMakeLists.txt` - Common library adjustments
- `libs/kiplatform/CMakeLists.txt` - WASM platform support
- `scripting/CMakeLists.txt` - Conditional on KICAD_SCRIPTING
---
## wxWidgets Fork Modifications (`/wxwidgets`)
| File | Change | Reason |
|------|--------|--------|
| `include/wx/wasm/glcanvas.h` | Moved `Show()` from protected to public | KiCad calls `Show()` on wxGLCanvas |
| `src/wasm/utils.cpp` | Added `wxFindWindowAtPointer()` | Missing function used by cshelp |
---
## Stub Libraries (`/wasm/stubs/`)
| File | Symbols | Reason |
|------|---------|--------|
| `libgit2_stub.c` | `git_libgit2_init`, `git_repository_*`, `git_reference_*`, `git_oid_*`, `git_error_last` | Git not available in browser |
| `curl_stub.c` | `curl_global_init`, `curl_global_cleanup` | Network via JS fetch instead |
wxWidgets stubs (created by build script):
- `libwx_wasmunivu_richtext-3.2.a` - Empty, not used by KiCad
- `libwx_wasmunivu_webview-3.2.a` - Empty, not used by KiCad
---
## Disabled Features Summary
| Feature | Reason |
|---------|--------|
| Python Scripting | pybind11/Python.h not available in WASM |
| 3D Viewer | GLU and OpenGL fixed-function pipeline not in WebGL |
| Altium Plugin | `char_traits<unsigned char>` Emscripten issue |
| SPICE | ngspice not ported |
| IPC API | IPC not supported in browser |
| GitHub Plugin | Network needs JS bridge |
| Fontconfig | No system font access |
| 3D Mouse (SPNAV) | Hardware not accessible |
---
## Dependencies
Built via `scripts/deps/build-all-deps.sh`:
- zstd, glm, freetype, harfbuzz, pixman, cairo
- OpenCASCADE (geometry kernel)
- Protobuf (headers only)
- Boost (headers only)
---
## Next Steps
1. Create HTML wrapper to load pcbnew.js
2. Implement file system access (MEMFS or IDBFS)
3. Test basic UI rendering
4. Bridge network operations to JS fetch

View file

@ -1,301 +0,0 @@
# Emscripten pthreads for KiCad WASM
## Overview
Emscripten can compile C++ threading code to WebAssembly using Web Workers and SharedArrayBuffer. This allows KiCad's existing `BS::thread_pool` code to work **unchanged** in the browser.
## How It Works
```
┌──────────────────────────────────────────────────────────────┐
│ Browser Environment │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SharedArrayBuffer (Shared Memory) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Main │ │ Worker │ │ Worker │ ... │ │
│ │ │ Thread │ │ Thread │ │ Thread │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ BOARD* │ │ BOARD* │ │ BOARD* │ │ │
│ │ │ (same!) │ │ (same!) │ │ (same!) │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ All threads share memory - C++ pointers work normally │
└──────────────────────────────────────────────────────────────┘
```
Emscripten:
1. Creates Web Workers for each pthread
2. Uses SharedArrayBuffer for shared memory between workers
3. Implements pthread synchronization primitives (mutex, condition variables)
4. Existing C++ thread code compiles without modification
## Build Configuration
### Minimal Flags
```bash
emcc main.cpp -o app.js \
-pthread \
-sPROXY_TO_PTHREAD=1
```
### Recommended Full Configuration
```bash
emcc main.cpp -o app.js \
-pthread \
-sPROXY_TO_PTHREAD=1 \
-sPTHREAD_POOL_SIZE="Math.min(navigator.hardwareConcurrency, 8)" \
-sALLOW_BLOCKING_ON_MAIN_THREAD=0 \
-sOFFSCREENCANVAS_SUPPORT=1 \
-sOFFSCREEN_FRAMEBUFFER=1 \
-sMALLOC=mimalloc \
-sINITIAL_MEMORY=512MB \
-sSTACK_SIZE=5MB \
-sPTHREAD_POOL_SIZE_STRICT=0
```
### Flag Explanations
| Flag | Purpose |
|------|---------|
| `-pthread` | Enable pthread support (required) |
| `-sPROXY_TO_PTHREAD=1` | Move main() to worker thread, keeps browser responsive |
| `-sPTHREAD_POOL_SIZE=N` | Pre-create N worker threads (avoids async creation delay) |
| `-sALLOW_BLOCKING_ON_MAIN_THREAD=0` | Error if main thread blocks (catches bugs) |
| `-sOFFSCREENCANVAS_SUPPORT=1` | WebGL rendering in worker threads |
| `-sOFFSCREEN_FRAMEBUFFER=1` | Fallback if OffscreenCanvas unavailable |
| `-sMALLOC=mimalloc` | Better allocator for multithreaded code |
| `-sPTHREAD_POOL_SIZE_STRICT=0` | Allow pool to grow beyond initial size |
### Dynamic Thread Pool Size
Use JavaScript expression for dynamic sizing:
```bash
-sPTHREAD_POOL_SIZE="Math.min(navigator.hardwareConcurrency, 8)"
```
This creates threads based on available CPU cores (max 8).
## Required HTTP Headers
**CRITICAL**: SharedArrayBuffer requires these headers or it won't be available.
```http
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
### Server Configuration Examples
**Nginx:**
```nginx
location / {
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
```
**Apache (.htaccess):**
```apache
Header set Cross-Origin-Embedder-Policy "require-corp"
Header set Cross-Origin-Opener-Policy "same-origin"
```
**Express.js:**
```javascript
app.use((req, res, next) => {
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
next();
});
```
**Vercel (vercel.json):**
```json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" }
]
}
]
}
```
**CloudFlare Pages (_headers):**
```
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
## Browser Support
| Browser | Version | Status |
|---------|---------|--------|
| Chrome | 92+ | ✅ Full support |
| Firefox | 79+ | ✅ Full support |
| Safari | 16.4+ | ✅ Full support |
| Edge | 92+ | ✅ Full support (Chromium) |
All modern browsers support SharedArrayBuffer with proper headers.
## Feature Detection
Check if threading is available at runtime:
```javascript
function checkThreadingSupport() {
const hasSharedArrayBuffer = typeof SharedArrayBuffer !== 'undefined';
const isCrossOriginIsolated = crossOriginIsolated;
console.log('SharedArrayBuffer available:', hasSharedArrayBuffer);
console.log('Cross-origin isolated:', isCrossOriginIsolated);
if (!hasSharedArrayBuffer) {
console.error('SharedArrayBuffer not available - check HTTPS');
}
if (!isCrossOriginIsolated) {
console.error('Not cross-origin isolated - check COOP/COEP headers');
}
return hasSharedArrayBuffer && isCrossOriginIsolated;
}
```
## PROXY_TO_PTHREAD Explained
Without `PROXY_TO_PTHREAD`:
- main() runs on browser's main thread
- Any blocking call (mutex, sleep, join) freezes the browser
- User can't interact while computation runs
With `PROXY_TO_PTHREAD`:
- Emscripten creates a new main() that spawns a worker
- Your original main() runs entirely in that worker
- Browser main thread stays responsive
- Blocking calls work normally
**Always use PROXY_TO_PTHREAD for applications with UI.**
## Memory Considerations
Each worker thread loads:
- Complete JavaScript glue code
- Complete WASM module
Memory usage scales with thread count:
- 2 threads: ~200MB typical
- 8 threads: ~400MB typical
- 20 threads: ~600MB+ typical
Keep thread pool size reasonable (4-8 threads recommended).
## Debugging
### Check if pthreads are working
```cpp
#include <emscripten/threading.h>
#include <thread>
int main() {
printf("Main thread: %d\n", emscripten_is_main_runtime_thread());
printf("Hardware concurrency: %d\n", std::thread::hardware_concurrency());
std::thread t([]() {
printf("Worker thread: %d\n", emscripten_is_main_runtime_thread());
});
t.join();
return 0;
}
```
### Common Issues
**"SharedArrayBuffer is not defined"**
- Missing COOP/COEP headers
- Not served over HTTPS (except localhost)
**"pthread_create failed"**
- Thread pool exhausted, increase PTHREAD_POOL_SIZE
- Or use PTHREAD_POOL_SIZE_STRICT=0 to allow growth
**Browser hangs/freezes**
- Missing PROXY_TO_PTHREAD flag
- Blocking on main thread
**Memory errors**
- Increase INITIAL_MEMORY
- Increase STACK_SIZE (each thread needs stack space)
## Comparison with Alternatives
| Approach | KiCad Changes | Deployment | Performance |
|----------|---------------|------------|-------------|
| **Emscripten pthreads** | None | Needs headers | Best |
| Synchronous stub | None (stub only) | Works everywhere | Slowest |
| Manual Web Workers | Significant | Works everywhere | Good |
## KiCad-Specific Notes
KiCad uses `BS::thread_pool` (Barak Shoshany's thread pool):
- Location: `kicad/thirdparty/thread-pool/bs_thread_pool.hpp`
- Uses standard C++ threads internally
- Works unchanged with Emscripten pthreads
Heavy threading usage in:
- DRC (Design Rule Check) - `pcbnew/drc/`
- Zone filling - `pcbnew/zone_filler.cpp`
- 3D raytracing - `3d-viewer/3d_rendering/raytracing/`
- Library loading - background tasks
With pthreads enabled, all these run in parallel as they do natively.
## Hosting Compatibility
| Host | Headers Configurable | pthreads Work |
|------|---------------------|---------------|
| GitHub Pages | ❌ No | ❌ No |
| Netlify Free | ❌ No | ❌ No |
| Netlify Pro | ✅ Yes | ✅ Yes |
| Vercel | ✅ Yes | ✅ Yes |
| CloudFlare Pages | ✅ Yes | ✅ Yes |
| AWS S3 + CloudFront | ✅ Yes | ✅ Yes |
| Your own server | ✅ Yes | ✅ Yes |
| localhost | ✅ N/A | ✅ Yes |
## Quick Start
1. **Add build flags:**
```bash
-pthread -sPROXY_TO_PTHREAD=1 -sPTHREAD_POOL_SIZE=4
```
2. **Configure server headers:**
```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```
3. **Test in browser:**
```javascript
console.log('Threading:', crossOriginIsolated && typeof SharedArrayBuffer !== 'undefined');
```
4. **Done.** Existing C++ threading code works.
## References
- [Emscripten pthreads documentation](https://emscripten.org/docs/porting/pthreads.html)
- [MDN SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
- [Chrome SharedArrayBuffer update](https://developer.chrome.com/blog/enabling-shared-array-buffer)
- [web.dev WebAssembly threads guide](https://web.dev/articles/webassembly-threads)

View file

@ -1,270 +0,0 @@
# KiCad Threading Analysis for WebAssembly Migration
> **See also:** [PTHREADS.md](./PTHREADS.md) for Emscripten pthreads implementation details.
## Executive Summary
KiCad relies **heavily** on multithreading for performance and UI responsiveness. Moving to single-threaded WASM will cause **significant performance degradation** in specific features, but the application will remain **functional**. The wxWidgets WASM layer already stubs out threading APIs, proving the approach is viable.
---
## 1. Threading Infrastructure Overview
### Core Thread Pool System
- **Implementation**: `BS::priority_thread_pool` (Barak Shoshany's library)
- **Location**: `kicad/include/thread_pool.h`, `kicad/thirdparty/thread-pool/bs_thread_pool.hpp`
- **Access**: Global `GetKiCadThreadPool()` function
- **Configuration**: Thread count via `ADVANCED_CFG::GetCfg().m_MaximumThreads`
### Synchronization Primitives Used
| Primitive | Usage Count | Purpose |
|-----------|-------------|---------|
| `std::mutex` | ~50+ files | Shared data protection |
| `std::atomic<T>` | 192 files | Lock-free counters/flags |
| `KISPINLOCK` | Core connectivity | Low-contention locking |
| `SYNC_QUEUE<T>` | PCM, tasks | Thread-safe queues |
| `std::condition_variable` | ~10 files | Thread signaling |
---
## 2. Features Using Threading (Impact Analysis)
### CRITICAL IMPACT (Will be noticeably slower)
#### DRC (Design Rule Check)
- **Files**: `pcbnew/drc/drc_test_provider_*.cpp` (10+ test providers)
- **Pattern**: `tp.submit_loop(0, items.size(), check_lambda)`
- **Why threads?**: Thousands of items, O(n²) clearance checks
- **Without threads**:
- 4-8x slower (proportional to CPU cores)
- UI freeze for 30 seconds to several minutes on large boards
- **Workaround**: Must add progress reporting + time-slicing
#### Zone Filling
- **File**: `pcbnew/zone_filler.cpp` (lines 609-697)
- **Pattern**: Two-stage parallel queue (fill → tessellate)
- **Why threads?**: Complex polygon operations (Clipper library), GPU tessellation
- **Without threads**:
- 5-30 second freeze per zone refill
- **Workaround**: Chunked processing with progress callbacks
#### 3D Viewer Raytracing
- **File**: `3d-viewer/3d_rendering/raytracing/render_3d_raytrace_base.cpp`
- **Pattern**: Work-stealing with `atomic<size_t>` counter, screen divided into blocks
- **Why threads?**: Embarrassingly parallel (each pixel independent)
- **Without threads**:
- 10-40 seconds per frame (unusable for interaction)
- **Workaround**: Progressive rendering, lower resolution, skip raytracing in WASM
### MEDIUM IMPACT (Noticeable but manageable)
#### Library Loading (Symbols/Footprints)
- **Files**: `eeschema/libraries/symbol_library_adapter.cpp`, `pcbnew/footprint_library_adapter.cpp`
- **Pattern**: Low-priority background tasks (`BS::pr::lowest`)
- **Why threads?**: Hundreds of libraries, file I/O blocking
- **Without threads**:
- 10+ second startup hang
- **Workaround**: Progressive loading with Asyncify, show loading indicator
#### Connectivity Graph
- **Files**: `eeschema/connection_graph.cpp`, `pcbnew/connectivity/connectivity_algo.cpp`
- **Pattern**: `submit_loop()` for parallel subgraph updates
- **Why threads?**: Large schematics have thousands of connections
- **Without threads**:
- 2-10 second lag on large schematics after edits
- **Workaround**: Incremental updates, defer full recalculation
#### 3D Layer Creation
- **File**: `3d-viewer/3d_canvas/create_layer_items.cpp`
- **Pattern**: Per-layer mutexes, parallel zone processing
- **Without threads**: Slower initial 3D view load
### LOW IMPACT (Acceptable degradation)
| Feature | File | Impact |
|---------|------|--------|
| Font polling | `common/widgets/font_choice.cpp` | 30s poll just runs on main thread |
| Git status | `kicad/project_tree_pane.cpp` | Slower git operations |
| Update checker | `kicad/update_manager.cpp` | Blocks during check |
| PCM downloads | `kicad/pcm/pcm_task_manager.cpp` | Sequential downloads |
| Python plugins | `scripting/python_manager.cpp` | Slower plugin load |
| STEP export | `pcbnew/dialogs/dialog_export_step_process.cpp` | Blocking export |
---
## 3. Current WASM Threading Status
### wxWidgets WASM Layer (Already Implemented)
```
wxUSE_THREADS = 1 (API available, but stubbed)
```
**Pthread Shim** (`wxwidgets/include/wx/wasm/pthread.h`):
- `pthread_attr_getschedpolicy()` → no-op
- `pthread_attr_setschedparam()` → no-op
- `pthread_setschedparam()` → no-op
- `sched_get_priority_*()` → returns 0
**Event Loop** (`wxwidgets/src/wasm/evtloop.cpp`):
- Uses `emscripten_set_main_loop()` - browser's requestAnimationFrame
- Single-threaded cooperative scheduler
- ~16ms per frame at 60fps
### Proven Async Patterns
| Pattern | File | Status |
|---------|------|--------|
| Modal dialogs | `wxwidgets/src/wasm/dialog.cpp` | Working via Asyncify |
| Clipboard | `wxwidgets/src/wasm/clipbrd.cpp` | Working via Asyncify |
| Timers | `wxwidgets/src/wasm/timer.cpp` | Working via `emscripten_async_call` |
**Asyncify Configuration** (`tests/wasm-app/Makefile.wasm`):
```makefile
-sASYNCIFY=1
-sASYNCIFY_STACK_SIZE=8192
-sASYNCIFY_IMPORTS=['startModal','js_writeTextToClipboard',...]
```
---
## 4. Migration Strategies
### Option A: Sequential with Progress Feedback (Recommended for MVP)
**Approach**: Let all operations run sequentially, add progress UI
```cpp
#ifdef __EMSCRIPTEN__
// Run sequentially with progress updates
for (size_t i = 0; i < items.size(); i++) {
checkItem(items[i]);
if (i % 100 == 0) {
reporter.SetCurrentProgress(i, items.size());
emscripten_sleep(0); // Yield to browser
}
}
#else
// Original threaded code
tp.submit_loop(0, items.size(), check_lambda);
#endif
```
**Pros**: Simple, predictable, works everywhere
**Cons**: Slower, UI shows "working" frequently
### Option B: Asyncify Time-Slicing
**Approach**: Break operations into 10-50ms chunks, yield between
```cpp
class TimeSlicedDRC {
void RunChunk() {
auto start = std::chrono::steady_clock::now();
while (m_currentItem < m_items.size()) {
checkItem(m_items[m_currentItem++]);
if (elapsed(start) > 16ms) { // One frame
emscripten_async_call(RunChunk, this, 0);
return; // Yield to browser
}
}
OnComplete();
}
};
```
**Pros**: Responsive UI during long operations
**Cons**: More complex, requires state machine refactoring
### Option C: Web Workers (Heavy Operations Only)
**Approach**: Run DRC/Router in separate WASM instance
```javascript
// Main thread
const drcWorker = new Worker('kicad-drc-worker.js');
drcWorker.postMessage({ type: 'runDRC', boardData: serializedBoard });
drcWorker.onmessage = (e) => updateDRCResults(e.data);
```
**Pros**: True parallelism, responsive UI
**Cons**:
- Requires `SharedArrayBuffer` (security headers)
- Memory duplication (can't share pointers)
- Complex serialization/deserialization
- Separate WASM binary for worker
---
## 5. Severity Assessment
| Severity | Features | User Experience |
|----------|----------|-----------------|
| **Blocking** | None | KiCad will run |
| **Degraded** | DRC, Zone Fill, 3D Raytrace | Slower, may need UI changes |
| **Minor** | Library loading, connectivity | Slight delays |
| **Unaffected** | Most editing, viewing | Normal operation |
### What Users Will Notice
1. **DRC takes 4-8x longer** - Was 5 seconds, now 20-40 seconds
2. **Zone fills freeze UI** - Need progress indicator
3. **3D raytracing unusable** - Should default to OpenGL mode
4. **Startup slightly slower** - Library preloading blocks
### What Won't Change
- Schematic editing responsiveness
- PCB editing (non-DRC operations)
- File save/load (already uses Asyncify patterns)
- Symbol/footprint placement
- Most dialogs and UI interactions
---
## 6. Recommended Approach
### Phase 1: Stub Threading (Immediate)
- Ensure `GetKiCadThreadPool()` returns a functional single-threaded shim
- All `submit_loop()` calls execute sequentially
- All `submit_task()` calls execute immediately
- Remove mutex locks (single-threaded guarantee)
### Phase 2: Add Progress Reporting (Short-term)
- DRC: Report progress every N items, add cancel button
- Zone fill: Report per-zone progress
- Library loading: Show loading indicator
### Phase 3: Time-Slicing (If Needed)
- Only if Phase 2 results in unacceptable UI freezes
- Prioritize DRC and zone filling
- Use Asyncify + `emscripten_sleep(0)` pattern
### Phase 4: Web Workers (Future/Optional)
- Only if WASM threading becomes critical
- Would require significant architecture changes
- Consider for heavy features like autorouter
---
## 7. Key Files to Modify
| File | Change Required |
|------|-----------------|
| `kicad/include/thread_pool.h` | Add WASM single-threaded shim |
| `kicad/common/thread_pool.cpp` | Implement sequential fallback |
| `kicad/pcbnew/drc/drc_engine.cpp` | Add progress reporting for WASM |
| `kicad/pcbnew/zone_filler.cpp` | Add chunked processing option |
| `kicad/3d-viewer/3d_rendering/raytracing/render_3d_raytrace_base.cpp` | Disable raytracing or make progressive |
---
## 8. Conclusion
**Is threading a blocker for WASM?** No.
**Will it impact performance?** Yes, significantly for DRC, zone filling, and 3D raytracing.
**Is it manageable?** Yes, with proper progress reporting and potentially disabling raytracing.
The wxWidgets WASM port already proves that threading can be stubbed out. KiCad will run slower for certain operations but remain fully functional. The recommended approach is to start simple (sequential with progress feedback) and only add complexity (time-slicing, Web Workers) if user experience demands it.

View file

@ -1,310 +0,0 @@
# wxWidgets 3.2.6 WASM Port Progress
## Build Command
```bash
cd /Users/V/IdeaProjects/kicad-wasm/build-wasm/wxwidgets-universal
emmake make -j1 2>&1 | tail -30
```
## Current Status: BUILD PASSING
Last verified: Build completes successfully with all libraries generated.
---
## COMPLETED CHANGES
### 1. build/wasm/ directory (DONE)
Copied from reference:
- `wxwidgets/build/wasm/common.mk`
- `wxwidgets/build/wasm/wxwasm.mk`
- `wxwidgets/build/wasm/wx.js` (JavaScript glue layer - critical!)
- `wxwidgets/build/wasm/template.html`
- `wxwidgets/build/wasm/httpd.py`
### 2. Locale warning fix (DONE)
**src/common/intl.cpp** - Added `#ifndef __WXWASM__` guard around locale warning at line ~394
**src/common/wxcrt.cpp** - Added `#ifdef __WXWASM__` to return NULL from wxSetlocale at line ~121
### 3. src/common/ modifications (DONE)
**include/wx/config.h** - Added WASM case to include wx/wasm/config.h and define wxConfig as wxLocalStorageConfig
**src/common/config.cpp** - Added WASM case to use wxLocalStorageConfig
**src/common/event.cpp** - Added `m_clickCount = event.m_clickCount;` in wxMouseEvent::Assign() at line ~615
**src/common/fontcmn.cpp** - Multiple changes:
- Added `#if !defined(__WXWASM__)` guard around FromString()/ToString() (lines 735-827)
- Added `m_isRendered = false;` in Init() for WASM (line 839-841)
- Added `#if !defined(__WXWASM__)` guard around setter functions (lines 884-927)
**include/wx/fontutil.h** - Added WASM-specific members at line ~222:
```cpp
#if defined(__WXWASM__)
mutable bool m_isRendered;
mutable wxString m_renderedString;
#endif
```
**src/common/combocmn.cpp** - Added WASM configuration block at line ~165:
```cpp
#elif defined(__WXWASM__)
#include "wx/dialog.h"
#define wxComboCtrlGenericTLW wxDialog
#define USE_TRANSIENT_POPUP 1
#define TRANSIENT_POPUPWIN_IS_PERFECT 1
#define POPUPWIN_IS_PERFECT 1
#define TEXTCTRL_TEXT_CENTERED 0
#define FOCUS_RING 0
```
### 4. src/univ/ Dialog async modal support (DONE)
**include/wx/univ/dialog.h** - Added:
- `#include <functional>` at top
- `virtual void ShowModal(std::function<void (int)> callback) wxOVERRIDE;` declaration
- `std::function<void (int)> m_modalCallback;` member
**include/wx/dialog.h** (base class) - Added:
- `#include <functional>` at top
- `virtual void ShowModal(std::function<void (int)> callback) = 0;` declaration
- Public `PopupMenu()` callback overloads
**include/wx/window.h** - Added:
- `#include <functional>`
- Public `PopupMenu()` callback overloads
- `virtual void DoPopupMenu(wxMenu *menu, int x, int y, std::function<void (bool)> callback) = 0;`
**src/univ/dialog.cpp** - Added:
- `#include <emscripten.h>`
- `m_modalCallback = NULL;` in Init()
- `EM_JS(int, startModal, ...)` JavaScript bridge function
- Modified ShowModal() to use async approach (returns wxID_CANCEL for now)
- Added `ShowModal(callback)` overload
- Modified EndModal() to call callback when present
### 5. src/univ/ Window modifications (DONE)
**include/wx/univ/window.h** - Added:
- `#include <functional>` at top
- WASM case for wxWindowNative (already present)
- `virtual void DoPopupMenu(wxMenu *menu, int x, int y, std::function<void (bool)> callback) wxOVERRIDE;`
- `std::function<void (int)> m_popupCallback;` member
**src/univ/winuniv.cpp** - Added:
- WASM case in `wxIMPLEMENT_DYNAMIC_CLASS`
- Commented out `EVT_KEY_DOWN(wxWindow::OnKeyDown)`
- `m_popupCallback = NULL;` in Init()
- SetScrollbar assertion already updated
### 6. src/univ/ Control rendering (DONE)
**src/univ/ctrlrend.cpp** - Changed GetLabel() to GetLabelText() in:
- DrawLabel()
- DrawButtonLabel()
- DrawFrame()
**src/univ/stdrend.cpp** - Changed wxBORDER_SIMPLE to use DrawStaticBorder()
### 7. src/univ/ Widget fixes (DONE)
**src/univ/anybutton.cpp** - Fixed:
- Moved Refresh() before Click() in Toggle()
- Changed GetLabel() to GetLabelText() in DoGetBestClientSize()
**src/univ/checkbox.cpp** - Changed GetLabel() to GetLabelText() in:
- DrawCheckButton() call
- GetMultiLineTextExtent() call
**src/univ/stattext.cpp** - Added:
- AutoResizeIfNecessary() call after WXSetVisibleLabel()
- Changed GetLabel() to GetLabelText() in WXGetVisibleLabel()
**src/univ/radiobut.cpp** - Added:
- Toggle() method
- PerformAction() override
- Changed GetLabel() to GetLabelText() in DoDraw()
**include/wx/univ/radiobut.h** - Added:
- Toggle() declaration
- PerformAction() override declaration
**src/univ/textctrl.cpp** - Added:
- SetBackgroundColour(*wxWHITE) in Create()
- DoGetSizeFromTextSize() method
**include/wx/univ/textctrl.h** - Changed:
- GetDefaultBorder() returns wxBORDER_STATIC instead of wxBORDER_SUNKEN
- Added DoGetSizeFromTextSize() declaration
### 8. GetScrollbarArrowSize signature + slider/spinbutt changes (DONE)
**include/wx/univ/renderer.h** - Changed:
- `GetScrollbarArrowSize()` to `GetScrollbarArrowSize(wxOrientation orientation)`
- `DrawSliderShaft` to add `double fracValue` parameter
- Added `GetOverflowHeight()` to wxMenuGeometryInfo
- Added `DrawMenuOverflowArrow()` method
- Updated wxDelegateRenderer wrappers
**src/univ/themes/gtk.cpp, mono.cpp, win32.cpp** - Updated:
- `GetScrollbarArrowSize(wxOrientation WXUNUSED(orientation))` signature
- `DrawSliderShaft` signature with `double WXUNUSED(fracValue)` parameter
**src/univ/scrolbar.cpp** - Added:
- `thumbSize = wxMax(wxMin(thumbSize, range), 0);` in SetScrollbar
- `GetScrollbarArrowSize()` helper method that calls renderer with orientation
- Changed all `m_renderer->GetScrollbarArrowSize()` to `GetScrollbarArrowSize()`
- Changed `size.x = SIZE` to `size.y = 15` for horizontal scrollbar
**include/wx/univ/scrolbar.h** - Added:
- `wxSize GetScrollbarArrowSize() const;` declaration
**src/univ/spinbutt.cpp** - Updated:
- `DoGetBestClientSize()` calls renderer with orientation
- `CalcArrowRects()` rewritten with hardcoded ARROW_WIDTH/HEIGHT
**src/univ/settingsuniv.cpp** - Updated:
- `GetMetric()` calls use orientation parameter
**src/univ/slider.cpp** - Updated:
- Added `IsInverted()` logic in CalcThumbRect
- Added fracValue calculation in DoDraw
- Updated PixelToThumbPos with IsInverted logic
- Simplified OnThumbDragStart/OnThumbDrag/OnThumbDragEnd
**include/wx/univ/slider.h** - Added:
- `bool IsInverted() const { return IsVert() != HasFlag(wxSL_INVERSE); }`
---
### 9. src/univ/menu.cpp (DONE)
Complete rewrite of popup menu handling for WASM:
**Timer-based submenu opening** - In browsers, no modal event loops. Timer allows diagonal mouse movement toward submenus without accidentally closing them.
- `m_subMenuTimer` - delays submenu opening (50ms)
- `m_subMenuPoint` - tracks mouse position when starting submenu tracking
- `IsPointTrackingToSubMenu()` - geometry check if mouse moving toward submenu
- `OnSubMenuTimer()` - timer callback
**Overflow handling** - Browser windows can be smaller than desktop. Menus need to scroll.
- `m_offsetY` - scroll offset
- `m_overflowTimer` - continuous scrolling when hovering arrows
- `HasOverflow()`, `HasOverflowArrowUp()`, `HasOverflowArrowDown()`
- `GetOverflowArrowUpRect()`, `GetOverflowArrowDownRect()`
- `OverflowArrowHitTest()`
- `GetMaxClientHeight()` - available screen height
- `SetOffsetY()` - set scroll and refresh
- `OnOverflowTimer()`, `OnMouseWheel()`
**Async popup menus** - DoPopupMenu skips blocking event loop for WASM
- Wrap blocking code in `#ifndef __WXWASM__`
- Add callback-based overload
- DismissPopupMenu calls callback
**Other changes:**
- Border from `wxBORDER_RAISED` to `wxBORDER_STATIC`
- ClickItem BEFORE DismissAndNotify (was after)
- GetRootWindow uses GetWindow() instead of GetInvokingWindow()
- Detach adds GetParent()->RemoveChild(this)
- OnLeftDown uses IsShowingMenu()/DismissMenu() instead of HasCapture()/OnDismiss()
---
### 10. src/generic/ modifications (DONE - partial)
**src/generic/spinctlg.cpp** - MARGIN=0, null check for m_spinButton in DoMoveWindow
**src/generic/msgdlgg.cpp** - Async ShowModal overload added
**include/wx/generic/msgdlgg.h** - ShowModal callback declaration added
**src/generic/renderg.cpp** - Visual tweaks (3DLIGHT color, transparent pen, highlight color)
**src/generic/treectlg.cpp** - Smaller indent/spacing (10 instead of 15/18), transparent pen
**src/generic/vlbox.cpp** - SetBackgroundColour(*wxWHITE)
Not yet applied (may not be needed for wxWidgets 3.2.6):
- **src/generic/caret.cpp** - Different API in 3.2.6
- **src/generic/grid.cpp** - Visual tweaks (low priority)
- **src/generic/gridctrl.cpp** - 4 lines (low priority)
- **src/generic/filedlgg.cpp** - 2 lines
- **src/generic/listctrl.cpp** - 2 lines
- **src/generic/stattextg.cpp** - 2 lines
---
## REMAINING CHANGES (NOT YET APPLIED)
### Priority 1: include/wx/ header modifications (Low priority - complex)
**include/wx/platinfo.h** - 77 lines (browser detection, wxBrowserInfo class, wxPORT_WASM)
**src/common/platinfo.cpp** - 6 lines (browser info init)
**src/common/utilscmn.cpp** - 57 lines (async wxMessageBox, wxPORT_WASM check)
**src/common/wincmn.cpp** - 19 lines (async popup menu)
Many other headers need `#elif defined(__WXWASM__)` or `#ifdef __WXWASM__` additions.
### Priority 4: Build system files
**Makefile.in** - 897 lines of changes
**autoconf_inc.m4** - 13 lines
**build/bakefiles/files.bkl** - 74 lines
**build/bakefiles/wx.bkl** - 2 lines
**build/cmake/files.cmake** - 71 lines
**build/cmake/setup.cmake** - 2 lines
**build/cmake/toolkit.cmake** - 6 lines
**build/files** - 71 lines
---
## HOW TO CHECK REFERENCE CHANGES
To see what a file changed in the reference:
```bash
cd /Users/V/IdeaProjects/kicad-wasm/wxWidgets-wasm-reference
git show d262364a0a -- path/to/file
```
The last 10 commits in reference (oldest to newest):
1. d262364a0a - Initial commit of wasm sources (main changes)
2. 595b16b855 - Add wasm files (theme, demo makefiles)
3. 0dbfab6b4c - Update README.md
4. 31a467a173 - Update README.md
5. 3c0f3b1954 - Update link to wavacity
6. 57ea7c9a04 - Fix crash if mouse window reset in event handler
7. ae94f56bd3 - Size top window before run
8. 255970e5e2 - Support font size in pixels
9. b44707a19a - Translate touch events to mouse events
10. 293bd9feba - Suppress locale warnings
Bug fixes 6-10 are already applied in wxwidgets/src/wasm/ files.
---
## KEY INSIGHT: Why config.sub in Submodules
wxWidgets 3.2.6 uses git submodules for bundled libraries (pcre, expat, jpeg, png, tiff). Each has its own config.sub that must recognize wasm32. The reference (older ~3.0.x) had libraries directly in-tree without submodules.
Current submodule config.sub files are already updated (showing `m` modified status in git).
---
## SKIPPED CHANGES (Low Priority or Complex)
- **src/common/appcmn.cpp** - Just debug printf (not needed)
- **src/common/init.cpp** - Just debug printf (not needed)
- **src/common/dcbufcmn.cpp** - Different API in 3.2.6 (already correct)
- **Browser info in platinfo.h/cpp** - Complex, adds wxBrowserInfo class (not critical)
- **Async wxMessageBox** - Changes function signature (complex)
---
## NEXT STEPS
1. **Complete menu.cpp changes** - Follow the 20-step list above
2. Apply src/generic/ changes
3. Apply remaining include/wx/ header changes
4. Test with a minimal WASM app
---
## FILES IN wxwidgets/ FOLDER
Key WASM-specific directories already present:
- `wxwidgets/src/wasm/` - 29 source files
- `wxwidgets/include/wx/wasm/` - 31 header files
- `wxwidgets/src/univ/themes/wasm.cpp` - WASM theme (98KB)
- `wxwidgets/build/wasm/` - Build support files (copied from reference)

View file

@ -1,63 +0,0 @@
# ccache Build Performance Benchmark
Testing build times before and after adding ccache to measure the impact.
## Test Environment
- Machine: Apple Silicon (M4 Max)
- Docker resources: 10 CPUs, 32GB RAM
- Build command: `./docker/build.sh` (defaults to incremental build)
## Results
### Step 1: Baseline (before ccache)
Command: `./docker/build.sh --skip-deps` (forces KiCad rebuild, old default)
**Time: 7:34.93** (7 min 35 sec)
### Step 2: First build with ccache (populating cache)
Command: `./docker/build.sh --skip-deps` (old default)
**Time: 9:35.52** (9 min 35 sec)
Note: Slower than baseline due to ccache overhead when populating cache
Cache stats: 1335 misses, 0 hits, 1.39GB cached
### Step 3: Single KiCad file change (incremental build)
Changed: `kicad/pcbnew/board.cpp` (added one line)
Command: `./docker/build.sh` (new incremental default)
**Time: 1:45.97** (1 min 46 sec)
- Only `board.cpp` recompiled
- Rest of compilation: instant (CMake detected no changes)
- Most time spent on post-processing (asyncify ~1 min)
- ccache hit rate: 25% (some preprocessed source matched)
### Step 4: wxWidgets incremental build baseline (configure ran)
Command: `./docker/build.sh` (after implementing skip-configure logic)
**Time: 7:43.87** (7 min 44 sec)
- First build after script changes, so configure ran
- This populates the wxWidgets build state for incremental builds
### Step 5: No-change rebuild (configure skipped)
Command: `./docker/build.sh` (no changes to any source files)
**Time: 1:31.13** (1 min 31 sec)
- wxWidgets configure skipped (Makefile exists, configure.in unchanged)
- wxWidgets make: instant (nothing to rebuild)
- KiCad CMake/make: instant (nothing changed)
- All time spent on post-processing (asyncify ~1 min)
### Step 6: Single wxWidgets file change (incremental build)
Changed: `wxwidgets/src/common/memory.cpp` (added one line)
Command: `./docker/build.sh`
**Time: 1:33.99** (1 min 34 sec)
- wxWidgets configure skipped
- Only `memory.cpp` recompiled, library re-archived
- KiCad links against updated wxWidgets
- Most time spent on post-processing (asyncify ~1 min)
## Summary
| Scenario | Before | After | Speedup |
|----------|--------|-------|---------|
| Full rebuild (baseline) | 7:35 | 9:35 | -27% (cache populating) |
| KiCad single file change | 7:35 | 1:46 | **4.3x faster** |
| wxWidgets single file change | ~7:35 | 1:34 | **4.8x faster** |
| No changes | ~7:35 | 1:31 | **5x faster** |
**Note**: Asyncify post-processing takes ~1 min and runs every build. This is the irreducible minimum build time.

View file

@ -1,358 +0,0 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fd20af211f..a8e2ea34b2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,7 +64,13 @@ set( CMAKE_EXPORT_COMPILE_COMMANDS ON )
# Path to KiCad's CMake modules.
set( KICAD_CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" )
-set( CMAKE_MODULE_PATH "${KICAD_CMAKE_MODULE_PATH}" )
+
+# Preserve any user-provided CMAKE_MODULE_PATH (e.g. for kicad-wasm overrides)
+if(CMAKE_MODULE_PATH)
+ set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${KICAD_CMAKE_MODULE_PATH}" )
+else()
+ set( CMAKE_MODULE_PATH "${KICAD_CMAKE_MODULE_PATH}" )
+endif()
include( ConfigurePlatform )
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 56e516acad..f29ae853ef 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -71,29 +71,6 @@ set( KICOMMON_SRCS
gal/color4d.cpp
gal/opengl/gl_context_mgr.cpp
- # Git
- git/git_add_to_index_handler.cpp
- git/git_branch_handler.cpp
- git/git_clone_handler.cpp
- git/git_commit_handler.cpp
- git/git_config_handler.cpp
- git/git_compare_handler.cpp
- git/git_init_handler.cpp
- git/project_git_utils.cpp
- git/git_pull_handler.cpp
- git/git_push_handler.cpp
- git/git_remove_from_index_handler.cpp
- git/git_remove_vcs_handler.cpp
- git/git_resolve_conflict_handler.cpp
- git/git_revert_handler.cpp
- git/git_status_handler.cpp
- git/git_switch_branch_handler.cpp
- git/git_sync_handler.cpp
- git/kicad_git_common.cpp
- git/kicad_git_errors.cpp
- git/git_backend.cpp
- git/libgit_backend.cpp
-
# Jobs
jobs/job.cpp
jobs/job_dispatcher.cpp
@@ -134,12 +111,6 @@ set( KICOMMON_SRCS
jobs/job_pcb_upgrade.cpp
jobs/job_sch_upgrade.cpp
- local_history.cpp
- history_lock.cpp
-
- kicad_curl/kicad_curl.cpp
- kicad_curl/kicad_curl_easy.cpp
-
libraries/library_manager.cpp
libraries/library_table.cpp
libraries/library_table_parser.cpp
@@ -280,6 +251,54 @@ if( UNIX AND NOT APPLE )
)
endif()
+# Git files - only include when git support is enabled
+if( KICAD_USE_GIT )
+ list( APPEND KICOMMON_SRCS
+ local_history.cpp
+ history_lock.cpp
+ git/git_add_to_index_handler.cpp
+ git/git_branch_handler.cpp
+ git/git_clone_handler.cpp
+ git/git_commit_handler.cpp
+ git/git_config_handler.cpp
+ git/git_compare_handler.cpp
+ git/git_init_handler.cpp
+ git/project_git_utils.cpp
+ git/git_pull_handler.cpp
+ git/git_push_handler.cpp
+ git/git_remove_from_index_handler.cpp
+ git/git_remove_vcs_handler.cpp
+ git/git_resolve_conflict_handler.cpp
+ git/git_revert_handler.cpp
+ git/git_status_handler.cpp
+ git/git_switch_branch_handler.cpp
+ git/git_sync_handler.cpp
+ git/kicad_git_common.cpp
+ git/kicad_git_errors.cpp
+ git/git_backend.cpp
+ git/libgit_backend.cpp
+ )
+endif()
+
+# CURL files - only include when curl support is enabled
+if( KICAD_USE_CURL )
+ list( APPEND KICOMMON_SRCS
+ kicad_curl/kicad_curl.cpp
+ kicad_curl/kicad_curl_easy.cpp
+ )
+endif()
+
+# Stub implementations for disabled features
+if( NOT KICAD_USE_GIT OR NOT KICAD_USE_CURL )
+ # Use absolute path to stub implementations
+ set( STUBS_SRC_DIR "${CMAKE_SOURCE_DIR}/../stubs/src" )
+ if( EXISTS "${STUBS_SRC_DIR}/disabled_features_stubs.cpp" )
+ list( APPEND KICOMMON_SRCS
+ "${STUBS_SRC_DIR}/disabled_features_stubs.cpp"
+ )
+ endif()
+endif()
+
if( KICAD_IPC_API )
set( KICOMMON_SRCS
${KICOMMON_SRCS}
@@ -322,12 +341,10 @@ target_link_libraries( kicommon
nlohmann_json_schema_validator
FastFloat::fast_float
fmt::fmt
- CURL::libcurl
picosha2
rapidcsv
${ZSTD_LIBRARY}
${wxWidgets_LIBRARIES}
- ${LIBGIT2_LIBRARIES}
${SPNAV_LIBRARIES}
# needed by kiid to allow linking for Boost for the UUID against bcrypt (msys2 only)
@@ -342,6 +359,15 @@ target_link_libraries( kicommon
${PYTHON_LIBRARIES}
)
+# Conditional library links for optional features
+if( KICAD_USE_CURL )
+ target_link_libraries( kicommon CURL::libcurl )
+endif()
+
+if( KICAD_USE_GIT )
+ target_link_libraries( kicommon ${LIBGIT2_LIBRARIES} )
+endif()
+
if( KICAD_USE_SENTRY )
target_link_libraries( kicommon
@@ -417,16 +443,30 @@ set( COMMON_ABOUT_DLG_SRCS
dialog_about/dialog_about_base.cpp
)
+# Git dialog sources - only include when git support is enabled
+# panel_git_repos_base.cpp has no git dependencies, so it's always compiled
set( COMMON_GIT_DLG_SRCS
- dialogs/git/dialog_git_commit.cpp
- dialogs/git/dialog_git_switch.cpp
- dialogs/git/dialog_git_auth.cpp
- dialogs/git/dialog_git_repository.cpp
- dialogs/git/dialog_git_repository_base.cpp
- dialogs/git/panel_git_repos.cpp
dialogs/git/panel_git_repos_base.cpp
+)
+if( KICAD_USE_GIT )
+ list( APPEND COMMON_GIT_DLG_SRCS
+ dialogs/git/dialog_git_commit.cpp
+ dialogs/git/dialog_git_switch.cpp
+ dialogs/git/dialog_git_auth.cpp
+ dialogs/git/dialog_git_repository.cpp
+ dialogs/git/dialog_git_repository_base.cpp
+ dialogs/git/panel_git_repos.cpp
)
+else()
+ # Add stub implementation for PANEL_GIT_REPOS when git is disabled
+ set( STUBS_SRC_DIR "${CMAKE_SOURCE_DIR}/../stubs/src" )
+ if( EXISTS "${STUBS_SRC_DIR}/panel_git_repos_stub.cpp" )
+ list( APPEND COMMON_GIT_DLG_SRCS
+ "${STUBS_SRC_DIR}/panel_git_repos_stub.cpp"
+ )
+ endif()
+endif()
set( COMMON_DLG_SRCS
${COMMON_GIT_DLG_SRCS}
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 1be2c291d1..bca563201a 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -697,15 +697,17 @@ if( APPLE )
set( OSX_BUNDLE_BUILD_KIFACE_DIR \"${OSX_BUNDLE_BUILD_KIFACE_DIR}\" )
" )
- # bundle libngspice and codemodels
- get_filename_component( ABS_LIBNGSPICE ${NGSPICE_LIBRARY} ABSOLUTE )
- get_filename_component( LIBNGSPICE_PATH ${ABS_LIBNGSPICE} DIRECTORY )
-
- install( DIRECTORY "${LIBNGSPICE_PATH}/"
- DESTINATION "${OSX_BUNDLE_INSTALL_PLUGIN_DIR}/sim"
- FILES_MATCHING PATTERN "*.dylib")
- install( DIRECTORY "${LIBNGSPICE_PATH}/ngspice"
- DESTINATION "${OSX_BUNDLE_INSTALL_PLUGIN_DIR}/sim" )
+ # bundle libngspice and codemodels (only when ngspice is enabled)
+ if( KICAD_USE_NGSPICE AND NGSPICE_LIBRARY )
+ get_filename_component( ABS_LIBNGSPICE ${NGSPICE_LIBRARY} ABSOLUTE )
+ get_filename_component( LIBNGSPICE_PATH ${ABS_LIBNGSPICE} DIRECTORY )
+
+ install( DIRECTORY "${LIBNGSPICE_PATH}/"
+ DESTINATION "${OSX_BUNDLE_INSTALL_PLUGIN_DIR}/sim"
+ FILES_MATCHING PATTERN "*.dylib")
+ install( DIRECTORY "${LIBNGSPICE_PATH}/ngspice"
+ DESTINATION "${OSX_BUNDLE_INSTALL_PLUGIN_DIR}/sim" )
+ endif()
install( CODE [[
include( ${KICAD_CMAKE_MODULE_PATH}/InstallSteps/InstallMacOS.cmake )
diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt
index b7e78725d0..c615c9022c 100644
--- a/kicad/CMakeLists.txt
+++ b/kicad/CMakeLists.txt
@@ -37,7 +37,6 @@ set( KICAD_SRCS
import_proj.cpp
import_project.cpp
kicad_manager_frame.cpp
- local_history_pane.cpp
menubar.cpp
project_template.cpp
project_tree_pane.cpp
@@ -50,6 +49,21 @@ set( KICAD_SRCS
tools/kicad_manager_control.cpp
)
+# Local history pane requires git support
+if( KICAD_USE_GIT )
+ list( APPEND KICAD_SRCS
+ local_history_pane.cpp
+ )
+else()
+ # Add comprehensive git stubs when git is disabled
+ set( STUBS_SRC_DIR "${CMAKE_SOURCE_DIR}/../stubs/src" )
+ if( EXISTS "${STUBS_SRC_DIR}/kicad_git_all_stubs.cpp" )
+ list( APPEND KICAD_SRCS
+ "${STUBS_SRC_DIR}/kicad_git_all_stubs.cpp"
+ )
+ endif()
+endif()
+
set( KICAD_CLI_SRCS
cli/command.cpp
cli/command_jobset_run.cpp
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 7ea9bdcda5..ba0be19156 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -248,15 +248,6 @@ set( PCBNEW_EXPORTERS
exporters/export_gencad.cpp
exporters/export_gencad_writer.cpp
exporters/export_idf.cpp
- exporters/step/exporter_step.cpp
- exporters/step/kicad3d_info.cpp
- exporters/step/step_pcb_model.cpp
- exporters/step/KI_XCAFDoc_AssemblyGraph.cxx
- exporters/u3d/bit_stream_writer.cpp
- exporters/u3d/constants.cpp
- exporters/u3d/context_manager.cpp
- exporters/u3d/data_block.cpp
- exporters/u3d/writer.cpp
exporters/exporter_vrml.cpp
exporters/place_file_exporter.cpp
exporters/gen_drill_report_files.cpp
@@ -267,6 +258,24 @@ set( PCBNEW_EXPORTERS
exporters/gerber_placefile_writer.cpp
)
+# STEP and U3D exporter files - require OpenCASCADE
+if( KICAD_USE_OCC )
+ list( APPEND PCBNEW_SRCS
+ exporters/step/exporter_step.cpp
+ exporters/step/kicad3d_info.cpp
+ exporters/step/step_pcb_model.cpp
+ exporters/step/KI_XCAFDoc_AssemblyGraph.cxx
+ exporters/u3d/bit_stream_writer.cpp
+ exporters/u3d/constants.cpp
+ exporters/u3d/context_manager.cpp
+ exporters/u3d/data_block.cpp
+ exporters/u3d/writer.cpp
+ )
+else()
+ # OCC is disabled - stubs will be added to PCBNEW_SRCS after it is defined (around line 491)
+ message(STATUS "PCBNEW: KICAD_USE_OCC is OFF")
+endif()
+
set( PCBNEW_MICROWAVE_SRCS
microwave/microwave_footprint.cpp
microwave/microwave_inductor.cpp
@@ -459,9 +468,14 @@ set( PCBNEW_CLASS_SRCS
)
-set( PCBNEW_GIT_SRCS
- git/kigit_pcb_merge.cpp
+# Git sources - only include when git support is enabled
+if( KICAD_USE_GIT )
+ set( PCBNEW_GIT_SRCS
+ git/kigit_pcb_merge.cpp
)
+else()
+ set( PCBNEW_GIT_SRCS "" )
+endif()
set( PCBNEW_SRCS
${PCBNEW_MICROWAVE_SRCS}
@@ -480,10 +494,27 @@ set( PCBNEW_SCRIPTING_PYTHON_HELPERS
python/scripting/pcbnew_footprint_wizards.cpp
python/scripting/pcbnew_scripting_helpers.cpp
python/scripting/pcbnew_scripting.cpp
- python/scripting/pcbnew_utils_3d.cpp
python/scripting/pcb_scripting_tool.cpp
)
+# 3D utils require OpenCASCADE
+if( KICAD_USE_OCC )
+ list( APPEND PCBNEW_SCRIPTING_PYTHON_HELPERS
+ python/scripting/pcbnew_utils_3d.cpp
+ )
+else()
+ # Add OCC stub implementations when OCC is disabled
+ set( STUBS_SRC_DIR "${CMAKE_SOURCE_DIR}/../stubs/src" )
+ if( EXISTS "${STUBS_SRC_DIR}/occ_stubs.cpp" )
+ message(STATUS "PCBNEW: Adding occ_stubs.cpp to PCBNEW_SRCS")
+ list( APPEND PCBNEW_SRCS
+ "${STUBS_SRC_DIR}/occ_stubs.cpp"
+ )
+ else()
+ message(WARNING "PCBNEW: occ_stubs.cpp NOT FOUND at ${STUBS_SRC_DIR}/occ_stubs.cpp")
+ endif()
+endif()
+
if( KICAD_IPC_API )
set( PCBNEW_SRCS ${PCBNEW_SRCS}
api/api_handler_pcb.cpp
diff --git a/plugins/3d/CMakeLists.txt b/plugins/3d/CMakeLists.txt
index 9c5e4095ae..ceda7f3aae 100644
--- a/plugins/3d/CMakeLists.txt
+++ b/plugins/3d/CMakeLists.txt
@@ -1,3 +1,7 @@
add_subdirectory( idf )
add_subdirectory( vrml )
-add_subdirectory( oce )
\ No newline at end of file
+
+# OCC plugin requires OpenCASCADE
+if( KICAD_USE_OCC )
+ add_subdirectory( oce )
+endif()
\ No newline at end of file

View file

@ -1,319 +0,0 @@
From d6e946b101a872d1aaea0fedd8393ddaee091277 Mon Sep 17 00:00:00 2001
From: Viktor Vaczi <viktor.vaczi@emergence-engineering.com>
Date: Thu, 27 Nov 2025 08:32:40 +0100
Subject: [PATCH 1/2] Add wxWidgets 3.2.6 submodule and wxBase WASM build
script
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add wxWidgets v3.2.6 as git submodule for wxBase utilities
- Create build-wxbase-wasm.sh script to compile wxBase for Emscripten
- Disable WASM-incompatible features: zlib, expat, xlocale, fswatcher
- Successfully builds libwx_baseu-3.2-Emscripten.a (3.1MB)
This provides wxString, wxFile, wxDateTime and other base utilities
needed for the KiCad board data model without GUI dependencies.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---
.gitmodules | 3 ++
scripts/build-wxbase-wasm.sh | 67 ++++++++++++++++++++++++++++++++++++
wxwidgets | 1 +
3 files changed, 71 insertions(+)
create mode 100755 scripts/build-wxbase-wasm.sh
create mode 160000 wxwidgets
diff --git a/.gitmodules b/.gitmodules
index 55e859a..7e8d09f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "kicad"]
path = kicad
url = git@github.com:VV-EE/kicad-source-mirror.git
+[submodule "wxwidgets"]
+ path = wxwidgets
+ url = https://github.com/wxWidgets/wxWidgets.git
diff --git a/scripts/build-wxbase-wasm.sh b/scripts/build-wxbase-wasm.sh
new file mode 100755
index 0000000..f74293f
--- /dev/null
+++ b/scripts/build-wxbase-wasm.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+# Build wxBase (non-GUI wxWidgets) for WebAssembly
+# This builds only the base utilities needed by KiCad core (wxString, wxFile, etc.)
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets"
+WX_SOURCE="$PROJECT_ROOT/wxwidgets"
+
+echo "=== Building wxBase for WASM ==="
+echo "Project root: $PROJECT_ROOT"
+echo "Build dir: $BUILD_DIR"
+echo "wxWidgets source: $WX_SOURCE"
+
+# Verify we're in the right place
+if [ ! -f "$WX_SOURCE/CMakeLists.txt" ]; then
+ echo "ERROR: wxWidgets source not found at $WX_SOURCE"
+ echo "Make sure the wxwidgets submodule is initialized"
+ exit 1
+fi
+
+# Clean if requested (must be before mkdir to properly reset CMake cache)
+if [ "$1" = "--clean" ]; then
+ echo "Cleaning build directory..."
+ rm -rf "$BUILD_DIR"
+fi
+
+# Create build directory
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+# Configure with emcmake
+# Disable all GUI and most optional features - we only need wxBase utilities
+# Note: zlib/expat "builtin" versions use POSIX file descriptors not available in WASM,
+# so we disable them. We'll use Emscripten's ports if compression is needed.
+emcmake cmake "$WX_SOURCE" \
+ -DwxUSE_GUI=OFF \
+ -DwxBUILD_SHARED=OFF \
+ -DwxBUILD_SAMPLES=OFF \
+ -DwxBUILD_TESTS=OFF \
+ -DwxBUILD_DEMOS=OFF \
+ -DwxBUILD_BENCHMARKS=OFF \
+ -DwxUSE_REGEX=OFF \
+ -DwxUSE_ZLIB=OFF \
+ -DwxUSE_EXPAT=OFF \
+ -DwxUSE_LIBJPEG=OFF \
+ -DwxUSE_LIBPNG=OFF \
+ -DwxUSE_LIBTIFF=OFF \
+ -DwxUSE_WEBREQUEST=OFF \
+ -DwxUSE_SECRETSTORE=OFF \
+ -DwxUSE_LIBSDL=OFF \
+ -DwxUSE_LIBMSPACK=OFF \
+ -DwxUSE_FSWATCHER=OFF \
+ -DwxUSE_XLOCALE=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_POLICY_VERSION_MINIMUM=3.5
+
+# Build
+echo ""
+echo "=== Building ==="
+emmake make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+echo ""
+echo "=== Build complete ==="
+ls -lh "$BUILD_DIR"/lib/*.a 2>/dev/null || echo "Libraries built in $BUILD_DIR"
\ No newline at end of file
diff --git a/wxwidgets b/wxwidgets
new file mode 160000
index 0000000..5ff2532
--- /dev/null
+++ b/wxwidgets
@@ -0,0 +1 @@
+Subproject commit 5ff25322553c1870cf20a2e1ba6f20ed50d9fe9a
--
2.50.1 (Apple Git-155)
From 9305aa5afbbc9a3085c4e73e5469fa7e64616418 Mon Sep 17 00:00:00 2001
From: Viktor Vaczi <viktor.vaczi@emergence-engineering.com>
Date: Thu, 27 Nov 2025 08:39:22 +0100
Subject: [PATCH 2/2] Update core library to use real wxBase instead of wx_shim
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Update CMakeLists.txt to link against wxBase WASM build
- Move config.h to wasm-config/ to avoid conflicts with wx headers
- Add build-core-wasm.sh script for reproducible builds
- Core now compiles with actual wxString, wxFile, etc. from wxWidgets
Libraries built:
- libkicad_core_utils.a (34K)
- libkimath.a (969K)
- libsexpr.a (45K)
- libclipper2.a (169K)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---
core/CMakeLists.txt | 42 +++++++++++++++++---
core/{include => wasm-config}/config.h | 2 +-
scripts/build-core-wasm.sh | 53 ++++++++++++++++++++++++++
3 files changed, 91 insertions(+), 6 deletions(-)
rename core/{include => wasm-config}/config.h (88%)
create mode 100755 scripts/build-core-wasm.sh
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 3b53175..5556c7e 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -14,6 +14,32 @@ endif()
message(STATUS "Using KiCad source at: ${KICAD_SOURCE}")
+# =============================================================================
+# wxWidgets Base (pre-built for WASM)
+# =============================================================================
+set(WXWIDGETS_SOURCE "${CMAKE_SOURCE_DIR}/../wxwidgets" CACHE PATH "Path to wxWidgets source")
+set(WXWIDGETS_BUILD "${CMAKE_SOURCE_DIR}/../build-wasm/wxwidgets" CACHE PATH "Path to wxWidgets WASM build")
+
+# Verify wxWidgets build exists
+if(NOT EXISTS "${WXWIDGETS_BUILD}/lib/libwx_baseu-3.2-Emscripten.a")
+ message(FATAL_ERROR "wxBase WASM build not found. Run scripts/build-wxbase-wasm.sh first")
+endif()
+
+# Create imported wxBase library
+add_library(wxbase STATIC IMPORTED)
+set_target_properties(wxbase PROPERTIES
+ IMPORTED_LOCATION "${WXWIDGETS_BUILD}/lib/libwx_baseu-3.2-Emscripten.a"
+)
+
+# wxWidgets include directories
+set(WX_INCLUDE_DIRS
+ "${WXWIDGETS_BUILD}/lib/wx/include/base-unicode-static-3.2" # Platform setup.h
+ "${WXWIDGETS_SOURCE}/include" # Main wx headers
+)
+
+message(STATUS "Using wxWidgets source at: ${WXWIDGETS_SOURCE}")
+message(STATUS "Using wxWidgets build at: ${WXWIDGETS_BUILD}")
+
# =============================================================================
# Options
# =============================================================================
@@ -45,20 +71,24 @@ target_include_directories(rtree INTERFACE
)
# =============================================================================
-# Core utilities library (minimal version without wx_stl_compat)
+# Core utilities library (with wxBase support)
# =============================================================================
add_library(kicad_core_utils STATIC
${KICAD_SOURCE}/libs/core/base64.cpp
${KICAD_SOURCE}/libs/core/observable.cpp
${KICAD_SOURCE}/libs/core/profile.cpp
- # Skip utf8.cpp and wx_stl_compat.cpp - they have heavy wx dependencies
+ ${KICAD_SOURCE}/libs/core/utf8.cpp
+ ${KICAD_SOURCE}/libs/core/wx_stl_compat.cpp
)
target_include_directories(kicad_core_utils PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/wasm-config # config.h for WASM build
+ ${WX_INCLUDE_DIRS}
${KICAD_SOURCE}/libs/core/include
- ${CMAKE_CURRENT_SOURCE_DIR}/include # For wx_shim.h
)
+target_link_libraries(kicad_core_utils PUBLIC wxbase)
+
# =============================================================================
# S-expression library (parser for .kicad_pcb files)
# =============================================================================
@@ -68,9 +98,11 @@ add_library(sexpr STATIC
)
target_include_directories(sexpr PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/include # wx_shim.h - FIRST!
+ ${CMAKE_CURRENT_SOURCE_DIR}/wasm-config # config.h for WASM build
+ ${WX_INCLUDE_DIRS}
${KICAD_SOURCE}/libs/sexpr/include
${KICAD_SOURCE}/libs/core/include
+ ${KICAD_SOURCE}/include # string_utils.h, etc.
)
target_link_libraries(sexpr PUBLIC kicad_core_utils)
@@ -121,7 +153,7 @@ set(KIMATH_SRCS
add_library(kimath STATIC ${KIMATH_SRCS})
target_include_directories(kimath PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/include # wx_shim.h and wx/ stubs - FIRST!
+ ${WX_INCLUDE_DIRS}
${KICAD_SOURCE}/libs/kimath/include
${KICAD_SOURCE}/libs/core/include
${KICAD_SOURCE}/include # Main KiCad includes (units, etc.)
diff --git a/core/include/config.h b/core/wasm-config/config.h
similarity index 88%
rename from core/include/config.h
rename to core/wasm-config/config.h
index d40b4e1..fdcec80 100644
--- a/core/include/config.h
+++ b/core/wasm-config/config.h
@@ -10,7 +10,7 @@
// Platform detection for timing functions
#if defined(_WIN32)
// Windows uses GetSystemTimeAsFileTime
-#elif defined(__APPLE__) || defined(__linux__) || defined(__unix__)
+#elif defined(__EMSCRIPTEN__) || defined(__APPLE__) || defined(__linux__) || defined(__unix__)
#define HAVE_CLOCK_GETTIME 1
#else
#define HAVE_GETTIMEOFDAY_FUNC 1
diff --git a/scripts/build-core-wasm.sh b/scripts/build-core-wasm.sh
new file mode 100755
index 0000000..8155ad4
--- /dev/null
+++ b/scripts/build-core-wasm.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Build KiCad core library for WebAssembly
+# This builds kimath, sexpr, and core utilities with wxBase
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+BUILD_DIR="$PROJECT_ROOT/build-wasm/core"
+CORE_SOURCE="$PROJECT_ROOT/core"
+
+echo "=== Building KiCad Core for WASM ==="
+echo "Project root: $PROJECT_ROOT"
+echo "Build dir: $BUILD_DIR"
+echo "Core source: $CORE_SOURCE"
+
+# Verify core source exists
+if [ ! -f "$CORE_SOURCE/CMakeLists.txt" ]; then
+ echo "ERROR: Core source not found at $CORE_SOURCE"
+ exit 1
+fi
+
+# Verify wxBase was built
+if [ ! -f "$PROJECT_ROOT/build-wasm/wxwidgets/lib/libwx_baseu-3.2-Emscripten.a" ]; then
+ echo "ERROR: wxBase not built. Run scripts/build-wxbase-wasm.sh first"
+ exit 1
+fi
+
+# Clean if requested (must be before mkdir to properly reset CMake cache)
+if [ "$1" = "--clean" ]; then
+ echo "Cleaning build directory..."
+ rm -rf "$BUILD_DIR"
+fi
+
+# Create build directory
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+# Configure with emcmake
+echo ""
+echo "=== Configuring ==="
+emcmake cmake "$CORE_SOURCE" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DBUILD_TESTS=OFF
+
+# Build
+echo ""
+echo "=== Building ==="
+emmake make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+echo ""
+echo "=== Build complete ==="
+ls -lh "$BUILD_DIR"/*.a 2>/dev/null || echo "Libraries built in $BUILD_DIR"
--
2.50.1 (Apple Git-155)

View file

@ -1,47 +0,0 @@
# wxWidgets WASM Port Patch
Single unified patch that transforms wxWidgets into a WASM-capable build.
## Base Version
- Repository: git@github.com:VV-EE/wxWidgets.git
- Commit: 5ff25322553c1870cf20a2e1ba6f20ed50d9fe9a
- Generated: 2025-11-27 14:10:12 UTC
## Patch Contents
This single patch includes:
- Main wxWidgets modifications (config.sub, configure.in, headers, sources)
- New WASM platform: src/wasm/, include/wx/wasm/
- New build files: build/wasm/
- WASM theme renderer: src/univ/themes/wasm.cpp
- Submodule config.sub updates for emscripten/wasm32 support:
- 3rdparty/pcre/config.sub
- src/expat/expat/conftools/config.sub
- src/jpeg/config.sub
- src/png/config.sub
- src/tiff/config/config.sub
## Apply Instructions
```bash
# Clone wxWidgets fork at the base commit
git clone git@github.com:VV-EE/wxWidgets.git wxwidgets-clean
cd wxwidgets-clean
git checkout 5ff25322553c1870cf20a2e1ba6f20ed50d9fe9a
git submodule update --init --recursive
# Apply the unified patch
patch -p1 < /path/to/patches/wxwidgets-wasm/wxwidgets-wasm.patch
# Build
mkdir build && cd build
emconfigure ../configure --host=emscripten --enable-universal ...
emmake make
```
## Verification
```bash
shasum -a 256 -c checksums.sha256
```

View file

@ -1 +0,0 @@
bc0e065e90bd8ef278532d972e74e975e968e9c3cb0b4f50b989ca4bf2bc48ee wxwidgets-wasm.patch

File diff suppressed because it is too large Load diff

View file

@ -30,7 +30,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
TESTS_DIR="$PROJECT_ROOT/tests"
WASM_APP_DIR="$TESTS_DIR/wasm-app"
WASM_APP_DIR="$TESTS_DIR/apps"
STANDALONE_DIR="$WASM_APP_DIR/standalone"
echo "=== Building wxWidgets WASM Test Applications ==="

View file

@ -107,4 +107,4 @@ All immediate mode tests pass with the color-per-vertex pattern:
- Cyan line strip
- Magenta line loop
See `wasm-app/minimal_test.cpp` for working examples.
See `apps/minimal_test.cpp` for working examples.

View file

@ -13,7 +13,7 @@ Playwright tests for verifying the wxWidgets WASM port.
../scripts/build-wasm-test.sh
```
This builds `wasm-app/minimal_test.{html,js,wasm}` and standalone test apps.
This builds `apps/minimal_test.{html,js,wasm}` and standalone test apps.
## Running Tests
@ -47,7 +47,7 @@ tests/
├── logs/ # Test logs (auto-generated)
├── test-results/ # Screenshots (auto-generated)
├── baseline-screenshots/ # Reference screenshots for comparison
├── wasm-app/ # Built WASM test applications
├── apps/ # Built WASM test applications
│ ├── minimal_test.html # Main test app
│ └── standalone/ # Individual component test apps
└── playwright.config.ts # Playwright configuration
@ -79,10 +79,10 @@ Tests capture screenshots to `test-results/`. Compare against baselines:
## Viewing the App Directly
Start a local server in the wasm-app directory:
Start a local server in the apps directory:
```bash
cd wasm-app
cd apps
npx serve .
```
@ -91,7 +91,7 @@ Then open http://localhost:3000/minimal_test.html in your browser.
Alternative using Python:
```bash
cd wasm-app
cd apps
python3 -m http.server 8000
```
@ -159,7 +159,7 @@ For deeper analysis, use Emscripten's LLVM tools:
LLVM_DIR="/opt/homebrew/Cellar/emscripten/4.0.20/libexec/llvm/bin"
# Check if WASM has DWARF info
$LLVM_DIR/llvm-dwarfdump --debug-info wasm-app/standalone/grid/grid_test.wasm
$LLVM_DIR/llvm-dwarfdump --debug-info apps/standalone/grid/grid_test.wasm
# Disassemble with function names
$LLVM_DIR/llvm-objdump -d grid_test.wasm | head -200

View file

@ -184,7 +184,7 @@ This section maps KiCad's wxWidgets usage to our test coverage.
## Standalone Test Apps
Organized in `wasm-app/standalone/` folders:
Organized in `apps/standalone/` folders:
| App | Status | Tests | KiCad Relevance |
|-----|--------|-------|-----------------|
@ -395,7 +395,7 @@ The `wasmedge_test` app verifies WASM-specific behaviors:
### Build Command
Always use the build script:
```bash
cd tests/wasm-app && ./build-test-apps.sh
cd tests/apps && ./build-test-apps.sh
```
---
@ -410,7 +410,7 @@ The test suite captures screenshots during test runs for visual regression testi
tests/
├── baseline-screenshots/ # Known-good reference screenshots (146+ files)
├── test-results/ # Screenshots from latest test run
└── wasm-app/
└── apps/
└── e2e/ # Playwright test specs
```
@ -467,7 +467,7 @@ cp tests/test-results/dialog-*.png tests/baseline-screenshots/
### Running Tests with Screenshots
```bash
cd tests/wasm-app
cd tests/apps
npm test # Run all tests (saves screenshots to test-results/)
npx playwright test --ui # Interactive mode with screenshot preview
```

View file

@ -60,7 +60,7 @@ BASE_LDFLAGS = -sALLOW_MEMORY_GROWTH -sERROR_ON_UNDEFINED_SYMBOLS=0 \
# GL-specific flags
EM_GL_FLAGS = -sLEGACY_GL_EMULATION -sMAX_WEBGL_VERSION=2
GL_SHIM = ../../lib/gl_immediate_shim.js
GL_SHIM = ../../wasm/shims/gl_immediate_shim.js
# LDFLAGS for GL apps (minimal_test)
LDFLAGS_GL = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) --js-library=$(GL_SHIM) $(WX_LDFLAGS_GL)

View file

@ -6,8 +6,8 @@
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"build-wasm": "cd wasm-app && make -f Makefile.wasm",
"serve": "npx serve wasm-app -p 8080 -c ../serve.json",
"build-wasm": "cd apps && make -f Makefile.wasm",
"serve": "npx serve apps -p 8080 -c ../serve.json",
"setup:kicad": "./scripts/setup-kicad-wasm.sh",
"test:kicad": "playwright test kicad/",
"test:kicad:headed": "playwright test kicad/ --headed"

View file

@ -28,7 +28,7 @@ export default defineConfig({
],
webServer: {
command: 'npx serve wasm-app -p 8080',
command: 'npx serve apps -p 8080',
port: 8080,
reuseExistingServer: true,
},

View file

@ -73,7 +73,7 @@ export default defineConfig({
],
webServer: {
command: `npx serve wasm-app -p ${port} -c ../serve.json`,
command: `npx serve apps -p ${port} -c ../serve.json`,
port: port,
reuseExistingServer: !process.env.CI,
},

View file

@ -8,7 +8,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
KICAD_TEST="$PROJECT_ROOT/tests/wasm-app/kicad"
KICAD_TEST="$PROJECT_ROOT/tests/apps/kicad"
OUTPUT_DIR="$PROJECT_ROOT/output"
mkdir -p "$KICAD_TEST"

View file

@ -1,120 +0,0 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; }
.window {
position: absolute;
pointer-events: none;
z-index: 10;
background-color: black;
overflow: hidden;
width: 0;
height: 0;
}
.window-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
</style>
</head>
<body style="margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden">
<div id="main-window"></div>
<div id="progress-container" class="centered">
<div id="progress-text">Loading...</div>
<div id="progress-bar">
<div id="progress-bar-position">
</div>
</div>
</div>
<div id="window-container"></div>
<script>
var mainWindow = document.getElementById('main-window');
var statusElement = document.getElementById('progress-text');
var progressElement = document.getElementById('progress-bar-position');
var showError = function(msg) {
console.error(msg);
statusElement.innerHTML = msg;
statusElement.style.color = 'red';
};
var createCanvas = function () {
var canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvas.style.display = 'none';
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
canvas.oncontextmenu = function () { event.preventDefault(); }
canvas.addEventListener("webglcontextlost", function (e) {
showError('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
mainWindow.appendChild(canvas);
Module.canvas = canvas;
};
var onRuntimeInitialized = function () {
var canvas = Module.canvas;
canvas.style.display = 'block';
};
var Module = {
preRun: [createCanvas],
postRun: [],
print: function(text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function(text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
if (m) {
text = m[1];
progressElement.style.width = m[2] / m[4] + '%';
progressElement.hidden = false;
} else {
progressElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
},
onRuntimeInitialized: onRuntimeInitialized
};
Module.setStatus('Downloading...');
window.onerror = function() {
showError('Exception thrown, see JavaScript console');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
<script async type="text/javascript" src="minimal_test.js"></script>
</body>
</html>

View file

@ -1,15 +0,0 @@
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1107,7 +1107,9 @@ if( wxWidgets_FIND_STYLE STREQUAL "win32" )
add_compile_definitions( __WXGTK__ )
endif()
elseif( _wx_selected_config )
- string(REGEX MATCH "(msw|qt|gtk|osx)" KICAD_WX_PORT "${_wx_selected_config}")
+ string(REGEX MATCH "(msw|qt|gtk|osx|wasm)" KICAD_WX_PORT "${_wx_selected_config}")
endif()
-if( KICAD_WX_PORT )
+if( KICAD_WX_PORT STREQUAL "wasm" )
+ message( STATUS "Detected wxWidgets port: wasm (WebAssembly)")
+elseif( KICAD_WX_PORT )
message( STATUS "Detected wxWidgets port: ${KICAD_WX_PORT}")

View file

@ -1,15 +0,0 @@
--- a/libs/kiplatform/CMakeLists.txt
+++ b/libs/kiplatform/CMakeLists.txt
@@ -37,6 +37,12 @@ elseif( KICAD_WX_PORT STREQUAL gtk )
message( STATUS "Configuring KiCad not to hide any GTK error messages" )
string( APPEND PLATFORM_COMPILE_DEFS "-DKICAD_SHOW_GTK_MESSAGES" )
endif()
+elseif( KICAD_WX_PORT STREQUAL wasm )
+ # WASM port - uses stub implementations from wasm layer
+ set( PLATFORM_SRCS
+ ${PROJECT_SOURCE_DIR}/../wasm/kiplatform/ui.cpp
+ )
+ message( STATUS "Using WASM kiplatform stub implementation" )
endif()