Skip to content
DeepReef11Public

About

Python emitter for DIYLC (.diy) circuit layout files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

88 Commits

Folders and files

Repository files navigation

pydiylc

tests python license corpus

A scriptable Python library — and a native Wayland GTK4 viewer — for DIYLC circuit layouts.

  • Write layouts in Python or JSON, save them as .diy (opens in DIYLC).
  • Read existing .diy files back into Python (100% component recognition on the DIYLC community corpus).
  • Preview natively — SVG for browsers, PNG via cairo, or a Wayland-native GTK4 viewer with pan/zoom/click-select and drag-to-move-with-diff.
  • CLI (pydiylc convert/render/info) and an MCP server for LLM clients.

DIYLC itself remains the canonical renderer; pydiylc produces and consumes the same .diy format and adds a scriptable, Java-free, Wayland-friendly workflow on top.

New here? docs/GETTING_STARTED.md is a 15-minute beginner walkthrough — assumes no Python virtual-environment experience and gets you from git clone to an open editor window.

Status

Beta (v0.2.0). 99 component types spanning boards, passives, semiconductors, connectivity, electromechanical hardware, tubes, chassis cutouts, guitar hardware, schematic symbols, shapes, SMD, and labels. The full taxonomy lives in LLMS.txt and the machine-readable schema in catalog.json.

Corpus coverage: 100% component recognition on the DIYLC regression corpus — all 106,599 components across 709 of 711 real community layouts (including legacy v1 <Layout> files) parse cleanly. All 709 readable files round-trip with identical component count + type signature through read → save → re-read; zero drift, zero errors. The 2 unreadable files are physically truncated XML in the upstream corpus, not a parser limitation.

LLM stress matrix: 8 end-to-end stress tests build real layouts through the MCP tool surface — LPB-1 booster, Champ amp schematic, Big Muff fuzz, two-stage tube preamp with negative feedback, two-channel switching amp, Stratocaster-style guitar wiring, tube-amp power supply, and a stompbox enclosure. Each test surfaced and locked in 1–21 real bugs ranging from single-component pin counts to systematic LLM-correction error messages.

CLI

pydiylc convert layout.py layout.diy          # build from Python
pydiylc convert layout.diy layout.json        # extract to JSON
pydiylc render  layout.diy --out preview.svg  # SVG preview
pydiylc info    layout.diy                    # summary + warnings

pydiylc convert accepts .py, .json, or .diy in and writes .diy, .json, or .svg out. Full py → diy → json → diy round-trip is exercised in the test suite. The Python loader recognizes project = Project(...), def build(), def main(), or any top-level Project assignment.

A complete pedal layout — stripboard, transistor, pot, 3PDT bypass, DC and 1/4" jacks — is buildable in code or via JSON (see examples/demo_lpb1_stripboard.py).

.diy files can also be read back into a Project:

p = Project.read("downloaded_pedal.diy")

Tolerates three on-disk formats: modern (4.x, <project> root), v3 XStream (<org.diylc.core.Project> root with nested measures + cross-element references), and legacy v1 (<Layout> root, integer perfboard-hole coordinates, attribute-only elements). v1 layouts get an implicit PerfBoard / VeroBoard / BlankBoard synthesized from Layout.Type. Unknown component types are dropped with a warning instead of failing the parse.

Native SVG preview

pydiylc.svg.render_svg(project) produces a quick-preview SVG of the layout. Component shape, position, and color are right; it is not pixel-identical to DIYLC's renderer. Good for iterating in a browser without launching DIYLC.

from pydiylc.svg import render_svg_file
render_svg_file(p, "preview.svg")  # open in any browser

See examples/demo_render_svg.py for a complete LPB-1 → SVG example.

GTK4 viewer (Wayland-native)

A native viewer ships with the package. It draws via Cairo (no Java, no Swing, no XWayland), reloads when your source file changes, and supports pan/zoom + click-to-select.

pip install -e ".[viewer]"        # installs PyGObject + pycairo
# you also need GTK 4 from the system: e.g. `sudo apt install gir1.2-gtk-4.0`

pydiylc-view examples/layout_for_viewer.py
pydiylc-view examples/layout.json

Keyboard:

  • R — manual reload
  • 0 — reset zoom + pan
  • +/- — zoom in/out
  • ? — keyboard-shortcut help dialog (which-key style)
  • Q / Esc — quit
  • right-drag / middle-drag — pan anywhere (left-drag starting on a component also pans)
  • scroll — zoom
  • click — select component (name shown in header bar)
  • Ctrl+click / Shift+click — toggle / add a component in the multi-selection; drag on empty canvas — rubber-band rectangle select (Shift+drag adds to the existing selection)
  • Ctrl+G — snap the selection to the 0.1 in grid (whole project if nothing is selected); Ctrl+L — align the multi-selection horizontally (y mean). Both work in and out of edit mode.
  • Right-click — context menu: Add component here / Edit value / Duplicate / Rotate / Delete / Send to / Focus. The "Add here" option places the new component at the cursor (snapped to the project grid). The menu opens on release, so a right-drag pans instead of popping the menu.
  • Ctrl + drag — move a component; on release the viewer proposes the source edit (snapped to the grid) and shows a diff with an Apply button. Auto-apply works when the layout is a .py file whose components are built with keyword args (e.g. Resistor(name="R1", x1=1.0, ...)); otherwise the move stays in-memory and the new coordinates are shown for manual editing.

Your .py file should expose either a top-level project = Project(...) or a def build() -> Project. The viewer calls it on every reload.

Edit mode (press T — vim-like "insert mode")

Outside edit mode the viewer is read-only (mouse pan/zoom/click). Press T to open the component side panel and enable the full keyboard editing surface. The status bar shows ✎ EDIT while you're in.

  • Tab / Shift-Tab — next / previous component; once you drill in, walks the focused component's nodes
  • Space — drill into the focused component's nodes / pop back out
  • PgUp / PgDn — page 10 components at a time
  • arrows — move the focused node by one board hole (grid step off-board)
  • Ctrl+arrows — nudge by one grid step; Ctrl+Shift+arrows — fine (1/10)
  • / — fuzzy-search to focus any node (searchable navigation)
  • g — fuzzy-search to send the focused node onto another node
  • a — add a component (auto-wires to focused pin); A — add without wiring
  • v — edit the focused component's primary value (value/text/resistance/tube_type)
  • D — duplicate the focused component (offset 0.3in right, incremented name)
  • dd — delete the focused component (press d twice)
  • u / Ctrl+Z — undo the last edit
  • U / Ctrl+Y — redo the last undone edit
  • R / Shift+R — rotate 90° CW / CCW (cycles orientation for oriented parts, rotates coordinates otherwise)
  • Multi-selection bulk edits — with several components selected (Ctrl/Shift+click or rubber-band), arrows nudge them together, R / Shift+R rotates each one, v edits the shared value field, D duplicates them all (the clones become the new selection), and dd deletes them all
  • Enter — save the working buffer to disk (silent)
  • Ctrl+S — save with the diff-on-save dialog (gated by a "don't show again" preference, persisted in ~/.config/pydiylc/prefs.json)
  • Esc / Q — exit edit mode

Moves and rotations carry what's soldered to them. Move a pad and the wire joined to it comes along — the far end stays anchored, so the lead stretches. Move a board and the components mounted on it ride with it. Nothing else follows.

In particular a wire never drags another wire: both ends are elastic, so two of them sharing a coordinate are merely touching, not joined. Parking a line on top of a bus and taking it off again leaves the bus exactly where it was.

Hold Alt to detach — move or rotate a part out of its joints and leave every wire behind. A node-level move (Tab into a node, then Ctrl+arrow) always moves just that one point, peeling it off its junction; drop it on a pin and it solders there.

Joints are remembered, not guessed. Stack two parts and a wire endpoint at that coordinate touches both of them — but it is soldered to exactly one, and which one is a fact about history, not position. pydiylc records the joint (links.py) while the layout still says it unambiguously and carries it from there, so you can park a part on top of another and move it off again without either one stealing the other's wires. The map is runtime state rebuilt from geometry on load, since the DIYLC file format has nowhere to record it.

See docs/keyboard-tree-editor.md for the full design.

Note: the tree-editor GTK UI is built against a fully unit-tested core (graph, move engine, rotation, navigation — 60+ tests) but the GTK panel and key wiring themselves haven't yet been run on real hardware. Report issues.

MCP server

For LLM clients (Claude Desktop, Claude Code, mcp-cli, etc.):

pip install -e ".[mcp]"
pydiylc-mcp                  # stdio transport

40 tools spanning the full lifecycle:

  • Catalog browsing: list_component_types, describe_component_type, enum_values, list_categories (passive / boards / tube / …), list_components_in_category, list_enums
  • Project lifecycle: create_project, create_project_from_dict, list_projects, delete_project, set_project_metadata
  • Inspection: list_components, get_component, find_components (fuzzy), get_pins (control-point coordinates by name), stats (counts + bounding box)
  • Edits (all undoable, all record history): add_component, add_components (batch), remove_component, move_component, move_node, move_node_to, rotate_component, duplicate_component, set_value, set_field (any attribute, with type coercion), set_fields (atomic multi-field update), add_wire, connect (wire two named components by pin index — picks nearest pair if pins omitted), align (line components up on x or y), snap_to_grid (clean up off-grid coords)
  • Validation: validate (duplicate names, off-canvas geometry, etc.) — run before save
  • History: undo, redo, history — full undo stack per project, mirrors the GUI's session history
  • I/O: save, render_svg, render_png, to_json, read_diy — all rendering tools accept return_content=True for inline content (no disk access needed)

Errors include "did you mean X?" hints for component types, component names, project ids, category slugs, and field names. Mutating tools support dry_run=True to preview without committing.

Resources for the static reference data: pydiylc://catalog and pydiylc://llms.txt.

Prompts for common workflows: build_pedal_layout, modify_existing_layout.

In-memory state is keyed by a project_id argument so a client can manage multiple parallel layouts. Full list with field signatures in LLMS.txt.

AI-friendly by design

  • LLMS.txt — one-screen overview meant to be fed to a coding assistant. Lists every component, the values its enum fields accept, and the value-string parsing rules.
  • catalog.json — machine-readable schema of every component, regenerated with python -m pydiylc.catalog. Fields, types, defaults, enum choices, and Measure units. Use this for codegen, prompt-stuffing, or downstream validation.
  • Every component validates its enum fields at construction with a clear ValueError listing the allowed values.

Install

pip install -e .

Use

from pydiylc import Project, PerfBoard, Resistor, SolderPad, CopperTrace

p = Project(title="Booster", width_cm=10, height_cm=8)
p.add(PerfBoard("Board1", x1=1.0, y1=1.0, x2=3.0, y2=2.5))
p.add(Resistor("R1", x1=1.2, y1=1.2, x2=1.2, y2=1.8, value="10K"))
p.add(SolderPad("P1", x=1.2, y=1.2))
p.add(SolderPad("P2", x=1.2, y=1.8))
p.add(CopperTrace("T1", points=[(1.2, 1.8), (2.0, 1.8)]))
p.save("booster.diy")

Or from JSON (the preferred path for LLMs / external tools):

from pydiylc import Project

Project.from_dict({
    "title": "Booster",
    "components": [
        {"type": "PerfBoard", "name": "Board1", "x1": 1.0, "y1": 1.0, "x2": 3.0, "y2": 2.5},
        {"type": "Resistor", "name": "R1", "x1": 1.2, "y1": 1.2, "x2": 1.2, "y2": 1.8, "value": "10K"},
    ],
}).save("booster.diy")

Open booster.diy in DIYLC.

File format

DIYLC ships file format v4.x. v5.x of the application reuses the same on-disk schema with additive components. pydiylc targets that schema and currently writes <fileVersion>5.7.0</fileVersion> so recent DIYLC builds open without a "created with older version" warning.

Coordinates are in inches by default (DIYLC's grid is 0.1 in).

License

GPL-3.0-or-later, matching upstream DIYLC.

About

Python emitter for DIYLC (.diy) circuit layout files

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages