Repository navigation
Perfboard Studio v0.13.0
Added
-
An example that is a real part list:
examples/nano-relay. An Arduino Nano switching
a 12 V relay through a BC547, fed by a 7805, with a push button and an LED -- imported
from its KiCad netlist exactly as File ▸ Import KiCad Netlist does, with no footprint
named for it: the catalog's parts by their values, the rest by their KiCad footprints, the
Nano's and the LED's pins renumbered by name. It is on the README's new Real parts
pictures, andtools/build_examples.pybuilds it through the importer, so it is the
end-to-end check of that path too. The other examples were rebuilt with this release's
placer;tools/screenshots.pynow takes its pictures with a settings store of its own
rather than the user's, which it used to rearrange. -
A T on the sheet. A drawn wire went from a pin to a pin and nowhere else, so a net
reaching four pins was three wires from pin to pin -- nothing like the one run with
branches off it that a person draws. The wire tool now takes a drawn wire as either click:
a pin and then a wire, or a wire and then a pin, and the pin branches off it in a T, with
a dot where it lands and its last run across the wire rather than along it. A T can land
on a T. The circuit is stilldoc.nets: a T joins its pin to the net of the wire it lands
on, exactly as a wire to one of that wire's pins would. It is stored as the wire it is
plus the far end of the wire it lands on (SheetWire.tap, written only when set, so
every sheet drawn before is byte for byte what it was), and where it lands is laid back
onto that wire each time the sheet is drawn -- a symbol drags only the end runs of its
wires, so a T in the middle of one stays where it was put. Rubbing a wire out takes the
T's off it with it, in one undo step; a T whose wire is no longer drawn (its net lost an
end) goes to its pin instead. -
A KiCad netlist's parts arrive as what they are. The importer kept the nets and threw
away everything else a netlist says about a component, and guessed each part from its
reference letter and pin count: a 7805 in a TO-220 ("U1", three pins) became a DIP-8,
every electrolytic a disc capacitor, and every part came with no value. Now
parsers.kicad_partsreads, in order: the value against the catalog ("BC547B", "L7805CV",
"NE555P" -- with the part's pin names and symbol, when the KiCad footprint is the same
package); the KiCad footprint name, which KiCad writes as the package's measurements
(resistors and diodes by span and body, electrolytics and discs by can and lead spacing,
DIPs, headers, IDC, Phoenix and bornier terminals, LEDs, TO-92, TO-220, HC-49); and only
then the old guess. A surface-mount footprint, a relay, a push button and a module are
not mapped and say why. The parts are placed beside what they connect to
(placer.arrange) rather than in a grid in reference order, with their values, as one
undo step. Over MCP,import_netlistreturnssuggested_partsandnotes, and
place_missing=trueplaces them. -
Which leg is which, from the schematic's pin names. A netlist's pin numbers are the
symbol's, and they are not always the part's: KiCad's LED is pin 1 = cathode where this
library's is pin 1 = anode, and a genericQ_NPN_EBCgiven "BC547" as its value is pin 1 =
emitter where a BC547's pin 1 is its collector. Taken at its numbers, either netlist wires
a board for a part that does not exist, and no check can see it, because the netlist is
what every check is measured against. The netlist now carries each pin's name
(pinfunction, which the parser used to drop); where every named pin matches exactly one
pin of the real part, the import renumbers it to the part's own numbers and says so, and
a transistor or diode whose names cannot be settled is reported instead. An LED with no
names in the netlist is read by KiCad's own numbering of its footprint. A module comes
through the same way: KiCad numbers an Arduino Nano, a Pico or a D1 mini down one side
and up the other, the catalog's are numbered across their rows, and every pin is found by
its name -- KiCad's overbar markup off (~{RESET}is RESET), a Pico'sGPIO26_ADC0read
as its silkscreen's GP26, and a name on several pins (two GNDs) matched in order when
every one of them is in the same net, and never otherwise. A part that has no
catalog entry but three or more named pins -- a 2N5088, an IC -- gets the schematic's
names printed beside its pins. -
Labels written on the board. A perfboard has no silkscreen; what a builder writes on
one with a marker -- "MOTOR 24V" beside the terminal that takes it, "CAN ->" where the
chain goes -- is now part of the board: Edit ▸ Add Label…, or Add Label Here… on a
right-click, dragged into place, double-clicked to change, Delete to remove. A label is
on the top or the solder side, turns by 90°, is printed on the 1:1 sheet and in the
guide's pictures, and in 3D is drawn by Qt so a Turkish "ALT YÜZ" or an arrow comes out
whole (VTK's vector text knows ASCII only). Stored asboardNotes, addressed like a
mounting hole -- a hole and millimetres off it -- and omitted when there are none, so no
existing board changes by a byte. Nothing derives anything from a label: DRC, LVS and
the router ignore it. View ▸ Show Labels on the Board hides them; over MCP,
add_board_label(the 53rd tool) andremove_board_feature. A label stays on the board:
a drag stops at its edge, and one whose offset would carry it past the substrate -- on
adding, editing, or narrowing the border under it -- is refused (off-board,
would-strand-label). -
A push button as made:
sw-tactile-6x6andsw-tactile-12x12. A 6 x 6 mm button's
legs stand 6.5 x 4.5 mm apart -- three holes by two on a perfboard, across the gutter on a
breadboard -- and the registry'ssw-tactileis two holes by one, which no such button
is. Which legs are which matters more: the pair straight across is one strip of metal,
the pair down one side is what pressing it joins, andsw-tactilecalls the pair its
pins 1 and 2 stand on one node -- so a KiCad switch guessed onto it was a button pressed
for ever. The new ones are numbered as KiCad'sSW_PUSHfootprints are (pins 1 and 2 the
switched pair, 3 and 4 joined inside to them, named A B A B), have KiCad's models on
their holes, are in the catalog under Switches, and are what a netlist'sSW_PUSH_6mm
andSW_PUSH-12mm, and a guessedSW, now become.sw-tactileis unchanged, for the
boards that have it. -
Real parts by name: the catalog. The library is sixty-one packages, and a package is
not a part -- a TO-92 is a BC547 or a 2N7000 or a 78L05, and which leg is the base is the
fact the board depends on.perfboard_studio.cataloglists 60 parts a perfboard is
actually built from -- bipolar and MOSFET transistors, regulators and references, diodes
and zeners, a PTC, sensors, DIP ICs, push buttons and five dev boards -- each with its
package, what its datasheet calls every lead, its symbol, its value and its reference
letter, and
sourcesaying whose datasheet that is. They head the Parts panel, grouped by kind and
found by what they are ("p-channel", "esp32"); picking one places it with all of that
filled in, dragged or clicked, on the board or the sheet. Nothing in the document refers
back: what lands is an ordinary part, so a board opens the same anywhere. Over MCP,
list_cataloglists them andplace_component/add_parttakepart="bc547". In
Turkish, what each part is and what to check on it are in Turkish, and the filter finds
a part in either language ("regülatör" or "regulator"); the datasheet citations stay as
written. -
A module on header pins:
mod-<cols>x<rows>-p<c>-r<r>-<W>x<D>x<T>-s<seat>[-o<X>x<Y>].
A devkit or a breakout is a board of its own standing on its pins, andbox-described
it as a solid block from the board up. The newmodule-boardarchetype knows its pins,
its own board, how high that sits -- 8.5 mm in a female header, 2.5 mm soldered on its
own pins -- and the height of what is on it. 3D draws the board on its header strips
with its pins through it; the guide says to solder the headers and plug the module in
last. The catalog's ESP32-DevKitC V4 is measured from Espressif's own DXF; the
ESP32-C3-DevKitM-1, Arduino Nano, Raspberry Pi Pico and WEMOS D1 mini from KiCad's
footprints. Custom Part… has a module family that takes the pin names too. -
Pin names on the board, and a switch for them. A part's pin names are printed beside
its pins in 2D and 3D: on a module's own board as its silkscreen is, and on this board
just outside anything else -- a DIP's down either side, a terminal's behind it rather
than across its mouth -- on a dark tag, since white ink across white pad rings cannot
be read. Only names a part declares are printed off a module. View ▸ Show Pin Names
turns them off for a crowded board being placed or routed.
The names are laid out for the whole board, not part by part: on the first real board two
terminals stood side by side and each one's names ran under the other's body. A name now
stops at whatever is in its way -- a body, a terminal's mouth, a screw head, a label, the
board's edge, another name. A part with its pins in one row prints them on its other side
when that is clear; a name short of room is narrowed, down to 60 % of its width; one with
none is left off, and the part's tooltip lists it. Both views ask
bodies.lay_out_pin_names, so they print and leave off the same names, and 3D draws each
one with Qt, as it does a board label, so "GİRİŞ" keeps its dots. -
A screw terminal whose wires go in from above:
screw-terminal-<n>-v. The pluggable
block on a vertical (180°) header — what is sold as a "dik klemens" — pinned exactly like
screw-terminal-<n>, so one swaps for the other without moving a pin. It has no side
mouth, soterminal-entry-blockedandterminal-entry-faces-insay nothing about it and
the placer does not pull it to an edge: it is the right part for the middle of a board,
where the first real board had a side-entry terminal facing its neighbour. The header is
measured from KiCad's Phoenix MSTBVA 2,5/N-G-5,08 model (N × 5.08 + 2.0 mm long, 8.6 mm
deep, 12 mm tall); the plug is not in KiCad's library, so the mated 12 × 22 mm is an
estimate and says so. Offered in Custom Part…; the guide says the wires come from above. -
Screw terminals of every length are drawn as the real part. Only the 2- and 3-way
blocks had a KiCad model; a 4-way or a 6-way — generated ids no single model could be
named for — was a flat box with its openings and screws painted on, and on the first
real board it looked it. A Phoenix MKDS-1,5 block turns out to be a repetition, like a
header: an end plate with the first way, identical middle ways, a last way. Three slices
of the 3-way model, placed one at a pin, match KiCad's own 2- to 16-way models colour by
colour — 38 KB for every length, where the thirteen models would have been 1.5 MB. A
build without them draws the generated block exactly as before. -
A board has a name now, and it is not "untitled". Nothing in the application could
set the document's name: every board the window or the MCP server created was called
"untitled" for good, and that is the title every build guide, exported schematic and
project folder went out with. A board nobody has named takes its file's name the first
time it is saved; File ▸ Rename Board… names it anything else;new_documentand
save_documenton the MCP server take aname. The rename is a command
(document.rename), so it undoes like any other edit — and a name somebody chose is
never overwritten by the first-save rule. -
A custom part's body can sit off its pins.
box-6x1-p1-r1-16x14.5x7-o0x6is a
16 x 14.5 mm breakout whose six pins run along one edge: its body is 6 mm down the rows
from the pins' centre. Until nowbox-centred every body on its pins, so a module with
its header on one edge could only be described as reaching as far past the header on the
side it does not use as on the side it does — a CAN transceiver board on the first real
board laid out with this tool had to be written as 16 x 29 mm to be safe. The offset is
signed, moves the body and the courtyard, turns with the part, and is what DRC, the placer
and both views measure. Zero is not written, so every existing id names the same part and
no fixture moves. The custom-part dialog has two fields for it. -
IDC box headers:
idc-2x3toidc-2x32. A 2xN header in a shroud, numbered exactly
ashdr-2xNis — a straight ribbon cable with both ends keyed joins pin N to pin N --
with the key slot in the long wall beside the pin-1 row, drawn in 2D and cut in 3D, because
the slot is what the cable's red stripe is lined up against. Wurth WR-BHD 61201621621's
outline: the pin span plus 10.2 mm long, 9.0 mm wide, 9.1 mm tall, a 4.5 mm slot. The
guide says where the key goes; the placer wants it at an edge like any connector. It is a
new body archetype,box-header, and a generated id rather than a library part, so the
61-part registry and its golden are untouched. -
A part can say what it is and what it calls its leads. Two optional fields on every
part, placed or not:pinNames(pin number to the datasheet's name for it) andsymbol
(npn,pnp,nmos,pmos,zener,fuse). They are set in the part's properties —
a symbol list and a table with a row per footprint pin — and byadd_part,
update_partandplace_componenton the MCP server. What they change:- The schematic draws the real symbol. A TO-220 declared
pmoswith its leads named
G, D and S is a P-channel MOSFET with its source on top; a TO-92 declarednpnwith
B, C and E is a transistor; a DO-35 declaredzenerhas the bent bar; a disc declared
fuseis a fuse and not a ceramic capacitor. The package numbers are printed on the
leads, because the symbol says G and the board says pin 1. - A module's box reads like its datasheet. A named pin prints its name inside the
body and its number on the lead outside, and the box widens to fit the longest name —
an ESP32 devkit's 38 pins read3V3,EN,IO21instead of 1 to 38. - The guide orients a three-legged part leg by leg — "G (gate) in P1; D (drain) in
Q1; S (source) in R1" instead of "check the package outline against the board" — and
its probes name the pin: "Probe J1 pin 1 (24V-L) and R3 pin 1."
The registry's refusal stands; only the claim has moved. A TO-92 is still a box
until somebody says otherwise, because BC547 and 2N3904 share the outline and disagree
about the base. The pinout is a fact about the PART, and the part is now somewhere it
can be written down by the one person who knows it. A declaration is also not taken on
trust: a transistor is drawn only when exactly its three leads are named B/C/E or
G/D/S, and a zener only when something says which lead is the cathode. Otherwise the
part is drawn as its package draws it, and the sheet's notes say why.Both fields are omitted from the file when empty, so every existing board and all
fifteen golden fixtures serialize to exactly the bytes they did, and
DOCUMENT_FORMAT_VERSIONhas still never moved. Copy and paste carry them. - The schematic draws the real symbol. A TO-220 declared
-
DRC says when a part's body hangs past the edge of the board
(component-overhangs-edge, a warning).component-off-boardonly ever asked about PIN
holes, so a TO-220 on row 1 — all three pins in holes, the body standing a millimetre past
the substrate — was clean, and the placer had put one exactly there on the first real
board laid out with this tool and called the placement legal. The rule measures the real
BODY, not the courtyard: the courtyard is padded by half a pitch, and by that measure
every resistor on the outermost row reaches a full millimetre over the edge. It measures
against the substrate, printed border included, not the hole grid. A quarter of a
millimetre is tolerated, because laid on the edge row a DO-41 reaches 0.08 mm past the
board and a 3 mm LED 0.23 mm; the parts that genuinely hang over clear it by a margin, a
TO-92 by 0.58 mm and a TO-220 by 1.03. A warning rather than an error, because a part can
be meant to overhang — a TO-220 reaching a heatsink off the edge is a real layout.The placer prices the same predicate, so Optimize Placement no longer leaves a body
over the edge that DRC then names, and saysn part(s) brought back over the boardwhen
it clears one. Both read one body (footprints.body_extent, which the 2D and 3D views now
draw from too), one set of edges (geometry.substrate_edges_mm) and one verdict
(geometry.hangs_over_edge); a test sweeps all 61 footprints at every rotation, mirrored
and not, against all four edges and holds the two to the same count. A placement with a
part over the edge is stilllegal— that word means "breaks no DRC error", and this is a
warning — but it costs more than any wire a part could save by standing there.The rule found one on a shipped example:
lpb1-booster's C3, a 6.3 mm electrolytic on
column A, stands 1.2 mm past the left edge. -
A wire on a net that declares a current is measured now. DRC's
current-capacity
rule used to look at solder traces only, so a 14 A motor rail carried by a length of
AWG 24 was a clean board. PLAN.md §5.2 rule 6 always said "the wire's cross-section or
the solder trace's"; the wire half is here, under the same rule id. A gauge is allowed
10 A/mm² of copper — the top of the range DRC already quoted for hookup wire in free air,
and below every figure of the usual chassis-wiring table — and the finding names the
gauge that would do. -
wire-too-thick-for-hole, a new warning: a wire whose copper is wider than the
board's holes. AWG 18 is 1.02 mm and most perfboard is drilled 1.0 mm, so the heavy wire
a heavy current asks for is exactly the one that will not go through the board. The step
in the build guide says the same thing where the builder has the wire in hand, and says
to lap-solder it onto the pad instead. -
The autorouter writes the gauge onto the wires it lays for a net that declares a
current, so the file says what was planned — and when that net is later declared to
carry more,current-capacitynotices the wire has been outgrown. A net that declares
nothing is routed exactly as before, with no gauge stored.One module,
wiregauge.py, answers what a gauge is, what it carries and which one to
cut, and DRC, the router and the build guide all ask it — so the cut list cannot name a
wire the design-rule check would reject. -
A screw terminal knows which way its wires go in. It takes them through one long
face, and it used to be a box centred on its pins: a terminal with its mouth pressed
against a capacitor looked exactly like one that could be wired. The face is MEASURED
from the KiCad Phoenix MKDS mesh the terminals are drawn with — the openings sit on its
-y face, which is this frame's +y — and every consumer reads it from one place
(footprints.wire_entry,footprints.entry_corridor):terminal-entry-blocked, a new warning: another part's body stands within 8 mm of
the mouth, across the terminal's width. One finding per terminal, naming every part in
the way. A mouth facing into the board over clear space in the MIDDLE of a board is
deliberately not reported — a cable can cross a board, and nothing here knows where it
goes.terminal-entry-faces-in, a new warning: a terminal standing ON an edge — its body
within two holes of it — with its mouth facing away from that edge. A terminal goes to
an edge because its wires come from outside; facing in from there, every one of them
doubles back over the board. On the first real board four terminals of six stood on an
edge facing in, over clear board, and nothing said so. Two holes and not one because one
of those four sat a hole and 0.3 mm in. A terminal in a corner may face either edge.- The placer prices exactly those (terminal, obstacle) pairs, and — as a preference no
rule holds — how much board lies between each mouth and the edge it faces, which is
what turns a terminal round. The arrangement puts each terminal's mouth out of the edge
it is placed on. - Choosing between anneals, what DRC warns cannot be built now outranks what it costs
to route, for bodies over the edge, blocked mouths and terminals facing in from an
edge. The routed cost alone
kept whatever board routed cheapest: on the first real board laid out with this tool
the annealer cleared both blocked terminals, that arrangement routed for 812 against
the original's 726, and the original — two terminals nobody could push a wire into —
came back as "nothing cheaper to build". - The guide says which edge each terminal's wires come in from, and both views draw
the openings on the entry face — the generated body too, for the four-way and wider
blocks KiCad ships no mesh for.
No golden fixture has a screw terminal, so no recorded finding, placement or render
moves. The project example's "connectors are on the edge" check now measures from the
part's courtyard, as the placer's ownedgeterm does: measured from the anchor, a
three-pin header lying across the right-hand edge read as two holes in.
Fixed
-
Four borrowed 3D models stood beside their holes. The TO-92 was KiCad's
TO-92_Inline, whose legs are 1.27 mm apart, on a footprint whose legs are 2.54 mm apart:
its middle leg came down between two holes and the case sat a hole to one side. The 3-hole
disc capacitor's legs stood 1.3 mm beside both its holes, the tactile switch's missed by
up to 2 mm, and the relay's model (a CUI SR5) had another pinout thanrelay-spdt
altogether. The TO-92 is now KiCad'sTO-92_Inline_Wide— the same part with its legs bent
out to the grid; the disc capacitor, the tactile switch and the radial electrolytics have
their legs bent onto the holes by the converter, as whoever fits them bends them
(splayintools/import_kicad_models.py); and the relay is drawn as its generated box,
which is its own footprint's size, because no KiCad relay has its on-grid pinout.
tests/test_model_leads.pymeasures every lead of every shipped mesh against its hole —
on the old meshes it fails for exactly these eight. -
Generated screw terminals were drawn with blotches in 3D. A terminal with no borrowed
model — four ways and up — had its screw heads' tops exactly in the block's top face and
its wire openings 0.02 mm proud of the front, and the depth buffer cannot tell either from
coplanar with the view's near plane pulled in: grey and white patches that moved as the
board turned. Both now stand 0.15 mm proud. -
perfboard-studio --lang tr board.perfopens the board. The spelling the README
gives tooktrfor a file to open and exited with "Cannot open tr"; only--lang=tr
worked with a file after it. The value after--langis no longer read as a path. -
The build guide printed AWG 18 for any current from 5 A up — for 5 A and for 50 A
alike, because its table stopped there. Past about 8 A it now names the gauge the current
needs (AWG 16 to AWG 10). Below that nothing it prints has changed. -
The cut list ignored a gauge the document stored. A wire saved as AWG 18 was printed
as whatever the net's current suggested, AWG 24 for a net that declared none. It prints
the stored gauge now, which is also the gauge DRC measures. -
The 3D view, the guide's step images and Save Project are fast again on a machine
without a graphics card — a virtual machine, a remote desktop, an old laptop. Before
its first frame, every 3D view filled two lighting tables at sizes meant for a
photographed environment, and this room is 64 pixels of smooth gradient; the step images
also used a view per face, and each one filled the tables again. Both tables are now
sized for the room and the step images come from one view. On the macOS CI runner, which
draws with Apple's software renderer, one guide's 33 step images went from 820 s to 30 s;
on Linux's software OpenGL the first frame is 1.4 s instead of 12.7. The picture is the
same — within 3 levels in 255 on any pixel. -
A component-side step image after a solder-side one had slightly different lighting
from the same step drawn on its own, up to 23 levels in 255. Drawing the solder side made
the component side's view work its lighting out again. One view for both faces cannot.
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.