Skip to main content

Trace EDA converter: .trace_sch/.trace_pcb <-> KiCad s-expression, plus the trace_parser. Single source of truth for the CLI, backend, and desktop.

Project description

buildwithtrace-converter

What's new in v0.1.6

Added

  • New IPC engine extracted as the canonical internal engine, exposed via the converter API
  • New Altium writer: export/convert designs to Altium format
  • Compliance checks added to conversion pipeline

Full history: release notes.

The Trace EDA converter, extracted into a single installable package so the CLI, backend, and desktop app stop carrying their own copies.

It converts between Trace's intermediate .trace_sch / .trace_pcb format and KiCad's native .kicad_sch / .kicad_pcb s-expression format (both directions), and exposes the trace_parser used to read the intermediate format.

  • Package (PyPI/dist) name: buildwithtrace-converter
  • Import root: buildwithtrace_converter (a self-contained top-level — deliberately NOT a buildwithtrace.* namespace, to avoid colliding with the CLI's buildwithtrace package and the backend's vendored buildwithtrace.altium)
  • GitHub: buildwithtrace/converter (org, canonical) / elcruzo/trace-converter (personal)

Install

# core (pure stdlib, no deps)
pip install buildwithtrace-converter

# with the optional vector-search helper (numpy / onnxruntime / tokenizers)
pip install "buildwithtrace-converter[vectorsearch]"

For local development, install from a checkout:

pip install -e ../converter   # local dev

Usage

from buildwithtrace_converter.eeschema.trace_parser import parse_trace_sch
from buildwithtrace_converter.eeschema.trace_json_to_sexp import trace_json_to_sexp
from buildwithtrace_converter.eeschema.sexp_to_trace_json import sexp_to_trace_json
from buildwithtrace_converter.pcbnew.trace_parser import parse_trace_pcb

The unified file CLI (used by the desktop via subprocess):

python -m buildwithtrace_converter.eeschema.trace IN OUT -f trace_sch -t kicad_sch
python -m buildwithtrace_converter.pcbnew.trace  IN OUT -f trace_pcb -t kicad_pcb

Tolerant reference resolver

common/reference_resolver.py makes the agent-edit merge paths tolerant of near-miss net/pin references on IMPORTED boards (alien vocabularies like +3V3, Net-(U1-Pad4), /power/VBUS_5V, ~{CS}). Pure stdlib (difflib).

  • EXACT match: behavior is bit-identical to before — nothing changes.
  • RESOLVED (unique, high-confidence: normalization equivalence, unique hierarchical leaf, or fuzzy ratio ≥ 0.88 with runner-up < 0.80): the merge auto-binds to the EXISTING board net / lib pin number instead of minting a phantom net (pcbnew pads) or dropping the pin (eeschema), and emits a [NET_WARNING]/[PIN_WARNING] stderr marker teaching the agent the canonical name. The desktop folds these markers into the tool result.
  • SUGGEST / MISS: today's fallback behavior is kept (mint the net / drop the pin); SUGGEST enriches the existing warning with up to 3 "did you mean ...?" candidates.
  • Resolution applies ONLY in the agent-edit direction (trace_json → kicad merge). The kicad → trace_json ingest and the DSL parsers are untouched; net names an ingested file DECLARES (net/label statements) are never resolved, so round-trips of existing boards stay lossless.

The new: escape hatch (pitfall notes)

  • A net written as new:VCC_SENSE in the trace_json ALWAYS force-creates VCC_SENSE verbatim — never resolved, never suggested, no warning. The prefix is stripped before net-table interning and can never leak into the emitted kicad output.
  • A net LITERALLY named new:x on an existing board/schematic is matched EXACTLY first and is never stripped — pathological imports still round-trip (guarded by tests).
  • Marker contract: reuse the existing [NET_WARNING] / [PIN_WARNING] tags ONLY (the desktop C++ executeWrite/executeSearchReplace scan exactly these). Do not invent new marker tags without adding the matching C++ fold blocks.

Tests: tests/test_reference_resolver.py (unit + pcbnew/eeschema merge integration).

June 2026 triage fixes (user-facing surface)

From the 41-user transcript triage (tests/test_user_triage_fixes.py):

  • Power-symbol detection is exact + flag-aware. A symbol is "power" iff its resolved lib definition carries the (power) flag OR its library is named exactly power. Parts from Power_Protection/Power_Management are normal components again (no forced # prefix / BOM exclusion).
  • body_style 0 / unit 0 (EasyEDA imports) are clamped to 1 at ingest and normalized (with a warning) by the .trace_sch parser, so files written by shipped versions re-parse.
  • Component properties round-trip. Ingest passes through MPN, Manufacturer, Datasheet, Description, and all user-defined instance properties (original name/case). Merge mode matches trace-side prop keys case-insensitively, the trace value always wins, and the library default only fills properties genuinely missing from the existing instance (never overwrites it).
  • Sheet-pin direction authority. When a .trace_sch sheet declaration moves a pin between ins/outs, the trace side wins and the kicad pin type is rewritten (input/output). Pins the trace side doesn't mention — and same-bucket types like bidirectional when no annotation is given — are preserved from the existing file.
  • Sheet-pin shape annotation. A pinmap entry takes an optional trailing shape: outs { D=DATA @ 80,55 rot 0 shape bidirectional } (also tri_state, passive, input, output). It is the DSL's way to express the kicad sheet-pin shapes the two ins/outs buckets alone cannot encode, and a valid explicit shape is AUTHORITATIVE in the merge (so output ↔ bidirectional edits finally stick instead of reverting every conversion). The ingest emits the annotation ONLY for non-default shapes (insinput, outsoutput stay bare), so input/output-only files are byte-identical to before, and old files without annotations parse exactly as before. An invalid shape value is warned about and ignored (bucket default applies). Tests: tests/test_sheet_pin_residuals.py.
  • Pin-net label materialization is bounded (one cycle), and the unbounded duplicate-label grower is fixed. The merge/generation path materializes every component pin's net as a label at the pin position; the first conversion of an ingested board therefore ADDS labels the source never had — intentional, one-time normalization (like wire splitting): the next ingest absorbs pin-position labels back into pin nets and the output is a fixpoint from then on. AUDITED BUG (June 2026): symbols with DUPLICATE PIN NUMBERS (e.g. a 2-level terminal block whose pin "1" exists at two physical positions) defeat that absorption — the two directions pick different positions for the same pin number — and the kept standalone label plus the regenerated copy were BOTH emitted: +1 stacked duplicate label per round-trip, forever (qa jumpers.kicad_sch grew 14→16→18→20 over 4 cycles). Fixed by the declared-duplicate dedup in trace_json_to_sexp (a generated pin label identical to a trace-declared label — same name, same rounded position — is skipped). Corpus-verified: all 220 qa schematics now reach a stable fixpoint (219 after one cycle, 1 after two). Tests: tests/test_label_growth_stability.py.
  • Merged-sheet pin labels are emitted again. The ingest absorbs a local label sitting at a sheet-pin position into the sheet's ins/outs net; the reverse direction re-creates that label — previously it was appended to an already-consumed list and silently dropped (round-trips lost the label and agent-added sheet pins were left unconnected). Emission is gated so unaffected round-trips are unchanged: the ingest's pin-name fallback (pin pre-existed, net == pin name, no label in the source) emits nothing, and a label already present at the position is never duplicated. Verified over the full sheet-bearing qa corpus (zero output diff vs the old behavior for ingested boards). Tests: tests/test_sheet_pin_residuals.py.
  • cwiredef ... keep marks a wire exempt from any wire-elimination pass; TRACE_KEEP_ALL_CWIRES=1 (env) disables elimination entirely.
  • Legacy .trace_pcb self-heal. A lexer-level parse failure raises LegacyTraceFileError (message prefixed LEGACY_TRACE_FILE:, a subclass of TraceParseError) so callers can regenerate the DSL from the .kicad_pcb via buildwithtrace_converter.pcbnew.trace.regenerate_trace_pcb. Both lexers also tolerate legacy bare tokens (/VPP/MCLR, ~{RESET}, /GPIO2{slash}SDA1); the writers keep emitting the quoted forms.
  • Router net-integrity check. A routed cwire whose path would electrically touch geometry of a different declared net is rejected (no wires/junction/ split committed) and replaced with net labels, with a [WIRE_OPT] stderr note — a cwire can no longer short two nets through nearby junctions.

Notes

  • Core is pure-stdlib (no runtime dependencies). Only common/local_vector_search.py needs the [vectorsearch] extra, and the embedding model + tokenizer are NOT bundled — pass their paths in.

  • Source of truth. Edit here. Consumers (trace-cli, backend, sdk-python) pip-depend on this package; do not re-fork the sources.

  • The desktop SELF-UPDATES from PyPI. The Trace desktop ships a bundled copy (Trace/trace/, the offline baseline/fallback) AND silently installs newer wheels from PyPI at launch into the product-wide store ~/.trace/converter/<version>/ (current.json pointer; only versions in the SAME major.minor series as the bundled Trace/trace/VERSION are auto-taken, non-yanked + sha256-verified). Releasing a PATCH version to PyPI therefore reaches existing desktop installs without an app release; a series (major.minor) bump ships with the next app release. Yanking a bad release on PyPI is the kill switch — clients skip yanked releases on the next launch. Other Trace clients may READ the store as a fallback; only the desktop writes it.

  • Drift is enforced. scripts/verify_sync.py diffs every shared converter source against the bundled copies and exits non-zero on drift, and enforces the version stamps (pyproject.toml == __init__.__version__ == each copy's VERSION file — the desktop's auto-update compat gate reads that stamp). Run it locally after editing (it defaults to the sibling trace-cli + Trace/trace bundles):

    python3 scripts/verify_sync.py
    

    It is also a CI gate: the converter-sync job in trace-cli/.github/workflows/ci.yml clones this repo and checks the CLI bundle on every PR.

  • License: see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

buildwithtrace_converter-0.1.6.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

buildwithtrace_converter-0.1.6-py3-none-any.whl (1.3 MB view details)

Uploaded Python 3

File details

Details for the file buildwithtrace_converter-0.1.6.tar.gz.

File metadata

  • Download URL: buildwithtrace_converter-0.1.6.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for buildwithtrace_converter-0.1.6.tar.gz
Algorithm Hash digest
SHA256 0ffc435c1b9731ea75a91963517d4f087b80ccf55bc33331aa98cc99cea9fc2e
MD5 d0b2984fca4963f18eaf9b602c2b7bea
BLAKE2b-256 b985118ad907f2ed3d22a3074d5797ef45d5738b20a809c27688ca101135c7a7

See more details on using hashes here.

Provenance

The following attestation bundles were made for buildwithtrace_converter-0.1.6.tar.gz:

Publisher: release.yml on elcruzo/trace-converter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file buildwithtrace_converter-0.1.6-py3-none-any.whl.

File metadata

File hashes

Hashes for buildwithtrace_converter-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 2c43229d46ffae27f06b35e8e695763471e3e621053ec339d18ae2651d0ee966
MD5 69cb1c7fc9003e6602d9530553a27b95
BLAKE2b-256 803fa72c60426f20664ca1a9111f034c134afad9752851c2c11d6d9a4336299a

See more details on using hashes here.

Provenance

The following attestation bundles were made for buildwithtrace_converter-0.1.6-py3-none-any.whl:

Publisher: release.yml on elcruzo/trace-converter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page