Repository navigation
Perfboard Studio v0.12.0
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
Deletetakes what is selected out of the design. The sheet's own toolbar carries the
tools;Escapealways 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: withdoc.sheetempty 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 ofArrangeproduces. 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..perffiles are untouched by all of this unless somebody draws on the sheet:sheet,
sheetWiresandsheetNotesare omitted when empty, so all fifteen golden fixtures
serialize to exactly the bytes they did before andDOCUMENT_FORMAT_VERSIONhas 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, andView ▸ 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+1Board,
Ctrl+2Schematic,Ctrl+33D,Ctrl+4Build Guide, thenCtrl+5Parts,Ctrl+6Nets
andCtrl+7DRC / LVS. Saved window layouts from before this are ignored once, on
purpose:restoreStateputs 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 eitherRemove from the Design
orTake off the Boarddepending 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_netshas 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 Sheethands 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 — intooutputs/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
.perfdoes 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.pydecides
what a project contains without a filesystem andui/project.pyhas the disk, the same
split asrecovery.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 callschematic.pymakes 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_boardsanswers 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 fromgeometry.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 noresizeDocks, 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=1turns 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 insrc/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
.perffile 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.bakfirst 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 singlepart.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 wayheat-proximityalready accounts for a body being
somewhere other than its anchor.The second is that nothing had ever asked the parts to line up.
alignmentprices the
pins of one NET, so a board could have every net tidy and still look scattered. The new
lanesterm 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
sheetwritten 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_plannerruns 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'sedgeterm 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 hadPerfStudio.Boardanswering 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 ignoredmirrored, 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.