mirror of
https://github.com/Djeeberjr/fw-anwesenheit.git
synced 2025-10-13 23:16:37 +00:00
4710 lines
238 KiB
HTML
4710 lines
238 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;
|
|
--pad-color-highlight-both: #D0D040;
|
|
--pad-color-highlight-marked: #44a344;
|
|
--pin1-outline-color: #ffb629;
|
|
--pin1-outline-color-highlight: #ffb629;
|
|
--pin1-outline-color-highlight-both: #fcbb39;
|
|
--pin1-outline-color-highlight-marked: #fdbe41;
|
|
--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: 0.4rem 0.4rem 0.4rem 0;
|
|
}
|
|
|
|
.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: 3px;
|
|
}
|
|
|
|
.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;
|
|
position: relative;
|
|
}
|
|
|
|
.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: #1cb53d;
|
|
}
|
|
|
|
.dark .bom tr.checked {
|
|
color: #2cce54;
|
|
}
|
|
|
|
.bom tr {
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.bom .numCol {
|
|
width: 30px;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.visbtn {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='none' stroke='%23333' d='M2.5 4.5h5v15h-5zM9.5 4.5h5v15h-5zM16.5 4.5h5v15h-5z'/%3E%3C/svg%3E");
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
padding: 15px;
|
|
}
|
|
|
|
#vismenu-content {
|
|
left: 0px;
|
|
font-family: Verdana, sans-serif;
|
|
}
|
|
|
|
.dark .statsbtn,
|
|
.dark .savebtn,
|
|
.dark .menubtn,
|
|
.dark .iobtn,
|
|
.dark .visbtn {
|
|
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: 0.4rem 0.4rem 0.4rem 0;
|
|
}
|
|
|
|
.menu-content {
|
|
font-size: 12pt !important;
|
|
text-align: left !important;
|
|
font-weight: normal !important;
|
|
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 {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
background-color: white;
|
|
transition: background-color 0.3s;
|
|
min-height: 100%;
|
|
}
|
|
|
|
#top {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
}
|
|
|
|
#topdivider {
|
|
border-bottom: 2px solid black;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.dark #topdivider {
|
|
border-bottom: 2px solid #ccc;
|
|
}
|
|
|
|
#topdivider>div {
|
|
position: relative;
|
|
}
|
|
|
|
#toptoggle {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
position: absolute;
|
|
padding: 0.1rem 0.3rem;
|
|
top: -0.4rem;
|
|
left: -1rem;
|
|
font-size: 1.4rem;
|
|
line-height: 60%;
|
|
border: 1px solid black;
|
|
border-radius: 1rem;
|
|
background-color: #fff;
|
|
z-index: 100;
|
|
}
|
|
|
|
.flipped {
|
|
transform: rotate(0.5turn);
|
|
}
|
|
|
|
.dark #toptoggle {
|
|
border: 1px solid #fff;
|
|
background-color: #222;
|
|
}
|
|
|
|
#fileinfodiv {
|
|
flex: 20rem 1 0;
|
|
overflow: auto;
|
|
}
|
|
|
|
#bomcontrols {
|
|
display: flex;
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
#bomcontrols>* {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
.placeholder {
|
|
border: 1px dashed #9f9fda !important;
|
|
background-color: #edf2f7 !important;
|
|
}
|
|
|
|
.dragging {
|
|
z-index: 999;
|
|
}
|
|
|
|
.dark .dragging>table>tbody>tr {
|
|
background-color: #252c30;
|
|
}
|
|
|
|
.dark .placeholder {
|
|
filter: invert(1);
|
|
}
|
|
|
|
.column-spacer {
|
|
top: 0;
|
|
left: 0;
|
|
width: calc(100% - 4px);
|
|
position: absolute;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
height: 100%;
|
|
}
|
|
|
|
.column-width-handle {
|
|
top: 0;
|
|
right: 0;
|
|
width: 4px;
|
|
position: absolute;
|
|
cursor: col-resize;
|
|
user-select: none;
|
|
height: 100%;
|
|
}
|
|
|
|
.column-width-handle:hover {
|
|
background-color: #4f99bd;
|
|
}
|
|
|
|
.help-link {
|
|
border: 1px solid #0278a4;
|
|
padding-inline: 0.3rem;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.dark .help-link {
|
|
border: 1px solid #00b9fd;
|
|
}
|
|
|
|
.bom-color {
|
|
width: 20%;
|
|
}
|
|
|
|
.color-column input {
|
|
width: 1.6rem;
|
|
height: 1rem;
|
|
border: 1px solid black;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
}
|
|
|
|
/* removes default styling from input color element */
|
|
::-webkit-color-swatch {
|
|
border: none;
|
|
}
|
|
|
|
::-webkit-color-swatch-wrapper {
|
|
padding: 0;
|
|
}
|
|
|
|
::-moz-color-swatch,
|
|
::-moz-focus-inner {
|
|
border: none;
|
|
}
|
|
|
|
::-moz-focus-inner {
|
|
padding: 0;
|
|
}
|
|
|
|
</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 = {"dark_mode": false, "show_pads": true, "show_fabrication": false, "show_silkscreen": true, "highlight_pin1": "none", "redraw_on_drag": true, "board_rotation": 0, "checkboxes": "Sourced,Placed", "bom_view": "left-right", "layer_view": "FB", "offset_back_rotation": false, "kicad_text_formatting": true, "fields": ["Value", "Footprint"]}
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
var pcbdata = JSON.parse(LZString.decompressFromBase64("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FkAUuCAAB1YgABgRAANADjgzuC+oLNg+E8GjVyg4iATvwvXG+Cu4Ozg6mCH4Ktg7WCtoObgkABe+xCnDNBOoOvg1BDE4Lvg0BCiEPAQkhDbYIwQ4mCR4NXg1CCV4KPggWCN4OngjBDh4OCIFwgAJ1IgpKCgYOmnTmC5IPgASrYbBDHQA0DJILngy+DAEI7glWDb4PNghhDe4Mfg5hDB4Mkg7eCQAHZgzmD4AGzXXmCv4I4Qh6DJ4N/g6BCL4JAAfgBsEKAQquC1EMIQjRDiEILgqBDmAEHgOBCEEJoAJBCUEJUQ/BCzoJEAcZB8x1lXEyCJhyrvQ+D7oPXgiAAhYJFg5gAjoHzHNcdSIK4QiJCf4L4QvSCU4MrnERCYIIgg92CkkOPg8xDUkICQuGDK5z7iAIgzAERHNgB4ADXHDYdpdBdHUpDykKigogBRAFgALYBq4LXHbNdB3g7AOSCaICCg+mCUAEhVQ4ctgC8AFwBKkOJAWIA4R0cAOSC1gDgAeE85ICCQkJCsD3CQvJDeELPgvSCBELXHLYAiAAEg7GC6AHQguCDUYPkgxSDxoJUgnGD1IMugfGDtIPYg3SCAkIUQ4IgzINIgoyDckJ4QqJDT4JiQ6xDdEKMgugAlYP2QogAFIKUgxwBjkP4AXGCzkLpgrSC2II4ggJCrEKCIIyDfoMeQsxDlkNeQtOBHYKmHIyDsAFIgtccYiFhQyJDokNwg3oc6IHXYUUCPQOpPSUDDQLzAjcdDQOfAtcdFQKPgZUCowPjSQMDs1w1AolDtQKjAyaB9QI63I0Dc1xNAosDL1x2AC0Dc1ytArUDjwKkAQ8D6UMdAvlCXQIDAiMCTv09A7NdvQOHAv0DwVElQoMDDQLfAhjcdgHlQlUDVUNjA8fVnwMTAzcDdUNTA3VDMwKVQnMDfIAlA6YcNQKLAtB9DQJvA2sCPQNVQxsC6wPvA5cC2wMNAjsDewJsg7sDnwL7Aw0DnUJHAw0DhwOmHE78n0GnAxcDvUJmHc8DnwMfAqQAo0NfAqNCVQK/Al898UIrA1cDVUOmHUcC6UJdQycCTULMApMD3UJdQjcDsAC3AvcCdwMDQmYcXXx7QdcDRwIvAh8DVwJvAikc5rw9oaWQ+hweg5EDRAGogXocCAH5A+ABvCEdHGFVtgGWAbwgfyFoACoggAAA=="))
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* 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 saveBomTable(output) {
|
|
var text = '';
|
|
for (var node of bomhead.childNodes[0].childNodes) {
|
|
if (node.firstChild) {
|
|
var name = node.firstChild.nodeValue ?? "";
|
|
text += (output == 'csv' ? `"${name}"` : name);
|
|
}
|
|
if (node != bomhead.childNodes[0].lastChild) {
|
|
text += (output == 'csv' ? ',' : '\t');
|
|
}
|
|
}
|
|
text += '\n';
|
|
for (var row of bombody.childNodes) {
|
|
for (var cell of row.childNodes) {
|
|
let val = '';
|
|
for (var node of cell.childNodes) {
|
|
if (node.nodeName == "INPUT") {
|
|
if (node.checked) {
|
|
val += '✓';
|
|
}
|
|
} else if ((node.nodeName == "MARK") || (node.nodeName == "A")) {
|
|
val += node.firstChild.nodeValue;
|
|
} else {
|
|
val += node.nodeValue;
|
|
}
|
|
}
|
|
if (output == 'csv') {
|
|
val = val.replace(/\"/g, '\"\"'); // pair of double-quote characters
|
|
if (isNumeric(val)) {
|
|
val = +val; // use number
|
|
} else {
|
|
val = `"${val}"`; // enclosed within double-quote
|
|
}
|
|
}
|
|
text += val;
|
|
if (cell != row.lastChild) {
|
|
text += (output == 'csv' ? ',' : '\t');
|
|
}
|
|
}
|
|
text += '\n';
|
|
}
|
|
|
|
if (output != 'clipboard') {
|
|
// To file: csv or txt
|
|
var blob = new Blob([text], {
|
|
type: `text/${output}`
|
|
});
|
|
saveFile(`${pcbdata.metadata.title}.${output}`, blob);
|
|
} else {
|
|
// To clipboard
|
|
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 isNumeric(str) {
|
|
/* https://stackoverflow.com/a/175787 */
|
|
return (typeof str != "string" ? false : !isNaN(str) && !isNaN(parseFloat(str)));
|
|
}
|
|
|
|
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,
|
|
valueAltRegex: 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.*)?$", "");
|
|
if (config.fields.includes("Value")) {
|
|
var index = config.fields.indexOf("Value");
|
|
pcbdata.bom["parsedValues"] = {};
|
|
var allList = getBomListByLayer('FB').flat();
|
|
for (var id in pcbdata.bom.fields) {
|
|
var ref_key = allList.find(item => item[1] == Number(id)) || [];
|
|
pcbdata.bom.parsedValues[id] = parseValue(pcbdata.bom.fields[id][index], ref_key[0] || '');
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseValue(val, ref) {
|
|
var inferUnit = (unit, ref) => {
|
|
if (unit) {
|
|
unit = unit.toLowerCase();
|
|
if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
|
|
unit = 'r';
|
|
}
|
|
return unit[0];
|
|
}
|
|
|
|
var resarr = /^([a-z]+)\d+$/i.exec(ref);
|
|
switch (Array.isArray(resarr) && resarr[1].toLowerCase()) {
|
|
case "c": return 'f';
|
|
case "l": return 'h';
|
|
case "r":
|
|
case "rv": return 'r';
|
|
}
|
|
return null;
|
|
};
|
|
val = val.replace(/,/g, "");
|
|
var match = units.valueRegex.exec(val);
|
|
if (Array.isArray(match)) {
|
|
var unit = inferUnit(match[3], ref);
|
|
var val_i = parseFloat(match[1]);
|
|
if (!unit) return null;
|
|
if (match[2]) {
|
|
val_i = val_i * units.getMultiplier(match[2]);
|
|
}
|
|
return {
|
|
val: val_i,
|
|
unit: unit,
|
|
extra: match[4],
|
|
}
|
|
}
|
|
|
|
match = units.valueAltRegex.exec(val);
|
|
if (Array.isArray(match) && (match[1] || match[4])) {
|
|
var unit = inferUnit(match[2], ref);
|
|
var val_i = parseFloat(match[1] + "." + match[4]);
|
|
if (!unit) return null;
|
|
if (match[3]) {
|
|
val_i = val_i * units.getMultiplier(match[3]);
|
|
}
|
|
return {
|
|
val: val_i,
|
|
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 resetSettings() {
|
|
if (!confirm(
|
|
`This will reset all checkbox states and other settings.\n\n` +
|
|
`Press OK if you want to continue.`)) {
|
|
return;
|
|
}
|
|
if (storage) {
|
|
var keys = [];
|
|
for (var i = 0; i < storage.length; i++) {
|
|
var key = storage.key(i);
|
|
if (key.startsWith(storagePrefix)) keys.push(key);
|
|
}
|
|
for (var key of keys) storage.removeItem(key);
|
|
}
|
|
location.reload();
|
|
}
|
|
|
|
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("markWhenChecked", settings.markWhenChecked);
|
|
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;
|
|
setHighlightRowOnClick(settings.highlightRowOnClick);
|
|
document.getElementById("highlightRowOnClickCheckbox").checked = settings.highlightRowOnClick;
|
|
setDarkMode(settings.darkMode);
|
|
document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
|
|
setHighlightPin1(settings.highlightpin1);
|
|
document.forms.highlightpin1.highlightpin1.value = settings.highlightpin1;
|
|
writeStorage("boardRotation", settings.boardRotation);
|
|
document.getElementById("boardRotation").value = settings.boardRotation / 5;
|
|
document.getElementById("rotationDegree").textContent = settings.boardRotation;
|
|
setOffsetBackRotation(settings.offsetBackRotation);
|
|
document.getElementById("offsetBackRotationCheckbox").checked = settings.offsetBackRotation;
|
|
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: "FB",
|
|
bomlayout: "left-right",
|
|
bommode: "grouped",
|
|
checkboxes: [],
|
|
checkboxStoredRefs: {},
|
|
darkMode: false,
|
|
highlightpin1: "none",
|
|
redrawOnDrag: true,
|
|
boardRotation: 0,
|
|
offsetBackRotation: false,
|
|
renderPads: true,
|
|
renderReferences: true,
|
|
renderValues: true,
|
|
renderSilkscreen: true,
|
|
renderFabrication: true,
|
|
renderDnpOutline: false,
|
|
renderTracks: true,
|
|
renderZones: true,
|
|
columnOrder: [],
|
|
hiddenColumns: [],
|
|
netColors: {},
|
|
}
|
|
|
|
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 (settings.bommode == "netlist" && !pcbdata.nets) {
|
|
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;
|
|
|
|
var highlightpin1 = readStorage("highlightpin1") || config.highlight_pin1;
|
|
if (highlightpin1 === "false") highlightpin1 = "none";
|
|
if (highlightpin1 === "true") highlightpin1 = "all";
|
|
setHighlightPin1(highlightpin1);
|
|
document.forms.highlightpin1.highlightpin1.value = highlightpin1;
|
|
|
|
settings.markWhenChecked = readStorage("markWhenChecked") || "";
|
|
populateMarkWhenCheckedOptions();
|
|
|
|
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("highlightRowOnClick", false, "highlightRowOnClickCheckbox", setHighlightRowOnClick);
|
|
initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
|
|
|
|
var fields = ["checkboxes", "References"].concat(config.fields).concat(["Quantity"]);
|
|
var hcols = JSON.parse(readStorage("hiddenColumns"));
|
|
if (hcols === null) {
|
|
hcols = [];
|
|
}
|
|
settings.hiddenColumns = hcols.filter(e => fields.includes(e));
|
|
|
|
var cord = JSON.parse(readStorage("columnOrder"));
|
|
if (cord === null) {
|
|
cord = fields;
|
|
} else {
|
|
cord = cord.filter(e => fields.includes(e));
|
|
if (cord.length != fields.length)
|
|
cord = fields;
|
|
}
|
|
settings.columnOrder = cord;
|
|
|
|
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;
|
|
initBooleanSetting("offsetBackRotation", config.offset_back_rotation, "offsetBackRotationCheckbox", setOffsetBackRotation);
|
|
|
|
settings.netColors = JSON.parse(readStorage("netColors")) || {};
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
if ("polygons" in text) {
|
|
ctx.fill(getPolygonsPath(text));
|
|
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 (config.kicad_text_formatting) {
|
|
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.fillStyle = color;
|
|
ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
|
|
ctx.lineCap = "round";
|
|
ctx.lineJoin = "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);
|
|
}
|
|
if("filled" in edge && edge.filled)
|
|
ctx.fill();
|
|
else
|
|
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, scalefactor, shape, color) {
|
|
ctx.save();
|
|
if (!("svgpath" in shape)) {
|
|
ctx.translate(...shape.pos);
|
|
ctx.rotate(deg2rad(-shape.angle));
|
|
}
|
|
if("filled" in shape && !shape.filled) {
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = Math.max(1 / scalefactor, shape.width);
|
|
ctx.lineCap = "round";
|
|
ctx.lineJoin = "round";
|
|
ctx.stroke(getPolygonsPath(shape));
|
|
} else {
|
|
ctx.fillStyle = color;
|
|
ctx.fill(getPolygonsPath(shape));
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawDrawing(ctx, scalefactor, drawing, color) {
|
|
if (["segment", "arc", "circle", "curve", "rect"].includes(drawing.type)) {
|
|
drawedge(ctx, scalefactor, drawing, color);
|
|
} else if (drawing.type == "polygon") {
|
|
drawPolygonShape(ctx, scalefactor, 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 if (pad.drillshape == "rect") {
|
|
ctx.fill(getChamferedRectPath(pad.drillsize, 0, 0, 0));
|
|
} else {
|
|
ctx.fill(getCirclePath(pad.drillsize[0] / 2));
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawFootprint(ctx, layer, scalefactor, footprint, colors, 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 = colors.pad;
|
|
ctx.fillRect(0, 0, ...footprint.bbox.size);
|
|
ctx.globalAlpha = 1;
|
|
ctx.strokeStyle = colors.pad;
|
|
ctx.lineWidth = 3 / scalefactor;
|
|
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, colors.pad);
|
|
}
|
|
}
|
|
ctx.lineWidth = 3 / scalefactor;
|
|
// draw pads
|
|
if (settings.renderPads) {
|
|
for (var pad of footprint.pads) {
|
|
if (pad.layers.includes(layer)) {
|
|
drawPad(ctx, pad, colors.pad, outline);
|
|
if (pad.pin1 &&
|
|
(settings.highlightpin1 == "all" ||
|
|
settings.highlightpin1 == "selected" && highlight)) {
|
|
drawPad(ctx, pad, colors.outline, true);
|
|
}
|
|
}
|
|
}
|
|
for (var pad of footprint.pads) {
|
|
drawPadHole(ctx, pad, colors.padHole);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawEdgeCuts(canvas, scalefactor) {
|
|
var ctx = canvas.getContext("2d");
|
|
var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
|
|
for (var edge of pcbdata.edges) {
|
|
drawDrawing(ctx, scalefactor, edge, edgecolor);
|
|
}
|
|
}
|
|
|
|
function drawFootprints(canvas, layer, scalefactor, highlight) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.lineWidth = 3 / scalefactor;
|
|
var style = getComputedStyle(topmostdiv);
|
|
|
|
var colors = {
|
|
pad: style.getPropertyValue('--pad-color'),
|
|
padHole: style.getPropertyValue('--pad-hole-color'),
|
|
outline: style.getPropertyValue('--pin1-outline-color'),
|
|
}
|
|
|
|
for (var i = 0; i < pcbdata.footprints.length; i++) {
|
|
var mod = pcbdata.footprints[i];
|
|
var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
|
|
var h = highlightedFootprints.includes(i);
|
|
var d = markedFootprints.has(i);
|
|
if (highlight) {
|
|
if(h && d) {
|
|
colors.pad = style.getPropertyValue('--pad-color-highlight-both');
|
|
colors.outline = style.getPropertyValue('--pin1-outline-color-highlight-both');
|
|
} else if (h) {
|
|
colors.pad = style.getPropertyValue('--pad-color-highlight');
|
|
colors.outline = style.getPropertyValue('--pin1-outline-color-highlight');
|
|
} else if (d) {
|
|
colors.pad = style.getPropertyValue('--pad-color-highlight-marked');
|
|
colors.outline = style.getPropertyValue('--pin1-outline-color-highlight-marked');
|
|
}
|
|
}
|
|
if( h || d || !highlight) {
|
|
drawFootprint(ctx, layer, scalefactor, mod, colors, 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, scalefactor, d, polygonColor);
|
|
} else {
|
|
drawText(ctx, d, textColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawTracks(canvas, layer, defaultColor, highlight) {
|
|
ctx = canvas.getContext("2d");
|
|
ctx.lineCap = "round";
|
|
|
|
var hasHole = (track) => (
|
|
'drillsize' in track &&
|
|
track.start[0] == track.end[0] &&
|
|
track.start[1] == track.end[1]);
|
|
|
|
// First draw tracks and tented vias
|
|
for (var track of pcbdata.tracks[layer]) {
|
|
if (highlight && highlightedNet != track.net) continue;
|
|
if (!hasHole(track)) {
|
|
ctx.strokeStyle = highlight ? defaultColor : settings.netColors[track.net] || defaultColor;
|
|
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();
|
|
}
|
|
}
|
|
// Second pass to draw untented vias
|
|
var style = getComputedStyle(topmostdiv);
|
|
var holeColor = style.getPropertyValue('--pad-hole-color')
|
|
|
|
for (var track of pcbdata.tracks[layer]) {
|
|
if (highlight && highlightedNet != track.net) continue;
|
|
if (hasHole(track)) {
|
|
ctx.strokeStyle = highlight ? defaultColor : settings.netColors[track.net] || defaultColor;
|
|
ctx.lineWidth = track.width;
|
|
ctx.beginPath();
|
|
ctx.moveTo(...track.start);
|
|
ctx.lineTo(...track.end);
|
|
ctx.stroke();
|
|
ctx.strokeStyle = holeColor;
|
|
ctx.lineWidth = track.drillsize;
|
|
ctx.lineTo(...track.end);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawZones(canvas, layer, defaultColor, highlight) {
|
|
ctx = canvas.getContext("2d");
|
|
ctx.lineJoin = "round";
|
|
for (var zone of pcbdata.zones[layer]) {
|
|
if (highlight && highlightedNet != zone.net) continue;
|
|
ctx.strokeStyle = highlight ? defaultColor : settings.netColors[zone.net] || defaultColor;
|
|
ctx.fillStyle = highlight ? defaultColor : settings.netColors[zone.net] || defaultColor;
|
|
if (!zone.path2d) {
|
|
zone.path2d = getPolygonsPath(zone);
|
|
}
|
|
ctx.fill(zone.path2d, zone.fillrule || "nonzero");
|
|
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 {
|
|
if (!window.matchMedia("print").matches)
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawNets(canvas, layer, highlight) {
|
|
var style = getComputedStyle(topmostdiv);
|
|
if (settings.renderZones) {
|
|
var zoneColor = style.getPropertyValue(highlight ? '--zone-color-highlight' : '--zone-color');
|
|
drawZones(canvas, layer, zoneColor, highlight);
|
|
}
|
|
if (settings.renderTracks) {
|
|
var trackColor = style.getPropertyValue(highlight ? '--track-color-highlight' : '--track-color');
|
|
drawTracks(canvas, layer, trackColor, 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 (markedFootprints.size > 0 || 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 * canvasdict.transform.zoom);
|
|
|
|
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);
|
|
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 + (flip && settings.offsetBackRotation ? - 180 : 0)));
|
|
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, flip) {
|
|
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 + (flip && settings.offsetBackRotation ? - 180 : 0)));
|
|
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 flip = (layerdict.layer === "B");
|
|
var bbox = applyRotation(pcbdata.edges_bbox, flip);
|
|
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;
|
|
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;
|
|
var flip = layerdict.layer === "B";
|
|
if (flip) {
|
|
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 + (flip && settings.offsetBackRotation ? - 180 : 0));
|
|
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 setOffsetBackRotation(value) {
|
|
settings.offsetBackRotation = value;
|
|
writeStorage("offsetBackRotation", value);
|
|
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);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/*
|
|
* Table reordering via Drag'n'Drop
|
|
* Inspired by: https://htmldom.dev/drag-and-drop-table-column
|
|
*/
|
|
|
|
function setBomHandlers() {
|
|
|
|
const bom = document.getElementById('bomtable');
|
|
|
|
let dragName;
|
|
let placeHolderElements;
|
|
let draggingElement;
|
|
let forcePopulation;
|
|
let xOffset;
|
|
let yOffset;
|
|
let wasDragged;
|
|
|
|
const mouseUpHandler = function(e) {
|
|
// Delete dragging element
|
|
draggingElement.remove();
|
|
|
|
// Make BOM selectable again
|
|
bom.style.removeProperty("userSelect");
|
|
|
|
// Remove listeners
|
|
document.removeEventListener('mousemove', mouseMoveHandler);
|
|
document.removeEventListener('mouseup', mouseUpHandler);
|
|
|
|
if (wasDragged) {
|
|
// Redraw whole BOM
|
|
populateBomTable();
|
|
}
|
|
}
|
|
|
|
const mouseMoveHandler = function(e) {
|
|
// Notice the dragging
|
|
wasDragged = true;
|
|
|
|
// Make the dragged element visible
|
|
draggingElement.style.removeProperty("display");
|
|
|
|
// Set elements position to mouse position
|
|
draggingElement.style.left = `${e.screenX - xOffset}px`;
|
|
draggingElement.style.top = `${e.screenY - yOffset}px`;
|
|
|
|
// Forced redrawing of BOM table
|
|
if (forcePopulation) {
|
|
forcePopulation = false;
|
|
// Copy array
|
|
phe = Array.from(placeHolderElements);
|
|
// populate BOM table again
|
|
populateBomHeader(dragName, phe);
|
|
populateBomBody(dragName, phe);
|
|
}
|
|
|
|
// Set up array of hidden columns
|
|
var hiddenColumns = Array.from(settings.hiddenColumns);
|
|
// In the ungrouped mode, quantity don't exist
|
|
if (settings.bommode === "ungrouped")
|
|
hiddenColumns.push("Quantity");
|
|
// If no checkbox fields can be found, we consider them hidden
|
|
if (settings.checkboxes.length == 0)
|
|
hiddenColumns.push("checkboxes");
|
|
|
|
// Get table headers and group them into checkboxes, extrafields and normal headers
|
|
const bh = document.getElementById("bomhead");
|
|
headers = Array.from(bh.querySelectorAll("th"))
|
|
headers.shift() // numCol is not part of the columnOrder
|
|
headerGroups = []
|
|
lastCompoundClass = null;
|
|
for (i = 0; i < settings.columnOrder.length; i++) {
|
|
cElem = settings.columnOrder[i];
|
|
if (hiddenColumns.includes(cElem)) {
|
|
// Hidden columns appear as a dummy element
|
|
headerGroups.push([]);
|
|
continue;
|
|
}
|
|
elem = headers.filter(e => getColumnOrderName(e) === cElem)[0];
|
|
if (elem.classList.contains("bom-checkbox")) {
|
|
if (lastCompoundClass === "bom-checkbox") {
|
|
cbGroup = headerGroups.pop();
|
|
cbGroup.push(elem);
|
|
headerGroups.push(cbGroup);
|
|
} else {
|
|
lastCompoundClass = "bom-checkbox";
|
|
headerGroups.push([elem])
|
|
}
|
|
} else {
|
|
headerGroups.push([elem])
|
|
}
|
|
}
|
|
|
|
// Copy settings.columnOrder
|
|
var columns = Array.from(settings.columnOrder)
|
|
|
|
// Set up array with indices of hidden columns
|
|
var hiddenIndices = hiddenColumns.map(e => settings.columnOrder.indexOf(e));
|
|
var dragIndex = columns.indexOf(dragName);
|
|
var swapIndex = dragIndex;
|
|
var swapDone = false;
|
|
|
|
// Check if the current dragged element is swapable with the left or right element
|
|
if (dragIndex > 0) {
|
|
// Get left headers boundingbox
|
|
swapIndex = dragIndex - 1;
|
|
while (hiddenIndices.includes(swapIndex) && swapIndex > 0)
|
|
swapIndex--;
|
|
if (!hiddenIndices.includes(swapIndex)) {
|
|
box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
|
|
if (e.clientX < box.left + window.scrollX + (box.width / 2)) {
|
|
swapElement = columns[dragIndex];
|
|
columns.splice(dragIndex, 1);
|
|
columns.splice(swapIndex, 0, swapElement);
|
|
forcePopulation = true;
|
|
swapDone = true;
|
|
}
|
|
}
|
|
}
|
|
if ((!swapDone) && dragIndex < headerGroups.length - 1) {
|
|
// Get right headers boundingbox
|
|
swapIndex = dragIndex + 1;
|
|
while (hiddenIndices.includes(swapIndex))
|
|
swapIndex++;
|
|
if (swapIndex < headerGroups.length) {
|
|
box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
|
|
if (e.clientX > box.left + window.scrollX + (box.width / 2)) {
|
|
swapElement = columns[dragIndex];
|
|
columns.splice(dragIndex, 1);
|
|
columns.splice(swapIndex, 0, swapElement);
|
|
forcePopulation = true;
|
|
swapDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write back change to storage
|
|
if (swapDone) {
|
|
settings.columnOrder = columns
|
|
writeStorage("columnOrder", JSON.stringify(columns));
|
|
}
|
|
|
|
}
|
|
|
|
const mouseDownHandler = function(e) {
|
|
var target = e.target;
|
|
if (target.tagName.toLowerCase() != "td")
|
|
target = target.parentElement;
|
|
|
|
// Used to check if a dragging has ever happened
|
|
wasDragged = false;
|
|
|
|
// Create new element which will be displayed as the dragged column
|
|
draggingElement = document.createElement("div")
|
|
draggingElement.classList.add("dragging");
|
|
draggingElement.style.display = "none";
|
|
draggingElement.style.position = "absolute";
|
|
draggingElement.style.overflow = "hidden";
|
|
|
|
// Get bomhead and bombody elements
|
|
const bh = document.getElementById("bomhead");
|
|
const bb = document.getElementById("bombody");
|
|
|
|
// Get all compound headers for the current column
|
|
var compoundHeaders;
|
|
if (target.classList.contains("bom-checkbox")) {
|
|
compoundHeaders = Array.from(bh.querySelectorAll("th.bom-checkbox"));
|
|
} else {
|
|
compoundHeaders = [target];
|
|
}
|
|
|
|
// Create new table which will display the column
|
|
var newTable = document.createElement("table");
|
|
newTable.classList.add("bom");
|
|
newTable.style.background = "white";
|
|
draggingElement.append(newTable);
|
|
|
|
// Create new header element
|
|
var newHeader = document.createElement("thead");
|
|
newTable.append(newHeader);
|
|
|
|
// Set up array for storing all placeholder elements
|
|
placeHolderElements = [];
|
|
|
|
// Add all compound headers to the new thead element and placeholders
|
|
compoundHeaders.forEach(function(h) {
|
|
clone = cloneElementWithDimensions(h);
|
|
newHeader.append(clone);
|
|
placeHolderElements.push(clone);
|
|
});
|
|
|
|
// Create new body element
|
|
var newBody = document.createElement("tbody");
|
|
newTable.append(newBody);
|
|
|
|
// Get indices for compound headers
|
|
var idxs = compoundHeaders.map(e => getBomTableHeaderIndex(e));
|
|
|
|
// For each row in the BOM body...
|
|
var rows = bb.querySelectorAll("tr");
|
|
rows.forEach(function(row) {
|
|
// ..get the cells for the compound column
|
|
const tds = row.querySelectorAll("td");
|
|
var copytds = idxs.map(i => tds[i]);
|
|
// Add them to the new element and the placeholders
|
|
var newRow = document.createElement("tr");
|
|
copytds.forEach(function(td) {
|
|
clone = cloneElementWithDimensions(td);
|
|
newRow.append(clone);
|
|
placeHolderElements.push(clone);
|
|
});
|
|
newBody.append(newRow);
|
|
});
|
|
|
|
// Compute width for compound header
|
|
var width = compoundHeaders.reduce((acc, x) => acc + x.clientWidth, 0);
|
|
draggingElement.style.width = `${width}px`;
|
|
|
|
// Insert the new dragging element and disable selection on BOM
|
|
bom.insertBefore(draggingElement, null);
|
|
bom.style.userSelect = "none";
|
|
|
|
// Determine the mouse position offset
|
|
xOffset = e.screenX - compoundHeaders.reduce((acc, x) => Math.min(acc, x.offsetLeft), compoundHeaders[0].offsetLeft);
|
|
yOffset = e.screenY - compoundHeaders[0].offsetTop;
|
|
|
|
// Get name for the column in settings.columnOrder
|
|
dragName = getColumnOrderName(target);
|
|
|
|
// Change text and class for placeholder elements
|
|
placeHolderElements = placeHolderElements.map(function(e) {
|
|
newElem = cloneElementWithDimensions(e);
|
|
newElem.textContent = "";
|
|
newElem.classList.add("placeholder");
|
|
return newElem;
|
|
});
|
|
|
|
// On next mouse move, the whole BOM needs to be redrawn to show the placeholders
|
|
forcePopulation = true;
|
|
|
|
// Add listeners for move and up on mouse
|
|
document.addEventListener('mousemove', mouseMoveHandler);
|
|
document.addEventListener('mouseup', mouseUpHandler);
|
|
}
|
|
|
|
// In netlist mode, there is nothing to reorder
|
|
if (settings.bommode === "netlist")
|
|
return;
|
|
|
|
// Add mouseDownHandler to every column except the numCol
|
|
bom.querySelectorAll("th")
|
|
.forEach(function(head) {
|
|
if (!head.classList.contains("numCol")) {
|
|
head.onmousedown = mouseDownHandler;
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
function getBoundingClientRectFromMultiple(elements) {
|
|
var elems = Array.from(elements);
|
|
|
|
if (elems.length == 0)
|
|
return null;
|
|
|
|
var box = elems.shift()
|
|
.getBoundingClientRect();
|
|
|
|
elems.forEach(function(elem) {
|
|
var elembox = elem.getBoundingClientRect();
|
|
box.left = Math.min(elembox.left, box.left);
|
|
box.top = Math.min(elembox.top, box.top);
|
|
box.width += elembox.width;
|
|
box.height = Math.max(elembox.height, box.height);
|
|
});
|
|
|
|
return box;
|
|
}
|
|
|
|
function cloneElementWithDimensions(elem) {
|
|
var newElem = elem.cloneNode(true);
|
|
newElem.style.height = window.getComputedStyle(elem).height;
|
|
newElem.style.width = window.getComputedStyle(elem).width;
|
|
return newElem;
|
|
}
|
|
|
|
function getBomTableHeaderIndex(elem) {
|
|
const bh = document.getElementById('bomhead');
|
|
const ths = Array.from(bh.querySelectorAll("th"));
|
|
return ths.indexOf(elem);
|
|
}
|
|
|
|
function getColumnOrderName(elem) {
|
|
var cname = elem.getAttribute("col_name");
|
|
if (cname === "bom-checkbox")
|
|
return "checkboxes";
|
|
else
|
|
return cname;
|
|
}
|
|
|
|
function resizableGrid(tablehead) {
|
|
var cols = tablehead.firstElementChild.children;
|
|
var rowWidth = tablehead.offsetWidth;
|
|
|
|
for (var i = 1; i < cols.length; i++) {
|
|
if (cols[i].classList.contains("bom-checkbox"))
|
|
continue;
|
|
cols[i].style.width = ((cols[i].clientWidth - paddingDiff(cols[i])) * 100 / rowWidth) + '%';
|
|
}
|
|
|
|
for (var i = 1; i < cols.length - 1; i++) {
|
|
var div = document.createElement('div');
|
|
div.className = "column-width-handle";
|
|
cols[i].appendChild(div);
|
|
setListeners(div);
|
|
}
|
|
|
|
function setListeners(div) {
|
|
var startX, curCol, nxtCol, curColWidth, nxtColWidth, rowWidth;
|
|
|
|
div.addEventListener('mousedown', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
curCol = e.target.parentElement;
|
|
nxtCol = curCol.nextElementSibling;
|
|
startX = e.pageX;
|
|
|
|
var padding = paddingDiff(curCol);
|
|
|
|
rowWidth = curCol.parentElement.offsetWidth;
|
|
curColWidth = curCol.clientWidth - padding;
|
|
nxtColWidth = nxtCol.clientWidth - padding;
|
|
});
|
|
|
|
document.addEventListener('mousemove', function(e) {
|
|
if (startX) {
|
|
var diffX = e.pageX - startX;
|
|
diffX = -Math.min(-diffX, curColWidth - 20);
|
|
diffX = Math.min(diffX, nxtColWidth - 20);
|
|
|
|
curCol.style.width = ((curColWidth + diffX) * 100 / rowWidth) + '%';
|
|
nxtCol.style.width = ((nxtColWidth - diffX) * 100 / rowWidth) + '%';
|
|
console.log(`${curColWidth + nxtColWidth} ${(curColWidth + diffX) * 100 / rowWidth + (nxtColWidth - diffX) * 100 / rowWidth}`);
|
|
}
|
|
});
|
|
|
|
document.addEventListener('mouseup', function(e) {
|
|
curCol = undefined;
|
|
nxtCol = undefined;
|
|
startX = undefined;
|
|
nxtColWidth = undefined;
|
|
curColWidth = undefined
|
|
});
|
|
}
|
|
|
|
function paddingDiff(col) {
|
|
|
|
if (getStyleVal(col, 'box-sizing') == 'border-box') {
|
|
return 0;
|
|
}
|
|
|
|
var padLeft = getStyleVal(col, 'padding-left');
|
|
var padRight = getStyleVal(col, 'padding-right');
|
|
return (parseInt(padLeft) + parseInt(padRight));
|
|
|
|
}
|
|
|
|
function getStyleVal(elm, css) {
|
|
return (window.getComputedStyle(elm, null).getPropertyValue(css))
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* 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 markedFootprints = new Set();
|
|
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();
|
|
if (initDone) {
|
|
populateBomTable();
|
|
}
|
|
}
|
|
|
|
function setShowBOMColumn(field, value) {
|
|
if (field === "references") {
|
|
var rl = document.getElementById("reflookup");
|
|
rl.disabled = !value;
|
|
if (!value) {
|
|
rl.value = "";
|
|
updateRefLookup("");
|
|
}
|
|
}
|
|
|
|
var n = settings.hiddenColumns.indexOf(field);
|
|
if (value) {
|
|
if (n != -1) {
|
|
settings.hiddenColumns.splice(n, 1);
|
|
}
|
|
} else {
|
|
if (n == -1) {
|
|
settings.hiddenColumns.push(field);
|
|
}
|
|
}
|
|
|
|
writeStorage("hiddenColumns", JSON.stringify(settings.hiddenColumns));
|
|
|
|
if (initDone) {
|
|
populateBomTable();
|
|
}
|
|
|
|
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 setHighlightRowOnClick(value) {
|
|
settings.highlightRowOnClick = value;
|
|
writeStorage("highlightRowOnClick", value);
|
|
if (initDone) {
|
|
populateBomTable();
|
|
}
|
|
}
|
|
|
|
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 createCheckboxHandlers(input, checkbox, references, row) {
|
|
var clickHandler = () => {
|
|
refsSet = getStoredCheckboxRefs(checkbox);
|
|
var markWhenChecked = settings.markWhenChecked == checkbox;
|
|
eventArgs = {
|
|
checkbox: checkbox,
|
|
refs: references,
|
|
}
|
|
if (input.checked) {
|
|
// checkbox ticked
|
|
for (var ref of references) {
|
|
refsSet.add(ref[1]);
|
|
}
|
|
if (markWhenChecked) {
|
|
row.classList.add("checked");
|
|
for (var ref of references) {
|
|
markedFootprints.add(ref[1]);
|
|
}
|
|
drawHighlights();
|
|
}
|
|
eventArgs.state = 'checked';
|
|
} else {
|
|
// checkbox unticked
|
|
for (var ref of references) {
|
|
refsSet.delete(ref[1]);
|
|
}
|
|
if (markWhenChecked) {
|
|
row.classList.remove("checked");
|
|
for (var ref of references) {
|
|
markedFootprints.delete(ref[1]);
|
|
}
|
|
drawHighlights();
|
|
}
|
|
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);
|
|
}
|
|
|
|
return [
|
|
(e) => {
|
|
clickHandler();
|
|
},
|
|
(e) => {
|
|
e.preventDefault();
|
|
if (row.onmousemove) row.onmousemove();
|
|
},
|
|
(e) => {
|
|
e.preventDefault();
|
|
input.checked = !input.checked;
|
|
input.indeterminate = false;
|
|
clickHandler();
|
|
}
|
|
];
|
|
}
|
|
|
|
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 updateNetColors() {
|
|
writeStorage("netColors", JSON.stringify(settings.netColors));
|
|
redrawIfInitDone();
|
|
}
|
|
|
|
function netColorChangeHandler(net) {
|
|
return (event) => {
|
|
settings.netColors[net] = event.target.value;
|
|
updateNetColors();
|
|
}
|
|
}
|
|
|
|
function netColorRightClick(net) {
|
|
return (event) => {
|
|
if (event.button == 2) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
var style = getComputedStyle(topmostdiv);
|
|
var defaultNetColor = style.getPropertyValue('--track-color').trim();
|
|
event.target.value = defaultNetColor;
|
|
delete settings.netColors[net];
|
|
updateNetColors();
|
|
}
|
|
}
|
|
}
|
|
|
|
function entryMatches(entry) {
|
|
if (settings.bommode == "netlist") {
|
|
// entry is just a net name
|
|
return entry.toLowerCase().indexOf(filter) >= 0;
|
|
}
|
|
// check refs
|
|
if (!settings.hiddenColumns.includes("References")) {
|
|
for (var ref of entry) {
|
|
if (ref[0].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// check fields
|
|
for (var i in config.fields) {
|
|
var f = config.fields[i];
|
|
if (!settings.hiddenColumns.includes(f)) {
|
|
for (var ref of entry) {
|
|
if (String(pcbdata.bom.fields[ref[1]][i]).toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findRefInEntry(entry) {
|
|
return entry.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 getBomListByLayer(layer) {
|
|
switch (layer) {
|
|
case 'F': return pcbdata.bom.F.slice();
|
|
case 'B': return pcbdata.bom.B.slice();
|
|
case 'FB': return pcbdata.bom.both.slice();
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function getSelectedBomList() {
|
|
if (settings.bommode == "netlist") {
|
|
return pcbdata.nets.slice();
|
|
}
|
|
var out = getBomListByLayer(settings.canvaslayout);
|
|
|
|
if (settings.bommode == "ungrouped") {
|
|
// expand bom table
|
|
var expandedTable = [];
|
|
for (var bomentry of out) {
|
|
for (var ref of bomentry) {
|
|
expandedTable.push([ref]);
|
|
}
|
|
}
|
|
return expandedTable;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
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, is_checkbox = false) {
|
|
var th = document.createElement("TH");
|
|
th.innerHTML = name;
|
|
th.classList.add(cls);
|
|
if (is_checkbox)
|
|
th.setAttribute("col_name", "bom-checkbox");
|
|
else
|
|
th.setAttribute("col_name", name);
|
|
var span = document.createElement("SPAN");
|
|
span.classList.add("sortmark");
|
|
span.classList.add("none");
|
|
th.appendChild(span);
|
|
var spacer = document.createElement("div");
|
|
spacer.className = "column-spacer";
|
|
th.appendChild(spacer);
|
|
spacer.onclick = function () {
|
|
if (currentSortColumn && th !== currentSortColumn) {
|
|
// Currently sorted by another column
|
|
currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
|
|
currentSortColumn.childNodes[1].classList.add("none");
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
}
|
|
if (currentSortColumn && th === 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 = th;
|
|
currentSortColumn.childNodes[1].classList.remove("none");
|
|
currentSortColumn.childNodes[1].classList.add("asc");
|
|
currentSortOrder = "asc";
|
|
}
|
|
populateBomBody();
|
|
}
|
|
if (is_checkbox) {
|
|
spacer.onclick = fancyDblClickHandler(
|
|
spacer, spacer.onclick, checkboxSetUnsetAllHandler(name));
|
|
}
|
|
return th;
|
|
}
|
|
|
|
function populateBomHeader(placeHolderColumn = null, placeHolderElements = null) {
|
|
while (bomhead.firstChild) {
|
|
bomhead.removeChild(bomhead.firstChild);
|
|
}
|
|
var tr = document.createElement("TR");
|
|
var th = document.createElement("TH");
|
|
th.classList.add("numCol");
|
|
|
|
var vismenu = document.createElement("div");
|
|
vismenu.id = "vismenu";
|
|
vismenu.classList.add("menu");
|
|
|
|
var visbutton = document.createElement("div");
|
|
visbutton.classList.add("visbtn");
|
|
visbutton.classList.add("hideonprint");
|
|
|
|
var viscontent = document.createElement("div");
|
|
viscontent.classList.add("menu-content");
|
|
viscontent.id = "vismenu-content";
|
|
|
|
settings.columnOrder.forEach(column => {
|
|
if (typeof column !== "string")
|
|
return;
|
|
|
|
// Skip empty columns
|
|
if (column === "checkboxes" && settings.checkboxes.length == 0)
|
|
return;
|
|
else if (column === "Quantity" && settings.bommode == "ungrouped")
|
|
return;
|
|
|
|
var label = document.createElement("label");
|
|
label.classList.add("menu-label");
|
|
|
|
var input = document.createElement("input");
|
|
input.classList.add("visibility_checkbox");
|
|
input.type = "checkbox";
|
|
input.onchange = function (e) {
|
|
setShowBOMColumn(column, e.target.checked)
|
|
};
|
|
input.checked = !(settings.hiddenColumns.includes(column));
|
|
|
|
label.appendChild(input);
|
|
if (column.length > 0)
|
|
label.append(column[0].toUpperCase() + column.slice(1));
|
|
|
|
viscontent.appendChild(label);
|
|
});
|
|
|
|
viscontent.childNodes[0].classList.add("menu-label-top");
|
|
|
|
vismenu.appendChild(visbutton);
|
|
if (settings.bommode != "netlist") {
|
|
vismenu.appendChild(viscontent);
|
|
th.appendChild(vismenu);
|
|
}
|
|
tr.appendChild(th);
|
|
|
|
var checkboxCompareClosure = function (checkbox) {
|
|
return (a, b) => {
|
|
var stateA = getCheckboxState(checkbox, a);
|
|
var stateB = getCheckboxState(checkbox, b);
|
|
if (stateA > stateB) return -1;
|
|
if (stateA < stateB) return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
var stringFieldCompareClosure = function (fieldIndex) {
|
|
return (a, b) => {
|
|
var fa = pcbdata.bom.fields[a[0][1]][fieldIndex];
|
|
var fb = pcbdata.bom.fields[b[0][1]][fieldIndex];
|
|
if (fa != fb) return fa > fb ? 1 : -1;
|
|
else return 0;
|
|
}
|
|
}
|
|
var referenceRegex = /(?<prefix>[^0-9]+)(?<number>[0-9]+)/;
|
|
var compareRefs = (a, b) => {
|
|
var ra = referenceRegex.exec(a);
|
|
var rb = referenceRegex.exec(b);
|
|
if (ra === null || rb === null) {
|
|
if (a != b) return a > b ? 1 : -1;
|
|
return 0;
|
|
} else {
|
|
if (ra.groups.prefix != rb.groups.prefix) {
|
|
return ra.groups.prefix > rb.groups.prefix ? 1 : -1;
|
|
}
|
|
if (ra.groups.number != rb.groups.number) {
|
|
return parseInt(ra.groups.number) > parseInt(rb.groups.number) ? 1 : -1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
if (settings.bommode == "netlist") {
|
|
tr.appendChild(createColumnHeader("Net name", "bom-netname", (a, b) => {
|
|
if (a > b) return -1;
|
|
if (a < b) return 1;
|
|
return 0;
|
|
}));
|
|
tr.appendChild(createColumnHeader("Color", "bom-color", (a, b) => {
|
|
return 0;
|
|
}));
|
|
} else {
|
|
// Filter hidden columns
|
|
var columns = settings.columnOrder.filter(e => !settings.hiddenColumns.includes(e));
|
|
var valueIndex = config.fields.indexOf("Value");
|
|
var footprintIndex = config.fields.indexOf("Footprint");
|
|
columns.forEach((column) => {
|
|
if (column === placeHolderColumn) {
|
|
var n = 1;
|
|
if (column === "checkboxes")
|
|
n = settings.checkboxes.length;
|
|
for (i = 0; i < n; i++) {
|
|
td = placeHolderElements.shift();
|
|
tr.appendChild(td);
|
|
}
|
|
return;
|
|
} else if (column === "checkboxes") {
|
|
for (var checkbox of settings.checkboxes) {
|
|
th = createColumnHeader(
|
|
checkbox, "bom-checkbox", checkboxCompareClosure(checkbox), true);
|
|
tr.appendChild(th);
|
|
}
|
|
} else if (column === "References") {
|
|
tr.appendChild(createColumnHeader("References", "references", (a, b) => {
|
|
var i = 0;
|
|
while (i < a.length && i < b.length) {
|
|
if (a[i][0] != b[i][0]) return compareRefs(a[i][0], b[i][0]);
|
|
i++;
|
|
}
|
|
return a.length - b.length;
|
|
}));
|
|
} else if (column === "Value") {
|
|
tr.appendChild(createColumnHeader("Value", "value", (a, b) => {
|
|
var ra = a[0][1], rb = b[0][1];
|
|
return valueCompare(
|
|
pcbdata.bom.parsedValues[ra], pcbdata.bom.parsedValues[rb],
|
|
pcbdata.bom.fields[ra][valueIndex], pcbdata.bom.fields[rb][valueIndex]);
|
|
}));
|
|
return;
|
|
} else if (column === "Footprint") {
|
|
tr.appendChild(createColumnHeader(
|
|
"Footprint", "footprint", stringFieldCompareClosure(footprintIndex)));
|
|
} else if (column === "Quantity" && settings.bommode == "grouped") {
|
|
tr.appendChild(createColumnHeader("Quantity", "quantity", (a, b) => {
|
|
return a.length - b.length;
|
|
}));
|
|
} else {
|
|
// Other fields
|
|
var i = config.fields.indexOf(column);
|
|
if (i < 0)
|
|
return;
|
|
tr.appendChild(createColumnHeader(
|
|
column, `field${i + 1}`, stringFieldCompareClosure(i)));
|
|
}
|
|
});
|
|
}
|
|
bomhead.appendChild(tr);
|
|
}
|
|
|
|
function populateBomBody(placeholderColumn = null, placeHolderElements = null) {
|
|
const urlRegex = /^(https?:\/\/[^\s\/$.?#][^\s]*|file:\/\/([a-zA-Z]:|\/)[^\x00]+)$/;
|
|
while (bom.firstChild) {
|
|
bom.removeChild(bom.firstChild);
|
|
}
|
|
highlightHandlers = [];
|
|
footprintIndexToHandler = {};
|
|
netsToHandler = {};
|
|
currentHighlightedRowId = null;
|
|
var first = true;
|
|
var style = getComputedStyle(topmostdiv);
|
|
var defaultNetColor = style.getPropertyValue('--track-color').trim();
|
|
|
|
bomtable = getSelectedBomList();
|
|
|
|
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);
|
|
var color = settings.netColors[netname] || defaultNetColor;
|
|
td = document.createElement("TD");
|
|
var colorBox = document.createElement("INPUT");
|
|
colorBox.type = "color";
|
|
colorBox.value = color;
|
|
colorBox.onchange = netColorChangeHandler(netname);
|
|
colorBox.onmouseup = netColorRightClick(netname);
|
|
colorBox.oncontextmenu = (e) => e.preventDefault();
|
|
td.appendChild(colorBox);
|
|
td.classList.add("color-column");
|
|
tr.appendChild(td);
|
|
} else {
|
|
if (reflookup) {
|
|
references = findRefInEntry(bomentry);
|
|
if (references.length == 0) {
|
|
continue;
|
|
}
|
|
} else {
|
|
references = bomentry;
|
|
}
|
|
// Filter hidden columns
|
|
var columns = settings.columnOrder.filter(e => !settings.hiddenColumns.includes(e));
|
|
columns.forEach((column) => {
|
|
if (column === placeholderColumn) {
|
|
var n = 1;
|
|
if (column === "checkboxes")
|
|
n = settings.checkboxes.length;
|
|
for (i = 0; i < n; i++) {
|
|
td = placeHolderElements.shift();
|
|
tr.appendChild(td);
|
|
}
|
|
return;
|
|
} else if (column === "checkboxes") {
|
|
for (var checkbox of settings.checkboxes) {
|
|
if (checkbox) {
|
|
td = document.createElement("TD");
|
|
var input = document.createElement("input");
|
|
input.type = "checkbox";
|
|
[input.onchange, td.ontouchstart, td.ontouchend] = createCheckboxHandlers(input, checkbox, references, tr);
|
|
setBomCheckboxState(checkbox, input, references);
|
|
if (input.checked && settings.markWhenChecked == checkbox) {
|
|
tr.classList.add("checked");
|
|
}
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
} else if (column === "References") {
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
|
|
tr.appendChild(td);
|
|
} else if (column === "Quantity" && settings.bommode == "grouped") {
|
|
// Quantity
|
|
td = document.createElement("TD");
|
|
td.textContent = references.length;
|
|
tr.appendChild(td);
|
|
} else {
|
|
// All the other fields
|
|
var field_index = config.fields.indexOf(column)
|
|
if (field_index < 0)
|
|
return;
|
|
var valueSet = new Set();
|
|
references.map(r => r[1]).forEach((id) => valueSet.add(pcbdata.bom.fields[id][field_index]));
|
|
td = document.createElement("TD");
|
|
var output = new Array();
|
|
for (let item of valueSet) {
|
|
const visible = highlightFilter(String(item));
|
|
if (typeof item === 'string' && item.match(urlRegex)) {
|
|
output.push(`<a href="${item}" target="_blank">${visible}</a>`);
|
|
} else {
|
|
output.push(visible);
|
|
}
|
|
}
|
|
td.innerHTML = output.join(", ");
|
|
tr.appendChild(td);
|
|
}
|
|
});
|
|
}
|
|
bom.appendChild(tr);
|
|
var handler = createRowHighlightHandler(tr.id, references, netname);
|
|
if (settings.highlightRowOnClick) {
|
|
tr.onmousedown = handler;
|
|
} else {
|
|
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();
|
|
setBomHandlers();
|
|
resizableGrid(bomhead);
|
|
}
|
|
|
|
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("topmostdiv").style.height = "";
|
|
document.getElementById("topmostdiv").style.display = "block";
|
|
break;
|
|
case 'top-bottom':
|
|
document.getElementById("tb-btn").classList.add("depressed");
|
|
document.getElementById("frontcanvas").style.display = "";
|
|
document.getElementById("backcanvas").style.display = "";
|
|
document.getElementById("topmostdiv").style.height = "100%";
|
|
document.getElementById("topmostdiv").style.display = "flex";
|
|
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("topmostdiv").style.height = "100%";
|
|
document.getElementById("topmostdiv").style.display = "flex";
|
|
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");
|
|
var chkbxs = document.getElementsByClassName("visibility_checkbox");
|
|
|
|
switch (mode) {
|
|
case 'grouped':
|
|
document.getElementById("bom-grouped-btn").classList.add("depressed");
|
|
for (var i = 0; i < chkbxs.length; i++) {
|
|
chkbxs[i].disabled = false;
|
|
}
|
|
break;
|
|
case 'ungrouped':
|
|
document.getElementById("bom-ungrouped-btn").classList.add("depressed");
|
|
for (var i = 0; i < chkbxs.length; i++) {
|
|
chkbxs[i].disabled = false;
|
|
}
|
|
break;
|
|
case 'netlist':
|
|
document.getElementById("bom-netlist-btn").classList.add("depressed");
|
|
for (var i = 0; i < chkbxs.length; i++) {
|
|
chkbxs[i].disabled = true;
|
|
}
|
|
}
|
|
|
|
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 childNum = checkboxnum + settings.columnOrder.indexOf("checkboxes");
|
|
var checkbox = bomrow.childNodes[childNum].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 childNum = checkboxnum + 1 + settings.columnOrder.indexOf("checkboxes");
|
|
var checkbox = bomrow.childNodes[childNum].childNodes[0];
|
|
checkbox.checked = true;
|
|
checkbox.indeterminate = false;
|
|
checkbox.onchange();
|
|
}
|
|
|
|
function setBomCheckboxes(value) {
|
|
writeStorage("bomCheckboxes", value);
|
|
settings.checkboxes = value.split(",").map((e) => e.trim()).filter((e) => e);
|
|
prepCheckboxes();
|
|
populateMarkWhenCheckedOptions();
|
|
setMarkWhenChecked(settings.markWhenChecked);
|
|
}
|
|
|
|
function setMarkWhenChecked(value) {
|
|
writeStorage("markWhenChecked", value);
|
|
settings.markWhenChecked = value;
|
|
markedFootprints.clear();
|
|
for (var ref of (value ? getStoredCheckboxRefs(value) : [])) {
|
|
markedFootprints.add(ref);
|
|
}
|
|
populateBomTable();
|
|
drawHighlights();
|
|
}
|
|
|
|
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 populateMarkWhenCheckedOptions() {
|
|
var container = document.getElementById("markWhenCheckedContainer");
|
|
|
|
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 = "markWhenChecked-" + name;
|
|
|
|
var div = document.createElement("div");
|
|
div.classList.add("radio-container");
|
|
|
|
var input = document.createElement("input");
|
|
input.type = "radio";
|
|
input.name = "markWhenChecked";
|
|
input.value = name;
|
|
input.id = id;
|
|
input.onchange = () => setMarkWhenChecked(name);
|
|
div.appendChild(input);
|
|
|
|
// Preserve the selected element when the checkboxes change
|
|
if (name == settings.markWhenChecked) {
|
|
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) + "%)";
|
|
}
|
|
|
|
function constrain(number, min, max) {
|
|
return Math.min(Math.max(parseInt(number), min), max);
|
|
}
|
|
|
|
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;
|
|
case "ArrowLeft":
|
|
case "ArrowRight":
|
|
if (document.activeElement.type != "text") {
|
|
e.preventDefault();
|
|
let boardRotationElement = document.getElementById("boardRotation")
|
|
settings.boardRotation = parseInt(boardRotationElement.value); // degrees / 5
|
|
if (e.key == "ArrowLeft") {
|
|
settings.boardRotation += 3; // 15 degrees
|
|
}
|
|
else {
|
|
settings.boardRotation -= 3;
|
|
}
|
|
settings.boardRotation = constrain(settings.boardRotation, boardRotationElement.min, boardRotationElement.max);
|
|
boardRotationElement.value = settings.boardRotation
|
|
setBoardRotation(settings.boardRotation);
|
|
}
|
|
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));
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
function topToggle() {
|
|
var top = document.getElementById("top");
|
|
var toptoggle = document.getElementById("toptoggle");
|
|
if (top.style.display === "none") {
|
|
top.style.display = "flex";
|
|
toptoggle.classList.remove("flipped");
|
|
} else {
|
|
top.style.display = "none";
|
|
toptoggle.classList.add("flipped");
|
|
}
|
|
}
|
|
|
|
window.onload = function (e) {
|
|
initRender();
|
|
initStorage();
|
|
initDefaults();
|
|
initUtils();
|
|
cleanGutters();
|
|
populateMetadata();
|
|
dbgdiv = document.getElementById("dbg");
|
|
bom = document.getElementById("bombody");
|
|
bomhead = document.getElementById("bomhead");
|
|
filter = "";
|
|
reflookup = "";
|
|
if (!("nets" in pcbdata)) {
|
|
hideNetlistButton();
|
|
}
|
|
initDone = true;
|
|
setBomCheckboxes(document.getElementById("bomCheckboxes").value);
|
|
// 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 id="fileinfodiv">
|
|
<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 id="bomcontrols">
|
|
<div class="hideonprint menu">
|
|
<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="highlightRowOnClickCheckbox" type="checkbox" checked onchange="setHighlightRowOnClick(this.checked)">
|
|
Highlight row on click
|
|
</label>
|
|
<label class="menu-label">
|
|
<input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
|
|
Continuous redraw on drag
|
|
</label>
|
|
<label class="menu-label">
|
|
Highlight first pin
|
|
<form id="highlightpin1">
|
|
<div class="flexbox">
|
|
<label>
|
|
<input type="radio" name="highlightpin1" value="none" onchange="setHighlightPin1('none')">
|
|
None
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="highlightpin1" value="all" onchange="setHighlightPin1('all')">
|
|
All
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="highlightpin1" value="selected" onchange="setHighlightPin1('selected')">
|
|
Selected
|
|
</label>
|
|
</div>
|
|
</form>
|
|
</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">
|
|
<input id="offsetBackRotationCheckbox" type="checkbox" onchange="setOffsetBackRotation(this.checked)">
|
|
Offset back rotation
|
|
</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">Mark when checked</div>
|
|
<div id="markWhenCheckedContainer"></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>
|
|
<a target="blank" title="Mouse and keyboard help" href="https://github.com/openscopeproject/InteractiveHtmlBom/wiki/Usage#bom-page-mouse-actions" style="text-decoration: none;"><label class="help-link">?</label></a>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="button-container hideonprint">
|
|
<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">
|
|
<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">
|
|
<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">
|
|
<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">
|
|
<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>
|
|
<button class="savebtn" onclick="resetSettings()">Reset</button>
|
|
</div>
|
|
</div>
|
|
<div class="menu-label">
|
|
<span style="margin-left: 5px;">Save bom table as</span>
|
|
<div class="flexbox">
|
|
<button class="savebtn" onclick="saveBomTable('csv')">csv</button>
|
|
<button class="savebtn" onclick="saveBomTable('txt')">txt</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="topdivider">
|
|
<div class="hideonprint">
|
|
<div id="toptoggle" onclick="topToggle()">︽</div>
|
|
</div>
|
|
</div>
|
|
<div id="bot" class="split" style="flex: 1 1">
|
|
<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="saveBomTable('clipboard')"></button>
|
|
</div>
|
|
</div>
|
|
<div id="dbg"></div>
|
|
<table class="bom" id="bomtable">
|
|
<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>
|