3682 lines
218 KiB
HTML
3682 lines
218 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Interactive BOM for KiCAD</title>
|
|
<style type="text/css">
|
|
:root {
|
|
--pcb-edge-color: black;
|
|
--pad-color: #878787;
|
|
--pad-hole-color: #CCCCCC;
|
|
--pad-color-highlight: #D04040;
|
|
--pin1-outline-color: #ffb629;
|
|
--pin1-outline-color-highlight: #b4ff03;
|
|
--silkscreen-edge-color: #aa4;
|
|
--silkscreen-polygon-color: #4aa;
|
|
--silkscreen-text-color: #4aa;
|
|
--fabrication-edge-color: #907651;
|
|
--fabrication-polygon-color: #907651;
|
|
--fabrication-text-color: #a27c24;
|
|
--track-color: #def5f1;
|
|
--track-color-highlight: #D04040;
|
|
--zone-color: #def5f1;
|
|
--zone-color-highlight: #d0404080;
|
|
}
|
|
|
|
html, body {
|
|
margin: 0px;
|
|
height: 100%;
|
|
font-family: Verdana, sans-serif;
|
|
}
|
|
|
|
.dark.topmostdiv {
|
|
--pcb-edge-color: #eee;
|
|
--pad-color: #808080;
|
|
--pin1-outline-color: #ffa800;
|
|
--pin1-outline-color-highlight: #ccff00;
|
|
--track-color: #42524f;
|
|
--zone-color: #42524f;
|
|
background-color: #252c30;
|
|
color: #eee;
|
|
}
|
|
|
|
button {
|
|
background-color: #eee;
|
|
border: 1px solid #888;
|
|
color: black;
|
|
height: 44px;
|
|
width: 44px;
|
|
text-align: center;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
font-size: 14px;
|
|
font-weight: bolder;
|
|
}
|
|
|
|
.dark button {
|
|
/* This will be inverted */
|
|
background-color: #c3b7b5;
|
|
}
|
|
|
|
button.depressed {
|
|
background-color: #0a0;
|
|
color: white;
|
|
}
|
|
|
|
.dark button.depressed {
|
|
/* This will be inverted */
|
|
background-color: #b3b;
|
|
}
|
|
|
|
button:focus {
|
|
outline: 0;
|
|
}
|
|
|
|
button#tb-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.32 290.12h5.82M1.32 291.45h5.82' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 292.5v4.23M.26 292.63H8.2' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='1.35' y='295.73'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
|
|
}
|
|
|
|
button#lr-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.06 290.12H3.7m-2.64 1.33H3.7m-2.64 1.32H3.7m-2.64 1.3H3.7m-2.64 1.33H3.7' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 288.8v7.94m0-4.11h3.96' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='5.11' y='291.96'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
|
|
}
|
|
|
|
button#bom-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)' fill='none' stroke='%23000' stroke-width='.4'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' stroke-linejoin='round'/%3E%3Cpath d='M1.59 290.12h5.29M1.59 291.45h5.33M1.59 292.75h5.33M1.59 294.09h5.33M1.59 295.41h5.33'/%3E%3C/g%3E%3C/svg%3E");
|
|
}
|
|
|
|
button#bom-grouped-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m4 0h5m4 0h3M6.1 22h3m3.9 0h5m4 0h4m-16-8h4m4 0h4'/%3E%3Cpath stroke-linecap='null' d='M5 17.5h22M5 26.6h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
|
|
}
|
|
|
|
button#bom-ungrouped-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m-4 8h3m-3 8h4'/%3E%3Cpath stroke-linecap='null' d='M5 13.5h22m-22 8h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
|
|
}
|
|
|
|
button#bom-netlist-btn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg fill='none' stroke='%23000' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-width='2' d='M6 26l6-6v-8m13.8-6.3l-6 6v8'/%3E%3Ccircle cx='11.8' cy='9.5' r='2.8' stroke-width='2'/%3E%3Ccircle cx='19.8' cy='22.8' r='2.8' stroke-width='2'/%3E%3C/g%3E%3C/svg%3E");
|
|
}
|
|
|
|
button#copy {
|
|
background-image: url("data:image/svg+xml,%3Csvg height='48' viewBox='0 0 48 48' width='48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h48v48h-48z' fill='none'/%3E%3Cpath d='M32 2h-24c-2.21 0-4 1.79-4 4v28h4v-28h24v-4zm6 8h-22c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h22c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-22v-28h22v28z'/%3E%3C/svg%3E");
|
|
background-position: 6px 6px;
|
|
background-repeat: no-repeat;
|
|
background-size: 26px 26px;
|
|
border-radius: 6px;
|
|
height: 40px;
|
|
width: 40px;
|
|
margin: 10px 5px;
|
|
}
|
|
|
|
button#copy:active {
|
|
box-shadow: inset 0px 0px 5px #6c6c6c;
|
|
}
|
|
|
|
textarea.clipboard-temp {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 2em;
|
|
height: 2em;
|
|
padding: 0;
|
|
border: None;
|
|
outline: None;
|
|
box-shadow: None;
|
|
background: transparent;
|
|
}
|
|
|
|
.left-most-button {
|
|
border-right: 0;
|
|
border-top-left-radius: 6px;
|
|
border-bottom-left-radius: 6px;
|
|
}
|
|
|
|
.middle-button {
|
|
border-right: 0;
|
|
}
|
|
|
|
.right-most-button {
|
|
border-top-right-radius: 6px;
|
|
border-bottom-right-radius: 6px;
|
|
}
|
|
|
|
.button-container {
|
|
font-size: 0;
|
|
margin: 10px 10px 10px 0px;
|
|
}
|
|
|
|
.dark .button-container {
|
|
filter: invert(1);
|
|
}
|
|
|
|
.button-container button {
|
|
background-size: 32px 32px;
|
|
background-position: 5px 5px;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
@media print {
|
|
.hideonprint {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
canvas {
|
|
cursor: crosshair;
|
|
}
|
|
|
|
canvas:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.fileinfo {
|
|
width: 100%;
|
|
max-width: 1000px;
|
|
border: none;
|
|
padding: 5px;
|
|
}
|
|
|
|
.fileinfo .title {
|
|
font-size: 20pt;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.fileinfo td {
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
max-width: 1px;
|
|
width: 50%;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.bom {
|
|
border-collapse: collapse;
|
|
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
|
|
font-size: 10pt;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
margin-top: 1px;
|
|
}
|
|
|
|
.bom th, .bom td {
|
|
border: 1px solid black;
|
|
padding: 5px;
|
|
word-wrap: break-word;
|
|
text-align: center;
|
|
position: relative;
|
|
}
|
|
|
|
.dark .bom th, .dark .bom td {
|
|
border: 1px solid #777;
|
|
}
|
|
|
|
.bom th {
|
|
background-color: #CCCCCC;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.dark .bom th {
|
|
background-color: #3b4749;
|
|
}
|
|
|
|
.bom tr.highlighted:nth-child(n) {
|
|
background-color: #cfc;
|
|
}
|
|
|
|
.dark .bom tr.highlighted:nth-child(n) {
|
|
background-color: #226022;
|
|
}
|
|
|
|
.bom tr:nth-child(even) {
|
|
background-color: #f2f2f2;
|
|
}
|
|
|
|
.dark .bom tr:nth-child(even) {
|
|
background-color: #313b40;
|
|
}
|
|
|
|
.bom tr.checked {
|
|
color: #aaa;
|
|
}
|
|
|
|
.dark .bom tr.checked {
|
|
color: #666;
|
|
}
|
|
|
|
.bom tr {
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.bom .numCol {
|
|
width: 25px;
|
|
}
|
|
|
|
.bom .Description {
|
|
width: 10%;
|
|
}
|
|
|
|
.bom .Part {
|
|
width: 10%;
|
|
}
|
|
|
|
.bom .Value {
|
|
width: 15%;
|
|
}
|
|
|
|
.bom .Quantity {
|
|
width: 65px;
|
|
}
|
|
|
|
.bom th .sortmark {
|
|
position: absolute;
|
|
right: 1px;
|
|
top: 1px;
|
|
margin-top: -5px;
|
|
border-width: 5px;
|
|
border-style: solid;
|
|
border-color: transparent transparent #221 transparent;
|
|
transform-origin: 50% 85%;
|
|
transition: opacity 0.2s, transform 0.4s;
|
|
}
|
|
|
|
.dark .bom th .sortmark {
|
|
filter: invert(1);
|
|
}
|
|
|
|
.bom th .sortmark.none {
|
|
opacity: 0;
|
|
}
|
|
|
|
.bom th .sortmark.desc {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.bom th:hover .sortmark.none {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.bom .bom-checkbox {
|
|
width: 30px;
|
|
position: relative;
|
|
user-select: none;
|
|
-moz-user-select: none;
|
|
}
|
|
|
|
.bom .bom-checkbox:before {
|
|
content: "";
|
|
position: absolute;
|
|
border-width: 15px;
|
|
border-style: solid;
|
|
border-color: #51829f transparent transparent transparent;
|
|
visibility: hidden;
|
|
top: -15px;
|
|
}
|
|
|
|
.bom .bom-checkbox:after {
|
|
content: "Double click to set/unset all";
|
|
position: absolute;
|
|
color: white;
|
|
top: -35px;
|
|
left: -26px;
|
|
background: #51829f;
|
|
padding: 5px 15px;
|
|
border-radius: 8px;
|
|
white-space: nowrap;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.bom .bom-checkbox:hover:before, .bom .bom-checkbox:hover:after {
|
|
visibility: visible;
|
|
transition: visibility 0.2s linear 1s;
|
|
}
|
|
|
|
.split {
|
|
-webkit-box-sizing: border-box;
|
|
-moz-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
background-color: inherit;
|
|
}
|
|
|
|
.split.split-horizontal, .gutter.gutter-horizontal {
|
|
height: 100%;
|
|
float: left;
|
|
}
|
|
|
|
.gutter {
|
|
background-color: #ddd;
|
|
background-repeat: no-repeat;
|
|
background-position: 50%;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
.dark .gutter {
|
|
background-color: #777;
|
|
}
|
|
|
|
.gutter.gutter-horizontal {
|
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
|
|
cursor: ew-resize;
|
|
width: 5px;
|
|
}
|
|
|
|
.gutter.gutter-vertical {
|
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
|
|
cursor: ns-resize;
|
|
height: 5px;
|
|
}
|
|
|
|
.searchbox {
|
|
float: left;
|
|
height: 40px;
|
|
margin: 10px 5px;
|
|
padding: 12px 32px;
|
|
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
|
|
font-size: 18px;
|
|
box-sizing: border-box;
|
|
border: 1px solid #888;
|
|
border-radius: 6px;
|
|
outline: none;
|
|
background-color: #eee;
|
|
transition: background-color 0.2s, border 0.2s;
|
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABNklEQVQ4T8XSMUvDQBQH8P/LElFa/AIZHcTBQSz0I/gFstTBRR2KUC4ldDxw7h0Bl3RRUATxi4iiODgoiLNrbQYp5J6cpJJqomkX33Z37/14d/dIa33MzDuYI4johOI4XhyNRteO46zNYjDzAxE1yBZprVeZ+QbAUhXEGJMA2Ox2u4+fQIa0mPmsCgCgJYQ4t7lfgF0opQYAdv9ABkKI/UnOFCClXKjX61cA1osQY8x9kiRNKeV7IWA3oyhaSdP0FkAtjxhj3hzH2RBCPOf3pzqYHCilfAAX+URm9oMguPzeWSGQvUcMYC8rOBJCHBRdqxTo9/vbRHRqi8bj8XKv1xvODbiuW2u32/bvf0SlDv4XYOY7z/Mavu+nM1+BmQ+NMc0wDF/LprP0DbTWW0T00ul0nn4b7Q87+X4Qmfiq2wAAAABJRU5ErkJggg==');
|
|
background-position: 10px 10px;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
.dark .searchbox {
|
|
background-color: #111;
|
|
color: #eee;
|
|
}
|
|
|
|
.searchbox::placeholder {
|
|
color: #ccc;
|
|
}
|
|
|
|
.dark .searchbox::placeholder {
|
|
color: #666;
|
|
}
|
|
|
|
.filter {
|
|
width: calc(60% - 64px);
|
|
}
|
|
|
|
.reflookup {
|
|
width: calc(40% - 10px);
|
|
}
|
|
|
|
input[type=text]:focus {
|
|
background-color: white;
|
|
border: 1px solid #333;
|
|
}
|
|
|
|
.dark input[type=text]:focus {
|
|
background-color: #333;
|
|
border: 1px solid #ccc;
|
|
}
|
|
|
|
mark.highlight {
|
|
background-color: #5050ff;
|
|
color: #fff;
|
|
padding: 2px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.dark mark.highlight {
|
|
background-color: #76a6da;
|
|
color: #111;
|
|
}
|
|
|
|
.menubtn {
|
|
background-color: white;
|
|
border: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 20 20'%3E%3Cpath fill='none' d='M0 0h20v20H0V0z'/%3E%3Cpath d='M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z'/%3E%3C/svg%3E%0A");
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
.statsbtn {
|
|
background-color: white;
|
|
border: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg width='36' height='36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h28v24H4V6zm0 8h28v8H4m9-16v24h10V5.8' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
.iobtn {
|
|
background-color: white;
|
|
border: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='M3 33v-7l6.8-7h16.5l6.7 7v7H3zM3.2 26H33M21 9l5-5.9 5 6h-2.5V15h-5V9H21zm-4.9 0l-5 6-5-6h2.5V3h5v6h2.5z'/%3E%3Cpath fill='none' stroke='%23000' d='M6.1 29.5H10'/%3E%3C/svg%3E");
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
.dark .statsbtn, .dark .savebtn, .dark .menubtn, .dark .iobtn {
|
|
filter: invert(1);
|
|
}
|
|
|
|
.flexbox {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
}
|
|
|
|
.savebtn {
|
|
background-color: #d6d6d6;
|
|
width: auto;
|
|
height: 30px;
|
|
flex-grow: 1;
|
|
margin: 5px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.savebtn:active {
|
|
background-color: #0a0;
|
|
color: white;
|
|
}
|
|
|
|
.dark .savebtn:active {
|
|
/* This will be inverted */
|
|
background-color: #b3b;
|
|
}
|
|
|
|
.stats {
|
|
border-collapse: collapse;
|
|
font-size: 12pt;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
min-width: 450px;
|
|
}
|
|
|
|
.dark .stats td {
|
|
border: 1px solid #bbb;
|
|
}
|
|
|
|
.stats td {
|
|
border: 1px solid black;
|
|
padding: 5px;
|
|
word-wrap: break-word;
|
|
text-align: center;
|
|
position: relative;
|
|
}
|
|
|
|
#checkbox-stats div {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
height: 100%;
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
#checkbox-stats .bar {
|
|
background-color: rgba(28, 251, 0, 0.6);
|
|
}
|
|
|
|
.menu {
|
|
position: relative;
|
|
display: inline-block;
|
|
margin: 10px 10px 10px 0px;
|
|
}
|
|
|
|
.menu-content {
|
|
display: none;
|
|
position: absolute;
|
|
background-color: white;
|
|
right: 0;
|
|
min-width: 300px;
|
|
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
|
z-index: 100;
|
|
padding: 8px;
|
|
}
|
|
|
|
.dark .menu-content {
|
|
background-color: #111;
|
|
}
|
|
|
|
.menu:hover .menu-content {
|
|
display: block;
|
|
}
|
|
|
|
.menu:hover .menubtn, .menu:hover .iobtn, .menu:hover .statsbtn {
|
|
background-color: #eee;
|
|
}
|
|
|
|
.menu-label {
|
|
display: inline-block;
|
|
padding: 8px;
|
|
border: 1px solid #ccc;
|
|
border-top: 0;
|
|
width: calc(100% - 18px);
|
|
}
|
|
|
|
.menu-label-top {
|
|
border-top: 1px solid #ccc;
|
|
}
|
|
|
|
.menu-textbox {
|
|
float: left;
|
|
height: 24px;
|
|
margin: 10px 5px;
|
|
padding: 5px 5px;
|
|
font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
|
|
font-size: 14px;
|
|
box-sizing: border-box;
|
|
border: 1px solid #888;
|
|
border-radius: 4px;
|
|
outline: none;
|
|
background-color: #eee;
|
|
transition: background-color 0.2s, border 0.2s;
|
|
width: calc(100% - 10px);
|
|
}
|
|
|
|
.menu-textbox.invalid, .dark .menu-textbox.invalid {
|
|
color: red;
|
|
}
|
|
|
|
.dark .menu-textbox {
|
|
background-color: #222;
|
|
color: #eee;
|
|
}
|
|
|
|
.radio-container {
|
|
margin: 4px;
|
|
}
|
|
|
|
.topmostdiv {
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: white;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
#top {
|
|
height: 78px;
|
|
border-bottom: 2px solid black;
|
|
}
|
|
|
|
.dark #top {
|
|
border-bottom: 2px solid #ccc;
|
|
}
|
|
|
|
#dbg {
|
|
display: block;
|
|
}
|
|
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #aaa;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #666;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
.slider {
|
|
-webkit-appearance: none;
|
|
width: 100%;
|
|
margin: 3px 0;
|
|
padding: 0;
|
|
outline: none;
|
|
opacity: 0.7;
|
|
-webkit-transition: .2s;
|
|
transition: opacity .2s;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.slider:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
.slider:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.slider::-webkit-slider-runnable-track {
|
|
-webkit-appearance: none;
|
|
width: 100%;
|
|
height: 8px;
|
|
background: #d3d3d3;
|
|
border-radius: 3px;
|
|
border: none;
|
|
}
|
|
|
|
.slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: #0a0;
|
|
cursor: pointer;
|
|
margin-top: -4px;
|
|
}
|
|
|
|
.dark .slider::-webkit-slider-thumb {
|
|
background: #3d3;
|
|
}
|
|
|
|
.slider::-moz-range-thumb {
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: #0a0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.slider::-moz-range-track {
|
|
height: 8px;
|
|
background: #d3d3d3;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.dark .slider::-moz-range-thumb {
|
|
background: #3d3;
|
|
}
|
|
|
|
.slider::-ms-track {
|
|
width: 100%;
|
|
height: 8px;
|
|
border-width: 3px 0;
|
|
background: transparent;
|
|
border-color: transparent;
|
|
color: transparent;
|
|
transition: opacity .2s;
|
|
}
|
|
|
|
.slider::-ms-fill-lower {
|
|
background: #d3d3d3;
|
|
border: none;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.slider::-ms-fill-upper {
|
|
background: #d3d3d3;
|
|
border: none;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.slider::-ms-thumb {
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
background: #0a0;
|
|
cursor: pointer;
|
|
margin: 0;
|
|
}
|
|
|
|
.shameless-plug {
|
|
font-size: 0.8em;
|
|
text-align: center;
|
|
display: block;
|
|
}
|
|
|
|
a {
|
|
color: #0278a4;
|
|
}
|
|
|
|
.dark a {
|
|
color: #00b9fd;
|
|
}
|
|
|
|
#frontcanvas, #backcanvas {
|
|
touch-action: none;
|
|
}
|
|
|
|
|
|
</style>
|
|
<script type="text/javascript" >
|
|
///////////////////////////////////////////////
|
|
/*
|
|
Split.js - v1.3.5
|
|
MIT License
|
|
https://github.com/nathancahill/Split.js
|
|
*/
|
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f<s.minSize&&(s.minSize=f),t>0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}});
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
|
|
// This work is free. You can redistribute it and/or modify it
|
|
// under the terms of the WTFPL, Version 2
|
|
// For more information see LICENSE.txt or http://www.wtfpl.net/
|
|
//
|
|
// For more information, the home page:
|
|
// http://pieroxy.net/blog/pages/lz-string/testing.html
|
|
//
|
|
// LZ-based compression algorithm, version 1.4.4
|
|
var LZString=function(){var o=String.fromCharCode,i={};var n={decompressFromBase64:function(o){return null==o?"":""==o?null:n._decompress(o.length,32,function(n){return function(o,n){if(!i[o]){i[o]={};for(var t=0;t<o.length;t++)i[o][o.charAt(t)]=t}return i[o][n]}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o.charAt(n))})},_decompress:function(i,n,t){var r,e,a,s,p,u,l,f=[],c=4,d=4,h=3,v="",g=[],m={val:t(0),position:n,index:1};for(r=0;r<3;r+=1)f[r]=r;for(a=0,p=Math.pow(2,2),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 2:return""}for(f[3]=l,e=l,g.push(l);;){if(m.index>i)return"";for(a=0,p=Math.pow(2,h),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(l=a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 2:return g.join("")}if(0==c&&(c=Math.pow(2,h),h++),f[l])v=f[l];else{if(l!==d)return null;v=e+e.charAt(0)}g.push(v),f[d++]=e+v.charAt(0),e=v,0==--c&&(c=Math.pow(2,h),h++)}}};return n}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/*!
|
|
* PEP v0.4.3 | https://github.com/jquery/PEP
|
|
* Copyright jQuery Foundation and other contributors | http://jquery.org/license
|
|
*/
|
|
!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.PointerEventsPolyfill=b()}(this,function(){"use strict";function a(a,b){b=b||Object.create(null);var c=document.createEvent("Event");c.initEvent(a,b.bubbles||!1,b.cancelable||!1);
|
|
for(var d,e=2;e<m.length;e++)d=m[e],c[d]=b[d]||n[e];c.buttons=b.buttons||0;
|
|
var f=0;return f=b.pressure&&c.buttons?b.pressure:c.buttons?.5:0,c.x=c.clientX,c.y=c.clientY,c.pointerId=b.pointerId||0,c.width=b.width||0,c.height=b.height||0,c.pressure=f,c.tiltX=b.tiltX||0,c.tiltY=b.tiltY||0,c.twist=b.twist||0,c.tangentialPressure=b.tangentialPressure||0,c.pointerType=b.pointerType||"",c.hwTimestamp=b.hwTimestamp||0,c.isPrimary=b.isPrimary||!1,c}function b(){this.array=[],this.size=0}function c(a,b,c,d){this.addCallback=a.bind(d),this.removeCallback=b.bind(d),this.changedCallback=c.bind(d),A&&(this.observer=new A(this.mutationWatcher.bind(this)))}function d(a){return"body /shadow-deep/ "+e(a)}function e(a){return'[touch-action="'+a+'"]'}function f(a){return"{ -ms-touch-action: "+a+"; touch-action: "+a+"; }"}function g(){if(F){D.forEach(function(a){String(a)===a?(E+=e(a)+f(a)+"\n",G&&(E+=d(a)+f(a)+"\n")):(E+=a.selectors.map(e)+f(a.rule)+"\n",G&&(E+=a.selectors.map(d)+f(a.rule)+"\n"))});var a=document.createElement("style");a.textContent=E,document.head.appendChild(a)}}function h(){if(!window.PointerEvent){if(window.PointerEvent=a,window.navigator.msPointerEnabled){var b=window.navigator.msMaxTouchPoints;Object.defineProperty(window.navigator,"maxTouchPoints",{value:b,enumerable:!0}),u.registerSource("ms",_)}else Object.defineProperty(window.navigator,"maxTouchPoints",{value:0,enumerable:!0}),u.registerSource("mouse",N),void 0!==window.ontouchstart&&u.registerSource("touch",V);u.register(document)}}function i(a){if(!u.pointermap.has(a)){var b=new Error("InvalidPointerId");throw b.name="InvalidPointerId",b}}function j(a){for(var b=a.parentNode;b&&b!==a.ownerDocument;)b=b.parentNode;if(!b){var c=new Error("InvalidStateError");throw c.name="InvalidStateError",c}}function k(a){var b=u.pointermap.get(a);return 0!==b.buttons}function l(){window.Element&&!Element.prototype.setPointerCapture&&Object.defineProperties(Element.prototype,{setPointerCapture:{value:W},releasePointerCapture:{value:X},hasPointerCapture:{value:Y}})}
|
|
var m=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","pageX","pageY"],n=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0],o=window.Map&&window.Map.prototype.forEach,p=o?Map:b;b.prototype={set:function(a,b){return void 0===b?this["delete"](a):(this.has(a)||this.size++,void(this.array[a]=b))},has:function(a){return void 0!==this.array[a]},"delete":function(a){this.has(a)&&(delete this.array[a],this.size--)},get:function(a){return this.array[a]},clear:function(){this.array.length=0,this.size=0},forEach:function(a,b){return this.array.forEach(function(c,d){a.call(b,c,d,this)},this)}};var q=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","buttons","pointerId","width","height","pressure","tiltX","tiltY","pointerType","hwTimestamp","isPrimary","type","target","currentTarget","which","pageX","pageY","timeStamp"],r=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0,0,0,0,0,0,"",0,!1,"",null,null,0,0,0,0],s={pointerover:1,pointerout:1,pointerenter:1,pointerleave:1},t="undefined"!=typeof SVGElementInstance,u={pointermap:new p,eventMap:Object.create(null),captureInfo:Object.create(null),eventSources:Object.create(null),eventSourceList:[],registerSource:function(a,b){var c=b,d=c.events;d&&(d.forEach(function(a){c[a]&&(this.eventMap[a]=c[a].bind(c))},this),this.eventSources[a]=c,this.eventSourceList.push(c))},register:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
|
|
b.register.call(b,a)},unregister:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
|
|
b.unregister.call(b,a)},contains:function(a,b){try{return a.contains(b)}catch(c){return!1}},down:function(a){a.bubbles=!0,this.fireEvent("pointerdown",a)},move:function(a){a.bubbles=!0,this.fireEvent("pointermove",a)},up:function(a){a.bubbles=!0,this.fireEvent("pointerup",a)},enter:function(a){a.bubbles=!1,this.fireEvent("pointerenter",a)},leave:function(a){a.bubbles=!1,this.fireEvent("pointerleave",a)},over:function(a){a.bubbles=!0,this.fireEvent("pointerover",a)},out:function(a){a.bubbles=!0,this.fireEvent("pointerout",a)},cancel:function(a){a.bubbles=!0,this.fireEvent("pointercancel",a)},leaveOut:function(a){this.out(a),this.propagate(a,this.leave,!1)},enterOver:function(a){this.over(a),this.propagate(a,this.enter,!0)},eventHandler:function(a){if(!a._handledByPE){var b=a.type,c=this.eventMap&&this.eventMap[b];c&&c(a),a._handledByPE=!0}},listen:function(a,b){b.forEach(function(b){this.addEvent(a,b)},this)},unlisten:function(a,b){b.forEach(function(b){this.removeEvent(a,b)},this)},addEvent:function(a,b){a.addEventListener(b,this.boundHandler)},removeEvent:function(a,b){a.removeEventListener(b,this.boundHandler)},makeEvent:function(b,c){this.captureInfo[c.pointerId]&&(c.relatedTarget=null);var d=new a(b,c);return c.preventDefault&&(d.preventDefault=c.preventDefault),d._target=d._target||c.target,d},fireEvent:function(a,b){var c=this.makeEvent(a,b);return this.dispatchEvent(c)},cloneEvent:function(a){for(var b,c=Object.create(null),d=0;d<q.length;d++)b=q[d],c[b]=a[b]||r[d],!t||"target"!==b&&"relatedTarget"!==b||c[b]instanceof SVGElementInstance&&(c[b]=c[b].correspondingUseElement);return a.preventDefault&&(c.preventDefault=function(){a.preventDefault()}),c},getTarget:function(a){var b=this.captureInfo[a.pointerId];return b?a._target!==b&&a.type in s?void 0:b:a._target},propagate:function(a,b,c){for(var d=a.target,e=[];d!==document&&!d.contains(a.relatedTarget);) if(e.push(d),d=d.parentNode,!d)return;c&&e.reverse(),e.forEach(function(c){a.target=c,b.call(this,a)},this)},setCapture:function(b,c,d){this.captureInfo[b]&&this.releaseCapture(b,d),this.captureInfo[b]=c,this.implicitRelease=this.releaseCapture.bind(this,b,d),document.addEventListener("pointerup",this.implicitRelease),document.addEventListener("pointercancel",this.implicitRelease);var e=new a("gotpointercapture");e.pointerId=b,e._target=c,d||this.asyncDispatchEvent(e)},releaseCapture:function(b,c){var d=this.captureInfo[b];if(d){this.captureInfo[b]=void 0,document.removeEventListener("pointerup",this.implicitRelease),document.removeEventListener("pointercancel",this.implicitRelease);var e=new a("lostpointercapture");e.pointerId=b,e._target=d,c||this.asyncDispatchEvent(e)}},dispatchEvent:/*scope.external.dispatchEvent || */function(a){var b=this.getTarget(a);if(b)return b.dispatchEvent(a)},asyncDispatchEvent:function(a){requestAnimationFrame(this.dispatchEvent.bind(this,a))}};u.boundHandler=u.eventHandler.bind(u);var v={shadow:function(a){if(a)return a.shadowRoot||a.webkitShadowRoot},canTarget:function(a){return a&&Boolean(a.elementFromPoint)},targetingShadow:function(a){var b=this.shadow(a);if(this.canTarget(b))return b},olderShadow:function(a){var b=a.olderShadowRoot;if(!b){var c=a.querySelector("shadow");c&&(b=c.olderShadowRoot)}return b},allShadows:function(a){for(var b=[],c=this.shadow(a);c;)b.push(c),c=this.olderShadow(c);return b},searchRoot:function(a,b,c){if(a){var d,e,f=a.elementFromPoint(b,c);for(e=this.targetingShadow(f);e;){if(d=e.elementFromPoint(b,c)){var g=this.targetingShadow(d);return this.searchRoot(g,b,c)||d} e=this.olderShadow(e)} return f}},owner:function(a){
|
|
for(var b=a;b.parentNode;)b=b.parentNode;
|
|
return b.nodeType!==Node.DOCUMENT_NODE&&b.nodeType!==Node.DOCUMENT_FRAGMENT_NODE&&(b=document),b},findTarget:function(a){var b=a.clientX,c=a.clientY,d=this.owner(a.target);
|
|
return d.elementFromPoint(b,c)||(d=document),this.searchRoot(d,b,c)}},w=Array.prototype.forEach.call.bind(Array.prototype.forEach),x=Array.prototype.map.call.bind(Array.prototype.map),y=Array.prototype.slice.call.bind(Array.prototype.slice),z=Array.prototype.filter.call.bind(Array.prototype.filter),A=window.MutationObserver||window.WebKitMutationObserver,B="[touch-action]",C={subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0,attributeFilter:["touch-action"]};c.prototype={watchSubtree:function(a){
|
|
//
|
|
this.observer&&v.canTarget(a)&&this.observer.observe(a,C)},enableOnSubtree:function(a){this.watchSubtree(a),a===document&&"complete"!==document.readyState?this.installOnLoad():this.installNewSubtree(a)},installNewSubtree:function(a){w(this.findElements(a),this.addElement,this)},findElements:function(a){return a.querySelectorAll?a.querySelectorAll(B):[]},removeElement:function(a){this.removeCallback(a)},addElement:function(a){this.addCallback(a)},elementChanged:function(a,b){this.changedCallback(a,b)},concatLists:function(a,b){return a.concat(y(b))},
|
|
installOnLoad:function(){document.addEventListener("readystatechange",function(){"complete"===document.readyState&&this.installNewSubtree(document)}.bind(this))},isElement:function(a){return a.nodeType===Node.ELEMENT_NODE},flattenMutationTree:function(a){
|
|
var b=x(a,this.findElements,this);
|
|
return b.push(z(a,this.isElement)),b.reduce(this.concatLists,[])},mutationWatcher:function(a){a.forEach(this.mutationHandler,this)},mutationHandler:function(a){if("childList"===a.type){var b=this.flattenMutationTree(a.addedNodes);b.forEach(this.addElement,this);var c=this.flattenMutationTree(a.removedNodes);c.forEach(this.removeElement,this)}else"attributes"===a.type&&this.elementChanged(a.target,a.oldValue)}};var D=["none","auto","pan-x","pan-y",{rule:"pan-x pan-y",selectors:["pan-x pan-y","pan-y pan-x"]}],E="",F=window.PointerEvent||window.MSPointerEvent,G=!window.ShadowDOMPolyfill&&document.head.createShadowRoot,H=u.pointermap,I=25,J=[1,4,2,8,16],K=!1;try{K=1===new MouseEvent("test",{buttons:1}).buttons}catch(L){}
|
|
var M,N={POINTER_ID:1,POINTER_TYPE:"mouse",events:["mousedown","mousemove","mouseup","mouseover","mouseout"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},lastTouches:[],
|
|
isEventSimulatedFromTouch:function(a){for(var b,c=this.lastTouches,d=a.clientX,e=a.clientY,f=0,g=c.length;f<g&&(b=c[f]);f++){
|
|
var h=Math.abs(d-b.x),i=Math.abs(e-b.y);if(h<=I&&i<=I)return!0}},prepareEvent:function(a){var b=u.cloneEvent(a),c=b.preventDefault;return b.preventDefault=function(){a.preventDefault(),c()},b.pointerId=this.POINTER_ID,b.isPrimary=!0,b.pointerType=this.POINTER_TYPE,b},prepareButtonsForMove:function(a,b){var c=H.get(this.POINTER_ID);
|
|
0!==b.which&&c?a.buttons=c.buttons:a.buttons=0,b.buttons=a.buttons},mousedown:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);K||(c.buttons=J[c.button],b&&(c.buttons|=b.buttons),a.buttons=c.buttons),H.set(this.POINTER_ID,a),b&&0!==b.buttons?u.move(c):u.down(c)}},mousemove:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.move(b)}},mouseup:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);if(!K){var d=J[c.button];
|
|
c.buttons=b?b.buttons&~d:0,a.buttons=c.buttons}H.set(this.POINTER_ID,a),
|
|
c.buttons&=~J[c.button],0===c.buttons?u.up(c):u.move(c)}},mouseover:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.enterOver(b)}},mouseout:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,u.leaveOut(b)}},cancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.deactivateMouse()},deactivateMouse:function(){H["delete"](this.POINTER_ID)}},O=u.captureInfo,P=v.findTarget.bind(v),Q=v.allShadows.bind(v),R=u.pointermap,S=2500,T=200,U="touch-action",V={events:["touchstart","touchmove","touchend","touchcancel"],register:function(a){M.enableOnSubtree(a)},unregister:function(){},elementAdded:function(a){var b=a.getAttribute(U),c=this.touchActionToScrollType(b);c&&(a._scrollType=c,u.listen(a,this.events),
|
|
Q(a).forEach(function(a){a._scrollType=c,u.listen(a,this.events)},this))},elementRemoved:function(a){a._scrollType=void 0,u.unlisten(a,this.events),
|
|
Q(a).forEach(function(a){a._scrollType=void 0,u.unlisten(a,this.events)},this)},elementChanged:function(a,b){var c=a.getAttribute(U),d=this.touchActionToScrollType(c),e=this.touchActionToScrollType(b);
|
|
d&&e?(a._scrollType=d,Q(a).forEach(function(a){a._scrollType=d},this)):e?this.elementRemoved(a):d&&this.elementAdded(a)},scrollTypes:{EMITTER:"none",XSCROLLER:"pan-x",YSCROLLER:"pan-y",SCROLLER:/^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/},touchActionToScrollType:function(a){var b=a,c=this.scrollTypes;return"none"===b?"none":b===c.XSCROLLER?"X":b===c.YSCROLLER?"Y":c.SCROLLER.exec(b)?"XY":void 0},POINTER_TYPE:"touch",firstTouch:null,isPrimaryTouch:function(a){return this.firstTouch===a.identifier},setPrimaryTouch:function(a){
|
|
(0===R.size||1===R.size&&R.has(1))&&(this.firstTouch=a.identifier,this.firstXY={X:a.clientX,Y:a.clientY},this.scrolling=!1,this.cancelResetClickCount())},removePrimaryPointer:function(a){a.isPrimary&&(this.firstTouch=null,this.firstXY=null,this.resetClickCount())},clickCount:0,resetId:null,resetClickCount:function(){var a=function(){this.clickCount=0,this.resetId=null}.bind(this);this.resetId=setTimeout(a,T)},cancelResetClickCount:function(){this.resetId&&clearTimeout(this.resetId)},typeToButtons:function(a){var b=0;return"touchstart"!==a&&"touchmove"!==a||(b=1),b},touchToPointer:function(a){var b=this.currentTouchEvent,c=u.cloneEvent(a),d=c.pointerId=a.identifier+2;c.target=O[d]||P(c),c.bubbles=!0,c.cancelable=!0,c.detail=this.clickCount,c.button=0,c.buttons=this.typeToButtons(b.type),c.width=2*(a.radiusX||a.webkitRadiusX||0),c.height=2*(a.radiusY||a.webkitRadiusY||0),c.pressure=a.force||a.webkitForce||.5,c.isPrimary=this.isPrimaryTouch(a),c.pointerType=this.POINTER_TYPE,
|
|
c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey;
|
|
var e=this;return c.preventDefault=function(){e.scrolling=!1,e.firstXY=null,b.preventDefault()},c},processTouches:function(a,b){var c=a.changedTouches;this.currentTouchEvent=a;for(var d,e=0;e<c.length;e++)d=c[e],b.call(this,this.touchToPointer(d))},
|
|
shouldScroll:function(a){if(this.firstXY){var b,c=a.currentTarget._scrollType;if("none"===c)
|
|
b=!1;else if("XY"===c)
|
|
b=!0;else{var d=a.changedTouches[0],e=c,f="Y"===c?"X":"Y",g=Math.abs(d["client"+e]-this.firstXY[e]),h=Math.abs(d["client"+f]-this.firstXY[f]);
|
|
b=g>=h}return this.firstXY=null,b}},findTouch:function(a,b){for(var c,d=0,e=a.length;d<e&&(c=a[d]);d++)if(c.identifier===b)return!0},
|
|
vacuumTouches:function(a){var b=a.touches;
|
|
if(R.size>=b.length){var c=[];R.forEach(function(a,d){
|
|
if(1!==d&&!this.findTouch(b,d-2)){var e=a.out;c.push(e)}},this),c.forEach(this.cancelOut,this)}},touchstart:function(a){this.vacuumTouches(a),this.setPrimaryTouch(a.changedTouches[0]),this.dedupSynthMouse(a),this.scrolling||(this.clickCount++,this.processTouches(a,this.overDown))},overDown:function(a){R.set(a.pointerId,{target:a.target,out:a,outTarget:a.target}),u.enterOver(a),u.down(a)},touchmove:function(a){this.scrolling||(this.shouldScroll(a)?(this.scrolling=!0,this.touchcancel(a)):(a.preventDefault(),this.processTouches(a,this.moveOverOut)))},moveOverOut:function(a){var b=a,c=R.get(b.pointerId);
|
|
if(c){var d=c.out,e=c.outTarget;u.move(b),d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,
|
|
d.target=e,b.target?(u.leaveOut(d),u.enterOver(b)):(
|
|
b.target=e,b.relatedTarget=null,this.cancelOut(b))),c.out=b,c.outTarget=b.target}},touchend:function(a){this.dedupSynthMouse(a),this.processTouches(a,this.upOut)},upOut:function(a){this.scrolling||(u.up(a),u.leaveOut(a)),this.cleanUpPointer(a)},touchcancel:function(a){this.processTouches(a,this.cancelOut)},cancelOut:function(a){u.cancel(a),u.leaveOut(a),this.cleanUpPointer(a)},cleanUpPointer:function(a){R["delete"](a.pointerId),this.removePrimaryPointer(a)},
|
|
dedupSynthMouse:function(a){var b=N.lastTouches,c=a.changedTouches[0];
|
|
if(this.isPrimaryTouch(c)){
|
|
var d={x:c.clientX,y:c.clientY};b.push(d);var e=function(a,b){var c=a.indexOf(b);c>-1&&a.splice(c,1)}.bind(null,b,d);setTimeout(e,S)}}};M=new c(V.elementAdded,V.elementRemoved,V.elementChanged,V);var W,X,Y,Z=u.pointermap,$=window.MSPointerEvent&&"number"==typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,_={events:["MSPointerDown","MSPointerMove","MSPointerUp","MSPointerOut","MSPointerOver","MSPointerCancel","MSGotPointerCapture","MSLostPointerCapture"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},POINTER_TYPES:["","unavailable","touch","pen","mouse"],prepareEvent:function(a){var b=a;return $&&(b=u.cloneEvent(a),b.pointerType=this.POINTER_TYPES[a.pointerType]),b},cleanup:function(a){Z["delete"](a)},MSPointerDown:function(a){Z.set(a.pointerId,a);var b=this.prepareEvent(a);u.down(b)},MSPointerMove:function(a){var b=this.prepareEvent(a);u.move(b)},MSPointerUp:function(a){var b=this.prepareEvent(a);u.up(b),this.cleanup(a.pointerId)},MSPointerOut:function(a){var b=this.prepareEvent(a);u.leaveOut(b)},MSPointerOver:function(a){var b=this.prepareEvent(a);u.enterOver(b)},MSPointerCancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.cleanup(a.pointerId)},MSLostPointerCapture:function(a){var b=u.makeEvent("lostpointercapture",a);u.dispatchEvent(b)},MSGotPointerCapture:function(a){var b=u.makeEvent("gotpointercapture",a);u.dispatchEvent(b)}},aa=window.navigator;aa.msPointerEnabled?(W=function(a){i(a),j(this),k(a)&&(u.setCapture(a,this,!0),this.msSetPointerCapture(a))},X=function(a){i(a),u.releaseCapture(a,!0),this.msReleasePointerCapture(a)}):(W=function(a){i(a),j(this),k(a)&&u.setCapture(a,this)},X=function(a){i(a),u.releaseCapture(a)}),Y=function(a){return!!u.captureInfo[a]},g(),h(),l();var ba={dispatcher:u,Installer:c,PointerEvent:a,PointerMap:p,targetFinding:v};return ba});
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
var config = {"show_fabrication": false, "redraw_on_drag": true, "highlight_pin1": false, "extra_fields": ["SPN"], "dark_mode": false, "bom_view": "left-right", "board_rotation": 0, "checkboxes": "Sourced,Placed", "show_silkscreen": true, "show_pads": true, "layer_view": "FB"}
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
var pcbdata = JSON.parse(LZString.decompressFromBase64("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"))
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* Utility functions */
|
|
|
|
var storagePrefix = 'KiCad_HTML_BOM__' + pcbdata.metadata.title + '__' +
|
|
pcbdata.metadata.revision + '__#';
|
|
var storage;
|
|
|
|
function initStorage(key) {
|
|
try {
|
|
window.localStorage.getItem("blank");
|
|
storage = window.localStorage;
|
|
} catch (e) {
|
|
// localStorage not available
|
|
}
|
|
if (!storage) {
|
|
try {
|
|
window.sessionStorage.getItem("blank");
|
|
storage = window.sessionStorage;
|
|
} catch (e) {
|
|
// sessionStorage also not available
|
|
}
|
|
}
|
|
}
|
|
|
|
function readStorage(key) {
|
|
if (storage) {
|
|
return storage.getItem(storagePrefix + key);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function writeStorage(key, value) {
|
|
if (storage) {
|
|
storage.setItem(storagePrefix + key, value);
|
|
}
|
|
}
|
|
|
|
function fancyDblClickHandler(el, onsingle, ondouble) {
|
|
return function() {
|
|
if (el.getAttribute("data-dblclick") == null) {
|
|
el.setAttribute("data-dblclick", 1);
|
|
setTimeout(function() {
|
|
if (el.getAttribute("data-dblclick") == 1) {
|
|
onsingle();
|
|
}
|
|
el.removeAttribute("data-dblclick");
|
|
}, 200);
|
|
} else {
|
|
el.removeAttribute("data-dblclick");
|
|
ondouble();
|
|
}
|
|
}
|
|
}
|
|
|
|
function smoothScrollToRow(rowid) {
|
|
document.getElementById(rowid).scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
inline: "nearest"
|
|
});
|
|
}
|
|
|
|
function focusInputField(input) {
|
|
input.scrollIntoView(false);
|
|
input.focus();
|
|
input.select();
|
|
}
|
|
|
|
function copyToClipboard() {
|
|
var text = '';
|
|
for (var node of bomhead.childNodes[0].childNodes) {
|
|
if (node.firstChild) {
|
|
text = text + node.firstChild.nodeValue;
|
|
}
|
|
if (node != bomhead.childNodes[0].lastChild) {
|
|
text += '\t';
|
|
}
|
|
}
|
|
text += '\n';
|
|
for (var row of bombody.childNodes) {
|
|
for (var cell of row.childNodes) {
|
|
for (var node of cell.childNodes) {
|
|
if (node.nodeName == "INPUT") {
|
|
if (node.checked) {
|
|
text = text + '✓';
|
|
}
|
|
} else if (node.nodeName == "MARK") {
|
|
text = text + node.firstChild.nodeValue;
|
|
} else {
|
|
text = text + node.nodeValue;
|
|
}
|
|
}
|
|
if (cell != row.lastChild) {
|
|
text += '\t';
|
|
}
|
|
}
|
|
text += '\n';
|
|
}
|
|
var textArea = document.createElement("textarea");
|
|
textArea.classList.add('clipboard-temp');
|
|
textArea.value = text;
|
|
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
|
|
try {
|
|
if (document.execCommand('copy')) {
|
|
console.log('Bom copied to clipboard.');
|
|
}
|
|
} catch (err) {
|
|
console.log('Can not copy to clipboard.');
|
|
}
|
|
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
function removeGutterNode(node) {
|
|
for (var i = 0; i < node.childNodes.length; i++) {
|
|
if (node.childNodes[i].classList &&
|
|
node.childNodes[i].classList.contains("gutter")) {
|
|
node.removeChild(node.childNodes[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function cleanGutters() {
|
|
removeGutterNode(document.getElementById("bot"));
|
|
removeGutterNode(document.getElementById("canvasdiv"));
|
|
}
|
|
|
|
var units = {
|
|
prefixes: {
|
|
giga: ["G", "g", "giga", "Giga", "GIGA"],
|
|
mega: ["M", "mega", "Mega", "MEGA"],
|
|
kilo: ["K", "k", "kilo", "Kilo", "KILO"],
|
|
milli: ["m", "milli", "Milli", "MILLI"],
|
|
micro: ["U", "u", "micro", "Micro", "MICRO", "μ", "µ"], // different utf8 μ
|
|
nano: ["N", "n", "nano", "Nano", "NANO"],
|
|
pico: ["P", "p", "pico", "Pico", "PICO"],
|
|
},
|
|
unitsShort: ["R", "r", "Ω", "F", "f", "H", "h"],
|
|
unitsLong: [
|
|
"OHM", "Ohm", "ohm", "ohms",
|
|
"FARAD", "Farad", "farad",
|
|
"HENRY", "Henry", "henry"
|
|
],
|
|
getMultiplier: function(s) {
|
|
if (this.prefixes.giga.includes(s)) return 1e9;
|
|
if (this.prefixes.mega.includes(s)) return 1e6;
|
|
if (this.prefixes.kilo.includes(s)) return 1e3;
|
|
if (this.prefixes.milli.includes(s)) return 1e-3;
|
|
if (this.prefixes.micro.includes(s)) return 1e-6;
|
|
if (this.prefixes.nano.includes(s)) return 1e-9;
|
|
if (this.prefixes.pico.includes(s)) return 1e-12;
|
|
return 1;
|
|
},
|
|
valueRegex: null,
|
|
}
|
|
|
|
function initUtils() {
|
|
var allPrefixes = units.prefixes.giga
|
|
.concat(units.prefixes.mega)
|
|
.concat(units.prefixes.kilo)
|
|
.concat(units.prefixes.milli)
|
|
.concat(units.prefixes.micro)
|
|
.concat(units.prefixes.nano)
|
|
.concat(units.prefixes.pico);
|
|
var allUnits = units.unitsShort.concat(units.unitsLong);
|
|
units.valueRegex = new RegExp("^([0-9\.]+)" +
|
|
"\\s*(" + allPrefixes.join("|") + ")?" +
|
|
"(" + allUnits.join("|") + ")?" +
|
|
"(\\b.*)?$", "");
|
|
units.valueAltRegex = new RegExp("^([0-9]*)" +
|
|
"(" + units.unitsShort.join("|") + ")?" +
|
|
"([GgMmKkUuNnPp])?" +
|
|
"([0-9]*)" +
|
|
"(\\b.*)?$", "");
|
|
for (var bom_type of ["both", "F", "B"]) {
|
|
for (var row of pcbdata.bom[bom_type]) {
|
|
row.push(parseValue(row[1], row[3][0][0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseValue(val, ref) {
|
|
var inferUnit = (unit, ref) => {
|
|
if (unit) {
|
|
unit = unit.toLowerCase();
|
|
if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
|
|
unit = 'r';
|
|
}
|
|
unit = unit[0];
|
|
} else {
|
|
ref = /^([a-z]+)\d+$/i.exec(ref);
|
|
if (ref) {
|
|
ref = ref[1].toLowerCase();
|
|
if (ref == "c") unit = 'f';
|
|
else if (ref == "l") unit = 'h';
|
|
else if (ref == "r" || ref == "rv") unit = 'r';
|
|
else unit = null;
|
|
}
|
|
}
|
|
return unit;
|
|
};
|
|
val = val.replace(/,/g, "");
|
|
var match = units.valueRegex.exec(val);
|
|
var unit;
|
|
if (match) {
|
|
val = parseFloat(match[1]);
|
|
if (match[2]) {
|
|
val = val * units.getMultiplier(match[2]);
|
|
}
|
|
unit = inferUnit(match[3], ref);
|
|
if (!unit) return null;
|
|
else return {
|
|
val: val,
|
|
unit: unit,
|
|
extra: match[4],
|
|
}
|
|
}
|
|
match = units.valueAltRegex.exec(val);
|
|
if (match && (match[1] || match[4])) {
|
|
val = parseFloat(match[1] + "." + match[4]);
|
|
if (match[3]) {
|
|
val = val * units.getMultiplier(match[3]);
|
|
}
|
|
unit = inferUnit(match[2], ref);
|
|
if (!unit) return null;
|
|
else return {
|
|
val: val,
|
|
unit: unit,
|
|
extra: match[5],
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function valueCompare(a, b, stra, strb) {
|
|
if (a === null && b === null) {
|
|
// Failed to parse both values, compare them as strings.
|
|
if (stra != strb) return stra > strb ? 1 : -1;
|
|
else return 0;
|
|
} else if (a === null) {
|
|
return 1;
|
|
} else if (b === null) {
|
|
return -1;
|
|
} else {
|
|
if (a.unit != b.unit) return a.unit > b.unit ? 1 : -1;
|
|
else if (a.val != b.val) return a.val > b.val ? 1 : -1;
|
|
else if (a.extra != b.extra) return a.extra > b.extra ? 1 : -1;
|
|
else return 0;
|
|
}
|
|
}
|
|
|
|
function validateSaveImgDimension(element) {
|
|
var valid = false;
|
|
var intValue = 0;
|
|
if (/^[1-9]\d*$/.test(element.value)) {
|
|
intValue = parseInt(element.value);
|
|
if (intValue <= 16000) {
|
|
valid = true;
|
|
}
|
|
}
|
|
if (valid) {
|
|
element.classList.remove("invalid");
|
|
} else {
|
|
element.classList.add("invalid");
|
|
}
|
|
return intValue;
|
|
}
|
|
|
|
function saveImage(layer) {
|
|
var width = validateSaveImgDimension(document.getElementById("render-save-width"));
|
|
var height = validateSaveImgDimension(document.getElementById("render-save-height"));
|
|
var bgcolor = null;
|
|
if (!document.getElementById("render-save-transparent").checked) {
|
|
var style = getComputedStyle(topmostdiv);
|
|
bgcolor = style.getPropertyValue("background-color");
|
|
}
|
|
if (!width || !height) return;
|
|
|
|
// Prepare image
|
|
var canvas = document.createElement("canvas");
|
|
var layerdict = {
|
|
transform: {
|
|
x: 0,
|
|
y: 0,
|
|
s: 1,
|
|
panx: 0,
|
|
pany: 0,
|
|
zoom: 1,
|
|
},
|
|
bg: canvas,
|
|
fab: canvas,
|
|
silk: canvas,
|
|
highlight: canvas,
|
|
layer: layer,
|
|
}
|
|
// Do the rendering
|
|
recalcLayerScale(layerdict, width, height);
|
|
prepareLayer(layerdict);
|
|
clearCanvas(canvas, bgcolor);
|
|
drawBackground(layerdict, false);
|
|
drawHighlightsOnLayer(layerdict, false);
|
|
|
|
// Save image
|
|
var imgdata = canvas.toDataURL("image/png");
|
|
|
|
var filename = pcbdata.metadata.title;
|
|
if (pcbdata.metadata.revision) {
|
|
filename += `.${pcbdata.metadata.revision}`;
|
|
}
|
|
filename += `.${layer}.png`;
|
|
saveFile(filename, dataURLtoBlob(imgdata));
|
|
}
|
|
|
|
function saveSettings() {
|
|
var data = {
|
|
type: "InteractiveHtmlBom settings",
|
|
version: 1,
|
|
pcbmetadata: pcbdata.metadata,
|
|
settings: settings,
|
|
}
|
|
var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json"});
|
|
saveFile(`${pcbdata.metadata.title}.settings.json`, blob);
|
|
}
|
|
|
|
function loadSettings() {
|
|
var input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = ".settings.json";
|
|
input.onchange = function(e) {
|
|
var file = e.target.files[0];
|
|
var reader = new FileReader();
|
|
reader.onload = readerEvent => {
|
|
var content = readerEvent.target.result;
|
|
var newSettings;
|
|
try {
|
|
newSettings = JSON.parse(content);
|
|
} catch(e) {
|
|
alert("Selected file is not InteractiveHtmlBom settings file.");
|
|
return;
|
|
}
|
|
if (newSettings.type != "InteractiveHtmlBom settings") {
|
|
alert("Selected file is not InteractiveHtmlBom settings file.");
|
|
return;
|
|
}
|
|
var metadataMatches = newSettings.hasOwnProperty("pcbmetadata");
|
|
if (metadataMatches) {
|
|
for (var k in pcbdata.metadata) {
|
|
if (!newSettings.pcbmetadata.hasOwnProperty(k) || newSettings.pcbmetadata[k] != pcbdata.metadata[k]) {
|
|
metadataMatches = false;
|
|
}
|
|
}
|
|
}
|
|
if (!metadataMatches) {
|
|
var currentMetadata = JSON.stringify(pcbdata.metadata, null, 4);
|
|
var fileMetadata = JSON.stringify(newSettings.pcbmetadata, null, 4);
|
|
if (!confirm(
|
|
`Settins file metadata does not match current metadata.\n\n` +
|
|
`Page metadata:\n${currentMetadata}\n\n` +
|
|
`Settings file metadata:\n${fileMetadata}\n\n` +
|
|
`Press OK if you would like to import settings anyway.`)) {
|
|
return;
|
|
}
|
|
}
|
|
overwriteSettings(newSettings.settings);
|
|
}
|
|
reader.readAsText(file, 'UTF-8');
|
|
}
|
|
input.click();
|
|
}
|
|
|
|
function overwriteSettings(newSettings) {
|
|
initDone = false;
|
|
Object.assign(settings, newSettings);
|
|
writeStorage("bomlayout", settings.bomlayout);
|
|
writeStorage("bommode", settings.bommode);
|
|
writeStorage("canvaslayout", settings.canvaslayout);
|
|
writeStorage("bomCheckboxes", settings.checkboxes.join(","));
|
|
document.getElementById("bomCheckboxes").value = settings.checkboxes.join(",");
|
|
for (var checkbox of settings.checkboxes) {
|
|
writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
|
|
}
|
|
writeStorage("darkenWhenChecked", settings.darkenWhenChecked);
|
|
padsVisible(settings.renderPads);
|
|
document.getElementById("padsCheckbox").checked = settings.renderPads;
|
|
fabricationVisible(settings.renderFabrication);
|
|
document.getElementById("fabricationCheckbox").checked = settings.renderFabrication;
|
|
silkscreenVisible(settings.renderSilkscreen);
|
|
document.getElementById("silkscreenCheckbox").checked = settings.renderSilkscreen;
|
|
referencesVisible(settings.renderReferences);
|
|
document.getElementById("referencesCheckbox").checked = settings.renderReferences;
|
|
valuesVisible(settings.renderValues);
|
|
document.getElementById("valuesCheckbox").checked = settings.renderValues;
|
|
tracksVisible(settings.renderTracks);
|
|
document.getElementById("tracksCheckbox").checked = settings.renderTracks;
|
|
zonesVisible(settings.renderZones);
|
|
document.getElementById("zonesCheckbox").checked = settings.renderZones;
|
|
dnpOutline(settings.renderDnpOutline);
|
|
document.getElementById("dnpOutlineCheckbox").checked = settings.renderDnpOutline;
|
|
setRedrawOnDrag(settings.redrawOnDrag);
|
|
document.getElementById("dragCheckbox").checked = settings.redrawOnDrag;
|
|
setDarkMode(settings.darkMode);
|
|
document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
|
|
setHighlightPin1(settings.highlightpin1);
|
|
document.getElementById("highlightpin1Checkbox").checked = settings.highlightpin1;
|
|
writeStorage("boardRotation", settings.boardRotation);
|
|
document.getElementById("boardRotation").value = settings.boardRotation / 5;
|
|
document.getElementById("rotationDegree").textContent = settings.boardRotation;
|
|
initDone = true;
|
|
prepCheckboxes();
|
|
changeBomLayout(settings.bomlayout);
|
|
}
|
|
|
|
function saveFile(filename, blob) {
|
|
var link = document.createElement("a");
|
|
var objurl = URL.createObjectURL(blob);
|
|
link.download = filename;
|
|
link.href = objurl;
|
|
link.click();
|
|
}
|
|
|
|
function dataURLtoBlob(dataurl) {
|
|
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
|
|
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
|
|
while(n--){
|
|
u8arr[n] = bstr.charCodeAt(n);
|
|
}
|
|
return new Blob([u8arr], {type:mime});
|
|
}
|
|
|
|
var settings = {
|
|
canvaslayout: "default",
|
|
bomlayout: "default",
|
|
bommode: "grouped",
|
|
checkboxes: [],
|
|
checkboxStoredRefs: {},
|
|
darkMode: false,
|
|
highlightpin1: false,
|
|
redrawOnDrag: true,
|
|
boardRotation: 0,
|
|
renderPads: true,
|
|
renderReferences: true,
|
|
renderValues: true,
|
|
renderSilkscreen: true,
|
|
renderFabrication: true,
|
|
renderDnpOutline: false,
|
|
renderTracks: true,
|
|
renderZones: true,
|
|
}
|
|
|
|
function initDefaults() {
|
|
settings.bomlayout = readStorage("bomlayout");
|
|
if (settings.bomlayout === null) {
|
|
settings.bomlayout = config.bom_view;
|
|
}
|
|
if (!['bom-only', 'left-right', 'top-bottom'].includes(settings.bomlayout)) {
|
|
settings.bomlayout = config.bom_view;
|
|
}
|
|
settings.bommode = readStorage("bommode");
|
|
if (settings.bommode === null) {
|
|
settings.bommode = "grouped";
|
|
}
|
|
if (!["grouped", "ungrouped", "netlist"].includes(settings.bommode)) {
|
|
settings.bommode = "grouped";
|
|
}
|
|
settings.canvaslayout = readStorage("canvaslayout");
|
|
if (settings.canvaslayout === null) {
|
|
settings.canvaslayout = config.layer_view;
|
|
}
|
|
var bomCheckboxes = readStorage("bomCheckboxes");
|
|
if (bomCheckboxes === null) {
|
|
bomCheckboxes = config.checkboxes;
|
|
}
|
|
settings.checkboxes = bomCheckboxes.split(",").filter((e) => e);
|
|
document.getElementById("bomCheckboxes").value = bomCheckboxes;
|
|
|
|
settings.darkenWhenChecked = readStorage("darkenWhenChecked") || "";
|
|
populateDarkenWhenCheckedOptions();
|
|
|
|
function initBooleanSetting(storageString, def, elementId, func) {
|
|
var b = readStorage(storageString);
|
|
if (b === null) {
|
|
b = def;
|
|
} else {
|
|
b = (b == "true");
|
|
}
|
|
document.getElementById(elementId).checked = b;
|
|
func(b);
|
|
}
|
|
|
|
initBooleanSetting("padsVisible", config.show_pads, "padsCheckbox", padsVisible);
|
|
initBooleanSetting("fabricationVisible", config.show_fabrication, "fabricationCheckbox", fabricationVisible);
|
|
initBooleanSetting("silkscreenVisible", config.show_silkscreen, "silkscreenCheckbox", silkscreenVisible);
|
|
initBooleanSetting("referencesVisible", true, "referencesCheckbox", referencesVisible);
|
|
initBooleanSetting("valuesVisible", true, "valuesCheckbox", valuesVisible);
|
|
if ("tracks" in pcbdata) {
|
|
initBooleanSetting("tracksVisible", true, "tracksCheckbox", tracksVisible);
|
|
initBooleanSetting("zonesVisible", true, "zonesCheckbox", zonesVisible);
|
|
} else {
|
|
document.getElementById("tracksAndZonesCheckboxes").style.display = "none";
|
|
tracksVisible(false);
|
|
zonesVisible(false);
|
|
}
|
|
initBooleanSetting("dnpOutline", false, "dnpOutlineCheckbox", dnpOutline);
|
|
initBooleanSetting("redrawOnDrag", config.redraw_on_drag, "dragCheckbox", setRedrawOnDrag);
|
|
initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
|
|
initBooleanSetting("highlightpin1", config.highlight_pin1, "highlightpin1Checkbox", setHighlightPin1);
|
|
settings.boardRotation = readStorage("boardRotation");
|
|
if (settings.boardRotation === null) {
|
|
settings.boardRotation = config.board_rotation * 5;
|
|
} else {
|
|
settings.boardRotation = parseInt(settings.boardRotation);
|
|
}
|
|
document.getElementById("boardRotation").value = settings.boardRotation / 5;
|
|
document.getElementById("rotationDegree").textContent = settings.boardRotation;
|
|
}
|
|
|
|
// Helper classes for user js callbacks.
|
|
|
|
const IBOM_EVENT_TYPES = {
|
|
ALL: "all",
|
|
HIGHLIGHT_EVENT: "highlightEvent",
|
|
CHECKBOX_CHANGE_EVENT: "checkboxChangeEvent",
|
|
BOM_BODY_CHANGE_EVENT: "bomBodyChangeEvent",
|
|
}
|
|
|
|
const EventHandler = {
|
|
callbacks: {},
|
|
init: function() {
|
|
for (eventType of Object.values(IBOM_EVENT_TYPES))
|
|
this.callbacks[eventType] = [];
|
|
},
|
|
registerCallback: function(eventType, callback) {
|
|
this.callbacks[eventType].push(callback);
|
|
},
|
|
emitEvent: function(eventType, eventArgs) {
|
|
event = {
|
|
eventType: eventType,
|
|
args: eventArgs,
|
|
}
|
|
var callback;
|
|
for(callback of this.callbacks[eventType])
|
|
callback(event);
|
|
for(callback of this.callbacks[IBOM_EVENT_TYPES.ALL])
|
|
callback(event);
|
|
}
|
|
}
|
|
EventHandler.init();
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* PCB rendering code */
|
|
|
|
var emptyContext2d = document.createElement("canvas").getContext("2d");
|
|
|
|
function deg2rad(deg) {
|
|
return deg * Math.PI / 180;
|
|
}
|
|
|
|
function calcFontPoint(linepoint, text, offsetx, offsety, tilt) {
|
|
var point = [
|
|
linepoint[0] * text.width + offsetx,
|
|
linepoint[1] * text.height + offsety
|
|
];
|
|
// This approximates pcbnew behavior with how text tilts depending on horizontal justification
|
|
point[0] -= (linepoint[1] + 0.5 * (1 + text.justify[0])) * text.height * tilt;
|
|
return point;
|
|
}
|
|
|
|
function drawText(ctx, text, color) {
|
|
if ("ref" in text && !settings.renderReferences) return;
|
|
if ("val" in text && !settings.renderValues) return;
|
|
ctx.save();
|
|
ctx.fillStyle = color;
|
|
ctx.strokeStyle = color;
|
|
ctx.lineCap = "round";
|
|
ctx.lineJoin = "round";
|
|
ctx.lineWidth = text.thickness;
|
|
if ("svgpath" in text) {
|
|
ctx.stroke(new Path2D(text.svgpath));
|
|
ctx.restore();
|
|
return;
|
|
}
|
|
ctx.translate(...text.pos);
|
|
ctx.translate(text.thickness * 0.5, 0);
|
|
var angle = -text.angle;
|
|
if (text.attr.includes("mirrored")) {
|
|
ctx.scale(-1, 1);
|
|
angle = -angle;
|
|
}
|
|
var tilt = 0;
|
|
if (text.attr.includes("italic")) {
|
|
tilt = 0.125;
|
|
}
|
|
var interline = text.height * 1.5 + text.thickness;
|
|
var txt = text.text.split("\n");
|
|
// KiCad ignores last empty line.
|
|
if (txt[txt.length - 1] == '') txt.pop();
|
|
ctx.rotate(deg2rad(angle));
|
|
var offsety = (1 - text.justify[1]) / 2 * text.height; // One line offset
|
|
offsety -= (txt.length - 1) * (text.justify[1] + 1) / 2 * interline; // Multiline offset
|
|
for (var i in txt) {
|
|
var lineWidth = text.thickness + interline / 2 * tilt;
|
|
for (var j = 0; j < txt[i].length; j++) {
|
|
if (txt[i][j] == '\t') {
|
|
var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
|
|
lineWidth += fourSpaces - lineWidth % fourSpaces;
|
|
} else {
|
|
if (txt[i][j] == '~') {
|
|
j++;
|
|
if (j == txt[i].length)
|
|
break;
|
|
}
|
|
lineWidth += pcbdata.font_data[txt[i][j]].w * text.width;
|
|
}
|
|
}
|
|
var offsetx = -lineWidth * (text.justify[0] + 1) / 2;
|
|
var inOverbar = false;
|
|
for (var j = 0; j < txt[i].length; j++) {
|
|
if (txt[i][j] == '\t') {
|
|
var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
|
|
offsetx += fourSpaces - offsetx % fourSpaces;
|
|
continue;
|
|
} else if (txt[i][j] == '~') {
|
|
j++;
|
|
if (j == txt[i].length)
|
|
break;
|
|
if (txt[i][j] != '~') {
|
|
inOverbar = !inOverbar;
|
|
}
|
|
}
|
|
var glyph = pcbdata.font_data[txt[i][j]];
|
|
if (inOverbar) {
|
|
var overbarStart = [offsetx, -text.height * 1.4 + offsety];
|
|
var overbarEnd = [offsetx + text.width * glyph.w, overbarStart[1]];
|
|
|
|
if (!lastHadOverbar) {
|
|
overbarStart[0] += text.height * 1.4 * tilt;
|
|
lastHadOverbar = true;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(...overbarStart);
|
|
ctx.lineTo(...overbarEnd);
|
|
ctx.stroke();
|
|
} else {
|
|
lastHadOverbar = false;
|
|
}
|
|
for (var line of glyph.l) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(...calcFontPoint(line[0], text, offsetx, offsety, tilt));
|
|
for (var k = 1; k < line.length; k++) {
|
|
ctx.lineTo(...calcFontPoint(line[k], text, offsetx, offsety, tilt));
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
offsetx += glyph.w * text.width;
|
|
}
|
|
offsety += interline;
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawedge(ctx, scalefactor, edge, color) {
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
|
|
ctx.lineCap = "round";
|
|
if ("svgpath" in edge) {
|
|
ctx.stroke(new Path2D(edge.svgpath));
|
|
} else {
|
|
ctx.beginPath();
|
|
if (edge.type == "segment") {
|
|
ctx.moveTo(...edge.start);
|
|
ctx.lineTo(...edge.end);
|
|
}
|
|
if (edge.type == "rect") {
|
|
ctx.moveTo(...edge.start);
|
|
ctx.lineTo(edge.start[0], edge.end[1]);
|
|
ctx.lineTo(...edge.end);
|
|
ctx.lineTo(edge.end[0], edge.start[1]);
|
|
ctx.lineTo(...edge.start);
|
|
}
|
|
if (edge.type == "arc") {
|
|
ctx.arc(
|
|
...edge.start,
|
|
edge.radius,
|
|
deg2rad(edge.startangle),
|
|
deg2rad(edge.endangle));
|
|
}
|
|
if (edge.type == "circle") {
|
|
ctx.arc(
|
|
...edge.start,
|
|
edge.radius,
|
|
0, 2 * Math.PI);
|
|
ctx.closePath();
|
|
}
|
|
if (edge.type == "curve") {
|
|
ctx.moveTo(...edge.start);
|
|
ctx.bezierCurveTo(...edge.cpa, ...edge.cpb, ...edge.end);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function getChamferedRectPath(size, radius, chamfpos, chamfratio) {
|
|
// chamfpos is a bitmask, left = 1, right = 2, bottom left = 4, bottom right = 8
|
|
var path = new Path2D();
|
|
var width = size[0];
|
|
var height = size[1];
|
|
var x = width * -0.5;
|
|
var y = height * -0.5;
|
|
var chamfOffset = Math.min(width, height) * chamfratio;
|
|
path.moveTo(x, 0);
|
|
if (chamfpos & 4) {
|
|
path.lineTo(x, y + height - chamfOffset);
|
|
path.lineTo(x + chamfOffset, y + height);
|
|
path.lineTo(0, y + height);
|
|
} else {
|
|
path.arcTo(x, y + height, x + width, y + height, radius);
|
|
}
|
|
if (chamfpos & 8) {
|
|
path.lineTo(x + width - chamfOffset, y + height);
|
|
path.lineTo(x + width, y + height - chamfOffset);
|
|
path.lineTo(x + width, 0);
|
|
} else {
|
|
path.arcTo(x + width, y + height, x + width, y, radius);
|
|
}
|
|
if (chamfpos & 2) {
|
|
path.lineTo(x + width, y + chamfOffset);
|
|
path.lineTo(x + width - chamfOffset, y);
|
|
path.lineTo(0, y);
|
|
} else {
|
|
path.arcTo(x + width, y, x, y, radius);
|
|
}
|
|
if (chamfpos & 1) {
|
|
path.lineTo(x + chamfOffset, y);
|
|
path.lineTo(x, y + chamfOffset);
|
|
path.lineTo(x, 0);
|
|
} else {
|
|
path.arcTo(x, y, x, y + height, radius);
|
|
}
|
|
path.closePath();
|
|
return path;
|
|
}
|
|
|
|
function getOblongPath(size) {
|
|
return getChamferedRectPath(size, Math.min(size[0], size[1]) / 2, 0, 0);
|
|
}
|
|
|
|
function getPolygonsPath(shape) {
|
|
if (shape.path2d) {
|
|
return shape.path2d;
|
|
}
|
|
if ("svgpath" in shape) {
|
|
shape.path2d = new Path2D(shape.svgpath);
|
|
} else {
|
|
var path = new Path2D();
|
|
for (var polygon of shape.polygons) {
|
|
path.moveTo(...polygon[0]);
|
|
for (var i = 1; i < polygon.length; i++) {
|
|
path.lineTo(...polygon[i]);
|
|
}
|
|
path.closePath();
|
|
}
|
|
shape.path2d = path;
|
|
}
|
|
return shape.path2d;
|
|
}
|
|
|
|
function drawPolygonShape(ctx, shape, color) {
|
|
ctx.save();
|
|
ctx.fillStyle = color;
|
|
if (!("svgpath" in shape)) {
|
|
ctx.translate(...shape.pos);
|
|
ctx.rotate(deg2rad(-shape.angle));
|
|
}
|
|
ctx.fill(getPolygonsPath(shape));
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawDrawing(ctx, scalefactor, drawing, color) {
|
|
if (["segment", "arc", "circle", "curve"].includes(drawing.type)) {
|
|
drawedge(ctx, scalefactor, drawing, color);
|
|
} else if (drawing.type == "polygon") {
|
|
drawPolygonShape(ctx, drawing, color);
|
|
} else {
|
|
drawText(ctx, drawing, color);
|
|
}
|
|
}
|
|
|
|
function getCirclePath(radius) {
|
|
var path = new Path2D();
|
|
path.arc(0, 0, radius, 0, 2 * Math.PI);
|
|
path.closePath();
|
|
return path;
|
|
}
|
|
|
|
function getCachedPadPath(pad) {
|
|
if (!pad.path2d) {
|
|
// if path2d is not set, build one and cache it on pad object
|
|
if (pad.shape == "rect") {
|
|
pad.path2d = new Path2D();
|
|
pad.path2d.rect(...pad.size.map(c => -c * 0.5), ...pad.size);
|
|
} else if (pad.shape == "oval") {
|
|
pad.path2d = getOblongPath(pad.size);
|
|
} else if (pad.shape == "circle") {
|
|
pad.path2d = getCirclePath(pad.size[0] / 2);
|
|
} else if (pad.shape == "roundrect") {
|
|
pad.path2d = getChamferedRectPath(pad.size, pad.radius, 0, 0);
|
|
} else if (pad.shape == "chamfrect") {
|
|
pad.path2d = getChamferedRectPath(pad.size, pad.radius, pad.chamfpos, pad.chamfratio)
|
|
} else if (pad.shape == "custom") {
|
|
pad.path2d = getPolygonsPath(pad);
|
|
}
|
|
}
|
|
return pad.path2d;
|
|
}
|
|
|
|
function drawPad(ctx, pad, color, outline) {
|
|
ctx.save();
|
|
ctx.translate(...pad.pos);
|
|
ctx.rotate(deg2rad(pad.angle));
|
|
if (pad.offset) {
|
|
ctx.translate(...pad.offset);
|
|
}
|
|
ctx.fillStyle = color;
|
|
ctx.strokeStyle = color;
|
|
var path = getCachedPadPath(pad);
|
|
if (outline) {
|
|
ctx.stroke(path);
|
|
} else {
|
|
ctx.fill(path);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawPadHole(ctx, pad, padHoleColor) {
|
|
if (pad.type != "th") return;
|
|
ctx.save();
|
|
ctx.translate(...pad.pos);
|
|
ctx.rotate(deg2rad(pad.angle));
|
|
ctx.fillStyle = padHoleColor;
|
|
if (pad.drillshape == "oblong") {
|
|
ctx.fill(getOblongPath(pad.drillsize));
|
|
} else {
|
|
ctx.fill(getCirclePath(pad.drillsize[0] / 2));
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawFootprint(ctx, layer, scalefactor, footprint, padColor, padHoleColor, outlineColor, highlight, outline) {
|
|
if (highlight) {
|
|
// draw bounding box
|
|
if (footprint.layer == layer) {
|
|
ctx.save();
|
|
ctx.globalAlpha = 0.2;
|
|
ctx.translate(...footprint.bbox.pos);
|
|
ctx.rotate(deg2rad(-footprint.bbox.angle));
|
|
ctx.translate(...footprint.bbox.relpos);
|
|
ctx.fillStyle = padColor;
|
|
ctx.fillRect(0, 0, ...footprint.bbox.size);
|
|
ctx.globalAlpha = 1;
|
|
ctx.strokeStyle = padColor;
|
|
ctx.strokeRect(0, 0, ...footprint.bbox.size);
|
|
ctx.restore();
|
|
}
|
|
}
|
|
// draw drawings
|
|
for (var drawing of footprint.drawings) {
|
|
if (drawing.layer == layer) {
|
|
drawDrawing(ctx, scalefactor, drawing.drawing, padColor);
|
|
}
|
|
}
|
|
// draw pads
|
|
if (settings.renderPads) {
|
|
for (var pad of footprint.pads) {
|
|
if (pad.layers.includes(layer)) {
|
|
drawPad(ctx, pad, padColor, outline);
|
|
if (pad.pin1 && settings.highlightpin1) {
|
|
drawPad(ctx, pad, outlineColor, true);
|
|
}
|
|
}
|
|
}
|
|
for (var pad of footprint.pads) {
|
|
drawPadHole(ctx, pad, padHoleColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawEdgeCuts(canvas, scalefactor) {
|
|
var ctx = canvas.getContext("2d");
|
|
var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
|
|
for (var edge of pcbdata.edges) {
|
|
drawedge(ctx, scalefactor, edge, edgecolor);
|
|
}
|
|
}
|
|
|
|
function drawFootprints(canvas, layer, scalefactor, highlight) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.lineWidth = 3 / scalefactor;
|
|
var style = getComputedStyle(topmostdiv);
|
|
var padColor = style.getPropertyValue('--pad-color');
|
|
var padHoleColor = style.getPropertyValue('--pad-hole-color');
|
|
var outlineColor = style.getPropertyValue('--pin1-outline-color');
|
|
if (highlight) {
|
|
padColor = style.getPropertyValue('--pad-color-highlight');
|
|
outlineColor = style.getPropertyValue('--pin1-outline-color-highlight');
|
|
}
|
|
for (var i = 0; i < pcbdata.footprints.length; i++) {
|
|
var mod = pcbdata.footprints[i];
|
|
var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
|
|
if (!highlight || highlightedFootprints.includes(i)) {
|
|
drawFootprint(ctx, layer, scalefactor, mod, padColor, padHoleColor, outlineColor, highlight, outline);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawBgLayer(layername, canvas, layer, scalefactor, edgeColor, polygonColor, textColor) {
|
|
var ctx = canvas.getContext("2d");
|
|
for (var d of pcbdata.drawings[layername][layer]) {
|
|
if (["segment", "arc", "circle", "curve", "rect"].includes(d.type)) {
|
|
drawedge(ctx, scalefactor, d, edgeColor);
|
|
} else if (d.type == "polygon") {
|
|
drawPolygonShape(ctx, d, polygonColor);
|
|
} else {
|
|
drawText(ctx, d, textColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawTracks(canvas, layer, color, highlight) {
|
|
ctx = canvas.getContext("2d");
|
|
ctx.strokeStyle = color;
|
|
ctx.lineCap = "round";
|
|
for(var track of pcbdata.tracks[layer]) {
|
|
if (highlight && highlightedNet != track.net) continue;
|
|
ctx.lineWidth = track.width;
|
|
ctx.beginPath();
|
|
if ('radius' in track) {
|
|
ctx.arc(
|
|
...track.center,
|
|
track.radius,
|
|
deg2rad(track.startangle),
|
|
deg2rad(track.endangle));
|
|
} else {
|
|
ctx.moveTo(...track.start);
|
|
ctx.lineTo(...track.end);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function drawZones(canvas, layer, color, highlight) {
|
|
ctx = canvas.getContext("2d");
|
|
ctx.strokeStyle = color;
|
|
ctx.fillStyle = color;
|
|
ctx.lineJoin = "round";
|
|
for(var zone of pcbdata.zones[layer]) {
|
|
if (!zone.path2d) {
|
|
zone.path2d = getPolygonsPath(zone);
|
|
}
|
|
if (highlight && highlightedNet != zone.net) continue;
|
|
ctx.fill(zone.path2d);
|
|
if (zone.width > 0) {
|
|
ctx.lineWidth = zone.width;
|
|
ctx.stroke(zone.path2d);
|
|
}
|
|
}
|
|
}
|
|
|
|
function clearCanvas(canvas, color = null) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.save();
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
if (color) {
|
|
ctx.fillStyle = color;
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
} else {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawNets(canvas, layer, highlight) {
|
|
var style = getComputedStyle(topmostdiv);
|
|
if (settings.renderTracks) {
|
|
var trackColor = style.getPropertyValue(highlight ? '--track-color-highlight' : '--track-color');
|
|
drawTracks(canvas, layer, trackColor, highlight);
|
|
}
|
|
if (settings.renderZones) {
|
|
var zoneColor = style.getPropertyValue(highlight ? '--zone-color-highlight' : '--zone-color');
|
|
drawZones(canvas, layer, zoneColor, highlight);
|
|
}
|
|
if (highlight && settings.renderPads) {
|
|
var padColor = style.getPropertyValue('--pad-color-highlight');
|
|
var padHoleColor = style.getPropertyValue('--pad-hole-color');
|
|
var ctx = canvas.getContext("2d");
|
|
for (var footprint of pcbdata.footprints) {
|
|
// draw pads
|
|
var padDrawn = false;
|
|
for (var pad of footprint.pads) {
|
|
if (highlightedNet != pad.net) continue;
|
|
if (pad.layers.includes(layer)) {
|
|
drawPad(ctx, pad, padColor, false);
|
|
padDrawn = true;
|
|
}
|
|
}
|
|
if (padDrawn) {
|
|
// redraw all pad holes because some pads may overlap
|
|
for (var pad of footprint.pads) {
|
|
drawPadHole(ctx, pad, padHoleColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawHighlightsOnLayer(canvasdict, clear = true) {
|
|
if (clear) {
|
|
clearCanvas(canvasdict.highlight);
|
|
}
|
|
if (highlightedFootprints.length > 0) {
|
|
drawFootprints(canvasdict.highlight, canvasdict.layer,
|
|
canvasdict.transform.s * canvasdict.transform.zoom, true);
|
|
}
|
|
if (highlightedNet !== null) {
|
|
drawNets(canvasdict.highlight, canvasdict.layer, true);
|
|
}
|
|
}
|
|
|
|
function drawHighlights() {
|
|
drawHighlightsOnLayer(allcanvas.front);
|
|
drawHighlightsOnLayer(allcanvas.back);
|
|
}
|
|
|
|
function drawBackground(canvasdict, clear = true) {
|
|
if (clear) {
|
|
clearCanvas(canvasdict.bg);
|
|
clearCanvas(canvasdict.fab);
|
|
clearCanvas(canvasdict.silk);
|
|
}
|
|
|
|
drawNets(canvasdict.bg, canvasdict.layer, false);
|
|
drawFootprints(canvasdict.bg, canvasdict.layer,
|
|
canvasdict.transform.s * canvasdict.transform.zoom, false);
|
|
|
|
drawEdgeCuts(canvasdict.bg, canvasdict.transform.s);
|
|
|
|
var style = getComputedStyle(topmostdiv);
|
|
var edgeColor = style.getPropertyValue('--silkscreen-edge-color');
|
|
var polygonColor = style.getPropertyValue('--silkscreen-polygon-color');
|
|
var textColor = style.getPropertyValue('--silkscreen-text-color');
|
|
if (settings.renderSilkscreen) {
|
|
drawBgLayer(
|
|
"silkscreen", canvasdict.silk, canvasdict.layer,
|
|
canvasdict.transform.s * canvasdict.transform.zoom,
|
|
edgeColor, polygonColor, textColor);
|
|
}
|
|
edgeColor = style.getPropertyValue('--fabrication-edge-color');
|
|
polygonColor = style.getPropertyValue('--fabrication-polygon-color');
|
|
textColor = style.getPropertyValue('--fabrication-text-color');
|
|
if (settings.renderFabrication) {
|
|
drawBgLayer(
|
|
"fabrication", canvasdict.fab, canvasdict.layer,
|
|
canvasdict.transform.s * canvasdict.transform.zoom,
|
|
edgeColor, polygonColor, textColor);
|
|
}
|
|
}
|
|
|
|
function prepareCanvas(canvas, flip, transform) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
var fontsize = 1.55;
|
|
ctx.scale(transform.zoom, transform.zoom);
|
|
ctx.translate(transform.panx, transform.pany);
|
|
if (flip) {
|
|
ctx.scale(-1, 1);
|
|
}
|
|
ctx.translate(transform.x, transform.y);
|
|
ctx.rotate(deg2rad(settings.boardRotation));
|
|
ctx.scale(transform.s, transform.s);
|
|
}
|
|
|
|
function prepareLayer(canvasdict) {
|
|
var flip = (canvasdict.layer == "B");
|
|
for (var c of ["bg", "fab", "silk", "highlight"]) {
|
|
prepareCanvas(canvasdict[c], flip, canvasdict.transform);
|
|
}
|
|
}
|
|
|
|
function rotateVector(v, angle) {
|
|
angle = deg2rad(angle);
|
|
return [
|
|
v[0] * Math.cos(angle) - v[1] * Math.sin(angle),
|
|
v[0] * Math.sin(angle) + v[1] * Math.cos(angle)
|
|
];
|
|
}
|
|
|
|
function applyRotation(bbox) {
|
|
var corners = [
|
|
[bbox.minx, bbox.miny],
|
|
[bbox.minx, bbox.maxy],
|
|
[bbox.maxx, bbox.miny],
|
|
[bbox.maxx, bbox.maxy],
|
|
];
|
|
corners = corners.map((v) => rotateVector(v, settings.boardRotation));
|
|
return {
|
|
minx: corners.reduce((a, v) => Math.min(a, v[0]), Infinity),
|
|
miny: corners.reduce((a, v) => Math.min(a, v[1]), Infinity),
|
|
maxx: corners.reduce((a, v) => Math.max(a, v[0]), -Infinity),
|
|
maxy: corners.reduce((a, v) => Math.max(a, v[1]), -Infinity),
|
|
}
|
|
}
|
|
|
|
function recalcLayerScale(layerdict, width, height) {
|
|
var bbox = applyRotation(pcbdata.edges_bbox);
|
|
var scalefactor = 0.98 * Math.min(
|
|
width / (bbox.maxx - bbox.minx),
|
|
height / (bbox.maxy - bbox.miny)
|
|
);
|
|
if (scalefactor < 0.1) {
|
|
scalefactor = 1;
|
|
}
|
|
layerdict.transform.s = scalefactor;
|
|
var flip = (layerdict.layer == "B");
|
|
if (flip) {
|
|
layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor + width) * 0.5;
|
|
} else {
|
|
layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor - width) * 0.5;
|
|
}
|
|
layerdict.transform.y = -((bbox.maxy + bbox.miny) * scalefactor - height) * 0.5;
|
|
for (var c of ["bg", "fab", "silk", "highlight"]) {
|
|
canvas = layerdict[c];
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
canvas.style.width = (width / devicePixelRatio) + "px";
|
|
canvas.style.height = (height / devicePixelRatio) + "px";
|
|
}
|
|
}
|
|
|
|
function redrawCanvas(layerdict) {
|
|
prepareLayer(layerdict);
|
|
drawBackground(layerdict);
|
|
drawHighlightsOnLayer(layerdict);
|
|
}
|
|
|
|
function resizeCanvas(layerdict) {
|
|
var canvasdivid = {
|
|
"F": "frontcanvas",
|
|
"B": "backcanvas"
|
|
} [layerdict.layer];
|
|
var width = document.getElementById(canvasdivid).clientWidth * devicePixelRatio;
|
|
var height = document.getElementById(canvasdivid).clientHeight * devicePixelRatio;
|
|
recalcLayerScale(layerdict, width, height);
|
|
redrawCanvas(layerdict);
|
|
}
|
|
|
|
function resizeAll() {
|
|
resizeCanvas(allcanvas.front);
|
|
resizeCanvas(allcanvas.back);
|
|
}
|
|
|
|
function pointWithinDistanceToSegment(x, y, x1, y1, x2, y2, d) {
|
|
var A = x - x1;
|
|
var B = y - y1;
|
|
var C = x2 - x1;
|
|
var D = y2 - y1;
|
|
|
|
var dot = A * C + B * D;
|
|
var len_sq = C * C + D * D;
|
|
var dx, dy;
|
|
if (len_sq == 0) {
|
|
// start and end of the segment coincide
|
|
dx = x - x1;
|
|
dy = y - y1;
|
|
} else {
|
|
var param = dot / len_sq;
|
|
var xx, yy;
|
|
if (param < 0) {
|
|
xx = x1;
|
|
yy = y1;
|
|
} else if (param > 1) {
|
|
xx = x2;
|
|
yy = y2;
|
|
} else {
|
|
xx = x1 + param * C;
|
|
yy = y1 + param * D;
|
|
}
|
|
dx = x - xx;
|
|
dy = y - yy;
|
|
}
|
|
return dx * dx + dy * dy <= d * d;
|
|
}
|
|
|
|
function modulo(n, mod) {
|
|
return ((n % mod) + mod ) % mod;
|
|
}
|
|
|
|
function pointWithinDistanceToArc(x, y, xc, yc, radius, startangle, endangle, d) {
|
|
var dx = x - xc;
|
|
var dy = y - yc;
|
|
var r_sq = dx * dx + dy * dy;
|
|
var rmin = Math.max(0, radius-d);
|
|
var rmax = radius + d;
|
|
|
|
if (r_sq < rmin * rmin || r_sq > rmax * rmax)
|
|
return false;
|
|
|
|
var angle1 = modulo(deg2rad(startangle), 2 * Math.PI);
|
|
var dx1 = xc + radius * Math.cos(angle1) - x;
|
|
var dy1 = yc + radius * Math.sin(angle1) - y;
|
|
if (dx1 * dx1 + dy1 * dy1 <= d * d)
|
|
return true;
|
|
|
|
var angle2 = modulo(deg2rad(endangle), 2 * Math.PI);
|
|
var dx2 = xc + radius * Math.cos(angle2) - x;
|
|
var dy2 = yc + radius * Math.sin(angle2) - y;
|
|
if (dx2 * dx2 + dy2 * dy2 <= d * d)
|
|
return true;
|
|
|
|
var angle = modulo(Math.atan2(dy, dx), 2 * Math.PI);
|
|
if (angle1 > angle2)
|
|
return (angle >= angle2 || angle <= angle1);
|
|
else
|
|
return (angle >= angle1 && angle <= angle2);
|
|
}
|
|
|
|
function pointWithinPad(x, y, pad) {
|
|
var v = [x - pad.pos[0], y - pad.pos[1]];
|
|
v = rotateVector(v, -pad.angle);
|
|
if (pad.offset) {
|
|
v[0] -= pad.offset[0];
|
|
v[1] -= pad.offset[1];
|
|
}
|
|
return emptyContext2d.isPointInPath(getCachedPadPath(pad), ...v);
|
|
}
|
|
|
|
function netHitScan(layer, x, y) {
|
|
// Check track segments
|
|
if (settings.renderTracks && pcbdata.tracks) {
|
|
for(var track of pcbdata.tracks[layer]) {
|
|
if ('radius' in track) {
|
|
if (pointWithinDistanceToArc(x, y, ...track.center, track.radius, track.startangle, track.endangle, track.width / 2)) {
|
|
return track.net;
|
|
}
|
|
} else {
|
|
if (pointWithinDistanceToSegment(x, y, ...track.start, ...track.end, track.width / 2)) {
|
|
return track.net;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check pads
|
|
if (settings.renderPads) {
|
|
for (var footprint of pcbdata.footprints) {
|
|
for(var pad of footprint.pads) {
|
|
if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
|
|
return pad.net;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function pointWithinFootprintBbox(x, y, bbox) {
|
|
var v = [x - bbox.pos[0], y - bbox.pos[1]];
|
|
v = rotateVector(v, bbox.angle);
|
|
return bbox.relpos[0] <= v[0] && v[0] <= bbox.relpos[0] + bbox.size[0] &&
|
|
bbox.relpos[1] <= v[1] && v[1] <= bbox.relpos[1] + bbox.size[1];
|
|
}
|
|
|
|
function bboxHitScan(layer, x, y) {
|
|
var result = [];
|
|
for (var i = 0; i < pcbdata.footprints.length; i++) {
|
|
var footprint = pcbdata.footprints[i];
|
|
if (footprint.layer == layer) {
|
|
if (pointWithinFootprintBbox(x, y, footprint.bbox)) {
|
|
result.push(i);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function handlePointerDown(e, layerdict) {
|
|
if (e.button != 0 && e.button != 1) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (!e.hasOwnProperty("offsetX")) {
|
|
// The polyfill doesn't set this properly
|
|
e.offsetX = e.pageX - e.currentTarget.offsetLeft;
|
|
e.offsetY = e.pageY - e.currentTarget.offsetTop;
|
|
}
|
|
|
|
layerdict.pointerStates[e.pointerId] = {
|
|
distanceTravelled: 0,
|
|
lastX: e.offsetX,
|
|
lastY: e.offsetY,
|
|
downTime: Date.now(),
|
|
};
|
|
}
|
|
|
|
function handleMouseClick(e, layerdict) {
|
|
if (!e.hasOwnProperty("offsetX")) {
|
|
// The polyfill doesn't set this properly
|
|
e.offsetX = e.pageX - e.currentTarget.offsetLeft;
|
|
e.offsetY = e.pageY - e.currentTarget.offsetTop;
|
|
}
|
|
|
|
var x = e.offsetX;
|
|
var y = e.offsetY;
|
|
var t = layerdict.transform;
|
|
if (layerdict.layer == "B") {
|
|
x = (devicePixelRatio * x / t.zoom - t.panx + t.x) / -t.s;
|
|
} else {
|
|
x = (devicePixelRatio * x / t.zoom - t.panx - t.x) / t.s;
|
|
}
|
|
y = (devicePixelRatio * y / t.zoom - t.y - t.pany) / t.s;
|
|
var v = rotateVector([x, y], -settings.boardRotation);
|
|
if ("nets" in pcbdata) {
|
|
var net = netHitScan(layerdict.layer, ...v);
|
|
if (net !== highlightedNet) {
|
|
netClicked(net);
|
|
}
|
|
}
|
|
if (highlightedNet === null) {
|
|
var footprints = bboxHitScan(layerdict.layer, ...v);
|
|
if (footprints.length > 0) {
|
|
footprintsClicked(footprints);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handlePointerLeave(e, layerdict) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (!settings.redrawOnDrag) {
|
|
redrawCanvas(layerdict);
|
|
}
|
|
|
|
delete layerdict.pointerStates[e.pointerId];
|
|
}
|
|
|
|
function resetTransform(layerdict) {
|
|
layerdict.transform.panx = 0;
|
|
layerdict.transform.pany = 0;
|
|
layerdict.transform.zoom = 1;
|
|
redrawCanvas(layerdict);
|
|
}
|
|
|
|
function handlePointerUp(e, layerdict) {
|
|
if (!e.hasOwnProperty("offsetX")) {
|
|
// The polyfill doesn't set this properly
|
|
e.offsetX = e.pageX - e.currentTarget.offsetLeft;
|
|
e.offsetY = e.pageY - e.currentTarget.offsetTop;
|
|
}
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (e.button == 2) {
|
|
// Reset pan and zoom on right click.
|
|
resetTransform(layerdict);
|
|
layerdict.anotherPointerTapped = false;
|
|
return;
|
|
}
|
|
|
|
// We haven't necessarily had a pointermove event since the interaction started, so make sure we update this now
|
|
var ptr = layerdict.pointerStates[e.pointerId];
|
|
ptr.distanceTravelled += Math.abs(e.offsetX - ptr.lastX) + Math.abs(e.offsetY - ptr.lastY);
|
|
|
|
if (e.button == 0 && ptr.distanceTravelled < 10 && Date.now() - ptr.downTime <= 500) {
|
|
if (Object.keys(layerdict.pointerStates).length == 1) {
|
|
if (layerdict.anotherPointerTapped) {
|
|
// This is the second pointer coming off of a two-finger tap
|
|
resetTransform(layerdict);
|
|
} else {
|
|
// This is just a regular tap
|
|
handleMouseClick(e, layerdict);
|
|
}
|
|
layerdict.anotherPointerTapped = false;
|
|
} else {
|
|
// This is the first finger coming off of what could become a two-finger tap
|
|
layerdict.anotherPointerTapped = true;
|
|
}
|
|
} else {
|
|
if (!settings.redrawOnDrag) {
|
|
redrawCanvas(layerdict);
|
|
}
|
|
layerdict.anotherPointerTapped = false;
|
|
}
|
|
|
|
delete layerdict.pointerStates[e.pointerId];
|
|
}
|
|
|
|
function handlePointerMove(e, layerdict) {
|
|
if (!layerdict.pointerStates.hasOwnProperty(e.pointerId)) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (!e.hasOwnProperty("offsetX")) {
|
|
// The polyfill doesn't set this properly
|
|
e.offsetX = e.pageX - e.currentTarget.offsetLeft;
|
|
e.offsetY = e.pageY - e.currentTarget.offsetTop;
|
|
}
|
|
|
|
var thisPtr = layerdict.pointerStates[e.pointerId];
|
|
|
|
var dx = e.offsetX - thisPtr.lastX;
|
|
var dy = e.offsetY - thisPtr.lastY;
|
|
|
|
// If this number is low on pointer up, we count the action as a click
|
|
thisPtr.distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
|
|
|
if (Object.keys(layerdict.pointerStates).length == 1) {
|
|
// This is a simple drag
|
|
layerdict.transform.panx += devicePixelRatio * dx / layerdict.transform.zoom;
|
|
layerdict.transform.pany += devicePixelRatio * dy / layerdict.transform.zoom;
|
|
} else if (Object.keys(layerdict.pointerStates).length == 2) {
|
|
var otherPtr = Object.values(layerdict.pointerStates).filter((ptr) => ptr != thisPtr)[0];
|
|
|
|
var oldDist = Math.sqrt(Math.pow(thisPtr.lastX - otherPtr.lastX, 2) + Math.pow(thisPtr.lastY - otherPtr.lastY, 2));
|
|
var newDist = Math.sqrt(Math.pow(e.offsetX - otherPtr.lastX, 2) + Math.pow(e.offsetY - otherPtr.lastY, 2));
|
|
|
|
var scaleFactor = newDist/oldDist;
|
|
|
|
if (scaleFactor != NaN) {
|
|
layerdict.transform.zoom *= scaleFactor;
|
|
|
|
var zoomd = (1 - scaleFactor) / layerdict.transform.zoom;
|
|
layerdict.transform.panx += devicePixelRatio * otherPtr.lastX * zoomd;
|
|
layerdict.transform.pany += devicePixelRatio * otherPtr.lastY * zoomd;
|
|
}
|
|
}
|
|
|
|
thisPtr.lastX = e.offsetX;
|
|
thisPtr.lastY = e.offsetY;
|
|
|
|
if (settings.redrawOnDrag) {
|
|
redrawCanvas(layerdict);
|
|
}
|
|
}
|
|
|
|
function handleMouseWheel(e, layerdict) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var t = layerdict.transform;
|
|
var wheeldelta = e.deltaY;
|
|
if (e.deltaMode == 1) {
|
|
// FF only, scroll by lines
|
|
wheeldelta *= 30;
|
|
} else if (e.deltaMode == 2) {
|
|
wheeldelta *= 300;
|
|
}
|
|
var m = Math.pow(1.1, -wheeldelta / 40);
|
|
// Limit amount of zoom per tick.
|
|
if (m > 2) {
|
|
m = 2;
|
|
} else if (m < 0.5) {
|
|
m = 0.5;
|
|
}
|
|
t.zoom *= m;
|
|
var zoomd = (1 - m) / t.zoom;
|
|
t.panx += devicePixelRatio * e.offsetX * zoomd;
|
|
t.pany += devicePixelRatio * e.offsetY * zoomd;
|
|
redrawCanvas(layerdict);
|
|
}
|
|
|
|
function addMouseHandlers(div, layerdict) {
|
|
div.addEventListener("pointerdown", function(e) {
|
|
handlePointerDown(e, layerdict);
|
|
});
|
|
div.addEventListener("pointermove", function(e) {
|
|
handlePointerMove(e, layerdict);
|
|
});
|
|
div.addEventListener("pointerup", function(e) {
|
|
handlePointerUp(e, layerdict);
|
|
});
|
|
var pointerleave = function(e) {
|
|
handlePointerLeave(e, layerdict);
|
|
}
|
|
div.addEventListener("pointercancel", pointerleave);
|
|
div.addEventListener("pointerleave", pointerleave);
|
|
div.addEventListener("pointerout", pointerleave);
|
|
|
|
div.onwheel = function(e) {
|
|
handleMouseWheel(e, layerdict);
|
|
}
|
|
for (var element of [div, layerdict.bg, layerdict.fab, layerdict.silk, layerdict.highlight]) {
|
|
element.addEventListener("contextmenu", function(e) {
|
|
e.preventDefault();
|
|
}, false);
|
|
}
|
|
}
|
|
|
|
function setRedrawOnDrag(value) {
|
|
settings.redrawOnDrag = value;
|
|
writeStorage("redrawOnDrag", value);
|
|
}
|
|
|
|
function setBoardRotation(value) {
|
|
settings.boardRotation = value * 5;
|
|
writeStorage("boardRotation", settings.boardRotation);
|
|
document.getElementById("rotationDegree").textContent = settings.boardRotation;
|
|
resizeAll();
|
|
}
|
|
|
|
function initRender() {
|
|
allcanvas = {
|
|
front: {
|
|
transform: {
|
|
x: 0,
|
|
y: 0,
|
|
s: 1,
|
|
panx: 0,
|
|
pany: 0,
|
|
zoom: 1,
|
|
},
|
|
pointerStates: {},
|
|
anotherPointerTapped: false,
|
|
bg: document.getElementById("F_bg"),
|
|
fab: document.getElementById("F_fab"),
|
|
silk: document.getElementById("F_slk"),
|
|
highlight: document.getElementById("F_hl"),
|
|
layer: "F",
|
|
},
|
|
back: {
|
|
transform: {
|
|
x: 0,
|
|
y: 0,
|
|
s: 1,
|
|
panx: 0,
|
|
pany: 0,
|
|
zoom: 1,
|
|
},
|
|
pointerStates: {},
|
|
anotherPointerTapped: false,
|
|
bg: document.getElementById("B_bg"),
|
|
fab: document.getElementById("B_fab"),
|
|
silk: document.getElementById("B_slk"),
|
|
highlight: document.getElementById("B_hl"),
|
|
layer: "B",
|
|
}
|
|
};
|
|
addMouseHandlers(document.getElementById("frontcanvas"), allcanvas.front);
|
|
addMouseHandlers(document.getElementById("backcanvas"), allcanvas.back);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* DOM manipulation and misc code */
|
|
|
|
var bomsplit;
|
|
var canvassplit;
|
|
var initDone = false;
|
|
var bomSortFunction = null;
|
|
var currentSortColumn = null;
|
|
var currentSortOrder = null;
|
|
var currentHighlightedRowId;
|
|
var highlightHandlers = [];
|
|
var footprintIndexToHandler = {};
|
|
var netsToHandler = {};
|
|
var highlightedFootprints = [];
|
|
var highlightedNet = null;
|
|
var lastClicked;
|
|
|
|
function dbg(html) {
|
|
dbgdiv.innerHTML = html;
|
|
}
|
|
|
|
function redrawIfInitDone() {
|
|
if (initDone) {
|
|
redrawCanvas(allcanvas.front);
|
|
redrawCanvas(allcanvas.back);
|
|
}
|
|
}
|
|
|
|
function padsVisible(value) {
|
|
writeStorage("padsVisible", value);
|
|
settings.renderPads = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function referencesVisible(value) {
|
|
writeStorage("referencesVisible", value);
|
|
settings.renderReferences = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function valuesVisible(value) {
|
|
writeStorage("valuesVisible", value);
|
|
settings.renderValues = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function tracksVisible(value) {
|
|
writeStorage("tracksVisible", value);
|
|
settings.renderTracks = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function zonesVisible(value) {
|
|
writeStorage("zonesVisible", value);
|
|
settings.renderZones = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function dnpOutline(value) {
|
|
writeStorage("dnpOutline", value);
|
|
settings.renderDnpOutline = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function setDarkMode(value) {
|
|
if (value) {
|
|
topmostdiv.classList.add("dark");
|
|
} else {
|
|
topmostdiv.classList.remove("dark");
|
|
}
|
|
writeStorage("darkmode", value);
|
|
settings.darkMode = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function setFullscreen(value) {
|
|
if (value) {
|
|
document.documentElement.requestFullscreen();
|
|
} else {
|
|
document.exitFullscreen();
|
|
}
|
|
}
|
|
|
|
function fabricationVisible(value) {
|
|
writeStorage("fabricationVisible", value);
|
|
settings.renderFabrication = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function silkscreenVisible(value) {
|
|
writeStorage("silkscreenVisible", value);
|
|
settings.renderSilkscreen = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function setHighlightPin1(value) {
|
|
writeStorage("highlightpin1", value);
|
|
settings.highlightpin1 = value;
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function getStoredCheckboxRefs(checkbox) {
|
|
function convert(ref) {
|
|
var intref = parseInt(ref);
|
|
if (isNaN(intref)) {
|
|
for (var i = 0; i < pcbdata.footprints.length; i++) {
|
|
if (pcbdata.footprints[i].ref == ref) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
} else {
|
|
return intref;
|
|
}
|
|
}
|
|
if (!(checkbox in settings.checkboxStoredRefs)) {
|
|
var val = readStorage("checkbox_" + checkbox);
|
|
settings.checkboxStoredRefs[checkbox] = val ? val : "";
|
|
}
|
|
if (!settings.checkboxStoredRefs[checkbox]) {
|
|
return new Set();
|
|
} else {
|
|
return new Set(settings.checkboxStoredRefs[checkbox].split(",").map(r => convert(r)).filter(a => a >= 0));
|
|
}
|
|
}
|
|
|
|
function getCheckboxState(checkbox, references) {
|
|
var storedRefsSet = getStoredCheckboxRefs(checkbox);
|
|
var currentRefsSet = new Set(references.map(r => r[1]));
|
|
// Get difference of current - stored
|
|
var difference = new Set(currentRefsSet);
|
|
for (ref of storedRefsSet) {
|
|
difference.delete(ref);
|
|
}
|
|
if (difference.size == 0) {
|
|
// All the current refs are stored
|
|
return "checked";
|
|
} else if (difference.size == currentRefsSet.size) {
|
|
// None of the current refs are stored
|
|
return "unchecked";
|
|
} else {
|
|
// Some of the refs are stored
|
|
return "indeterminate";
|
|
}
|
|
}
|
|
|
|
function setBomCheckboxState(checkbox, element, references) {
|
|
var state = getCheckboxState(checkbox, references);
|
|
element.checked = (state == "checked");
|
|
element.indeterminate = (state == "indeterminate");
|
|
}
|
|
|
|
function createCheckboxChangeHandler(checkbox, references, row) {
|
|
return function() {
|
|
refsSet = getStoredCheckboxRefs(checkbox);
|
|
var darkenWhenChecked = settings.darkenWhenChecked == checkbox;
|
|
eventArgs = {
|
|
checkbox: checkbox,
|
|
refs: references,
|
|
}
|
|
if (this.checked) {
|
|
// checkbox ticked
|
|
for (var ref of references) {
|
|
refsSet.add(ref[1]);
|
|
}
|
|
if (darkenWhenChecked) {
|
|
row.classList.add("checked");
|
|
}
|
|
eventArgs.state = 'checked';
|
|
} else {
|
|
// checkbox unticked
|
|
for (var ref of references) {
|
|
refsSet.delete(ref[1]);
|
|
}
|
|
if (darkenWhenChecked) {
|
|
row.classList.remove("checked");
|
|
}
|
|
eventArgs.state = 'unchecked';
|
|
}
|
|
settings.checkboxStoredRefs[checkbox] = [...refsSet].join(",");
|
|
writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
|
|
updateCheckboxStats(checkbox);
|
|
EventHandler.emitEvent(IBOM_EVENT_TYPES.CHECKBOX_CHANGE_EVENT, eventArgs);
|
|
}
|
|
}
|
|
|
|
function clearHighlightedFootprints() {
|
|
if (currentHighlightedRowId) {
|
|
document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
|
|
currentHighlightedRowId = null;
|
|
highlightedFootprints = [];
|
|
highlightedNet = null;
|
|
}
|
|
}
|
|
|
|
function createRowHighlightHandler(rowid, refs, net) {
|
|
return function() {
|
|
if (currentHighlightedRowId) {
|
|
if (currentHighlightedRowId == rowid) {
|
|
return;
|
|
}
|
|
document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
|
|
}
|
|
document.getElementById(rowid).classList.add("highlighted");
|
|
currentHighlightedRowId = rowid;
|
|
highlightedFootprints = refs ? refs.map(r => r[1]) : [];
|
|
highlightedNet = net;
|
|
drawHighlights();
|
|
EventHandler.emitEvent(
|
|
IBOM_EVENT_TYPES.HIGHLIGHT_EVENT,
|
|
{
|
|
rowid: rowid,
|
|
refs: refs,
|
|
net: net
|
|
});
|
|
}
|
|
}
|
|
|
|
function entryMatches(entry) {
|
|
if (settings.bommode == "netlist") {
|
|
// entry is just a net name
|
|
return entry.toLowerCase().indexOf(filter) >= 0;
|
|
}
|
|
// check refs
|
|
for (var ref of entry[3]) {
|
|
if (ref[0].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
// check extra fields
|
|
for (var i in config.extra_fields) {
|
|
if (entry[4][i].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
// check value
|
|
if (entry[1].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
// check footprint
|
|
if (entry[2].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findRefInEntry(entry) {
|
|
return entry[3].filter(r => r[0].toLowerCase() == reflookup);
|
|
}
|
|
|
|
function highlightFilter(s) {
|
|
if (!filter) {
|
|
return s;
|
|
}
|
|
var parts = s.toLowerCase().split(filter);
|
|
if (parts.length == 1) {
|
|
return s;
|
|
}
|
|
var r = "";
|
|
var pos = 0;
|
|
for (var i in parts) {
|
|
if (i > 0) {
|
|
r += '<mark class="highlight">' +
|
|
s.substring(pos, pos + filter.length) +
|
|
'</mark>';
|
|
pos += filter.length;
|
|
}
|
|
r += s.substring(pos, pos + parts[i].length);
|
|
pos += parts[i].length;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
function checkboxSetUnsetAllHandler(checkboxname) {
|
|
return function() {
|
|
var checkboxnum = 0;
|
|
while (checkboxnum < settings.checkboxes.length &&
|
|
settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
|
|
checkboxnum++;
|
|
}
|
|
if (checkboxnum >= settings.checkboxes.length) {
|
|
return;
|
|
}
|
|
var allset = true;
|
|
var checkbox;
|
|
var row;
|
|
for (row of bombody.childNodes) {
|
|
checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
|
|
if (!checkbox.checked || checkbox.indeterminate) {
|
|
allset = false;
|
|
break;
|
|
}
|
|
}
|
|
for (row of bombody.childNodes) {
|
|
checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
|
|
checkbox.checked = !allset;
|
|
checkbox.indeterminate = false;
|
|
checkbox.onchange();
|
|
}
|
|
}
|
|
}
|
|
|
|
function createColumnHeader(name, cls, comparator) {
|
|
var th = document.createElement("TH");
|
|
th.innerHTML = name;
|
|
th.classList.add(cls);
|
|
th.style.cursor = "pointer";
|
|
var span = document.createElement("SPAN");
|
|
span.classList.add("sortmark");
|
|
span.classList.add("none");
|
|
th.appendChild(span);
|
|
th.onclick = function() {
|
|
if (currentSortColumn && this !== currentSortColumn) {
|
|
// Currently sorted by another column
|
|
currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
|
|
currentSortColumn.childNodes[1].classList.add("none");
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
}
|
|
if (currentSortColumn && this === currentSortColumn) {
|
|
// Already sorted by this column
|
|
if (currentSortOrder == "asc") {
|
|
// Sort by this column, descending order
|
|
bomSortFunction = function(a, b) {
|
|
return -comparator(a, b);
|
|
}
|
|
currentSortColumn.childNodes[1].classList.remove("asc");
|
|
currentSortColumn.childNodes[1].classList.add("desc");
|
|
currentSortOrder = "desc";
|
|
} else {
|
|
// Unsort
|
|
bomSortFunction = null;
|
|
currentSortColumn.childNodes[1].classList.remove("desc");
|
|
currentSortColumn.childNodes[1].classList.add("none");
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
}
|
|
} else {
|
|
// Sort by this column, ascending order
|
|
bomSortFunction = comparator;
|
|
currentSortColumn = this;
|
|
currentSortColumn.childNodes[1].classList.remove("none");
|
|
currentSortColumn.childNodes[1].classList.add("asc");
|
|
currentSortOrder = "asc";
|
|
}
|
|
populateBomBody();
|
|
}
|
|
return th;
|
|
}
|
|
|
|
function populateBomHeader() {
|
|
while (bomhead.firstChild) {
|
|
bomhead.removeChild(bomhead.firstChild);
|
|
}
|
|
var tr = document.createElement("TR");
|
|
var th = document.createElement("TH");
|
|
th.classList.add("numCol");
|
|
tr.appendChild(th);
|
|
var checkboxCompareClosure = function(checkbox) {
|
|
return (a, b) => {
|
|
var stateA = getCheckboxState(checkbox, a[3]);
|
|
var stateB = getCheckboxState(checkbox, b[3]);
|
|
if (stateA > stateB) return -1;
|
|
if (stateA < stateB) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
if (settings.bommode == "netlist") {
|
|
th = createColumnHeader("Net name", "bom-netname", (a, b) => {
|
|
if (a > b) return -1;
|
|
if (a < b) return 1;
|
|
return 0;
|
|
});
|
|
tr.appendChild(th);
|
|
} else {
|
|
for (var checkbox of settings.checkboxes) {
|
|
th = createColumnHeader(
|
|
checkbox, "bom-checkbox", checkboxCompareClosure(checkbox));
|
|
th.onclick = fancyDblClickHandler(
|
|
th, th.onclick.bind(th), checkboxSetUnsetAllHandler(checkbox));
|
|
tr.appendChild(th);
|
|
}
|
|
tr.appendChild(createColumnHeader("References", "References", (a, b) => {
|
|
var i = 0;
|
|
while (i < a[3].length && i < b[3].length) {
|
|
if (a[3][i] != b[3][i]) return a[3][i] > b[3][i] ? 1 : -1;
|
|
i++;
|
|
}
|
|
return a[3].length - b[3].length;
|
|
}));
|
|
// Extra fields
|
|
if (config.extra_fields.length > 0) {
|
|
var extraFieldCompareClosure = function(fieldIndex) {
|
|
return (a, b) => {
|
|
var fa = a[4][fieldIndex];
|
|
var fb = b[4][fieldIndex];
|
|
if (fa != fb) return fa > fb ? 1 : -1;
|
|
else return 0;
|
|
}
|
|
}
|
|
for (var i in config.extra_fields) {
|
|
tr.appendChild(createColumnHeader(
|
|
config.extra_fields[i], "extra", extraFieldCompareClosure(i)));
|
|
}
|
|
}
|
|
tr.appendChild(createColumnHeader("Value", "Value", (a, b) => {
|
|
return valueCompare(a[5], b[5], a[1], b[1]);
|
|
}));
|
|
tr.appendChild(createColumnHeader("Footprint", "Footprint", (a, b) => {
|
|
if (a[2] != b[2]) return a[2] > b[2] ? 1 : -1;
|
|
else return 0;
|
|
}));
|
|
if (settings.bommode == "grouped") {
|
|
tr.appendChild(createColumnHeader("Quantity", "Quantity", (a, b) => {
|
|
return a[3].length - b[3].length;
|
|
}));
|
|
}
|
|
}
|
|
bomhead.appendChild(tr);
|
|
}
|
|
|
|
function populateBomBody() {
|
|
while (bom.firstChild) {
|
|
bom.removeChild(bom.firstChild);
|
|
}
|
|
highlightHandlers = [];
|
|
footprintIndexToHandler = {};
|
|
netsToHandler = {};
|
|
currentHighlightedRowId = null;
|
|
var first = true;
|
|
if (settings.bommode == "netlist") {
|
|
bomtable = pcbdata.nets.slice();
|
|
} else {
|
|
switch (settings.canvaslayout) {
|
|
case 'F':
|
|
bomtable = pcbdata.bom.F.slice();
|
|
break;
|
|
case 'FB':
|
|
bomtable = pcbdata.bom.both.slice();
|
|
break;
|
|
case 'B':
|
|
bomtable = pcbdata.bom.B.slice();
|
|
break;
|
|
}
|
|
if (settings.bommode == "ungrouped") {
|
|
// expand bom table
|
|
expandedTable = []
|
|
for (var bomentry of bomtable) {
|
|
for (var ref of bomentry[3]) {
|
|
expandedTable.push([1, bomentry[1], bomentry[2], [ref], bomentry[4], bomentry[5]]);
|
|
}
|
|
}
|
|
bomtable = expandedTable;
|
|
}
|
|
}
|
|
if (bomSortFunction) {
|
|
bomtable = bomtable.sort(bomSortFunction);
|
|
}
|
|
for (var i in bomtable) {
|
|
var bomentry = bomtable[i];
|
|
if (filter && !entryMatches(bomentry)) {
|
|
continue;
|
|
}
|
|
var references = null;
|
|
var netname = null;
|
|
var tr = document.createElement("TR");
|
|
var td = document.createElement("TD");
|
|
var rownum = +i + 1;
|
|
tr.id = "bomrow" + rownum;
|
|
td.textContent = rownum;
|
|
tr.appendChild(td);
|
|
if (settings.bommode == "netlist") {
|
|
netname = bomentry;
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(netname ? netname : "<no net>");
|
|
tr.appendChild(td);
|
|
} else {
|
|
if (reflookup) {
|
|
references = findRefInEntry(bomentry);
|
|
if (references.length == 0) {
|
|
continue;
|
|
}
|
|
} else {
|
|
references = bomentry[3];
|
|
}
|
|
// Checkboxes
|
|
for (var checkbox of settings.checkboxes) {
|
|
if (checkbox) {
|
|
td = document.createElement("TD");
|
|
var input = document.createElement("input");
|
|
input.type = "checkbox";
|
|
input.onchange = createCheckboxChangeHandler(checkbox, references, tr);
|
|
setBomCheckboxState(checkbox, input, references);
|
|
if (input.checked && settings.darkenWhenChecked == checkbox) {
|
|
tr.classList.add("checked");
|
|
}
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
// References
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
|
|
tr.appendChild(td);
|
|
// Extra fields
|
|
for (var i in config.extra_fields) {
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[4][i]);
|
|
tr.appendChild(td);
|
|
}
|
|
// Value
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[1]);
|
|
tr.appendChild(td);
|
|
// Footprint
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[2]);
|
|
tr.appendChild(td);
|
|
if (settings.bommode == "grouped") {
|
|
// Quantity
|
|
td = document.createElement("TD");
|
|
td.textContent = bomentry[3].length;
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
bom.appendChild(tr);
|
|
var handler = createRowHighlightHandler(tr.id, references, netname);
|
|
tr.onmousemove = handler;
|
|
highlightHandlers.push({
|
|
id: tr.id,
|
|
handler: handler,
|
|
});
|
|
if (references !== null) {
|
|
for (var refIndex of references.map(r => r[1])) {
|
|
footprintIndexToHandler[refIndex] = handler;
|
|
}
|
|
}
|
|
if (netname !== null) {
|
|
netsToHandler[netname] = handler;
|
|
}
|
|
if ((filter || reflookup) && first) {
|
|
handler();
|
|
first = false;
|
|
}
|
|
}
|
|
EventHandler.emitEvent(
|
|
IBOM_EVENT_TYPES.BOM_BODY_CHANGE_EVENT,
|
|
{
|
|
filter: filter,
|
|
reflookup: reflookup,
|
|
checkboxes: settings.checkboxes,
|
|
bommode: settings.bommode,
|
|
});
|
|
}
|
|
|
|
function highlightPreviousRow() {
|
|
if (!currentHighlightedRowId) {
|
|
highlightHandlers[highlightHandlers.length - 1].handler();
|
|
} else {
|
|
if (highlightHandlers.length > 1 &&
|
|
highlightHandlers[0].id == currentHighlightedRowId) {
|
|
highlightHandlers[highlightHandlers.length - 1].handler();
|
|
} else {
|
|
for (var i = 0; i < highlightHandlers.length - 1; i++) {
|
|
if (highlightHandlers[i + 1].id == currentHighlightedRowId) {
|
|
highlightHandlers[i].handler();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
}
|
|
|
|
function highlightNextRow() {
|
|
if (!currentHighlightedRowId) {
|
|
highlightHandlers[0].handler();
|
|
} else {
|
|
if (highlightHandlers.length > 1 &&
|
|
highlightHandlers[highlightHandlers.length - 1].id == currentHighlightedRowId) {
|
|
highlightHandlers[0].handler();
|
|
} else {
|
|
for (var i = 1; i < highlightHandlers.length; i++) {
|
|
if (highlightHandlers[i - 1].id == currentHighlightedRowId) {
|
|
highlightHandlers[i].handler();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
}
|
|
|
|
function populateBomTable() {
|
|
populateBomHeader();
|
|
populateBomBody();
|
|
}
|
|
|
|
function footprintsClicked(footprintIndexes) {
|
|
var lastClickedIndex = footprintIndexes.indexOf(lastClicked);
|
|
for (var i = 1; i <= footprintIndexes.length; i++) {
|
|
var refIndex = footprintIndexes[(lastClickedIndex + i) % footprintIndexes.length];
|
|
if (refIndex in footprintIndexToHandler) {
|
|
lastClicked = refIndex;
|
|
footprintIndexToHandler[refIndex]();
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function netClicked(net) {
|
|
if (net in netsToHandler) {
|
|
netsToHandler[net]();
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
} else {
|
|
clearHighlightedFootprints();
|
|
highlightedNet = net;
|
|
drawHighlights();
|
|
}
|
|
}
|
|
|
|
function updateFilter(input) {
|
|
filter = input.toLowerCase();
|
|
populateBomTable();
|
|
}
|
|
|
|
function updateRefLookup(input) {
|
|
reflookup = input.toLowerCase();
|
|
populateBomTable();
|
|
}
|
|
|
|
function changeCanvasLayout(layout) {
|
|
document.getElementById("fl-btn").classList.remove("depressed");
|
|
document.getElementById("fb-btn").classList.remove("depressed");
|
|
document.getElementById("bl-btn").classList.remove("depressed");
|
|
switch (layout) {
|
|
case 'F':
|
|
document.getElementById("fl-btn").classList.add("depressed");
|
|
if (settings.bomlayout != "bom-only") {
|
|
canvassplit.collapse(1);
|
|
}
|
|
break;
|
|
case 'B':
|
|
document.getElementById("bl-btn").classList.add("depressed");
|
|
if (settings.bomlayout != "bom-only") {
|
|
canvassplit.collapse(0);
|
|
}
|
|
break;
|
|
default:
|
|
document.getElementById("fb-btn").classList.add("depressed");
|
|
if (settings.bomlayout != "bom-only") {
|
|
canvassplit.setSizes([50, 50]);
|
|
}
|
|
}
|
|
settings.canvaslayout = layout;
|
|
writeStorage("canvaslayout", layout);
|
|
resizeAll();
|
|
changeBomMode(settings.bommode);
|
|
}
|
|
|
|
function populateMetadata() {
|
|
document.getElementById("title").innerHTML = pcbdata.metadata.title;
|
|
document.getElementById("revision").innerHTML = "Rev: " + pcbdata.metadata.revision;
|
|
document.getElementById("company").innerHTML = pcbdata.metadata.company;
|
|
document.getElementById("filedate").innerHTML = pcbdata.metadata.date;
|
|
if (pcbdata.metadata.title != "") {
|
|
document.title = pcbdata.metadata.title + " BOM";
|
|
}
|
|
// Calculate board stats
|
|
var fp_f = 0, fp_b = 0, pads_f = 0, pads_b = 0, pads_th = 0;
|
|
for (var i = 0; i < pcbdata.footprints.length; i++) {
|
|
if (pcbdata.bom.skipped.includes(i)) continue;
|
|
var mod = pcbdata.footprints[i];
|
|
if (mod.layer == "F") {
|
|
fp_f++;
|
|
} else {
|
|
fp_b++;
|
|
}
|
|
for (var pad of mod.pads) {
|
|
if (pad.type == "th") {
|
|
pads_th++;
|
|
} else {
|
|
if (pad.layers.includes("F")) {
|
|
pads_f++;
|
|
}
|
|
if (pad.layers.includes("B")) {
|
|
pads_b++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
document.getElementById("stats-components-front").innerHTML = fp_f;
|
|
document.getElementById("stats-components-back").innerHTML = fp_b;
|
|
document.getElementById("stats-components-total").innerHTML = fp_f + fp_b;
|
|
document.getElementById("stats-groups-front").innerHTML = pcbdata.bom.F.length;
|
|
document.getElementById("stats-groups-back").innerHTML = pcbdata.bom.B.length;
|
|
document.getElementById("stats-groups-total").innerHTML = pcbdata.bom.both.length;
|
|
document.getElementById("stats-smd-pads-front").innerHTML = pads_f;
|
|
document.getElementById("stats-smd-pads-back").innerHTML = pads_b;
|
|
document.getElementById("stats-smd-pads-total").innerHTML = pads_f + pads_b;
|
|
document.getElementById("stats-th-pads").innerHTML = pads_th;
|
|
// Update version string
|
|
document.getElementById("github-link").innerHTML = "InteractiveHtmlBom " +
|
|
/^v\d+\.\d+/.exec(pcbdata.ibom_version)[0];
|
|
}
|
|
|
|
function changeBomLayout(layout) {
|
|
document.getElementById("bom-btn").classList.remove("depressed");
|
|
document.getElementById("lr-btn").classList.remove("depressed");
|
|
document.getElementById("tb-btn").classList.remove("depressed");
|
|
switch (layout) {
|
|
case 'bom-only':
|
|
document.getElementById("bom-btn").classList.add("depressed");
|
|
if (bomsplit) {
|
|
bomsplit.destroy();
|
|
bomsplit = null;
|
|
canvassplit.destroy();
|
|
canvassplit = null;
|
|
}
|
|
document.getElementById("frontcanvas").style.display = "none";
|
|
document.getElementById("backcanvas").style.display = "none";
|
|
document.getElementById("bot").style.height = "";
|
|
break;
|
|
case 'top-bottom':
|
|
document.getElementById("tb-btn").classList.add("depressed");
|
|
document.getElementById("frontcanvas").style.display = "";
|
|
document.getElementById("backcanvas").style.display = "";
|
|
document.getElementById("bot").style.height = "calc(100% - 80px)";
|
|
document.getElementById("bomdiv").classList.remove("split-horizontal");
|
|
document.getElementById("canvasdiv").classList.remove("split-horizontal");
|
|
document.getElementById("frontcanvas").classList.add("split-horizontal");
|
|
document.getElementById("backcanvas").classList.add("split-horizontal");
|
|
if (bomsplit) {
|
|
bomsplit.destroy();
|
|
bomsplit = null;
|
|
canvassplit.destroy();
|
|
canvassplit = null;
|
|
}
|
|
bomsplit = Split(['#bomdiv', '#canvasdiv'], {
|
|
sizes: [50, 50],
|
|
onDragEnd: resizeAll,
|
|
direction: "vertical",
|
|
gutterSize: 5
|
|
});
|
|
canvassplit = Split(['#frontcanvas', '#backcanvas'], {
|
|
sizes: [50, 50],
|
|
gutterSize: 5,
|
|
onDragEnd: resizeAll
|
|
});
|
|
break;
|
|
case 'left-right':
|
|
document.getElementById("lr-btn").classList.add("depressed");
|
|
document.getElementById("frontcanvas").style.display = "";
|
|
document.getElementById("backcanvas").style.display = "";
|
|
document.getElementById("bot").style.height = "calc(100% - 80px)";
|
|
document.getElementById("bomdiv").classList.add("split-horizontal");
|
|
document.getElementById("canvasdiv").classList.add("split-horizontal");
|
|
document.getElementById("frontcanvas").classList.remove("split-horizontal");
|
|
document.getElementById("backcanvas").classList.remove("split-horizontal");
|
|
if (bomsplit) {
|
|
bomsplit.destroy();
|
|
bomsplit = null;
|
|
canvassplit.destroy();
|
|
canvassplit = null;
|
|
}
|
|
bomsplit = Split(['#bomdiv', '#canvasdiv'], {
|
|
sizes: [50, 50],
|
|
onDragEnd: resizeAll,
|
|
gutterSize: 5
|
|
});
|
|
canvassplit = Split(['#frontcanvas', '#backcanvas'], {
|
|
sizes: [50, 50],
|
|
gutterSize: 5,
|
|
direction: "vertical",
|
|
onDragEnd: resizeAll
|
|
});
|
|
}
|
|
settings.bomlayout = layout;
|
|
writeStorage("bomlayout", layout);
|
|
changeCanvasLayout(settings.canvaslayout);
|
|
}
|
|
|
|
function changeBomMode(mode) {
|
|
document.getElementById("bom-grouped-btn").classList.remove("depressed");
|
|
document.getElementById("bom-ungrouped-btn").classList.remove("depressed");
|
|
document.getElementById("bom-netlist-btn").classList.remove("depressed");
|
|
switch (mode) {
|
|
case 'grouped':
|
|
document.getElementById("bom-grouped-btn").classList.add("depressed");
|
|
break;
|
|
case 'ungrouped':
|
|
document.getElementById("bom-ungrouped-btn").classList.add("depressed");
|
|
break;
|
|
case 'netlist':
|
|
document.getElementById("bom-netlist-btn").classList.add("depressed");
|
|
}
|
|
writeStorage("bommode", mode);
|
|
if (mode != settings.bommode) {
|
|
settings.bommode = mode;
|
|
bomSortFunction = null;
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
clearHighlightedFootprints();
|
|
}
|
|
populateBomTable();
|
|
}
|
|
|
|
function focusFilterField() {
|
|
focusInputField(document.getElementById("filter"));
|
|
}
|
|
|
|
function focusRefLookupField() {
|
|
focusInputField(document.getElementById("reflookup"));
|
|
}
|
|
|
|
function toggleBomCheckbox(bomrowid, checkboxnum) {
|
|
if (!bomrowid || checkboxnum > settings.checkboxes.length) {
|
|
return;
|
|
}
|
|
var bomrow = document.getElementById(bomrowid);
|
|
var checkbox = bomrow.childNodes[checkboxnum].childNodes[0];
|
|
checkbox.checked = !checkbox.checked;
|
|
checkbox.indeterminate = false;
|
|
checkbox.onchange();
|
|
}
|
|
|
|
function checkBomCheckbox(bomrowid, checkboxname) {
|
|
var checkboxnum = 0;
|
|
while (checkboxnum < settings.checkboxes.length &&
|
|
settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
|
|
checkboxnum++;
|
|
}
|
|
if (!bomrowid || checkboxnum >= settings.checkboxes.length) {
|
|
return;
|
|
}
|
|
var bomrow = document.getElementById(bomrowid);
|
|
var checkbox = bomrow.childNodes[checkboxnum + 1].childNodes[0];
|
|
checkbox.checked = true;
|
|
checkbox.indeterminate = false;
|
|
checkbox.onchange();
|
|
}
|
|
|
|
function setBomCheckboxes(value) {
|
|
writeStorage("bomCheckboxes", value);
|
|
settings.checkboxes = value.split(",").filter((e) => e);
|
|
prepCheckboxes();
|
|
populateBomTable();
|
|
populateDarkenWhenCheckedOptions();
|
|
}
|
|
|
|
function setDarkenWhenChecked(value) {
|
|
writeStorage("darkenWhenChecked", value);
|
|
settings.darkenWhenChecked = value;
|
|
populateBomTable();
|
|
}
|
|
|
|
function prepCheckboxes() {
|
|
var table = document.getElementById("checkbox-stats");
|
|
while (table.childElementCount > 1) {
|
|
table.removeChild(table.lastChild);
|
|
}
|
|
if (settings.checkboxes.length) {
|
|
table.style.display = "";
|
|
} else {
|
|
table.style.display = "none";
|
|
}
|
|
for (var checkbox of settings.checkboxes) {
|
|
var tr = document.createElement("TR");
|
|
var td = document.createElement("TD");
|
|
td.innerHTML = checkbox;
|
|
tr.appendChild(td);
|
|
td = document.createElement("TD");
|
|
td.id = "checkbox-stats-" + checkbox;
|
|
var progressbar = document.createElement("div");
|
|
progressbar.classList.add("bar");
|
|
td.appendChild(progressbar);
|
|
var text = document.createElement("div");
|
|
text.classList.add("text");
|
|
td.appendChild(text);
|
|
tr.appendChild(td);
|
|
table.appendChild(tr);
|
|
updateCheckboxStats(checkbox);
|
|
}
|
|
}
|
|
|
|
function populateDarkenWhenCheckedOptions() {
|
|
var container = document.getElementById("darkenWhenCheckedContainer");
|
|
|
|
if (settings.checkboxes.length == 0) {
|
|
container.parentElement.style.display = "none";
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = '';
|
|
container.parentElement.style.display = "inline-block";
|
|
|
|
function createOption(name, displayName) {
|
|
var id = "darkenWhenChecked-" + name;
|
|
|
|
var div = document.createElement("div");
|
|
div.classList.add("radio-container");
|
|
|
|
var input = document.createElement("input");
|
|
input.type = "radio";
|
|
input.name = "darkenWhenChecked";
|
|
input.value = name;
|
|
input.id = id;
|
|
input.onchange = () => setDarkenWhenChecked(name);
|
|
div.appendChild(input);
|
|
|
|
// Preserve the selected element when the checkboxes change
|
|
if (name == settings.darkenWhenChecked) {
|
|
input.checked = true;
|
|
}
|
|
|
|
var label = document.createElement("label");
|
|
label.innerHTML = displayName;
|
|
label.htmlFor = id;
|
|
div.appendChild(label);
|
|
|
|
container.appendChild(div);
|
|
}
|
|
createOption("", "None");
|
|
for (var checkbox of settings.checkboxes) {
|
|
createOption(checkbox, checkbox);
|
|
}
|
|
}
|
|
|
|
function updateCheckboxStats(checkbox) {
|
|
var checked = getStoredCheckboxRefs(checkbox).size;
|
|
var total = pcbdata.footprints.length - pcbdata.bom.skipped.length;
|
|
var percent = checked * 100.0 / total;
|
|
var td = document.getElementById("checkbox-stats-" + checkbox);
|
|
td.firstChild.style.width = percent + "%";
|
|
td.lastChild.innerHTML = checked + "/" + total + " (" + Math.round(percent) + "%)";
|
|
}
|
|
|
|
document.onkeydown = function(e) {
|
|
switch (e.key) {
|
|
case "n":
|
|
if (document.activeElement.type == "text") {
|
|
return;
|
|
}
|
|
if (currentHighlightedRowId !== null) {
|
|
checkBomCheckbox(currentHighlightedRowId, "placed");
|
|
highlightNextRow();
|
|
e.preventDefault();
|
|
}
|
|
break;
|
|
case "ArrowUp":
|
|
highlightPreviousRow();
|
|
e.preventDefault();
|
|
break;
|
|
case "ArrowDown":
|
|
highlightNextRow();
|
|
e.preventDefault();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (e.altKey) {
|
|
switch (e.key) {
|
|
case "f":
|
|
focusFilterField();
|
|
e.preventDefault();
|
|
break;
|
|
case "r":
|
|
focusRefLookupField();
|
|
e.preventDefault();
|
|
break;
|
|
case "z":
|
|
changeBomLayout("bom-only");
|
|
e.preventDefault();
|
|
break;
|
|
case "x":
|
|
changeBomLayout("left-right");
|
|
e.preventDefault();
|
|
break;
|
|
case "c":
|
|
changeBomLayout("top-bottom");
|
|
e.preventDefault();
|
|
break;
|
|
case "v":
|
|
changeCanvasLayout("F");
|
|
e.preventDefault();
|
|
break;
|
|
case "b":
|
|
changeCanvasLayout("FB");
|
|
e.preventDefault();
|
|
break;
|
|
case "n":
|
|
changeCanvasLayout("B");
|
|
e.preventDefault();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (e.key >= '1' && e.key <= '9') {
|
|
toggleBomCheckbox(currentHighlightedRowId, parseInt(e.key));
|
|
}
|
|
}
|
|
}
|
|
|
|
function hideNetlistButton() {
|
|
document.getElementById("bom-ungrouped-btn").classList.remove("middle-button");
|
|
document.getElementById("bom-ungrouped-btn").classList.add("right-most-button");
|
|
document.getElementById("bom-netlist-btn").style.display = "none";
|
|
}
|
|
|
|
window.onload = function(e) {
|
|
initUtils();
|
|
initRender();
|
|
initStorage();
|
|
initDefaults();
|
|
cleanGutters();
|
|
populateMetadata();
|
|
dbgdiv = document.getElementById("dbg");
|
|
bom = document.getElementById("bombody");
|
|
bomhead = document.getElementById("bomhead");
|
|
filter = "";
|
|
reflookup = "";
|
|
if (!("nets" in pcbdata)) {
|
|
hideNetlistButton();
|
|
}
|
|
initDone = true;
|
|
prepCheckboxes();
|
|
// Triggers render
|
|
changeBomLayout(settings.bomlayout);
|
|
|
|
// Users may leave fullscreen without touching the checkbox. Uncheck.
|
|
document.addEventListener('fullscreenchange', () => {
|
|
if (!document.fullscreenElement)
|
|
document.getElementById('fullscreenCheckbox').checked = false;
|
|
});
|
|
}
|
|
|
|
window.onresize = resizeAll;
|
|
window.matchMedia("print").addListener(resizeAll);
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="topmostdiv" class="topmostdiv">
|
|
<div id="top">
|
|
<div style="float: right; height: 100%;">
|
|
<div class="hideonprint menu" style="float: right; top: 8px;">
|
|
<button class="menubtn"></button>
|
|
<div class="menu-content">
|
|
<label class="menu-label menu-label-top" style="width: calc(50% - 18px)">
|
|
<input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
|
|
Dark mode
|
|
</label><!-- This comment eats space! All of it!
|
|
--><label class="menu-label menu-label-top" style="width: calc(50% - 17px); border-left: 0;">
|
|
<input id="fullscreenCheckbox" type="checkbox" onchange="setFullscreen(this.checked)">
|
|
Full Screen
|
|
</label>
|
|
<label class="menu-label" style="width: calc(50% - 18px)">
|
|
<input id="fabricationCheckbox" type="checkbox" checked onchange="fabricationVisible(this.checked)">
|
|
Fab layer
|
|
</label><!-- This comment eats space! All of it!
|
|
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
|
|
<input id="silkscreenCheckbox" type="checkbox" checked onchange="silkscreenVisible(this.checked)">
|
|
Silkscreen
|
|
</label>
|
|
<label class="menu-label" style="width: calc(50% - 18px)">
|
|
<input id="referencesCheckbox" type="checkbox" checked onchange="referencesVisible(this.checked)">
|
|
References
|
|
</label><!-- This comment eats space! All of it!
|
|
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
|
|
<input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
|
|
Values
|
|
</label>
|
|
<div id="tracksAndZonesCheckboxes">
|
|
<label class="menu-label" style="width: calc(50% - 18px)">
|
|
<input id="tracksCheckbox" type="checkbox" checked onchange="tracksVisible(this.checked)">
|
|
Tracks
|
|
</label><!-- This comment eats space! All of it!
|
|
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
|
|
<input id="zonesCheckbox" type="checkbox" checked onchange="zonesVisible(this.checked)">
|
|
Zones
|
|
</label>
|
|
</div>
|
|
<label class="menu-label" style="width: calc(50% - 18px)">
|
|
<input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
|
|
Pads
|
|
</label><!-- This comment eats space! All of it!
|
|
--><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
|
|
<input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
|
|
DNP outlined
|
|
</label>
|
|
<label class="menu-label">
|
|
<input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
|
|
Highlight first pin
|
|
</label>
|
|
<label class="menu-label">
|
|
<input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
|
|
Continuous redraw on drag
|
|
</label>
|
|
<label class="menu-label">
|
|
<span>Board rotation</span>
|
|
<span style="float: right"><span id="rotationDegree">0</span>°</span>
|
|
<input id="boardRotation" type="range" min="-36" max="36" value="0" class="slider" oninput="setBoardRotation(this.value)">
|
|
</label>
|
|
<label class="menu-label">
|
|
<div style="margin-left: 5px">Bom checkboxes</div>
|
|
<input id="bomCheckboxes" class="menu-textbox" type=text
|
|
oninput="setBomCheckboxes(this.value)">
|
|
</label>
|
|
<label class="menu-label">
|
|
<div style="margin-left: 5px">Darken when checked</div>
|
|
<div id="darkenWhenCheckedContainer"></div>
|
|
</label>
|
|
<label class="menu-label">
|
|
<span class="shameless-plug">
|
|
<span>Created using</span>
|
|
<a id="github-link" target="blank" href="https://github.com/openscopeproject/InteractiveHtmlBom">InteractiveHtmlBom</a>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="button-container hideonprint"
|
|
style="float: right; position: relative; top: 8px">
|
|
<button id="fl-btn" class="left-most-button" onclick="changeCanvasLayout('F')"
|
|
title="Front only">F
|
|
</button>
|
|
<button id="fb-btn" class="middle-button" onclick="changeCanvasLayout('FB')"
|
|
title="Front and Back">FB
|
|
</button>
|
|
<button id="bl-btn" class="right-most-button" onclick="changeCanvasLayout('B')"
|
|
title="Back only">B
|
|
</button>
|
|
</div>
|
|
<div class="button-container hideonprint"
|
|
style="float: right; position: relative; top: 8px">
|
|
<button id="bom-btn" class="left-most-button" onclick="changeBomLayout('bom-only')"
|
|
title="BOM only"></button>
|
|
<button id="lr-btn" class="middle-button" onclick="changeBomLayout('left-right')"
|
|
title="BOM left, drawings right"></button>
|
|
<button id="tb-btn" class="right-most-button" onclick="changeBomLayout('top-bottom')"
|
|
title="BOM top, drawings bot"></button>
|
|
</div>
|
|
<div class="button-container hideonprint"
|
|
style="float: right; position: relative; top: 8px">
|
|
<button id="bom-grouped-btn" class="left-most-button" onclick="changeBomMode('grouped')"
|
|
title="Grouped BOM"></button>
|
|
<button id="bom-ungrouped-btn" class="middle-button" onclick="changeBomMode('ungrouped')"
|
|
title="Ungrouped BOM"></button>
|
|
<button id="bom-netlist-btn" class="right-most-button" onclick="changeBomMode('netlist')"
|
|
title="Netlist"></button>
|
|
</div>
|
|
<div class="hideonprint menu" style="float: right; top: 8px;">
|
|
<button class="statsbtn"></button>
|
|
<div class="menu-content">
|
|
<table class="stats">
|
|
<tbody>
|
|
<tr>
|
|
<td width="40%">Board stats</td>
|
|
<td>Front</td>
|
|
<td>Back</td>
|
|
<td>Total</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Components</td>
|
|
<td id="stats-components-front">~</td>
|
|
<td id="stats-components-back">~</td>
|
|
<td id="stats-components-total">~</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Groups</td>
|
|
<td id="stats-groups-front">~</td>
|
|
<td id="stats-groups-back">~</td>
|
|
<td id="stats-groups-total">~</td>
|
|
</tr>
|
|
<tr>
|
|
<td>SMD pads</td>
|
|
<td id="stats-smd-pads-front">~</td>
|
|
<td id="stats-smd-pads-back">~</td>
|
|
<td id="stats-smd-pads-total">~</td>
|
|
</tr>
|
|
<tr>
|
|
<td>TH pads</td>
|
|
<td colspan=3 id="stats-th-pads">~</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<table class="stats">
|
|
<col width="40%"/><col />
|
|
<tbody id="checkbox-stats">
|
|
<tr>
|
|
<td colspan=2 style="border-top: 0">Checkboxes</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="hideonprint menu" style="float: right; top: 8px;">
|
|
<button class="iobtn"></button>
|
|
<div class="menu-content">
|
|
<div class="menu-label menu-label-top">
|
|
<div style="margin-left: 5px;">Save board image</div>
|
|
<div class="flexbox">
|
|
<input id="render-save-width" class="menu-textbox" type="text" value="1000" placeholder="Width"
|
|
style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
|
|
<span>X</span>
|
|
<input id="render-save-height" class="menu-textbox" type="text" value="1000" placeholder="Height"
|
|
style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
|
|
</div>
|
|
<label>
|
|
<input id="render-save-transparent" type="checkbox">
|
|
Transparent background
|
|
</label>
|
|
<div class="flexbox">
|
|
<button class="savebtn" onclick="saveImage('F')">Front</button>
|
|
<button class="savebtn" onclick="saveImage('B')">Back</button>
|
|
</div>
|
|
</div>
|
|
<div class="menu-label">
|
|
<span style="margin-left: 5px;">Config and checkbox state</span>
|
|
<div class="flexbox">
|
|
<button class="savebtn" onclick="saveSettings()">Export</button>
|
|
<button class="savebtn" onclick="loadSettings()">Import</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="fileinfodiv" style="overflow: auto;">
|
|
<table class="fileinfo">
|
|
<tbody>
|
|
<tr>
|
|
<td id="title" class="title" style="width: 70%">
|
|
Title
|
|
</td>
|
|
<td id="revision" class="title" style="width: 30%">
|
|
Revision
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td id="company">
|
|
Company
|
|
</td>
|
|
<td id="filedate">
|
|
Date
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div id="bot" class="split" style="height: calc(100% - 80px)">
|
|
<div id="bomdiv" class="split split-horizontal">
|
|
<div style="width: 100%">
|
|
<input id="reflookup" class="textbox searchbox reflookup hideonprint" type="text" placeholder="Ref lookup"
|
|
oninput="updateRefLookup(this.value)">
|
|
<input id="filter" class="textbox searchbox filter hideonprint" type="text" placeholder="Filter"
|
|
oninput="updateFilter(this.value)">
|
|
<div class="button-container hideonprint" style="float: left; margin: 0;">
|
|
<button id="copy" title="Copy bom table to clipboard"
|
|
onclick="copyToClipboard()"></button>
|
|
</div>
|
|
</div>
|
|
<div id="dbg"></div>
|
|
<table class="bom">
|
|
<thead id="bomhead">
|
|
</thead>
|
|
<tbody id="bombody">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div id="canvasdiv" class="split split-horizontal">
|
|
<div id="frontcanvas" class="split" touch-action="none" style="overflow: hidden">
|
|
<div style="position: relative; width: 100%; height: 100%;">
|
|
<canvas id="F_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
|
|
<canvas id="F_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
|
|
<canvas id="F_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
|
|
<canvas id="F_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
|
|
</div>
|
|
</div>
|
|
<div id="backcanvas" class="split" touch-action="none" style="overflow: hidden">
|
|
<div style="position: relative; width: 100%; height: 100%;">
|
|
<canvas id="B_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
|
|
<canvas id="B_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
|
|
<canvas id="B_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
|
|
<canvas id="B_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|