Add standalone core library with kimath and sexpr (Phase 2 Step 1)
Create standalone build system for KiCad's core computation libraries that compiles without wxWidgets, targeting both native and WebAssembly. Key additions: - wx_shim.h: ~250 lines replacing wx utilities with standard C++ - wxString class with Format() method - wxFFile for file I/O - wxASSERT, wxCHECK, wxFAIL_MSG macros - wxLog stubs - Stub headers: wx/debug.h, wx/log.h, wx/string.h, wx/file.h, etc. - config.h, advanced_config.h: Platform and triangulation config - CMakeLists.txt: Builds kimath, sexpr, clipper2, rtree Build results: - Native: libkimath.a (1.4MB), libsexpr.a, test passes - WASM: test_kimath.wasm (867KB), runs in Node.js Test verifies: - VECTOR2I, SEG, SHAPE_POLY_SET geometry operations - S-expression parsing of .kicad_pcb format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f1526d8c24
commit
92faf7c082
17 changed files with 1068 additions and 0 deletions
3
core/.gitignore
vendored
Normal file
3
core/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
build-wasm/
|
||||||
177
core/CMakeLists.txt
Normal file
177
core/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(kicad_core VERSION 1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Path to KiCad source (submodule)
|
||||||
|
set(KICAD_SOURCE "${CMAKE_SOURCE_DIR}/../kicad" CACHE PATH "Path to KiCad source")
|
||||||
|
|
||||||
|
# Verify KiCad source exists
|
||||||
|
if(NOT EXISTS "${KICAD_SOURCE}/libs/kimath")
|
||||||
|
message(FATAL_ERROR "KiCad source not found at ${KICAD_SOURCE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Using KiCad source at: ${KICAD_SOURCE}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Options
|
||||||
|
# =============================================================================
|
||||||
|
option(KICAD_CORE_VERBOSE "Enable verbose logging in core library" OFF)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Clipper2 library (for polygon boolean operations)
|
||||||
|
# =============================================================================
|
||||||
|
set(CLIPPER2_DIR "${KICAD_SOURCE}/thirdparty/clipper2")
|
||||||
|
|
||||||
|
add_library(clipper2 STATIC
|
||||||
|
${CLIPPER2_DIR}/Clipper2Lib/src/clipper.engine.cpp
|
||||||
|
${CLIPPER2_DIR}/Clipper2Lib/src/clipper.offset.cpp
|
||||||
|
${CLIPPER2_DIR}/Clipper2Lib/src/clipper.rectclip.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(clipper2 PUBLIC
|
||||||
|
${CLIPPER2_DIR}/Clipper2Lib/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(clipper2 PUBLIC USINGZ)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RTree library (header-only spatial index)
|
||||||
|
# =============================================================================
|
||||||
|
add_library(rtree INTERFACE)
|
||||||
|
target_include_directories(rtree INTERFACE
|
||||||
|
${KICAD_SOURCE}/thirdparty/rtree
|
||||||
|
)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Core utilities library (minimal version without wx_stl_compat)
|
||||||
|
# =============================================================================
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(kicad_core_utils PUBLIC
|
||||||
|
${KICAD_SOURCE}/libs/core/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include # For wx_shim.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# S-expression library (parser for .kicad_pcb files)
|
||||||
|
# =============================================================================
|
||||||
|
add_library(sexpr STATIC
|
||||||
|
${KICAD_SOURCE}/libs/sexpr/sexpr.cpp
|
||||||
|
${KICAD_SOURCE}/libs/sexpr/sexpr_parser.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(sexpr PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include # wx_shim.h - FIRST!
|
||||||
|
${KICAD_SOURCE}/libs/sexpr/include
|
||||||
|
${KICAD_SOURCE}/libs/core/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(sexpr PUBLIC kicad_core_utils)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# KiMath library (geometry and math)
|
||||||
|
# =============================================================================
|
||||||
|
set(KIMATH_SRCS
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/bezier_curves.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/convert_basic_shapes_to_polygon.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/md5_hash.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/transform.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/trigo.cpp
|
||||||
|
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/corner_operations.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/distribute.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/eda_angle.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/ellipse.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/circle.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/convex_hull.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/direction_45.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/geometry_utils.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/half_line.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/intersection.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/line.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/nearest.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/oval.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/roundrect.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/seg.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_arc.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_collisions.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_compound.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_file_io.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_line_chain.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_nearest_points.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_poly_set.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_rect.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_segment.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/vector_utils.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/shape_utils.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/geometry/vertex_set.cpp
|
||||||
|
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/math/vector2.cpp
|
||||||
|
${KICAD_SOURCE}/libs/kimath/src/math/util.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(kimath STATIC ${KIMATH_SRCS})
|
||||||
|
|
||||||
|
target_include_directories(kimath PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include # wx_shim.h and wx/ stubs - FIRST!
|
||||||
|
${KICAD_SOURCE}/libs/kimath/include
|
||||||
|
${KICAD_SOURCE}/libs/core/include
|
||||||
|
${KICAD_SOURCE}/include # Main KiCad includes (units, etc.)
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(kimath PUBLIC
|
||||||
|
kicad_core_utils
|
||||||
|
clipper2
|
||||||
|
rtree
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define KICAD_CORE_ONLY to enable any conditional compilation
|
||||||
|
target_compile_definitions(kimath PRIVATE
|
||||||
|
KICAD_CORE_ONLY=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if(KICAD_CORE_VERBOSE)
|
||||||
|
target_compile_definitions(kimath PRIVATE KICAD_CORE_VERBOSE=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Combined kicad_core library
|
||||||
|
# =============================================================================
|
||||||
|
add_library(kicad_core INTERFACE)
|
||||||
|
target_link_libraries(kicad_core INTERFACE
|
||||||
|
kimath
|
||||||
|
sexpr
|
||||||
|
kicad_core_utils
|
||||||
|
clipper2
|
||||||
|
rtree
|
||||||
|
)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Test executable (optional)
|
||||||
|
# =============================================================================
|
||||||
|
option(BUILD_TESTS "Build test executables" ON)
|
||||||
|
|
||||||
|
if(BUILD_TESTS)
|
||||||
|
add_executable(test_kimath
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/test_kimath.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(test_kimath PRIVATE kimath sexpr)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Summary
|
||||||
|
# =============================================================================
|
||||||
|
message(STATUS "")
|
||||||
|
message(STATUS "kicad_core configuration:")
|
||||||
|
message(STATUS " KiCad source: ${KICAD_SOURCE}")
|
||||||
|
message(STATUS " Verbose logging: ${KICAD_CORE_VERBOSE}")
|
||||||
|
message(STATUS " Build tests: ${BUILD_TESTS}")
|
||||||
|
message(STATUS "")
|
||||||
70
core/include/advanced_config.h
Normal file
70
core/include/advanced_config.h
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* advanced_config.h - Stub for kicad-core standalone build
|
||||||
|
*
|
||||||
|
* Provides default configuration values without wx dependencies
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KICAD_CORE_ADVANCED_CONFIG_H
|
||||||
|
#define KICAD_CORE_ADVANCED_CONFIG_H
|
||||||
|
|
||||||
|
// Stub ADVANCED_CFG class with default values
|
||||||
|
class ADVANCED_CFG {
|
||||||
|
public:
|
||||||
|
// Triangulation settings (defaults from KiCad)
|
||||||
|
int m_TriangulateSimplificationLevel = 50;
|
||||||
|
double m_TriangulateMinimumArea = 1000.0;
|
||||||
|
|
||||||
|
// Other settings that might be accessed
|
||||||
|
bool m_EnableLibWithText = false;
|
||||||
|
bool m_EnableEeschemaPrintCairo = false;
|
||||||
|
int m_UpdateUIEventInterval = 0;
|
||||||
|
double m_DrawArcAccuracy = 0.005;
|
||||||
|
double m_DrawArcCenterMaxAngle = 50.0;
|
||||||
|
int m_MaxUndoItems = 0;
|
||||||
|
double m_MinPlotPenWidth = 0.0;
|
||||||
|
int m_3DRT_BevelExtentFactor = 1;
|
||||||
|
int m_3DRT_BevelHeight_um = 30;
|
||||||
|
bool m_ShowRepairSchematic = false;
|
||||||
|
bool m_ShowPropertiesPanel = true;
|
||||||
|
bool m_ShowEventCounters = false;
|
||||||
|
bool m_AllowManualCanvasScale = false;
|
||||||
|
double m_CanvasScale = 1.0;
|
||||||
|
bool m_CompactSave = false;
|
||||||
|
int m_CoroutineStackSize = 0;
|
||||||
|
int m_DrawBoundingBoxes = 0;
|
||||||
|
bool m_ShowPcbnewExportNetlist = false;
|
||||||
|
bool m_Skip3DModelFileCache = false;
|
||||||
|
bool m_Skip3DModelMemoryCache = false;
|
||||||
|
bool m_HideVersionFromTitle = false;
|
||||||
|
bool m_TraceMasks = false;
|
||||||
|
bool m_ShowRouterDebugGraphics = false;
|
||||||
|
bool m_ExtraZoneDisplayModes = false;
|
||||||
|
double m_MinClrDistance = 0.0;
|
||||||
|
bool m_DebugZoneFiller = false;
|
||||||
|
bool m_DebugPDFWriter = false;
|
||||||
|
int m_HotkeysDumper = 0;
|
||||||
|
bool m_DrawTriangulationOutlines = false;
|
||||||
|
bool m_StrokeTriangulation = false;
|
||||||
|
bool m_ExtraClearance = false;
|
||||||
|
double m_SmallDrillMarkSize = 0.0;
|
||||||
|
int m_HoleijWallThickness = 0;
|
||||||
|
int m_MaxTangentAngleDeviation = 1;
|
||||||
|
int m_MaxClearanceDistanceFactor = 2;
|
||||||
|
int m_ViaijFillMinRatio = 0;
|
||||||
|
bool m_RealtimeConnectivity = true;
|
||||||
|
bool m_EnableCacheFriendlyFracture = true;
|
||||||
|
double m_FontErrorSize = 0.0;
|
||||||
|
double m_OcctVerbosity = 0.0;
|
||||||
|
bool m_Use3DConnexionDriver = false;
|
||||||
|
bool m_IncrementalConnectivity = true;
|
||||||
|
|
||||||
|
static ADVANCED_CFG& GetCfg() {
|
||||||
|
static ADVANCED_CFG instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ADVANCED_CFG() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KICAD_CORE_ADVANCED_CONFIG_H
|
||||||
31
core/include/config.h
Normal file
31
core/include/config.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* config.h - Minimal config for kicad-core standalone build
|
||||||
|
*
|
||||||
|
* This replaces the auto-generated config.h from KiCad's CMake
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KICAD_CORE_CONFIG_H
|
||||||
|
#define KICAD_CORE_CONFIG_H
|
||||||
|
|
||||||
|
// Platform detection for timing functions
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Windows uses GetSystemTimeAsFileTime
|
||||||
|
#elif defined(__APPLE__) || defined(__linux__) || defined(__unix__)
|
||||||
|
#define HAVE_CLOCK_GETTIME 1
|
||||||
|
#else
|
||||||
|
#define HAVE_GETTIMEOFDAY_FUNC 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Version info
|
||||||
|
#define KICAD_MAJOR_VERSION 8
|
||||||
|
#define KICAD_MINOR_VERSION 0
|
||||||
|
#define KICAD_PATCH_VERSION 0
|
||||||
|
#define KICAD_VERSION_FULL "8.0.0-wasm"
|
||||||
|
|
||||||
|
// Feature flags - all disabled for core-only build
|
||||||
|
#define KICAD_USE_CURL 0
|
||||||
|
#define KICAD_USE_GIT 0
|
||||||
|
#define KICAD_USE_OCC 0
|
||||||
|
#define KICAD_USE_NGSPICE 0
|
||||||
|
|
||||||
|
#endif // KICAD_CORE_CONFIG_H
|
||||||
19
core/include/string_utils.h
Normal file
19
core/include/string_utils.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* string_utils.h - Minimal stub for kicad-core
|
||||||
|
*
|
||||||
|
* The sexpr library only uses From_UTF8() which is defined in wx_shim.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KICAD_CORE_STRING_UTILS_H
|
||||||
|
#define KICAD_CORE_STRING_UTILS_H
|
||||||
|
|
||||||
|
#include "wx_shim.h"
|
||||||
|
|
||||||
|
// From_UTF8 is already defined in wx_shim.h
|
||||||
|
|
||||||
|
// Additional string utilities that might be needed
|
||||||
|
inline std::string To_UTF8(const wxString& str) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // KICAD_CORE_STRING_UTILS_H
|
||||||
27
core/include/wx/confbase.h
Normal file
27
core/include/wx/confbase.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Stub wx/confbase.h - minimal config base for kicad-core
|
||||||
|
#ifndef _WX_CONFBASE_H_
|
||||||
|
#define _WX_CONFBASE_H_
|
||||||
|
|
||||||
|
#include "wx_shim.h"
|
||||||
|
|
||||||
|
// Minimal wxConfigBase stub
|
||||||
|
class wxConfigBase {
|
||||||
|
public:
|
||||||
|
virtual ~wxConfigBase() = default;
|
||||||
|
|
||||||
|
// Minimal interface - returns false/empty for everything
|
||||||
|
virtual bool Read(const wxString& key, wxString* str) const { return false; }
|
||||||
|
virtual bool Read(const wxString& key, long* val) const { return false; }
|
||||||
|
virtual bool Read(const wxString& key, double* val) const { return false; }
|
||||||
|
virtual bool Read(const wxString& key, bool* val) const { return false; }
|
||||||
|
|
||||||
|
virtual bool Write(const wxString& key, const wxString& value) { return false; }
|
||||||
|
virtual bool Write(const wxString& key, long value) { return false; }
|
||||||
|
virtual bool Write(const wxString& key, double value) { return false; }
|
||||||
|
virtual bool Write(const wxString& key, bool value) { return false; }
|
||||||
|
|
||||||
|
static wxConfigBase* Get(bool createOnDemand = true) { return nullptr; }
|
||||||
|
static wxConfigBase* Set(wxConfigBase* config) { return nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _WX_CONFBASE_H_
|
||||||
5
core/include/wx/debug.h
Normal file
5
core/include/wx/debug.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Stub wx/debug.h - redirects to wx_shim.h
|
||||||
|
#ifndef _WX_DEBUG_H_
|
||||||
|
#define _WX_DEBUG_H_
|
||||||
|
#include "wx_shim.h"
|
||||||
|
#endif
|
||||||
5
core/include/wx/ffile.h
Normal file
5
core/include/wx/ffile.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Stub wx/ffile.h - redirects to wx_shim.h
|
||||||
|
#ifndef _WX_FFILE_H_
|
||||||
|
#define _WX_FFILE_H_
|
||||||
|
#include "wx_shim.h"
|
||||||
|
#endif
|
||||||
5
core/include/wx/file.h
Normal file
5
core/include/wx/file.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Stub wx/file.h - redirects to wx_shim.h
|
||||||
|
#ifndef _WX_FILE_H_
|
||||||
|
#define _WX_FILE_H_
|
||||||
|
#include "wx_shim.h"
|
||||||
|
#endif
|
||||||
5
core/include/wx/log.h
Normal file
5
core/include/wx/log.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Stub wx/log.h - redirects to wx_shim.h
|
||||||
|
#ifndef _WX_LOG_H_
|
||||||
|
#define _WX_LOG_H_
|
||||||
|
#include "wx_shim.h"
|
||||||
|
#endif
|
||||||
5
core/include/wx/string.h
Normal file
5
core/include/wx/string.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Stub wx/string.h - redirects to wx_shim.h
|
||||||
|
#ifndef _WX_STRING_H_
|
||||||
|
#define _WX_STRING_H_
|
||||||
|
#include "wx_shim.h"
|
||||||
|
#endif
|
||||||
264
core/include/wx_shim.h
Normal file
264
core/include/wx_shim.h
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
* wx_shim.h - Minimal wxWidgets compatibility layer for kicad-core
|
||||||
|
*
|
||||||
|
* Provides drop-in replacements for wx utility macros/functions used in
|
||||||
|
* KiCad's core computation libraries. This is NOT a port of wxWidgets -
|
||||||
|
* just ~100 lines of standard C++ that replaces debug/logging utilities.
|
||||||
|
*
|
||||||
|
* Used macros in kimath:
|
||||||
|
* - wxASSERT, wxASSERT_MSG
|
||||||
|
* - wxCHECK, wxCHECK_MSG, wxCHECK2_MSG, wxCHECK_RET
|
||||||
|
* - wxFAIL_MSG
|
||||||
|
* - wxLogTrace, wxLogDebug, wxLogWarning
|
||||||
|
* - wxString, wxString::Format
|
||||||
|
* - wxT()
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KICAD_WX_SHIM_H
|
||||||
|
#define KICAD_WX_SHIM_H
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Assertions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#define wxASSERT(cond) assert(cond)
|
||||||
|
#define wxASSERT_MSG(cond, msg) assert(cond) // msg ignored in release
|
||||||
|
|
||||||
|
// wxCHECK variants - check condition, return if false
|
||||||
|
#define wxCHECK(cond, ret) do { if(!(cond)) return ret; } while(0)
|
||||||
|
#define wxCHECK_MSG(cond, ret, msg) do { if(!(cond)) return ret; } while(0)
|
||||||
|
#define wxCHECK_RET(cond, msg) do { if(!(cond)) return; } while(0)
|
||||||
|
#define wxCHECK2(cond, op) do { if(!(cond)) { op; } } while(0)
|
||||||
|
#define wxCHECK2_MSG(cond, op, msg) do { if(!(cond)) { op; } } while(0)
|
||||||
|
|
||||||
|
#define wxFAIL_MSG(msg) assert(false) // msg ignored in standalone build
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Logging - mostly no-ops for core library
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Trace logging - typically disabled in release builds anyway
|
||||||
|
#define wxLogTrace(...) ((void)0)
|
||||||
|
#define wxLogDebug(...) ((void)0)
|
||||||
|
|
||||||
|
// Warnings - optionally print to stderr
|
||||||
|
#ifdef KICAD_CORE_VERBOSE
|
||||||
|
#define wxLogWarning(fmt, ...) fprintf(stderr, "Warning: " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define wxLogWarning(...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Variable argument version
|
||||||
|
inline void wxVLogWarning(const char* format, va_list args) {
|
||||||
|
#ifdef KICAD_CORE_VERBOSE
|
||||||
|
vfprintf(stderr, format, args);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
#else
|
||||||
|
(void)format;
|
||||||
|
(void)args;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log level checking - always returns false (logging disabled)
|
||||||
|
namespace wxLog {
|
||||||
|
inline bool IsLevelEnabled(int, const std::string&) { return false; }
|
||||||
|
inline void EnableLogging(bool enable = true) { (void)enable; }
|
||||||
|
inline bool IsLoggingEnabled() { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log level constants
|
||||||
|
constexpr int wxLOG_Debug = 0;
|
||||||
|
|
||||||
|
// Log component macro
|
||||||
|
#ifndef wxLOG_COMPONENT
|
||||||
|
#define wxLOG_COMPONENT "kicad-core"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// String utilities
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// wxChar type
|
||||||
|
using wxChar = char;
|
||||||
|
|
||||||
|
// wxT() macro - pass through (we're always using UTF-8)
|
||||||
|
#define wxT(x) x
|
||||||
|
|
||||||
|
// wxASCII_STR for older wx compatibility
|
||||||
|
#define wxASCII_STR(s) std::string(s)
|
||||||
|
|
||||||
|
// Helper to convert args for snprintf - strings need c_str()
|
||||||
|
template<typename T>
|
||||||
|
struct FormatArg {
|
||||||
|
static auto convert(const T& arg) { return arg; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct FormatArg<std::string> {
|
||||||
|
static const char* convert(const std::string& arg) { return arg.c_str(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declaration for wxString specialization
|
||||||
|
class wxString;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct FormatArg<wxString> {
|
||||||
|
static const char* convert(const wxString& arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
// wxString class with static Format method
|
||||||
|
class wxString : public std::string {
|
||||||
|
public:
|
||||||
|
// Inherit constructors
|
||||||
|
using std::string::string;
|
||||||
|
|
||||||
|
// Additional constructors for compatibility
|
||||||
|
wxString() : std::string() {}
|
||||||
|
wxString(const std::string& s) : std::string(s) {}
|
||||||
|
wxString(const char* s) : std::string(s ? s : "") {}
|
||||||
|
|
||||||
|
// Static Format method - converts string args to c_str()
|
||||||
|
template<typename... Args>
|
||||||
|
static wxString Format(const char* fmt, Args... args) {
|
||||||
|
char buf[2048];
|
||||||
|
snprintf(buf, sizeof(buf), fmt, FormatArg<Args>::convert(args)...);
|
||||||
|
return wxString(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static wxString Format(const wxString& fmt) {
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromAscii static method
|
||||||
|
static wxString FromAscii(const char* s) {
|
||||||
|
return wxString(s ? s : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// c_str() for compatibility (inherited from std::string but explicit)
|
||||||
|
const char* c_str() const { return std::string::c_str(); }
|
||||||
|
|
||||||
|
// RemoveLast - removes last n characters
|
||||||
|
void RemoveLast(size_t n = 1) {
|
||||||
|
if (n <= size()) {
|
||||||
|
resize(size() - n);
|
||||||
|
} else {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate - truncate to given length
|
||||||
|
void Truncate(size_t len) {
|
||||||
|
if (len < size()) {
|
||||||
|
resize(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the wxString FormatArg specialization now that wxString is defined
|
||||||
|
inline const char* FormatArg<wxString>::convert(const wxString& arg) {
|
||||||
|
return arg.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty string constant
|
||||||
|
inline const wxString wxEmptyString = wxString("");
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// File I/O utilities
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
// wxFFile - File wrapper class
|
||||||
|
class wxFFile {
|
||||||
|
FILE* m_fp = nullptr;
|
||||||
|
bool m_close = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
wxFFile() = default;
|
||||||
|
wxFFile(const wxString& filename, const char* mode = "r") {
|
||||||
|
Open(filename, mode);
|
||||||
|
}
|
||||||
|
~wxFFile() { Close(); }
|
||||||
|
|
||||||
|
bool Open(const wxString& filename, const char* mode = "r") {
|
||||||
|
Close();
|
||||||
|
m_fp = fopen(filename.c_str(), mode);
|
||||||
|
m_close = (m_fp != nullptr);
|
||||||
|
return m_fp != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOpened() const { return m_fp != nullptr; }
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
if (m_fp && m_close) {
|
||||||
|
fclose(m_fp);
|
||||||
|
}
|
||||||
|
m_fp = nullptr;
|
||||||
|
m_close = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Read(void* buffer, size_t count) {
|
||||||
|
if (!m_fp) return 0;
|
||||||
|
return fread(buffer, 1, count, m_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Write(const void* buffer, size_t count) {
|
||||||
|
if (!m_fp) return 0;
|
||||||
|
return fwrite(buffer, 1, count, m_fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadAll(wxString* str) {
|
||||||
|
if (!m_fp || !str) return false;
|
||||||
|
// Get file size
|
||||||
|
long pos = ftell(m_fp);
|
||||||
|
fseek(m_fp, 0, SEEK_END);
|
||||||
|
long size = ftell(m_fp);
|
||||||
|
fseek(m_fp, pos, SEEK_SET);
|
||||||
|
// Read content
|
||||||
|
str->resize(size);
|
||||||
|
size_t read = fread(&(*str)[0], 1, size, m_fp);
|
||||||
|
str->resize(read);
|
||||||
|
return read > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Length() const {
|
||||||
|
if (!m_fp) return 0;
|
||||||
|
long pos = ftell(m_fp);
|
||||||
|
fseek(m_fp, 0, SEEK_END);
|
||||||
|
long size = ftell(m_fp);
|
||||||
|
fseek(m_fp, pos, SEEK_SET);
|
||||||
|
return static_cast<size_t>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Eof() const {
|
||||||
|
return m_fp ? feof(m_fp) != 0 : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Seek(long offset, int origin = SEEK_SET) {
|
||||||
|
return m_fp ? fseek(m_fp, offset, origin) == 0 : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long Tell() const {
|
||||||
|
return m_fp ? ftell(m_fp) : -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// From_UTF8 - passthrough for UTF-8 strings
|
||||||
|
inline wxString From_UTF8(const char* s) {
|
||||||
|
return wxString(s ? s : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Replacement for wx headers
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Empty stubs for wx includes - these get included but do nothing
|
||||||
|
// Create empty headers in core/include/wx/
|
||||||
|
|
||||||
|
#endif // KICAD_WX_SHIM_H
|
||||||
57
core/src/test_kimath.cpp
Normal file
57
core/src/test_kimath.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* test_kimath.cpp - Test kimath and sexpr without wxWidgets
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <math/vector2d.h>
|
||||||
|
#include <geometry/seg.h>
|
||||||
|
#include <geometry/shape_poly_set.h>
|
||||||
|
#include <sexpr/sexpr.h>
|
||||||
|
#include <sexpr/sexpr_parser.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Testing kimath standalone build...\n";
|
||||||
|
|
||||||
|
// Test VECTOR2I
|
||||||
|
VECTOR2I p1(0, 0);
|
||||||
|
VECTOR2I p2(100, 100);
|
||||||
|
std::cout << "Created VECTOR2I: (" << p1.x << "," << p1.y << ") and ("
|
||||||
|
<< p2.x << "," << p2.y << ")\n";
|
||||||
|
|
||||||
|
// Test SEG
|
||||||
|
SEG segment(p1, p2);
|
||||||
|
int length = segment.Length();
|
||||||
|
std::cout << "SEG length: " << length << "\n";
|
||||||
|
|
||||||
|
// Test SHAPE_POLY_SET
|
||||||
|
SHAPE_POLY_SET poly;
|
||||||
|
poly.NewOutline();
|
||||||
|
poly.Append(0, 0);
|
||||||
|
poly.Append(1000, 0);
|
||||||
|
poly.Append(1000, 1000);
|
||||||
|
poly.Append(0, 1000);
|
||||||
|
|
||||||
|
std::cout << "Created polygon with " << poly.OutlineCount() << " outline(s)\n";
|
||||||
|
std::cout << "Polygon area: " << poly.Area() << "\n";
|
||||||
|
|
||||||
|
// Test S-expression parser
|
||||||
|
std::cout << "\nTesting S-expression parser...\n";
|
||||||
|
std::string test_sexpr = "(kicad_pcb (version 20231014) (generator \"test\") (layer F.Cu front copper))";
|
||||||
|
|
||||||
|
SEXPR::PARSER parser;
|
||||||
|
std::unique_ptr<SEXPR::SEXPR> parsed = parser.Parse(test_sexpr);
|
||||||
|
|
||||||
|
if (parsed && parsed->IsList()) {
|
||||||
|
const SEXPR::SEXPR_VECTOR* list = parsed->GetChildren();
|
||||||
|
std::cout << "Parsed S-expr with " << list->size() << " elements\n";
|
||||||
|
if (!list->empty() && (*list)[0]->IsSymbol()) {
|
||||||
|
std::cout << "Root element: " << (*list)[0]->GetSymbol() << "\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed to parse S-expression\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\nkimath + sexpr standalone build: SUCCESS!\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
395
docs/02-PHASE2-CORE-EXTRACTION.md
Normal file
395
docs/02-PHASE2-CORE-EXTRACTION.md
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
# 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)
|
||||||
Loading…
Reference in a new issue