{f"{hole_dia:.2f}" if hole_dia is not None else ""}
'
f'
{area_count(patterns)}
'
f'
{"Yes" if symmetric else "No"}
'
f'
{"Yes" if has_ground_plane(patterns) else "No"}
'
f'
{format_pitches(tht_pitches(patterns))}
'
f'
{format_pitches(smd_pitches(patterns))}
'
'
')
for path, (w, h, hole_dia, symmetric, patterns) in index.items()
]
table_content = '\n'.join(table_rows)
length_sort = lambda length: float(length.partition(' ')[0])
filter_cols = {
'Width': sorted(set(w for w, h, *rest in index.values())),
'Height': sorted(set(h for w, h, *rest in index.values())),
'Mounting Hole Diameter': sorted(set(dia for w, h, dia, *rest in index.values() if dia)) + ['None'],
'Number of Areas': sorted(set(area_count(patterns) for *_rest, patterns in index.values())),
'Symmetric Top and Bottom?': ['Yes', 'No'],
'Ground Plane?': ['Yes', 'No'],
'THT Pitches': sorted(set(p for *_rest, patterns in index.values() for p in tht_pitches(patterns))) + ['None'],
'SMD Pitches': sorted(set(p for *_rest, patterns in index.values() for p in smd_pitches(patterns))) + ['None'],
}
filter_headers = '\n'.join(f'
{key}
' for key in filter_cols)
key_id = lambda key: key.lower().replace("?", "").replace(" ", "_")
val_id = lambda value: str(value).replace(".", "_")
def format_value(value):
if isinstance(value, str):
return value
elif isinstance(value, int):
return str(value)
elif isinstance(value, bool):
return value and 'Yes' or 'No'
else:
return format_length(value)
filter_cols = {
key: '\n'.join(f'' for value in values)
for key, values in filter_cols.items() }
filter_cols = [f'
{values}
' for key, values in filter_cols.items()]
filter_content = '\n'.join(filter_cols)
filter_js = textwrap.dedent('''
function get_filters(){
let filters = {};
table = document.querySelector('#filter');
for (let filter of table.querySelectorAll('td')) {
selected = [];
for (let checkbox of filter.querySelectorAll('input')) {
if (checkbox.checked) {
selected.push(checkbox.nextElementSibling.textContent.replace(/ mm$/, ''));
}
}
filters[filter.id.replace(/^filter-/, '')] = selected;
}
return filters;
}
filter_indices = {
};
for (const [i, header] of document.querySelectorAll("#listing th").entries()) {
if (header.hasAttribute('data-filter-key')) {
filter_indices[header.attributes['data-filter-key'].value] = i;
}
}
function filter_row(filters, row) {
cols = row.querySelectorAll('td');
for (const [filter_id, values] of Object.entries(filters)) {
if (values.length == 0) {
continue;
}
const row_value = cols[filter_indices[filter_id]].textContent;
if (values.includes("None") && !row_value) {
continue;
}
if (values.includes(row_value)) {
continue;
}
return false;
}
return true;
}
let timeout = undefined;
function apply_filters() {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
const filters = get_filters();
for (let row of document.querySelectorAll("#listing tbody tr")) {
if (filter_row(filters, row)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
}
function refresh_filters() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(apply_filters, 2000);
}
function reset_filters() {
for (let checkbox of document.querySelectorAll("#filter input")) {
checkbox.checked = false;
}
refresh_filters();
}
document.querySelector("#apply").onclick = apply_filters;
document.querySelector("#reset-filters").onclick = reset_filters;
for (let checkbox of document.querySelectorAll("#filter input")) {
checkbox.onchange = refresh_filters;
}
apply_filters();
'''.strip())
style = textwrap.dedent('''
:root {
--gray1: #d0d0d0;
--gray2: #eeeeee;
font-family: sans-serif;
}
table {
border-collapse: collapse;
box-shadow: 0 0 3px gray;
width: 100%;
}
td {
border: 1px solid var(--gray1);
padding: .1em .5em;
}
th {
border: 1px solid var(--gray1);
padding: .5em;
background: linear-gradient(0deg, #e0e0e0, #eeeeee);
}
#listing tr:hover {
background-color: #ffff80;
}
#listing tr td {
text-align: center;
}
#listing tr td:nth-child(4), #listing tr td:nth-child(5) {
text-align: right;
}
#filter {
margin-top: 2em;
}
button {
margin: 2em 0.2em;
padding: .5em 1em;
}
body {
max-width: 80em;
margin: 3em auto;
}
body > div {
width: 100%;
}
'''.strip())
html = textwrap.dedent(f'''
Gerbolyze Protoboard Index
Gerbolyze Protoboard Index
This page contains gerbers for many different types of prototype circuit boards. Everything from different pitches
of THT hole patterns to SMD pad patterns is included in many different sizes and with several mounting hole options.
All downloads on this page are licensed under the Unlicense. This means you can
download what you like and do with it whatever you want. Just note that everything here is provided without any
warranty, so if you send files you find here to a pcb board house and what you get back from them is all wrong,
that's your problem.
All files on this page have been generated automatically from a number of templates using
gerbolyze
(github mirror). If you have any suggestions for additional layouts
or layout options, please feel free to file an issue on
Gerbolyze's issue tracker on github.