Skip to content

Perfboard Studio v0.12.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 13:58
· 197 commits to main since this release

Added

  • The schematic is an editor now. Symbols are dragged where you want them, turned a
    quarter at a time and flipped; wires are drawn from pin to pin by hand; a pin can be
    joined to a net by NAME instead, with a label at the pin, which is how every schematic
    ever drawn handles a reset line reaching six parts; and text, boxes, lines and circles can
    be put on the drawing. Parts are dragged onto the sheet out of the Parts panel, and
    Delete takes what is selected out of the design. The sheet's own toolbar carries the
    tools; Escape always comes back to the pointer; the middle button pans in every tool.

    PLAN.md D3 is reversed on purpose, and only half of it. D3 declined to write a
    geometric schematic editor because that is a year of work whose output this tool already
    accepts from KiCad. What it did not weigh is that a tool which can capture a circuit and
    cannot DRAW one sends people back to KiCad anyway. So there are two kinds of sheet and the
    document decides which: with doc.sheet empty the whole drawing is derived exactly as
    before — symbols in cells, wires in the channels between them, no wire able to cross a
    symbol — and that is still what every imported netlist, every fresh document and every
    press of Arrange produces. The moment anything is moved, turned or wired, every symbol
    gets a position of its own in one command, and from then on the sheet is the drawing
    somebody made.

    The netlist is still the one answer to what is connected. A drawn wire carries no net
    id: which net it belongs to is whichever net holds both of its ends, looked up every time.
    That is what makes a wire left over from a connection somebody has since removed stop
    being drawn instead of quietly asserting a join that no longer exists — and it is what
    keeps LVS, the router, the placer, the build guide and the board from having to learn
    anything about geometry.

    Arrow keys nudge the selection one grid square, through the same command a drag ends in,
    so it undoes as one thing. A selection survives the redraw every command causes — turning
    a symbol twice takes one click on it.

    Right-clicking a wire offers Rub Out This Wire, which takes the line off the sheet
    and leaves the pins connected — the net says so by name instead. It is a separate entry
    from Delete Net on purpose: deleting the picture of a join is not the same decision as
    taking a pin off one.

    .perf files are untouched by all of this unless somebody draws on the sheet: sheet,
    sheetWires and sheetNotes are omitted when empty, so all fifteen golden fixtures
    serialize to exactly the bytes they did before and DOCUMENT_FORMAT_VERSION has still
    never moved.

  • Parts are dragged out of the Parts panel onto the board. Picking a part and then
    clicking where it goes is still the way to put down a run of them — five 10k resistors,
    one after another, without going back to the list — and it was the ONLY way, which is the
    problem: dragging is what everybody tries first, and there was nothing to discover that it
    was not supported. The row simply did nothing. The drop goes through the same arming the
    list has always used, so the next free reference, the typed value and the "this hole is
    already taken" warning are all the ones that were already there, and the part stays armed
    afterwards.

  • The board is a panel now, and so is the schematic. Everything else in the window
    could be moved, floated, stacked or closed; the two views this application exists for
    were the one pair nailed down — a central widget with a tab beside it — so "board on the
    left, sheet on the right" was not something anybody could ask for, and wanting both at
    once meant a second window built by a button. They are dock widgets like the 3D view and
    the build guide: side by side is a drag, a window of its own is the title bar's float
    button, stacked is a drag onto the other one, and Qt remembers whatever it was left as.
    The toolbar carries a button per panel beside 3D, and View ▸ Reset the Panel Layout
    puts everything back where a new installation opens it.

    The panel shortcuts are renumbered around the four views of the design: Ctrl+1 Board,
    Ctrl+2 Schematic, Ctrl+3 3D, Ctrl+4 Build Guide, then Ctrl+5 Parts, Ctrl+6 Nets
    and Ctrl+7 DRC / LVS. Saved window layouts from before this are ignored once, on
    purpose: restoreState puts back the docks it knows and leaves the ones it has never
    heard of wherever the constructor put them, which landed the two new panels off the side
    of the window.

  • A tactile switch is drawn as a pushbutton and a relay as a coil and a contact block,
    instead of both being boxes with numbered pins. Both shapes come from what the PACKAGE
    says about itself — a 6 mm switch's four legs are two bonded pairs, and a relay's winding
    is the pair of pins along one side — read off the footprint's own geometry rather than
    assumed. The relay deliberately stops there: which contact is common is a fact about the
    part and not the package, so the contacts stay numbered leads out of a block. A TO-92 and
    a TO-220 stay boxes for the same reason, and there is now a test saying so for each.

  • Right-click the sheet to edit the circuit. A symbol offers its properties, a rename
    that carries every net it is wired into, a duplicate, and either Remove from the Design
    or Take off the Board depending on which of the two it is in; a pin under the pointer
    offers to come off its net, named in full — Disconnect U1.3 from VOUT; a wire offers its
    net's name, its class, the full net dialog and delete. All of it existed already, in a
    tree of twenty net names or behind a dialog with three fields — except taking one pin off
    a net, which had no door on the sheet at all. A symbol somebody has dragged also offers to
    go back to the layout on its own, without undoing an afternoon of other drags.

  • The Part Properties dialog no longer apologises for a bug that was fixed. It said
    renaming a part takes it off the nets that name it and to rename before importing a
    netlist rather than after; rename_in_nets has carried the wiring for some time.

  • A symbol can be dragged to another cell on the sheet, and it stays there. The
    drawing is still derived: what the document carries is a CELL per part, not millimetres,
    so the layout keeps every position and keeps its guarantee that no wire crosses a symbol —
    which is what lets this exist without reversing PLAN.md D3. Positioning one symbol leaves
    every other one exactly where it was, Arrange the Sheet hands the whole lot back to the
    layout in one step, and a document nobody has rearranged serializes to exactly the bytes
    it did before, so all fifteen golden fixtures are untouched and the format version has not
    moved.

  • Drag a symbol onto the board to place that one part. "Place on the Board" moves the
    whole design; until now there was no way to put down a single part from the sheet at all
    — double-clicking a symbol opened its properties. Dropping one whose part is already on
    the board moves it instead, because that is the only thing the gesture can mean. Dragging
    onto the Board tab switches to it and lets the drag carry on, so it works whether the
    sheet is a tab or a window of its own.

  • A fifth, much larger example: atmega328-relay. An ATmega328 with a 7805, a
    crystal, a relay and four connectors — 24 parts, 24 nets, 52 conductors and a 76-step
    guide with 104 checkpoints, on a 9 × 15 cm board. The four existing examples are between
    8 and 12 parts, which is the size at which everything works; this is the size at which
    the interesting rules all fire at once, and it is what the placer, the router and the
    build guide now have to keep working against.

  • geometry.unusable_holes — every hole nothing can be soldered into, which is a
    mounting bore that has taken the pad or an edge-connector finger that is solid copper
    with no bore. One fact with three consumers that were not allowed to disagree and did:
    DRC reported a pin or a run on one as an error while the placer and the router happily
    produced them.

  • A project is a folder, and the board inside it is still an ordinary .perf.
    File ▸ New Project… names the board, asks what it is built on, makes the directory and
    saves it immediately — so it has somewhere to autosave to, somewhere to put a build guide
    and something to put in the recent list from the first minute rather than from whenever
    it was first saved. File ▸ Save Pro&ject (Ctrl+Alt+S) writes the board and rewrites
    everything generated from it — the 1:1 sheets, the schematic as SVG/PDF/PNG, the build
    guide, the bill of materials, the cut list — into outputs/ beside it. File ▸ Open Project… opens the board inside one, and refuses a folder with two boards in it rather
    than guessing which was meant.

    Not an archive, not a manifest, not a new format. The byte-for-byte .perf does not
    move, the document still opens in a text editor and an agent's file tools, and throwing
    the folder away loses nothing but files the tool can write again. project.py decides
    what a project contains without a filesystem and ui/project.py has the disk, the same
    split as recovery.py/ui/autosave.py.

  • A welcome dialog offers the way back to last night's board, over a window that is
    already up rather than instead of one. Everything on it was already reachable; none of
    it was reachable in the two seconds after a launch, which is when it is wanted. Skipped
    entirely on a document that has anything in it — one opened from the command line, or
    handed back by crash recovery — and it has a checkbox that means what it says.

  • The schematic panel opens by itself on a document with nothing in it, and the
    board's empty hint now leads with the circuit rather than with the Parts panel. The
    order this application recommends is the order every EDA tool works in — draw the
    circuit, then put it on a board — and a panel that has to be found in a menu before the
    recommendation makes sense is a recommendation nobody reads. Only on a document with no
    parts, no components, no nets and no copper, so it never overrules a saved layout.

  • "Place on the Board" suggests a stock board size first, while the board is still
    empty and the answer is still free. Every size offered is one a supplier stocks
    (geometry.STANDARD_PRESETS) and every row was measured by laying the circuit out on
    that board for real, so "fits, 18% full" means it has been fitted. The recommendation is
    the smallest board with room left to WIRE it, everything larger is offered, and so is
    the board the user already has — somebody with a 7 × 9 in a drawer is not helped by
    being told to buy the 5 × 7.

  • A first arrangement built from the netlist (placer.arrange), and half the
    annealer's restarts now begin from it. Every restart used to start from the placement
    the document already had, so the search only sampled basins around wherever the parts
    happened to be. The arrangement orders parts by connectivity — the best-connected part
    first, then whichever unplaced part is most tied to those already down — puts the
    connectors on the edges before anything else can take the room, and packs the rest into
    lanes with a hole of board between parts and a clear row between lanes. Ground and
    power nets that reach three or more parts are left out of the connectivity graph, the
    same call schematic.py makes when it draws them as rail glyphs rather than wires: a
    rail touching everything makes everything adjacent and the ordering degenerates to the
    alphabet.

    Doing nothing is now a candidate in its own right, routed alongside the rest, because
    a constructive placement is not descended from the user's board and nothing else would
    stop it being worse than one.

  • placer.suggest_boards answers which stock board a circuit needs, by arranging the
    circuit on each one rather than by adding up footprint areas — a design is limited by
    the shape of its biggest part and the lanes it packs into, not by the sum of its parts.
    The sizes come from geometry.STANDARD_PRESETS, so every suggestion is a board a
    supplier actually stocks, and only the family the user is already on is offered.

Changed

  • The DRC / LVS panel opens under the board rather than across the bottom of the
    window
    , which is a layout fact rather than a preference. A window whose central widget
    is capped to nothing — which is what gives the panels the whole of it — hands every
    leftover pixel of height to the bottom dock area: a findings list with four rows in it
    opened 556 px tall and squeezed the board into 302, and no resizeDocks, size hint or
    size policy would take it back. It also puts the findings directly under the thing they
    are findings about.

  • The schematic panel's tools are a toolbar, not a row of nine push buttons. That row
    gave the panel a minimum width of 1362 px — a panel inherits its minimum from whatever is
    in it — so the window could not be made narrower than the row and nothing in the layout
    could be resized at all.

  • The update strip is a panel across the top of the window rather than a band inside
    the central widget, which is where the board used to be.

  • PERFBOARD_STUDIO_SIMPLE_3D=1 turns off image-based lighting and contact shadows.
    They are the only two things in the 3D view that ask a graphics driver for anything
    unusual, and VTK ends the process rather than raising when one cannot oblige — so there
    is now a way to run without them. Parts keep their materials and their real shapes.

  • Through-hole parts are drawn as the real packages, borrowed from KiCad's model
    library: a TO-220 with its tab and bolt hole, a DIP with gull-wing leads and a moulded
    notch, an electrolytic with its crimped rim and relief cross, a screw terminal with slots
    in its screws, a tactile switch, a relay, a crystal can. Only the shape above the board is
    borrowed — the leads through the holes are still drawn from your board's own thickness,
    the body still takes its colour from this project's table so the 2D and 3D views agree,
    and a resistor still gets its colour code printed on the borrowed barrel. Every part
    without a model draws exactly as it did, which is most of the library and all of the
    custom parts.

    These meshes are the one part of the distribution that is not Apache-2.0. They are
    CC-BY-SA 4.0, derived from KiCad's packages3D library, and they carry their own licence
    and notice in src/perfboard_studio/ui/models/. KiCad's exception means a board you
    design is unaffected; a fork keeps that directory intact. tools/import_kicad_models.py
    rebuilds them from a KiCad installation.

  • The 3D view now shades parts as materials rather than as highlights. Everything on
    the board was lit by a hand-picked specular value and an exponent, which describe a
    highlight and say nothing about what a thing is made of — so a metal can was a grey
    plastic case with a brighter dot on it, and the whole board read as painted card. Each
    material is now a metallic/roughness pair named once: moulded epoxy, glossy nylon,
    ceramic, an electrolytic's printed sleeve, tinned copper, solder, bare steel, a plated
    pin, silkscreen ink, solder mask, raw laminate. Colours reach the shader as albedo rather
    than as sRGB, which is what stops dark parts washing out. Lighting gained a room to
    reflect — generated in code, not shipped, so there is still no asset library and no
    licence inherited — and contact shadows, so a part sits on the board instead of hovering
    over it. Moulded cases have their edges broken, the way a part out of an
    injection tool does, so there is somewhere for a highlight to run; cylinders got enough
    sides to stop showing facets.

  • The sheet folds thin columns together instead of drawing a strip. BFS depth is not
    a constraint, it is a hint — the half of that sentence the layout already acted on split a
    layer too TALL, and a real circuit produces the opposite: a signal chain twelve hops deep
    and one part wide at every hop. The ATmega example came out 462 × 284 mm with 24 symbols
    on it and six of its twelve columns holding a single part; it is 292 × 279 mm now, and
    the six sheets in this repository lost a fifth of their total area between them.

  • The schematic is a view over the whole workspace, not a dock down the side, and it
    detaches into a window of its own so a sheet and a board can sit side by side on a second
    monitor. It was a panel on the right edge — a whole circuit in a third of the window, with
    the other two thirds showing a board nobody was looking at while they drew. Ctrl+5 brings
    it to the front wherever it is.

  • The sheet is drawn on a grid, at the layout's own 2.54 mm pitch, so the squares mean
    something. A schematic without one is a drawing floating in the dark: nothing says how big
    the sheet is, nothing says whether two symbols line up, and a pan has no landmarks.

  • Every example ships on a board a supplier stocks, in the exact advertised size, with
    the finger strips and corner screw holes it is sold with — and on the size this
    application's own board suggestion would have picked for that circuit. They were on grids
    nobody sells (32 × 22, 30 × 20, 24 × 18), which taught two wrong things at once: that
    perfboard comes in whatever size you like, and that the fingers a real board arrives with
    are somebody else's problem.

  • The .perf file type reads the same in both languages. "Perfboard Studio kartı" put
    a Turkish suffix on an English product noun; a file-type description sits beside "PNG
    image" in a file manager and is read as part of the product's name, which is the same
    call the application's own catalogue makes about "Perfboard Studio" itself.

  • Autosave writes the document itself, not only a crash-recovery record
    (File ▸ Autosave to the &File, on by default). This is the one thing the application
    does to somebody's file without being told to, so it is hedged three ways and each one
    is load-bearing: never on a board with no path — that board is exactly what the recovery
    record is for, and it keeps doing that job; the file's last deliberately saved
    contents are copied to a .bak first and only once per save, because a backup refreshed
    every tick would mean that half a minute after a mistake there was nothing left to go
    back to; and if the backup cannot be written, nothing is.

  • "Place on the Board" arranges the design instead of dropping it in a grid. The
    argument for the grid was real — working out where parts go is the one step of this
    application somebody most wants to watch and re-run, and hiding it inside a button hides
    it. What it missed is that the grid is not a neutral starting point: it is ordered by
    REFERENCE, which is to say by nothing at all, and a board laid out by the alphabet is a
    worse first sight of your own circuit than a second of waiting. Ctrl+Shift+A is still
    there to do it again from another seed.

    Still one undo step: the optimised anchors are worked out on a preview document and
    committed as a single part.place. Parts already on the board are locked while it runs
    — putting a design on the board is not the moment to rearrange what somebody has already
    positioned.

  • Auto-place puts connectors on the edge of the board, and lines the rest of the parts
    up into lanes.
    Two things were wrong with the arrangement it produced, and the first
    one made boards that cannot be used: the edge term measured the ANCHOR hole, which is
    pin 1, so a four-pin header lying towards the edge and the same header lying away from
    it scored identically — and on the NE555 fixture the header came out of a full search
    seven holes inside the board, where nothing can be plugged into it. It is now measured
    from the part's courtyard to the outer edge of the SUBSTRATE, which is the physical
    question ("how much board is between this connector and the outside"), and it accounts
    for the printed border the way heat-proximity already accounts for a body being
    somewhere other than its anchor.

    The second is that nothing had ever asked the parts to line up. alignment prices the
    pins of one NET, so a board could have every net tidy and still look scattered. The new
    lanes term counts the distinct rows — or columns, whichever is fewer — that parts
    start on, maintained incrementally beside the collision and strip-conflict counters.
    Its weight is set where the measurement says the curve turns: over ten fixtures and
    three seeds, alignment is essentially FREE up to 1.5 (1030.2 → 1030.9 of routed cost
    for 6.20 → 4.57 lanes) and starts being bought with wire past it.

    Together, over the same ten fixtures: connectors ended 3.89 → 1.82 holes from the
    edge, lanes 5.93 → 4.63, and the routed cost of the boards produced went up by
    1.5%. No new DRC errors and no connection that could not be routed.

Fixed

  • A sheet written while positions were cells is dropped with a warning rather than
    refusing to open the document.
    A cell means nothing without the layout that produced
    it, so it cannot be converted; what is lost is where somebody dragged one symbol, and
    what replaces it is the derived sheet they had before they dragged it.

  • A symbol could not be dragged at all. The sheet documented the gesture in three
    places — drag one to another cell, drag one onto the board to place that one part — and
    the view never recorded a press or called the method that starts the drag. Both halves
    were unreachable from the first commit that described them. The pointer now carries a
    picture of the symbol it is dragging, drawn by the sheet itself so it cannot disagree
    with what is on screen.

  • Autoplace could never move anything on a board that had already been routed. It
    scored each arrangement by routing it — but the board you have keeps its copper, so the
    router found nothing left to do and priced it at almost nothing, while every alternative
    had to be routed from scratch. On the ATmega example with one part shoved into a corner
    the comparison was 119 against 936. Arrangements are now all costed from a bare board,
    which is the only way the question "what would this cost to build" has one answer.

  • "Placement unchanged" now says what it compared — what the board you have would cost
    to build against the best arrangement found — instead of leaving you to guess whether the
    button worked.

  • Moving a part and then autorouting could kill the application. Not reliably — about
    half the time, and only in a real window. _run_planner runs the placer and the router
    on a worker thread while the UI thread pumps Qt, and Python's cyclic garbage collector
    runs on whichever thread happens to trip its threshold: on the worker, where it would
    finalise Qt wrappers the UI thread was painting with at that moment. The collector is now
    held off for the length of a planner run and swept once when it finishes.

  • The placer and the router keep off holes with no pad. It surfaced the moment the
    shipped examples were moved onto the boards suppliers actually sell: a 6 × 8 cm board
    comes with a finger strip down two of its edges, a board edge is exactly where the
    placer's edge term pulls connectors, and the result was five DRC errors on a board the
    tool had just laid out and routed. A pin on one now costs what a collision costs and
    makes the placement illegal; a solder trace will not pass through one, and a route to one
    is refused with a reason rather than searched for.

  • The Windows installer removes the ProgID left behind by the rename. A machine that
    had a build from before "Perfboard Studio" still had PerfStudio.Board answering for
    .perf, so the shell went on calling a board a "PerfStudio kartı" two releases after
    nothing was called that. The installer now deletes it, and the uninstaller takes the
    extension back from it too.

  • A part mounted on the solder side is arranged with its pins where they actually
    are.
    The first cut of the arrangement ignored mirrored, which put a mirrored DIP-8
    a column off the board and another one on top of its neighbour.


Windows - PerfboardStudio_..._Setup.exe. It is unsigned, so SmartScreen will
warn on first run: More info -> Run anyway. A signed build is planned.

Linux - perfboard-studio-...-x86_64.AppImage. chmod +x and run it; it needs
glibc 2.35 or newer. If it will not start, the machine has no FUSE 2: install
libfuse2, or run the file with --appimage-extract-and-run, which unpacks it
instead of mounting it.

macOS - perfboard-studio-...-arm64.dmg, Apple silicon. Drag it to Applications,
then run this before opening it:

xattr -dr com.apple.quarantine /Applications/PerfboardStudio.app

It is signed ad-hoc but not notarized, so Gatekeeper blocks the first launch.
On macOS 15 and later the dialog it raises defaults to Move to Trash, which
deletes the install. Clearing the flag first avoids the dialog entirely. On an
Intel Mac, install from source.

Anywhere with Python 3.12+: pip install -U perfboard-studio, then perfboard-studio.
Same release, no installer and no SmartScreen warning; Qt and VTK are most of a
400 MB download the first time.

Checking your download - SHA256SUMS carries the SHA-256 of all three
files. sha256sum -c SHA256SUMS on Linux, shasum -a 256 -c SHA256SUMS on
macOS, Get-FileHash <file> in PowerShell. Perfboard Studio does this for you when it
fetches an update itself: Help - Check for Updates. It proves the file
arrived intact, not who built it - that second claim needs the code signing
this project has not bought yet.

This is pre-alpha software. Nobody has yet built a real board by following a
generated guide - check every measurement against the part in your hand.