From 92faf7c082e32410b49a85452fca3882bbe15603 Mon Sep 17 00:00:00 2001 From: Viktor Vaczi Date: Wed, 26 Nov 2025 17:39:53 +0100 Subject: [PATCH] Add standalone core library with kimath and sexpr (Phase 2 Step 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- core/.gitignore | 3 + core/CMakeLists.txt | 177 ++++++++ core/include/advanced_config.h | 70 ++++ core/include/config.h | 31 ++ core/include/string_utils.h | 19 + core/include/wx/confbase.h | 27 ++ core/include/wx/debug.h | 5 + core/include/wx/ffile.h | 5 + core/include/wx/file.h | 5 + core/include/wx/log.h | 5 + core/include/wx/string.h | 5 + core/include/wx_shim.h | 264 ++++++++++++ core/src/test_kimath.cpp | 57 +++ ...{IMPLEMENTATION_PLAN.md => 00-OVERVIEW.md} | 0 ...BASE_FULL.md => 01-KNOWLEDGE-BASE-FULL.md} | 0 ...KNOWLEDGE_BASE.md => 01-KNOWLEDGE-BASE.md} | 0 docs/02-PHASE2-CORE-EXTRACTION.md | 395 ++++++++++++++++++ 17 files changed, 1068 insertions(+) create mode 100644 core/.gitignore create mode 100644 core/CMakeLists.txt create mode 100644 core/include/advanced_config.h create mode 100644 core/include/config.h create mode 100644 core/include/string_utils.h create mode 100644 core/include/wx/confbase.h create mode 100644 core/include/wx/debug.h create mode 100644 core/include/wx/ffile.h create mode 100644 core/include/wx/file.h create mode 100644 core/include/wx/log.h create mode 100644 core/include/wx/string.h create mode 100644 core/include/wx_shim.h create mode 100644 core/src/test_kimath.cpp rename docs/{IMPLEMENTATION_PLAN.md => 00-OVERVIEW.md} (100%) rename docs/{KNOWLEDGE_BASE_FULL.md => 01-KNOWLEDGE-BASE-FULL.md} (100%) rename docs/{KNOWLEDGE_BASE.md => 01-KNOWLEDGE-BASE.md} (100%) create mode 100644 docs/02-PHASE2-CORE-EXTRACTION.md diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..e2c8737 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,3 @@ +# Build directories +build/ +build-wasm/ diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..3b53175 --- /dev/null +++ b/core/CMakeLists.txt @@ -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 "") diff --git a/core/include/advanced_config.h b/core/include/advanced_config.h new file mode 100644 index 0000000..9a757e9 --- /dev/null +++ b/core/include/advanced_config.h @@ -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 diff --git a/core/include/config.h b/core/include/config.h new file mode 100644 index 0000000..d40b4e1 --- /dev/null +++ b/core/include/config.h @@ -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 diff --git a/core/include/string_utils.h b/core/include/string_utils.h new file mode 100644 index 0000000..cf7d93d --- /dev/null +++ b/core/include/string_utils.h @@ -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 diff --git a/core/include/wx/confbase.h b/core/include/wx/confbase.h new file mode 100644 index 0000000..b85ff16 --- /dev/null +++ b/core/include/wx/confbase.h @@ -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_ diff --git a/core/include/wx/debug.h b/core/include/wx/debug.h new file mode 100644 index 0000000..b086e13 --- /dev/null +++ b/core/include/wx/debug.h @@ -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 diff --git a/core/include/wx/ffile.h b/core/include/wx/ffile.h new file mode 100644 index 0000000..597fa60 --- /dev/null +++ b/core/include/wx/ffile.h @@ -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 diff --git a/core/include/wx/file.h b/core/include/wx/file.h new file mode 100644 index 0000000..9ef7ca7 --- /dev/null +++ b/core/include/wx/file.h @@ -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 diff --git a/core/include/wx/log.h b/core/include/wx/log.h new file mode 100644 index 0000000..78d9f6f --- /dev/null +++ b/core/include/wx/log.h @@ -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 diff --git a/core/include/wx/string.h b/core/include/wx/string.h new file mode 100644 index 0000000..0445b01 --- /dev/null +++ b/core/include/wx/string.h @@ -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 diff --git a/core/include/wx_shim.h b/core/include/wx_shim.h new file mode 100644 index 0000000..6e0dd54 --- /dev/null +++ b/core/include/wx_shim.h @@ -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 +#include +#include +#include +#include + +// ============================================================================= +// 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 +struct FormatArg { + static auto convert(const T& arg) { return arg; } +}; + +template<> +struct FormatArg { + static const char* convert(const std::string& arg) { return arg.c_str(); } +}; + +// Forward declaration for wxString specialization +class wxString; + +template<> +struct FormatArg { + 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 + static wxString Format(const char* fmt, Args... args) { + char buf[2048]; + snprintf(buf, sizeof(buf), fmt, FormatArg::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::convert(const wxString& arg) { + return arg.c_str(); +} + +// Empty string constant +inline const wxString wxEmptyString = wxString(""); + +// ============================================================================= +// File I/O utilities +// ============================================================================= + +#include + +// 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); + } + + 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 diff --git a/core/src/test_kimath.cpp b/core/src/test_kimath.cpp new file mode 100644 index 0000000..058f46a --- /dev/null +++ b/core/src/test_kimath.cpp @@ -0,0 +1,57 @@ +/* + * test_kimath.cpp - Test kimath and sexpr without wxWidgets + */ + +#include +#include +#include +#include +#include +#include + +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 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; +} diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/00-OVERVIEW.md similarity index 100% rename from docs/IMPLEMENTATION_PLAN.md rename to docs/00-OVERVIEW.md diff --git a/docs/KNOWLEDGE_BASE_FULL.md b/docs/01-KNOWLEDGE-BASE-FULL.md similarity index 100% rename from docs/KNOWLEDGE_BASE_FULL.md rename to docs/01-KNOWLEDGE-BASE-FULL.md diff --git a/docs/KNOWLEDGE_BASE.md b/docs/01-KNOWLEDGE-BASE.md similarity index 100% rename from docs/KNOWLEDGE_BASE.md rename to docs/01-KNOWLEDGE-BASE.md diff --git a/docs/02-PHASE2-CORE-EXTRACTION.md b/docs/02-PHASE2-CORE-EXTRACTION.md new file mode 100644 index 0000000..3202368 --- /dev/null +++ b/docs/02-PHASE2-CORE-EXTRACTION.md @@ -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 +#include +#include + +// 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 +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` 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)