diff --git a/dist/assets/index-CXz5-edo.css b/dist/assets/index-CXz5-edo.css deleted file mode 100644 index c46b02b..0000000 --- a/dist/assets/index-CXz5-edo.css +++ /dev/null @@ -1 +0,0 @@ -*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0a0f1e;min-height:100vh;color:#e2e8f0;line-height:1.6;padding:48px 24px}.container{max-width:1600px;margin:0 auto;transition:max-width .3s ease,padding .3s ease}h1{font-size:2.5em;font-weight:700;color:#fff;margin-bottom:8px;transition:all .3s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out}h1:hover{color:#6495ed;text-shadow:0 0 20px rgba(100,149,237,.5);transform:translate(5px)}.subtitle{color:#94a3b8;font-size:1em;margin-bottom:32px;transition:color .3s ease;animation:fadeInUp .6s ease-out .1s both}.main-layout{display:grid;grid-template-columns:460px 1fr;gap:28px;transition:grid-template-columns .4s cubic-bezier(.4,0,.2,1),gap .3s ease}.section{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:12px;padding:36px;margin-bottom:28px;transition:all .4s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out backwards}.section:nth-child(1){animation-delay:.2s}.section:nth-child(2){animation-delay:.26s}.section:nth-child(3){animation-delay:.32s}.section:nth-child(4){animation-delay:.38s}.section:nth-child(5){animation-delay:.44s}.section:nth-child(6){animation-delay:.5s}.config-sidebar{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:14px;padding:20px 20px 24px;transition:border-color .3s ease,box-shadow .3s ease}.config-sidebar:hover{border-color:#6495ed47;box-shadow:0 16px 44px #6495ed24}.sidebar-tabs{position:relative;display:flex;gap:4px;background:#00000047;border:1px solid rgba(100,149,237,.08);border-radius:14px;padding:6px;margin-bottom:22px}.sidebar-tabs .tab{flex:1 1 0;min-width:0;position:relative;z-index:1;background:transparent;border:none;color:#94a3b8;padding:12px 4px 10px;font-size:.78em;font-weight:600;letter-spacing:.35px;cursor:pointer;border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;transition:color .25s ease,transform .25s cubic-bezier(.4,0,.2,1)}.sidebar-tabs .tab-icon{width:22px;height:22px;color:inherit;transition:transform .35s cubic-bezier(.34,1.56,.64,1),color .25s ease}.sidebar-tabs .tab-label{line-height:1;white-space:nowrap}.sidebar-tabs .tab:hover{color:#cbd5e1}.sidebar-tabs .tab:hover .tab-icon{transform:translateY(-1px) scale(1.05)}.sidebar-tabs .tab:focus-visible{outline:2px solid rgba(100,149,237,.55);outline-offset:2px}.sidebar-tabs .tab.active{color:#fff}.sidebar-tabs .tab.active .tab-icon{color:#fff;transform:translateY(-1px) scale(1.08)}.sidebar-tabs .tab-indicator{position:absolute;top:6px;bottom:6px;left:0;width:0;background:linear-gradient(135deg,#667eeaf2,#764ba2e6);border-radius:10px;box-shadow:0 6px 18px #667eea61,inset 0 1px #ffffff14;transition:left .38s cubic-bezier(.4,0,.2,1),width .38s cubic-bezier(.4,0,.2,1);z-index:0;pointer-events:none}.tab-panels{position:relative;padding:4px;min-height:220px}.tab-panel{display:none}.tab-panel.active{display:block;animation:tabFadeIn .32s cubic-bezier(.4,0,.2,1)}@keyframes tabFadeIn{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.tab-panel .form-group:last-child{margin-bottom:0}.field-hint{display:block;margin-top:6px;color:#64748b;font-size:.8em;letter-spacing:.1px}.pack-summary{margin-top:18px;padding:12px 16px;background:linear-gradient(135deg,#667eea1a,#764ba214);border:1px solid rgba(100,149,237,.25);border-radius:10px;color:#cbd5e1;font-size:.9em;line-height:1.5}.pack-summary strong{color:#fff;font-weight:700;font-size:1.05em;letter-spacing:.3px}.pack-summary .muted{color:#94a3b8;font-size:.92em}.config-sidebar>.btn{margin-top:20px;padding:16px 24px;font-size:1.05em;letter-spacing:.3px}.seg-toggle{position:relative;display:inline-flex;background:#00000047;border:1px solid rgba(100,149,237,.1);border-radius:10px;padding:4px;margin-bottom:18px;width:100%}.seg-toggle .seg{position:relative;z-index:1;flex:1 1 0;background:transparent;border:none;color:#94a3b8;padding:9px 14px;font-size:.85em;font-weight:600;letter-spacing:.25px;cursor:pointer;border-radius:7px;transition:color .25s ease;white-space:nowrap}.seg-toggle .seg:hover{color:#cbd5e1}.seg-toggle .seg.active{color:#fff}.seg-toggle .seg-indicator{position:absolute;top:4px;bottom:4px;left:0;width:0;background:linear-gradient(135deg,#667eeaeb,#764ba2e0);border-radius:7px;box-shadow:0 4px 14px #667eea52;transition:left .32s cubic-bezier(.4,0,.2,1),width .32s cubic-bezier(.4,0,.2,1);z-index:0;pointer-events:none}.panel-divider{height:1px;background:linear-gradient(90deg,transparent,rgba(100,149,237,.22),transparent);margin:18px 0 20px}.preview-container{padding:28px}.section:hover{border-color:#6495ed66;box-shadow:0 12px 40px #6495ed33;filter:brightness(1.02);transform:translateY(-3px)}.section h2{color:#fff;font-size:1.3em;margin-bottom:20px;font-weight:600;transition:all .3s cubic-bezier(.4,0,.2,1)}.section:hover h2{color:#6495ed;transform:translate(4px) scale(1.02);text-shadow:0 0 15px rgba(100,149,237,.3)}.section h3{color:#94a3b8;font-size:.9em;margin-bottom:12px;margin-top:20px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;transition:color .2s ease}.section:hover h3{color:#cbd5e1}.form-group{margin-bottom:20px;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative}.form-group:hover{transform:translate(2px) scale(1.005);background:#6495ed05;border-radius:8px}.form-group:has(select){z-index:100}.form-group:hover{opacity:1}label{display:block;color:#cbd5e1;font-size:.9em;margin-bottom:6px;font-weight:500;transition:all .3s cubic-bezier(.4,0,.2,1)}.form-group:hover label{color:#e2e8f0;filter:brightness(1.1);transform:scale(1.02) translate(2px)}select,input[type=number]{width:100%;padding:10px 12px;background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:6px;color:#e2e8f0;font-size:.95em;transition:all .3s cubic-bezier(.4,0,.2,1)}input[type=number]::-webkit-outer-spin-button,input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield;transition:all .3s cubic-bezier(.4,0,.2,1);isolation:isolate}input[type=number]:hover{border-color:#6495ed80;box-shadow:0 4px 12px #6495ed33;filter:brightness(1.05);transform:translateY(-1px)}input[type=number]:focus{outline:none;border-color:#6495ed99;background:#0006;box-shadow:0 0 0 3px #6495ed1a}select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;padding-right:36px;cursor:pointer}select:hover{border-color:#6495ed66}select:focus{outline:none;border-color:#6495ed99;background:#0006}select option{background:#1e293b;color:#e2e8f0;padding:10px}.custom-select{position:relative;width:100%;isolation:isolate}.custom-select select{display:none}.select-selected{background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:6px;padding:10px 36px 10px 12px;color:#e2e8f0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;-webkit-user-select:none;user-select:none;font-size:.95em;z-index:1;isolation:isolate}.select-selected:after{content:"";position:absolute;top:50%;right:12px;width:0;height:0;border:5px solid transparent;border-top-color:#94a3b8;transform:translateY(-30%);transition:transform .3s ease}.select-selected.select-arrow-active:after{transform:translateY(-50%) rotate(180deg);border-top-color:#6495ed}.select-selected:hover{border-color:#6495ed99;background:#0006;box-shadow:0 4px 12px #6495ed33;filter:brightness(1.05);transform:translateY(-1px)}.select-items{position:absolute;background:#1e293b;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(100,149,237,.3);border-radius:8px;top:calc(100% + 4px);left:0;right:0;z-index:10000;max-height:0;overflow:hidden;opacity:0;visibility:hidden;transform:translateY(-15px) scale(.95);transform-origin:top center;transition:max-height .4s cubic-bezier(.34,1.56,.64,1),opacity .3s ease,transform .4s cubic-bezier(.34,1.56,.64,1),visibility 0s .4s;box-shadow:0 10px 40px #00000080;pointer-events:none}.select-items.show{max-height:300px;opacity:1;visibility:visible;transform:translateY(0) scale(1);overflow-y:auto;transition-delay:0s;pointer-events:auto}.select-items div{padding:12px 16px;color:#cbd5e1;cursor:pointer;transition:all .2s ease;border-left:3px solid transparent;font-size:.95em;opacity:0;transform:translate(-10px)}.select-items.show div{opacity:1;transform:translate(0)}.select-items.show div:nth-child(1){transition-delay:.05s}.select-items.show div:nth-child(2){transition-delay:.08s}.select-items.show div:nth-child(3){transition-delay:.11s}.select-items.show div:nth-child(4){transition-delay:.14s}.select-items.show div:nth-child(5){transition-delay:.17s}.select-items div:hover{background:#6495ed26;color:#fff;border-left-color:#6495ed;padding-left:20px}.select-items div.same-as-selected{background:#6495ed33;color:#6495ed;font-weight:600;border-left-color:#6495ed}.select-items::-webkit-scrollbar{width:6px}.select-items::-webkit-scrollbar-track{background:#0003;border-radius:3px}.select-items::-webkit-scrollbar-thumb{background:#6495ed66;border-radius:3px}.select-items::-webkit-scrollbar-thumb:hover{background:#6495ed99}.select-hide{display:none}.checkbox-group{display:flex;align-items:center;gap:10px;margin-bottom:16px;transition:all .2s ease}.checkbox-group:hover{filter:brightness(1.1);transform:translate(3px)}input[type=checkbox]{width:22px;height:22px;cursor:pointer;position:relative;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#0000004d;border:2px solid rgba(100,149,237,.3);border-radius:6px;transition:all .3s cubic-bezier(.4,0,.2,1);outline:none;overflow:hidden}input[type=checkbox]:before{content:"";position:absolute;top:50%;left:50%;width:0;height:0;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);transform:translate(-50%,-50%);transition:width .4s cubic-bezier(.34,1.56,.64,1),height .4s cubic-bezier(.34,1.56,.64,1)}input[type=checkbox]:checked:before{width:150%;height:150%}input[type=checkbox]:hover{border-color:#6495ed99;background:#6495ed1a;transform:scale(1.05)}input[type=checkbox]:active{transform:scale(.95)}input[type=checkbox]:checked{border-color:#667eea;background:transparent}input[type=checkbox]:checked:after{content:"✓";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(0);color:#fff;font-size:14px;font-weight:700;z-index:1;animation:checkmarkPop .5s cubic-bezier(.34,1.56,.64,1) .1s forwards}@keyframes checkmarkPop{0%{opacity:0;transform:translate(-50%,-50%) scale(0) rotate(-45deg)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.3) rotate(10deg)}to{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0)}}input[type=checkbox]:focus{box-shadow:0 0 0 3px #6495ed33}.checkbox-group label{margin-bottom:0;cursor:pointer;transition:color .2s ease}.checkbox-group:hover label{color:#fff}.btn{width:100%;padding:14px 24px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:8px;font-size:1em;font-weight:600;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);margin-top:8px;position:relative;overflow:hidden;isolation:isolate}.btn:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transition:left .5s ease}.btn:hover:before{left:100%}.btn:hover{transform:translateY(-2px);box-shadow:0 12px 24px #667eea66;filter:brightness(1.1);animation:pulse 2s ease-in-out infinite}.btn:active{transform:translateY(0) scale(.98);box-shadow:0 5px 10px #667eea33;filter:brightness(.95);animation:none}.row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;transition:grid-template-columns .3s ease,gap .3s ease}.row .form-group{margin-bottom:0}.preview-container{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:12px;padding:36px;height:fit-content;transition:all .3s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out .3s backwards}.preview-container:hover{border-color:#6495ed66;box-shadow:0 12px 40px #6495ed33;filter:brightness(1.02);transform:translateY(-3px)}.preview-container h2{margin-bottom:20px;color:#fff;font-size:1.3em;font-weight:600;transition:all .3s cubic-bezier(.4,0,.2,1)}.preview-container:hover h2{color:#6495ed;transform:translate(4px) scale(1.02);text-shadow:0 0 15px rgba(100,149,237,.3)}.preview-container.updating{border-color:#6495ed66}#preview{width:100%;height:720px;border:1px solid rgba(100,149,237,.2);border-radius:10px;background:#1e293b;display:block;cursor:grab;transition:all .3s cubic-bezier(.4,0,.2,1)}#preview:hover{border-color:#6495ed80}#preview:active{cursor:grabbing}#previewStats{margin-top:12px;padding:12px;background:#0003;border-radius:6px;text-align:center;font-size:.95em;color:#94a3b8;transition:all .3s ease}#previewStats:hover{background:#0000004d;color:#e2e8f0}.loading-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#0a0f1ef2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:none;justify-content:center;align-items:center;z-index:9999;animation:fadeIn .3s ease}.loading-overlay.active{display:flex}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.loading-content{text-align:center;animation:slideUp .4s ease}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.spinner{width:60px;height:60px;border:4px solid rgba(100,149,237,.2);border-top-color:#6495ed;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 24px}@keyframes spin{to{transform:rotate(360deg)}}.loading-text{font-size:1.2em;color:#fff;font-weight:600;margin-bottom:8px}.loading-subtext{font-size:.95em;color:#94a3b8}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%,to{box-shadow:0 4px 24px #6495ed4d}50%{box-shadow:0 4px 32px #6495ed80}}@keyframes ripple{0%{transform:scale(0);opacity:.5}to{transform:scale(2);opacity:0}}@media (max-width: 1500px){.main-layout{grid-template-columns:420px 1fr}#preview{height:640px}}@media (max-width: 1200px){.main-layout{grid-template-columns:1fr}.config-sidebar{order:2}.preview-container{order:1}#preview{height:520px}}@media (max-width: 768px){body{padding:20px 16px}h1{font-size:2em;margin-bottom:12px}.subtitle{font-size:.95em;margin-bottom:28px}.section{padding:24px;margin-bottom:20px}.preview-container{padding:24px}.row{grid-template-columns:1fr;gap:16px}#preview{height:400px;cursor:default}.form-group{margin-bottom:16px}label{font-size:.9em;margin-bottom:8px}select,input[type=number]{font-size:16px;padding:12px 14px}.btn{padding:14px 24px;font-size:1em}.loading-text{font-size:1.1em}.loading-subtext{font-size:.9em}}@media (max-width: 480px){body{padding:16px 12px}h1{font-size:1.75em}.section{padding:20px;border-radius:10px}.preview-container{padding:20px}.section h2{font-size:1.2em;margin-bottom:20px}.section h3{font-size:.85em}#preview{height:350px}.spinner{width:50px;height:50px}}@media (max-width: 768px) and (orientation: landscape){.main-layout{grid-template-columns:300px 1fr}.config-sidebar{order:1}.preview-container{order:2}#preview{height:350px}}@media (hover: none) and (pointer: coarse){select,input[type=number],.btn{min-height:48px;font-size:16px}.checkbox-group input[type=checkbox]{width:28px;height:28px}.checkbox-group label{padding-left:12px}.form-group:hover{transform:none;background:transparent}input[type=number]:hover,select:hover,.btn:hover{transform:none;filter:none}.btn:active{transform:scale(.97)}#preview{cursor:default;touch-action:pan-x pan-y pinch-zoom}}.axis-diagram{display:flex;justify-content:center;align-items:center;padding:8px 12px 4px;margin:-4px -4px 16px;background:#0000002e;border:1px solid rgba(100,149,237,.12);border-radius:10px}.axis-diagram svg{width:100%;max-width:260px;height:auto;display:block}#busbarList{margin-bottom:8px}.busbar-empty{color:#94a3b8;font-size:.85em;font-style:italic;text-align:center;padding:12px 8px;border:1px dashed rgba(100,149,237,.2);border-radius:6px}.busbar-row{padding:8px 10px;border:1px solid rgba(100,149,237,.15);border-radius:8px;margin-bottom:6px;cursor:pointer;transition:all .2s ease;background:#0003}.busbar-row:hover{border-color:#6495ed59;background:#0000004d}.busbar-row.active{border-color:#6495ed;box-shadow:0 0 0 1px #6495ed66,0 4px 12px #6495ed26;background:#6495ed14}.busbar-header{display:flex;align-items:center;gap:8px}.busbar-swatch{width:16px;height:16px;border-radius:4px;flex-shrink:0;box-shadow:0 0 0 1px #ffffff26}.busbar-name{flex:1;min-width:0;padding:4px 6px;background:transparent;border:1px solid transparent;color:#e2e8f0;font-size:.9em;border-radius:4px}.busbar-name:hover{border-color:#6495ed40}.busbar-name:focus{outline:none;border-color:#6495ed99;background:#0000004d}.busbar-del{background:none;border:none;color:#94a3b8;cursor:pointer;font-size:1.25em;padding:0 6px;line-height:1;border-radius:4px;transition:all .15s ease}.busbar-del:hover{color:#ef4444;background:#ef444426}.busbar-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:6px;padding-left:24px;font-size:.8em;color:#94a3b8}.busbar-thickness-label{display:flex;align-items:center;gap:6px;color:#94a3b8;font-size:.95em;cursor:text}.busbar-thickness{width:64px;padding:3px 6px;font-size:.9em;background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:4px;color:#e2e8f0}.busbar-thickness:focus{outline:none;border-color:#6495ed99}.busbar-blocked{margin-top:6px;padding:6px 8px;font-size:.8em;color:#ef4444;background:#ef444414;border-radius:4px}.btn-secondary{width:100%;padding:10px 16px;background:#6495ed1f;border:1px solid rgba(100,149,237,.3);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:.9em;font-weight:500;transition:all .2s ease;margin-bottom:12px}.btn-secondary:hover{background:#6495ed38;border-color:#6495ed80;transform:translateY(-1px)}.btn-secondary:active{transform:translateY(0)}#copyShareUrlBtn{margin-top:10px;margin-bottom:0} diff --git a/dist/assets/index-DFysVyGJ.css b/dist/assets/index-DFysVyGJ.css new file mode 100644 index 0000000..c5d40f1 --- /dev/null +++ b/dist/assets/index-DFysVyGJ.css @@ -0,0 +1 @@ +*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0a0f1e;min-height:100vh;color:#e2e8f0;line-height:1.6;padding:48px 24px}.container{max-width:1600px;margin:0 auto;transition:max-width .3s ease,padding .3s ease}.header-row{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;margin-bottom:10px}h1{font-size:2.5em;font-weight:700;color:#fff;margin-bottom:0;transition:all .3s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out}h1:hover{color:#6495ed;text-shadow:0 0 20px rgba(100,149,237,.5);transform:translate(5px)}.subtitle{color:#94a3b8;font-size:1em;margin-bottom:32px;transition:color .3s ease;animation:fadeInUp .6s ease-out .1s both}.repo-credit{margin:0;padding:10px 14px;color:#dbeafe;font-size:.95em;line-height:1.5;text-align:center;max-width:560px;background:linear-gradient(135deg,#10b98129,#3b82f61f);border:1px solid rgba(110,231,183,.35);border-radius:10px;animation:fadeInUp .6s ease-out .15s both}.repo-credit a{color:#a7f3d0;font-weight:600;text-decoration:underline;text-underline-offset:2px}.repo-credit a:hover{color:#ecfdf5}.main-layout{display:grid;grid-template-columns:460px 1fr;gap:28px;transition:grid-template-columns .4s cubic-bezier(.4,0,.2,1),gap .3s ease}.section{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:12px;padding:36px;margin-bottom:28px;transition:all .4s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out backwards}.section:nth-child(1){animation-delay:.2s}.section:nth-child(2){animation-delay:.26s}.section:nth-child(3){animation-delay:.32s}.section:nth-child(4){animation-delay:.38s}.section:nth-child(5){animation-delay:.44s}.section:nth-child(6){animation-delay:.5s}.config-sidebar{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:14px;padding:20px 20px 24px;transition:border-color .3s ease,box-shadow .3s ease}.config-sidebar:hover{border-color:#6495ed47;box-shadow:0 16px 44px #6495ed24}.sidebar-tabs{position:relative;display:flex;gap:4px;background:#00000047;border:1px solid rgba(100,149,237,.08);border-radius:14px;padding:6px;margin-bottom:22px}.sidebar-tabs .tab{flex:1 1 0;min-width:0;position:relative;z-index:1;background:transparent;border:none;color:#94a3b8;padding:12px 4px 10px;font-size:.78em;font-weight:600;letter-spacing:.35px;cursor:pointer;border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;transition:color .25s ease,transform .25s cubic-bezier(.4,0,.2,1)}.sidebar-tabs .tab-icon{width:22px;height:22px;color:inherit;transition:transform .35s cubic-bezier(.34,1.56,.64,1),color .25s ease}.sidebar-tabs .tab-label{line-height:1;white-space:nowrap}.sidebar-tabs .tab:hover{color:#cbd5e1}.sidebar-tabs .tab:hover .tab-icon{transform:translateY(-1px) scale(1.05)}.sidebar-tabs .tab:focus-visible{outline:2px solid rgba(100,149,237,.55);outline-offset:2px}.sidebar-tabs .tab.active{color:#fff}.sidebar-tabs .tab.active .tab-icon{color:#fff;transform:translateY(-1px) scale(1.08)}.sidebar-tabs .tab-indicator{position:absolute;top:6px;bottom:6px;left:0;width:0;background:linear-gradient(135deg,#667eeaf2,#764ba2e6);border-radius:10px;box-shadow:0 6px 18px #667eea61,inset 0 1px #ffffff14;transition:left .38s cubic-bezier(.4,0,.2,1),width .38s cubic-bezier(.4,0,.2,1);z-index:0;pointer-events:none}.tab-panels{position:relative;padding:4px;min-height:220px}.tab-panel{display:none}.tab-panel.active{display:block;animation:tabFadeIn .32s cubic-bezier(.4,0,.2,1)}@keyframes tabFadeIn{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.tab-panel .form-group:last-child{margin-bottom:0}.field-hint{display:block;margin-top:6px;color:#64748b;font-size:.8em;letter-spacing:.1px}.pack-summary{margin-top:18px;padding:12px 16px;background:linear-gradient(135deg,#667eea1a,#764ba214);border:1px solid rgba(100,149,237,.25);border-radius:10px;color:#cbd5e1;font-size:.9em;line-height:1.5}.pack-summary strong{color:#fff;font-weight:700;font-size:1.05em;letter-spacing:.3px}.pack-summary .muted{color:#94a3b8;font-size:.92em}.config-sidebar>.btn{margin-top:20px;padding:16px 24px;font-size:1.05em;letter-spacing:.3px}.seg-toggle{position:relative;display:inline-flex;background:#00000047;border:1px solid rgba(100,149,237,.1);border-radius:10px;padding:4px;margin-bottom:18px;width:100%}.seg-toggle .seg{position:relative;z-index:1;flex:1 1 0;background:transparent;border:none;color:#94a3b8;padding:9px 14px;font-size:.85em;font-weight:600;letter-spacing:.25px;cursor:pointer;border-radius:7px;transition:color .25s ease;white-space:nowrap}.seg-toggle .seg:hover{color:#cbd5e1}.seg-toggle .seg.active{color:#fff}.seg-toggle .seg-indicator{position:absolute;top:4px;bottom:4px;left:0;width:0;background:linear-gradient(135deg,#667eeaeb,#764ba2e0);border-radius:7px;box-shadow:0 4px 14px #667eea52;transition:left .32s cubic-bezier(.4,0,.2,1),width .32s cubic-bezier(.4,0,.2,1);z-index:0;pointer-events:none}.panel-divider{height:1px;background:linear-gradient(90deg,transparent,rgba(100,149,237,.22),transparent);margin:18px 0 20px}.preview-container{padding:28px}.section:hover{border-color:#6495ed66;box-shadow:0 12px 40px #6495ed33;filter:brightness(1.02);transform:translateY(-3px)}.section h2{color:#fff;font-size:1.3em;margin-bottom:20px;font-weight:600;transition:all .3s cubic-bezier(.4,0,.2,1)}.section:hover h2{color:#6495ed;transform:translate(4px) scale(1.02);text-shadow:0 0 15px rgba(100,149,237,.3)}.section h3{color:#94a3b8;font-size:.9em;margin-bottom:12px;margin-top:20px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;transition:color .2s ease}.section:hover h3{color:#cbd5e1}.form-group{margin-bottom:20px;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative}.form-group:hover{transform:translate(2px) scale(1.005);background:#6495ed05;border-radius:8px}.form-group:has(select){z-index:100}.form-group:hover{opacity:1}label{display:block;color:#cbd5e1;font-size:.9em;margin-bottom:6px;font-weight:500;transition:all .3s cubic-bezier(.4,0,.2,1)}.form-group:hover label{color:#e2e8f0;filter:brightness(1.1);transform:scale(1.02) translate(2px)}select,input[type=number]{width:100%;padding:10px 12px;background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:6px;color:#e2e8f0;font-size:.95em;transition:all .3s cubic-bezier(.4,0,.2,1)}input[type=number]::-webkit-outer-spin-button,input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield;transition:all .3s cubic-bezier(.4,0,.2,1);isolation:isolate}input[type=number]:hover{border-color:#6495ed80;box-shadow:0 4px 12px #6495ed33;filter:brightness(1.05);transform:translateY(-1px)}input[type=number]:focus{outline:none;border-color:#6495ed99;background:#0006;box-shadow:0 0 0 3px #6495ed1a}select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M6 9L1 4h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center;padding-right:36px;cursor:pointer}select:hover{border-color:#6495ed66}select:focus{outline:none;border-color:#6495ed99;background:#0006}select option{background:#1e293b;color:#e2e8f0;padding:10px}.custom-select{position:relative;width:100%;isolation:isolate}.custom-select select{display:none}.select-selected{background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:6px;padding:10px 36px 10px 12px;color:#e2e8f0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;-webkit-user-select:none;user-select:none;font-size:.95em;z-index:1;isolation:isolate}.select-selected:after{content:"";position:absolute;top:50%;right:12px;width:0;height:0;border:5px solid transparent;border-top-color:#94a3b8;transform:translateY(-30%);transition:transform .3s ease}.select-selected.select-arrow-active:after{transform:translateY(-50%) rotate(180deg);border-top-color:#6495ed}.select-selected:hover{border-color:#6495ed99;background:#0006;box-shadow:0 4px 12px #6495ed33;filter:brightness(1.05);transform:translateY(-1px)}.select-items{position:absolute;background:#1e293b;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(100,149,237,.3);border-radius:8px;top:calc(100% + 4px);left:0;right:0;z-index:10000;max-height:0;overflow:hidden;opacity:0;visibility:hidden;transform:translateY(-15px) scale(.95);transform-origin:top center;transition:max-height .4s cubic-bezier(.34,1.56,.64,1),opacity .3s ease,transform .4s cubic-bezier(.34,1.56,.64,1),visibility 0s .4s;box-shadow:0 10px 40px #00000080;pointer-events:none}.select-items.show{max-height:300px;opacity:1;visibility:visible;transform:translateY(0) scale(1);overflow-y:auto;transition-delay:0s;pointer-events:auto}.select-items div{padding:12px 16px;color:#cbd5e1;cursor:pointer;transition:all .2s ease;border-left:3px solid transparent;font-size:.95em;opacity:0;transform:translate(-10px)}.select-items.show div{opacity:1;transform:translate(0)}.select-items.show div:nth-child(1){transition-delay:.05s}.select-items.show div:nth-child(2){transition-delay:.08s}.select-items.show div:nth-child(3){transition-delay:.11s}.select-items.show div:nth-child(4){transition-delay:.14s}.select-items.show div:nth-child(5){transition-delay:.17s}.select-items div:hover{background:#6495ed26;color:#fff;border-left-color:#6495ed;padding-left:20px}.select-items div.same-as-selected{background:#6495ed33;color:#6495ed;font-weight:600;border-left-color:#6495ed}.select-items::-webkit-scrollbar{width:6px}.select-items::-webkit-scrollbar-track{background:#0003;border-radius:3px}.select-items::-webkit-scrollbar-thumb{background:#6495ed66;border-radius:3px}.select-items::-webkit-scrollbar-thumb:hover{background:#6495ed99}.select-hide{display:none}.checkbox-group{display:flex;align-items:center;gap:10px;margin-bottom:16px;transition:all .2s ease}.checkbox-group:hover{filter:brightness(1.1);transform:translate(3px)}input[type=checkbox]{width:22px;height:22px;cursor:pointer;position:relative;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#0000004d;border:2px solid rgba(100,149,237,.3);border-radius:6px;transition:all .3s cubic-bezier(.4,0,.2,1);outline:none;overflow:hidden}input[type=checkbox]:before{content:"";position:absolute;top:50%;left:50%;width:0;height:0;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);transform:translate(-50%,-50%);transition:width .4s cubic-bezier(.34,1.56,.64,1),height .4s cubic-bezier(.34,1.56,.64,1)}input[type=checkbox]:checked:before{width:150%;height:150%}input[type=checkbox]:hover{border-color:#6495ed99;background:#6495ed1a;transform:scale(1.05)}input[type=checkbox]:active{transform:scale(.95)}input[type=checkbox]:checked{border-color:#667eea;background:transparent}input[type=checkbox]:checked:after{content:"✓";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(0);color:#fff;font-size:14px;font-weight:700;z-index:1;animation:checkmarkPop .5s cubic-bezier(.34,1.56,.64,1) .1s forwards}@keyframes checkmarkPop{0%{opacity:0;transform:translate(-50%,-50%) scale(0) rotate(-45deg)}50%{opacity:1;transform:translate(-50%,-50%) scale(1.3) rotate(10deg)}to{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0)}}input[type=checkbox]:focus{box-shadow:0 0 0 3px #6495ed33}.checkbox-group label{margin-bottom:0;cursor:pointer;transition:color .2s ease}.checkbox-group:hover label{color:#fff}.btn{width:100%;padding:14px 24px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border:none;border-radius:8px;font-size:1em;font-weight:600;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);margin-top:8px;position:relative;overflow:hidden;isolation:isolate}.btn:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transition:left .5s ease}.btn:hover:before{left:100%}.btn:hover{transform:translateY(-2px);box-shadow:0 12px 24px #667eea66;filter:brightness(1.1);animation:pulse 2s ease-in-out infinite}.btn:active{transform:translateY(0) scale(.98);box-shadow:0 5px 10px #667eea33;filter:brightness(.95);animation:none}.row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;transition:grid-template-columns .3s ease,gap .3s ease}.row .form-group{margin-bottom:0}.preview-container{background:#0f172a99;border:1px solid rgba(100,149,237,.1);border-radius:12px;padding:36px;height:fit-content;transition:all .3s cubic-bezier(.4,0,.2,1);animation:fadeInUp .6s ease-out .3s backwards}.preview-container:hover{border-color:#6495ed66;box-shadow:0 12px 40px #6495ed33;filter:brightness(1.02);transform:translateY(-3px)}.preview-container h2{margin-bottom:20px;color:#fff;font-size:1.3em;font-weight:600;transition:all .3s cubic-bezier(.4,0,.2,1)}.preview-container:hover h2{color:#6495ed;transform:translate(4px) scale(1.02);text-shadow:0 0 15px rgba(100,149,237,.3)}.preview-container.updating{border-color:#6495ed66}.previews-row{display:flex;gap:12px;align-items:flex-start}.preview-face-wrap{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}.preview-face-wrap[hidden]{display:none!important}.face-label{text-align:center;font-size:.85em;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#94a3b8;padding:2px 0}#preview,#preview-bottom{width:100%;height:720px;border:1px solid rgba(100,149,237,.2);border-radius:10px;background:#1e293b;display:block;cursor:grab;transition:all .3s cubic-bezier(.4,0,.2,1)}#preview:hover,#preview-bottom:hover{border-color:#6495ed80}#preview:active{cursor:grabbing}.busbar-add-row{display:flex;gap:8px;margin-top:8px}.busbar-add-row .btn-secondary{flex:1;font-size:.85em;padding:8px 6px}.busbar-controls-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.busbar-controls-right{display:flex;align-items:center;gap:6px}.busbar-face-filter{display:flex;border:1px solid rgba(100,149,237,.25);border-radius:6px;overflow:hidden}.face-filter-btn{flex:1;padding:5px 10px;font-size:.78em;font-weight:600;background:none;border:none;color:#64748b;cursor:pointer;transition:background .15s,color .15s;white-space:nowrap}.face-filter-btn+.face-filter-btn{border-left:1px solid rgba(100,149,237,.25)}.face-filter-btn.active{background:#6495ed33;color:#93c5fd}.face-filter-btn:not(.active):hover{background:#6495ed14;color:#94a3b8}.btn-clear-markings{padding:5px 10px;font-size:.78em;font-weight:600;background:none;border:1px solid rgba(239,68,68,.25);border-radius:6px;color:#94a3b8;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}.btn-clear-markings:hover{background:#ef44441a;border-color:#ef444480;color:#ef4444}.btn-dl-all{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;font-size:.78em;font-weight:600;background:none;border:1px solid rgba(100,149,237,.25);border-radius:6px;color:#94a3b8;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}.btn-dl-all:hover{background:#6495ed1a;border-color:#6495ed80;color:#93c5fd}.busbar-dl{background:none;border:none;color:#64748b;cursor:pointer;padding:0 5px;line-height:1;border-radius:4px;display:flex;align-items:center;transition:color .15s,background .15s}.busbar-dl:hover{color:#93c5fd;background:#6495ed26}.busbar-list-dxf .busbar-thickness-label{display:none}.busbar-face-section{margin-bottom:12px}.busbar-face-label{font-size:.75em;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#64748b;padding:4px 0 6px;border-bottom:1px solid rgba(100,149,237,.12);margin-bottom:6px}.busbar-face-empty{font-size:.82em;color:#475569;padding:4px 0}#preview:active,#preview-bottom:active{cursor:grabbing}#previewStats{margin-top:12px;padding:12px;background:#0003;border-radius:6px;text-align:center;font-size:.95em;color:#94a3b8;transition:all .3s ease}#previewStats:hover{background:#0000004d;color:#e2e8f0}.loading-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#0a0f1ef2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);display:none;justify-content:center;align-items:center;z-index:9999;animation:fadeIn .3s ease}.loading-overlay.active{display:flex}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.loading-content{text-align:center;animation:slideUp .4s ease}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.spinner{width:60px;height:60px;border:4px solid rgba(100,149,237,.2);border-top-color:#6495ed;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 24px}@keyframes spin{to{transform:rotate(360deg)}}.loading-text{font-size:1.2em;color:#fff;font-weight:600;margin-bottom:8px}.loading-subtext{font-size:.95em;color:#94a3b8}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%,to{box-shadow:0 4px 24px #6495ed4d}50%{box-shadow:0 4px 32px #6495ed80}}@keyframes ripple{0%{transform:scale(0);opacity:.5}to{transform:scale(2);opacity:0}}@media (max-width: 1500px){.main-layout{grid-template-columns:420px 1fr}#preview{height:640px}}@media (max-width: 1200px){.main-layout{grid-template-columns:1fr}.config-sidebar{order:2}.preview-container{order:1}#preview{height:520px}}@media (max-width: 768px){body{padding:20px 16px}.header-row{flex-direction:column;align-items:stretch;gap:12px;margin-bottom:12px}h1{font-size:2em;margin-bottom:0}.subtitle{font-size:.95em;margin-bottom:28px}.repo-credit{font-size:.9em;margin:0;padding:10px 12px;text-align:left;max-width:none}.section{padding:24px;margin-bottom:20px}.preview-container{padding:24px}.row{grid-template-columns:1fr;gap:16px}#preview{height:400px;cursor:default}.form-group{margin-bottom:16px}label{font-size:.9em;margin-bottom:8px}select,input[type=number]{font-size:16px;padding:12px 14px}.btn{padding:14px 24px;font-size:1em}#copyShareUrlBtn{font-size:1em;min-height:48px;padding:12px 16px}.loading-text{font-size:1.1em}.loading-subtext{font-size:.9em}}@media (max-width: 480px){body{padding:16px 12px}h1{font-size:1.75em}.section{padding:20px;border-radius:10px}.preview-container{padding:20px}.section h2{font-size:1.2em;margin-bottom:20px}.section h3{font-size:.85em}#preview{height:350px}.spinner{width:50px;height:50px}}@media (max-width: 768px) and (orientation: landscape){.main-layout{grid-template-columns:300px 1fr}.config-sidebar{order:1}.preview-container{order:2}#preview{height:350px}}@media (hover: none) and (pointer: coarse){select,input[type=number],.btn{min-height:48px;font-size:16px}.checkbox-group input[type=checkbox]{width:28px;height:28px}.checkbox-group label{padding-left:12px}.form-group:hover{transform:none;background:transparent}input[type=number]:hover,select:hover,.btn:hover{transform:none;filter:none}.btn:active{transform:scale(.97)}#preview{cursor:default;touch-action:pan-x pan-y pinch-zoom}}.axis-diagram{display:flex;justify-content:center;align-items:center;padding:8px 12px 4px;margin:-4px -4px 16px;background:#0000002e;border:1px solid rgba(100,149,237,.12);border-radius:10px}.axis-diagram svg{width:100%;max-width:260px;height:auto;display:block}#busbarList{margin-bottom:8px}.busbar-empty{color:#94a3b8;font-size:.85em;font-style:italic;text-align:center;padding:12px 8px;border:1px dashed rgba(100,149,237,.2);border-radius:6px}.busbar-row{padding:8px 10px;border:1px solid rgba(100,149,237,.15);border-radius:8px;margin-bottom:6px;cursor:pointer;transition:all .2s ease;background:#0003}.busbar-row:hover{border-color:#6495ed59;background:#0000004d}.busbar-row.active{border-color:#6495ed;box-shadow:0 0 0 1px #6495ed66,0 4px 12px #6495ed26;background:#6495ed14}.busbar-header{display:flex;align-items:center;gap:8px}.busbar-color-wrap{width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;border-radius:6px;overflow:hidden;box-shadow:0 0 0 1px #ffffff26;background:#ffffff0a}.busbar-color{width:100%;height:100%;border:none;padding:0;background:transparent;cursor:pointer}.busbar-color::-webkit-color-swatch-wrapper{padding:0}.busbar-color::-webkit-color-swatch{border:none;border-radius:0}.busbar-color::-moz-color-swatch{border:none;border-radius:0}.busbar-name{flex:1;min-width:0;padding:4px 6px;background:transparent;border:1px solid transparent;color:#e2e8f0;font-size:.9em;border-radius:4px}.busbar-name:hover{border-color:#6495ed40}.busbar-name:focus{outline:none;border-color:#6495ed99;background:#0000004d}.busbar-del{background:none;border:none;color:#94a3b8;cursor:pointer;font-size:1.25em;padding:0 6px;line-height:1;border-radius:4px;transition:all .15s ease}.busbar-del:hover{color:#ef4444;background:#ef444426}.busbar-meta{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-top:6px;padding-left:24px;font-size:.8em;color:#94a3b8}.busbar-overlap-label{display:inline-flex;align-items:center;gap:6px;color:#cbd5e1;cursor:pointer;-webkit-user-select:none;user-select:none}.busbar-overlap{accent-color:#6495ed}.busbar-overlap-size-label{display:inline-flex;align-items:center;gap:6px;color:#94a3b8;font-size:.95em}.busbar-overlap-size{width:64px;padding:3px 6px;font-size:.9em;background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:4px;color:#e2e8f0}.busbar-overlap-size:focus{outline:none;border-color:#6495ed99}.busbar-overlap-size:disabled{opacity:.45;cursor:not-allowed}.busbar-thickness-label{display:flex;align-items:center;gap:6px;color:#94a3b8;font-size:.95em;cursor:text}.busbar-thickness{width:64px;padding:3px 6px;font-size:.9em;background:#0000004d;border:1px solid rgba(100,149,237,.2);border-radius:4px;color:#e2e8f0}.busbar-thickness:focus{outline:none;border-color:#6495ed99}.busbar-blocked{margin-top:6px;padding:6px 8px;font-size:.8em;color:#ef4444;background:#ef444414;border-radius:4px}.btn-secondary{width:100%;padding:10px 16px;background:#6495ed1f;border:1px solid rgba(100,149,237,.3);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:.9em;font-weight:500;transition:all .2s ease;margin-bottom:12px}.btn-secondary:hover{background:#6495ed38;border-color:#6495ed80;transform:translateY(-1px)}.btn-secondary:active{transform:translateY(0)}#copyShareUrlBtn{margin-top:10px;margin-bottom:0;min-height:44px}#copyShareUrlBtn.is-success{background:#10b98142;border-color:#10b981bf;color:#ecfdf5}#copyShareUrlBtn.is-error{background:#ef444433;border-color:#f87171bf;color:#fee2e2} diff --git a/dist/assets/index-DwWFs8GA.js b/dist/assets/index-DwWFs8GA.js new file mode 100644 index 0000000..cbc7adc --- /dev/null +++ b/dist/assets/index-DwWFs8GA.js @@ -0,0 +1,26 @@ +(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))o(r);new MutationObserver(r=>{for(const s of r)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&o(a)}).observe(document,{childList:!0,subtree:!0});function t(r){const s={};return r.integrity&&(s.integrity=r.integrity),r.referrerPolicy&&(s.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?s.credentials="include":r.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function o(r){if(r.ep)return;r.ep=!0;const s=t(r);fetch(r.href,s)}})();const B={zoom:1,panX:0,panY:0,isDragging:!1,dragStartX:0,dragStartY:0,dragMoved:!1,lastMouseX:0,lastMouseY:0,currentPositions:[],currentCellSize:18,viewTransform:null},_e={key:null,positions:null};function ae(e,n="success"){const t=document.getElementById("previewStats");t&&(t.textContent=e,n==="error"?t.style.color="#ef4444":n==="success"?t.style.color="#10b981":t.style.color="#94a3b8")}function be(e,n="Generating 3D Model",t="Please be patient..."){const o=document.getElementById("loadingOverlay");if(!o)return;const r=document.getElementById("loadingText"),s=document.getElementById("loadingSubtext");r&&(r.textContent=n),s&&(s.textContent=t),e?(o.classList.add("active"),o.style.display="flex"):(o.classList.remove("active"),o.style.display="none")}function Qe(){const e=document.getElementById("bmsHolesType").value,n=document.getElementById("bmsHoleDiameterGroup"),t=document.getElementById("tabDimensionsGroup"),o=document.getElementById("tabOverlapSideGroup");n.style.display=e==="halfcircles"||e==="fullcircles"?"block":"none",t.style.display=e==="tabs"?"grid":"none",o&&(o.style.display=e==="tabs"?"block":"none")}function Nt(){document.querySelectorAll("select").forEach(n=>{const t=document.createElement("div");t.className="custom-select",n.parentNode.insertBefore(t,n),t.appendChild(n);const o=document.createElement("div");o.className="select-selected",o.textContent=n.options[n.selectedIndex].text,t.appendChild(o);const r=document.createElement("div");r.className="select-items",Array.from(n.options).forEach((s,a)=>{const i=document.createElement("div");i.textContent=s.text,i.dataset.value=s.value,a===n.selectedIndex&&(i.className="same-as-selected"),i.addEventListener("click",function(u){u.stopPropagation(),n.selectedIndex=a,o.textContent=this.textContent;const c=r.querySelector(".same-as-selected");c&&c.classList.remove("same-as-selected"),this.classList.add("same-as-selected"),o.click(),n.dispatchEvent(new Event("change"))}),r.appendChild(i)}),t.appendChild(r),o.addEventListener("click",function(s){s.stopPropagation(),ut(this),r.classList.toggle("show"),this.classList.toggle("select-arrow-active")})}),document.addEventListener("click",ut)}function ut(e){const n=document.querySelectorAll(".select-items"),t=document.querySelectorAll(".select-selected");n.forEach((o,r)=>{e!==t[r]&&(o.classList.remove("show"),t[r].classList.remove("select-arrow-active"))})}const Yt={BASE_URL:"./",DEV:!1,MODE:"production",PROD:!0,SSR:!1},we={instance:null,initialized:!1};async function wt(){if(!we.initialized)try{const n=`${(typeof import.meta<"u"&&Yt?"./":"/").replace(/\/?$/,"/")}vendor/opencascade.wasm.wasm`;we.instance=await opencascade({locateFile:()=>n}),we.initialized=!0,console.log("OpenCascade initialized successfully"),be(!1)}catch(e){console.error("Failed to initialize OpenCascade:",e),ae("Failed to initialize 3D engine. Please ensure opencascade.wasm.js and opencascade.wasm.wasm are in vendor/.","error"),be(!1)}}function xt(e,n,t,o,r,s,a){e.strokeStyle="#94a3b8",e.fillStyle="#94a3b8",e.lineWidth=1/a,e.font=`${12/a}px Arial`,e.textAlign="center",e.textBaseline="middle";const i=15/a,u=5/a,c=t+r*s+i;e.setLineDash([2/a,2/a]),e.beginPath(),e.moveTo(n,t+r*s),e.lineTo(n,c+i/2),e.stroke(),e.beginPath(),e.moveTo(n+o*s,t+r*s),e.lineTo(n+o*s,c+i/2),e.stroke(),e.setLineDash([]),e.beginPath(),e.moveTo(n,c),e.lineTo(n+o*s,c),e.stroke(),e.beginPath(),e.moveTo(n,c),e.lineTo(n+u,c-u/2),e.lineTo(n+u,c+u/2),e.closePath(),e.fill(),e.beginPath(),e.moveTo(n+o*s,c),e.lineTo(n+o*s-u,c-u/2),e.lineTo(n+o*s-u,c+u/2),e.closePath(),e.fill(),e.fillText(`${o.toFixed(1)}mm`,n+o*s/2,c+i);const h=n+o*s+i;e.setLineDash([2/a,2/a]),e.beginPath(),e.moveTo(n+o*s,t),e.lineTo(h+i/2,t),e.stroke(),e.beginPath(),e.moveTo(n+o*s,t+r*s),e.lineTo(h+i/2,t+r*s),e.stroke(),e.setLineDash([]),e.beginPath(),e.moveTo(h,t),e.lineTo(h,t+r*s),e.stroke(),e.beginPath(),e.moveTo(h,t),e.lineTo(h-u/2,t+u),e.lineTo(h+u/2,t+u),e.closePath(),e.fill(),e.beginPath(),e.moveTo(h,t+r*s),e.lineTo(h-u/2,t+r*s-u),e.lineTo(h+u/2,t+r*s-u),e.closePath(),e.fill(),e.save(),e.translate(h+i,t+r*s/2),e.rotate(-Math.PI/2),e.fillText(`${r.toFixed(1)}mm`,0,0),e.restore()}function Pe(e="preview"){const n=document.getElementById(e);if(!n)return;const t=n.getContext("2d");t.save(),t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,n.width,n.height),t.fillStyle="#1e293b",t.fillRect(0,0,n.width,n.height),t.restore()}function Ot(e){const n=document.getElementById("preview"),t=document.getElementById(e),o=B.viewTransform;if(!n||!t||!o)return;const r=t.getContext("2d");r.save(),r.setTransform(1,0,0,1,0,0),r.clearRect(0,0,t.width,t.height),r.translate(t.width,0),r.scale(-1,1),r.drawImage(n,0,0,t.width,t.height),r.restore();const s=B.panX+B.zoom*o.offsetX;B.panY+B.zoom*o.offsetY;const a=B.panX+B.zoom*(o.offsetX+o.packWidth*o.scale),i=B.panY+B.zoom*(o.offsetY+o.packHeight*o.scale);r.save(),r.setTransform(1,0,0,1,0,0),r.fillStyle="#1e293b",r.fillRect(0,Math.max(0,i),t.width,Math.max(0,t.height-i)),r.fillRect(0,0,Math.max(0,s),t.height),r.fillRect(Math.max(0,a),0,Math.max(0,t.width-a),t.height),r.restore(),r.save(),r.translate(B.panX,B.panY),r.scale(B.zoom,B.zoom),xt(r,o.offsetX,o.offsetY,o.packWidth,o.packHeight,o.scale,B.zoom),r.restore()}function zt(e,n){B.currentPositions=e,B.currentCellSize=n;const t=document.getElementById("preview");if(!t){console.error("Canvas element not found!");return}const o=t.getContext("2d");if(o.save(),o.setTransform(1,0,0,1,0,0),o.clearRect(0,0,t.width,t.height),o.fillStyle="#1e293b",o.fillRect(0,0,t.width,t.height),o.restore(),e.length===0)return;o.save(),o.translate(B.panX,B.panY),o.scale(B.zoom,B.zoom);const r=parseFloat(document.getElementById("spacing").value),s=document.getElementById("bmsHolesType").value,a=s!=="off",i=s==="tabs",u=s==="fullcircles",c=document.getElementById("roundedCorners").checked,h=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,y=parseFloat(document.getElementById("ledgeWidth").value)||0,l=n/2,g=Math.min(...e.map(M=>M[0])),p=Math.min(...e.map(M=>M[1])),v=Math.max(...e.map(M=>M[0])),d=Math.max(...e.map(M=>M[1])),m=v-g+n+r*2,f=d-p+n+r*2,_=t.getBoundingClientRect(),D=_.width,T=_.height,I=80,E=(D-I*2)/m,w=(T-I*2)/f,S=Math.min(E,w),k=(D-m*S)/2,z=(T-f*S)/2;B.viewTransform={offsetX:k,offsetY:z,scale:S,minX:g,minY:p,spacing:r,r:l,packWidth:m,packHeight:f};const V=B.zoom;if(c){const M=5*S,b=k,x=z,C=m*S,F=f*S;o.fillStyle="rgba(100, 149, 237, 0.15)",o.beginPath(),o.moveTo(b+M,x),o.lineTo(b+C-M,x),o.arcTo(b+C,x,b+C,x+M,M),o.lineTo(b+C,x+F-M),o.arcTo(b+C,x+F,b+C-M,x+F,M),o.lineTo(b+M,x+F),o.arcTo(b,x+F,b,x+F-M,M),o.lineTo(b,x+M),o.arcTo(b,x,b+M,x,M),o.closePath(),o.fill()}else o.fillStyle="rgba(100, 149, 237, 0.15)",o.fillRect(k,z,m*S,f*S);if(o.strokeStyle="#667eea",o.lineWidth=2/V,c){const M=5*S,b=k,x=z,C=m*S,F=f*S;o.beginPath(),o.moveTo(b+M,x),o.lineTo(b+C-M,x),o.arcTo(b+C,x,b+C,x+M,M),o.lineTo(b+C,x+F-M),o.arcTo(b+C,x+F,b+C-M,x+F,M),o.lineTo(b+M,x+F),o.arcTo(b,x+F,b,x+F-M,M),o.lineTo(b,x+M),o.arcTo(b,x,b+M,x,M),o.closePath(),o.stroke()}else o.strokeRect(k,z,m*S,f*S);const $=[];if(a){const M=p-l-r,b=d+l+r,x={};for(const[P,R]of e){const L=Math.round(R*1e3);x[L]||(x[L]=[]),x[L].push([P,R])}const C=Object.keys(x).map(Number).sort((P,R)=>P-R),F=C[C.length-1],W=C[0],oe=C[0],j=C[C.length-1];x[F].sort((P,R)=>P[0]-R[0]),x[W].sort((P,R)=>P[0]-R[0]),x[F][0][1],x[W][0][1];let J,ne;if(u){const P=x[oe][0][1],R=x[j][0][1],L=x[oe],te=x[j],Z=(Q,re,se,A)=>{const K=(se+A)/2,H=re0?ie=O:le=O:H>0?le=O:ie=O}const X=(ie+le)/2,ue=re+l*Math.sin(X);return(Q+2*ue)/3};L.length>=2&&(J=Z(M,P,L[0][0],L[1][0])),te.length>=2&&(ne=Z(b,R,te[0][0],te[1][0]))}else J=b,ne=M;const N=u?oe:F,Y=u?j:W;for(let P=0;P0?-Math.PI/2:0,K=se>0?0:Math.PI/2;for(let ue=0;ue<80;ue++){const ee=(A+K)/2,O=R-(te+l*Math.cos(ee)),G=(L+l*Math.sin(ee)-Q)*se-O*Math.sqrt(3);if(Math.abs(G)<1e-8)break;G<0?se>0?A=ee:K=ee:se>0?K=ee:A=ee}const H=(A+K)/2,ie={x:te+l*Math.cos(H),y:L+l*Math.sin(H)},le={x:Z-l*Math.cos(H),y:L+l*Math.sin(H)},X=u?{apex:{x:R,y:Q},left:ie,right:le}:null;$.push({x:R,y:re,diameter:h,isTab:!1,isFull:u,debugTri:X})}for(let P=0;P0?-Math.PI/2:0,K=se>0?0:Math.PI/2;for(let ue=0;ue<80;ue++){const ee=(A+K)/2,O=R-(te+l*Math.cos(ee)),G=(L+l*Math.sin(ee)-Q)*se-O*Math.sqrt(3);if(Math.abs(G)<1e-8)break;G<0?se>0?A=ee:K=ee:se>0?K=ee:A=ee}const H=(A+K)/2,ie={x:te+l*Math.cos(H),y:L+l*Math.sin(H)},le={x:Z-l*Math.cos(H),y:L+l*Math.sin(H)},X=u?{apex:{x:R,y:Q},left:ie,right:le}:null;$.push({x:R,y:re,diameter:h,isTab:!1,isFull:u,debugTri:X})}}o.fillStyle="#1e293b",o.strokeStyle="rgba(102, 126, 234, 0.8)",o.lineWidth=1.5/V;for(const[M,b]of e){const x=(M-g+l+r)*S+k,C=(b-p+l+r)*S+z,F=l*S;if(o.beginPath(),o.arc(x,C,F,0,Math.PI*2),o.fill(),o.stroke(),y>0){const W=(l-y)*S;o.strokeStyle="rgba(255, 193, 7, 0.8)",o.setLineDash([3/V,3/V]),o.lineWidth=1/V,o.beginPath(),o.arc(x,C,W,0,Math.PI*2),o.stroke(),o.setLineDash([]),o.strokeStyle="rgba(102, 126, 234, 0.8)",o.lineWidth=1.5/V}}if(a&&$.length>0)if(i){o.fillStyle="rgba(255, 193, 7, 0.5)",o.strokeStyle="rgba(255, 193, 7, 0.9)",o.lineWidth=1.5/V;const b=(parseFloat(document.getElementById("tabWidth").value)||4)*S,x=28*S;for(const C of $){const F=(C.x-g+l+r)*S+k;C.yd){const C=3/V;o.arc(b,z+f*S,x,Math.PI,0,!1),o.lineTo(b+x,z+f*S+C),o.lineTo(b-x,z+f*S+C)}else{const C=3/V;o.arc(b,z,x,0,Math.PI,!1),o.lineTo(b-x,z-C),o.lineTo(b+x,z-C)}o.closePath(),o.fill()}o.globalCompositeOperation="source-over";for(const M of $){const b=(M.x-g+l+r)*S+k,x=M.diameter/2*S;o.strokeStyle="rgba(16, 185, 129, 0.9)",o.lineWidth=1.5/V,o.beginPath(),M.y>d?o.arc(b,z+f*S,x,Math.PI,0,!1):o.arc(b,z,x,0,Math.PI,!1),o.stroke()}o.restore()}xt(o,k,z,m,f,S,V),o.restore()}const Xt="modulepreload",Kt=function(e,n){return new URL(e,n).href},ht={},qt=function(n,t,o){let r=Promise.resolve();if(t&&t.length>0){const a=document.getElementsByTagName("link"),i=document.querySelector("meta[property=csp-nonce]"),u=(i==null?void 0:i.nonce)||(i==null?void 0:i.getAttribute("nonce"));r=Promise.allSettled(t.map(c=>{if(c=Kt(c,o),c in ht)return;ht[c]=!0;const h=c.endsWith(".css"),y=h?'[rel="stylesheet"]':"";if(!!o)for(let p=a.length-1;p>=0;p--){const v=a[p];if(v.href===c&&(!h||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${c}"]${y}`))return;const g=document.createElement("link");if(g.rel=h?"stylesheet":Xt,h||(g.as="script"),g.crossOrigin="",g.href=c,u&&g.setAttribute("nonce",u),document.head.appendChild(g),h)return new Promise((p,v)=>{g.addEventListener("load",p),g.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${c}`)))})}))}function s(a){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=a,window.dispatchEvent(i),!i.defaultPrevented)throw a}return r.then(a=>{for(const i of a||[])i.status==="rejected"&&s(i.reason);return n().catch(s)})};function Et(e,n,t,o){const r=[],s=o/2,a=s+t,i=s+t;for(let u=i;u+s+t<=n;u+=o+t)for(let c=a;c+s+t<=e;c+=o+t)r.push([c,u]);return r}function St(e,n,t,o){const r=[],s=o/2;let a=s+t,i=0;for(;a+s+t<=n;){const u=i%2===0?0:(o+t)/2;let c=s+t+u;for(;c+s+t<=e;)r.push([c,a]),c+=o+t;a+=Math.sqrt(3)*(s+t/2),i++}return r}function Mt(e,n,t,o){const r=[],s=o/2;let a=s+t,i=0;for(;a+s+t<=e;){const u=i%2===0?0:(o+t)/2;let c=s+t+u;for(;c+s+t<=n;)r.push([a,c]),c+=o+t;a+=Math.sqrt(3)*(s+t/2),i++}return r}function Wt(e,n,t,o,r){return e==="grid"?Et(n,t,o,r):e==="honeycomb"?St(n,t,o,r):Mt(n,t,o,r)}function Ht(e,n,t,o,r){const s=`${e}_${n}_${t}_${o}_${r}`;if(_e.key===s&&_e.positions)return _e.positions;const a=Wt(r,e,n,t,o);return _e.key=s,_e.positions=a,a}function Ut(e,n){const t=we.instance;if(!t||e.length===0)return null;const{cellSize:o,spacing:r,height:s,terminalDiameter:a,terminalDepth:i,coverThickness:u,ledgeWidth:c,roundedCorners:h,bmsHoles:y,useTabs:l,useFullCircles:g}=n,p=o/2,v=Math.min(...e.map(M=>M[0]))-p-r,d=Math.min(...e.map(M=>M[1]))-p-r,m=Math.max(...e.map(M=>M[0]))+p+r,f=Math.max(...e.map(M=>M[1]))+p+r,_=(v+m)/2,D=(d+f)/2,T=e.map(([M,b])=>[M-_,b-D]),I=m-v,E=f-d;performance.now();const w=M=>{performance.now()};let k=new t.BRepPrimAPI_MakeBox(I,E,s).Shape();const z=new t.gp_Trsf;if(z.SetTranslation(new t.gp_Vec(-I/2,-E/2,0)),k=new t.BRepBuilderAPI_Transform(k,z,!1).Shape(),h)try{const b=(C,F)=>{const W={};let oe=0;const j=new t.TopExp_Explorer(C,t.TopAbs_EDGE);for(j.Init(C,t.TopAbs_EDGE);j.More();j.Next()){const J=t.TopoDS.prototype.Edge(j.Current()),ne=J.HashCode(1e8);W.hasOwnProperty(ne)||(W[ne]=oe,F(oe++,J))}return W},x=[];if(b(k,(C,F)=>{try{const W=new t.Bnd_Box;t.BRepBndLib.prototype.Add(F,W,!1);const oe=W.CornerMin(),j=W.CornerMax(),J=Math.abs(j.X()-oe.X()),ne=Math.abs(j.Y()-oe.Y()),N=Math.abs(j.Z()-oe.Z());J<1&&ne<1&&N>s*.8&&x.push(C)}catch{}}),x.length>0){const C=new t.BRepFilletAPI_MakeFillet(k);let F=0;b(k,(W,oe)=>{if(x.includes(W))try{C.Add(5,oe),F++}catch(j){console.error(` Failed to add edge ${W}:`,j.message)}}),F>0&&(k=new t.TopoDS_Solid(C.Shape()),console.log(" Applied rounded corners"),w("Rounded corners"))}else console.log(" No vertical edges found to fillet")}catch(M){console.error(" Fillet operation failed:",M.message),console.log(" Continuing without rounded corners")}const $=c>0||u>0;if($){const M=u,b=s-u,x=Math.max(.1,p-c),C=T.map(([j,J])=>{const ne=new t.gp_Ax2(new t.gp_Pnt(j,J,M),t.gp.prototype.DZ());return new t.BRepPrimAPI_MakeCylinder(ne,p,b).Shape()});let F=C[0];for(let j=1;j{const ne=new t.gp_Ax2(new t.gp_Pnt(j,J,0),t.gp.prototype.DZ());return new t.BRepPrimAPI_MakeCylinder(ne,x,s).Shape()});let oe=W[0];for(let j=1;j{const F=new t.gp_Ax2(new t.gp_Pnt(x,C,0),t.gp.prototype.DZ());return new t.BRepPrimAPI_MakeCylinder(F,p,s).Shape()});let b=M[0];for(let x=1;x{const L=Math.round(R*1e3);C[L]||(C[L]=[]),C[L].push([P,R])});const F=Object.keys(C).map(P=>parseInt(P)).sort((P,R)=>R-P),W=F[0],oe=F[F.length-1];if(g){const P=C[W][0][1],R=C[oe][0][1],L=E/2,te=-E/2,Z=(Q,re,se,A)=>{const K=(se+A)/2,H=re0?-Math.PI/2:0,le=H>0?0:Math.PI/2;for(let ee=0;ee<80;ee++){const O=(ie+le)/2,fe=se+p*Math.cos(O),G=re+p*Math.sin(O),ce=K-fe,q=(G-Q)*H-ce*Math.sqrt(3);if(Math.abs(q)<1e-8)break;q<0?H>0?ie=O:le=O:H>0?le=O:ie=O}const X=(ie+le)/2,ue=re+p*Math.sin(X);return(Q+2*ue)/3};b=Z(L,P,C[W][0][0],C[W][1][0]),x=Z(te,R,C[oe][0][0],C[oe][1][0])}const j=C[W].sort((P,R)=>P[0]-R[0]),J=C[oe].sort((P,R)=>P[0]-R[0]),ne=[];for(let P=0;P{const se=new t.BRepPrimAPI_MakeBox(P,R,s).Shape(),A=new t.gp_Trsf;A.SetTranslation(new t.gp_Vec(Q-P/2,L-R,0));const K=new t.BRepBuilderAPI_Transform(se,A,!1);Z.push(K.Shape())}),N.forEach(([Q])=>{const se=new t.BRepPrimAPI_MakeBox(P,R,s).Shape(),A=new t.gp_Trsf;A.SetTranslation(new t.gp_Vec(Q-P/2,te,0));const K=new t.BRepBuilderAPI_Transform(se,A,!1);Z.push(K.Shape())}),Z.length>0)if(Z.length===1)k=new t.BRepAlgoAPI_Cut(k,Z[0]).Shape();else{let Q=Z[0];for(let re=1;re{const L=new t.gp_Ax2(new t.gp_Pnt(P,R,0),t.gp.prototype.DZ()),te=new t.BRepPrimAPI_MakeCylinder(L,M/2,s).Shape();k=new t.BRepAlgoAPI_Cut(k,te).Shape()});else{const P=Y.map(([L,te])=>{const Z=new t.gp_Ax2(new t.gp_Pnt(L,te,0),t.gp.prototype.DZ());return new t.BRepPrimAPI_MakeCylinder(Z,M/2,s).Shape()});let R=P[0];for(let L=1;L{const x=new t.gp_Ax2(new t.gp_Pnt(M,b,s-i),t.gp.prototype.DZ()),C=new t.BRepPrimAPI_MakeCylinder(x,a/2,i).Shape();k=new t.BRepAlgoAPI_Cut(k,C).Shape()});else{const M=T.map(([F,W])=>{const oe=new t.gp_Ax2(new t.gp_Pnt(F,W,s-i),t.gp.prototype.DZ());return new t.BRepPrimAPI_MakeCylinder(oe,a/2,i).Shape()}),b=30,x=[];for(let F=0;Fi*180/Math.PI;return["0","ARC","8",s,"10",e.toFixed(4),"20",n.toFixed(4),"30","0.0","40",t.toFixed(4),"50",a(o).toFixed(4),"51",a(r).toFixed(4)]}function De(e,n,t,o,r){return["0","LINE","8",r,"10",e.toFixed(4),"20",n.toFixed(4),"30","0.0","11",t.toFixed(4),"21",o.toFixed(4),"31","0.0"]}const We=2*Math.PI,he=1e-5,Fe=e=>{const n=e%We;return n<0?n+We:n},Qt=(e,n)=>{const t=Math.abs(Fe(e)-Fe(n));return t>Math.PI?We-t:t};function eo(e,n,t,o){const r=n[0]-e[0],s=n[1]-e[1],a=e[0]-t[0],i=e[1]-t[1],u=r*r+s*s;if(u[(I[0]-s)*l+(I[1]-a)*g,-(I[0]-s)*g+(I[1]-a)*l],v=p(e),d=p(n);let m=0,f=1;const _=d[0]-v[0],D=d[1]-v[1],T=[[-_,v[0]-0],[_,y-v[0]],[-D,v[1]- -r],[D,r-v[1]]];for(const[I,E]of T)if(Math.abs(I)f+he)return[];w>m&&(m=w)}else{if(ws[0]-a[0]),t=[];for(const s of n)t.length&&s[0]<=t[t.length-1][1]+he?t[t.length-1][1]=Math.max(t[t.length-1][1],s[1]):t.push([s[0],s[1]]);const o=[];let r=0;for(const[s,a]of t)s>r+he&&o.push([r,Math.min(s,1)]),r=Math.max(r,a);return r<1-he&&o.push([r,1]),o}function oo(e,n){for(const t of n)if(Qt(e,t)(u.has(l)?u.get(l).radius=Math.max(u.get(l).radius,p):u.set(l,{pos:g,dirs:[],radius:p}),u.get(l));for(const l of e.padIndices){const g=n[l];g&&h(`c${l}`,g,t)}for(const l of s)!(l!=null&&l.key)||!Array.isArray(l.pos)||h(l.key,l.pos,l.radius??t);e.edges.forEach((l,g)=>{const p=n[l.from],v=n[l.to];if(!p||!v)return;const d=[{key:`c${l.from}`,pos:p},...l.waypoints.map((m,f)=>({key:`w${g}_${f}`,pos:m})),{key:`c${l.to}`,pos:v}];for(let m=1;m{if(!Array.isArray(l==null?void 0:l.from)||!Array.isArray(l==null?void 0:l.to))return;const p=l.fromKey||`extra_from_${g}`,v=l.toKey||`extra_to_${g}`,d=l.radius??t;h(p,l.from,d),h(v,l.to,d);const m=l.to[0]-l.from[0],f=l.to[1]-l.from[1];if(Math.hypot(m,f)({key:l,...g}));for(const l of y){const[g,p]=l.pos,v=l.radius??t;if(l.dirs.length===0){r.push(...Vt(g,p,v,o));continue}const d=[];for(const f of l.dirs)d.push(Fe(f-Math.PI/2)),d.push(Fe(f+Math.PI/2));d.sort((f,_)=>f-_);const m=[];for(const f of d)(m.length===0||Math.abs(f-m[m.length-1])>he)&&m.push(f);for(let f=0;fthis.listeners.delete(e)},subscribeMutations(e){return this.mutationListeners.add(e),()=>this.mutationListeners.delete(e)},_emitMutation(e){this.mutationListeners.forEach(n=>n(e))},_notify(){this.listeners.forEach(e=>e())},getSnapshot(){return{activeId:this.activeId,list:this.list.map(e=>({id:e.id,name:e.name,color:e.color,cellIndices:Array.isArray(e.cellIndices)?[...e.cellIndices]:[],thickness:e.thickness,overlapEnabled:e.overlapEnabled===!0,overlapSize:Number.isFinite(Number(e.overlapSize))&&Number(e.overlapSize)>0?Number(e.overlapSize):10,face:e.face==="bottom"?"bottom":"top"}))}},replaceFromSnapshot(e){const n=e&&Array.isArray(e.list)?e.list:[];this.list=n.map((o,r)=>({id:typeof o.id=="string"&&o.id?o.id:`bb-${r+1}`,name:typeof o.name=="string"&&o.name?o.name:`Busbar ${r+1}`,color:typeof o.color=="string"&&o.color?o.color:Ce[r%Ce.length],cellIndices:Array.isArray(o.cellIndices)?o.cellIndices.map(s=>Number(s)).filter(s=>Number.isInteger(s)&&s>=0):[],thickness:Number.isFinite(Number(o.thickness))&&Number(o.thickness)>0?Number(o.thickness):1,overlapEnabled:o.overlapEnabled===!0,overlapSize:Number.isFinite(Number(o.overlapSize))&&Number(o.overlapSize)>0?Number(o.overlapSize):10,face:o.face==="bottom"?"bottom":"top"})),typeof(e==null?void 0:e.activeId)=="string"&&this.list.some(o=>o.id===e.activeId)?this.activeId=e.activeId:this.activeId=this.list.length?this.list[0].id:null,Ge=this.list.reduce((o,r)=>{const s=parseInt(String(r.id).replace(/^bb-/,""),10);return Number.isFinite(s)?Math.max(o,s):o},0)+1,Ne=this.list.length,this._emitMutation("replaceFromSnapshot"),this._notify()},add(e="top"){const n=e==="bottom"?"top":"bottom",t=new Set(this.list.filter(a=>a.face===n).map(a=>a.color)),o=new Set(this.list.filter(a=>a.face===e).map(a=>a.color));let r=Ce.find(a=>!t.has(a)&&!o.has(a));r||(r=Ce.find(a=>!t.has(a))),r||(r=Ce[Ne%Ce.length]);const s={id:"bb-"+Ge++,name:`Busbar ${this.list.length+1}`,color:r,cellIndices:[],thickness:1,overlapEnabled:!1,overlapSize:10,face:e==="bottom"?"bottom":"top"};return Ne++,this.list.push(s),this.activeId=s.id,this._emitMutation("add"),this._notify(),s},remove(e){this.list=this.list.filter(n=>n.id!==e),this.activeId===e&&(this.activeId=this.list.length?this.list[0].id:null),this._emitMutation("remove"),this._notify()},rename(e,n){const t=this.list.find(o=>o.id===e);t&&(t.name=n,this._emitMutation("rename"),this._notify())},setColor(e,n){const t=this.list.find(o=>o.id===e);t&&(t.color=n,this._emitMutation("setColor"),this._notify())},setThickness(e,n){const t=this.list.find(o=>o.id===e);t&&(t.thickness=n,this._emitMutation("setThickness"),this._notify())},setOverlapEnabled(e,n){const t=this.list.find(o=>o.id===e);t&&(t.overlapEnabled=n===!0,this._emitMutation("setOverlapEnabled"),this._notify())},setOverlapSize(e,n){const t=this.list.find(o=>o.id===e);t&&Number.isFinite(Number(n))&&Number(n)>0&&(t.overlapSize=Number(n),this._emitMutation("setOverlapSize"),this._notify())},setFace(e,n){const t=this.list.find(o=>o.id===e);t&&(t.face=n==="bottom"?"bottom":"top",this._emitMutation("setFace"),this._notify())},setActive(e){this.activeId=e,this._emitMutation("setActive"),this._notify()},getActive(){return this.list.find(e=>e.id===this.activeId)||null},toggleCell(e){const n=this.getActive();if(!n)return!1;const t=n.cellIndices.indexOf(e);if(t>=0)n.cellIndices.splice(t,1);else{if(this.list.find(r=>r.id!==n.id&&(r.face||"top")===(n.face||"top")&&r.cellIndices.includes(e)))return!1;n.cellIndices.push(e)}return this._emitMutation("toggleCell"),this._notify(),!0},clearAll(){this.list.length!==0&&(this.list=[],this.activeId=null,Ne=0,Ge=1,this._emitMutation("clearAll"),this._notify())},clearAllCells(){this.list.every(e=>e.cellIndices.length===0)||(this.list.forEach(e=>{e.cellIndices=[]}),this._emitMutation("clearAllCells"),this._notify())}};function no(e,n,t,o){const r=[],s=(2*t+o)*1.3;for(let c=0;ca.set(c,[]));for(const[c,h]of r)a.get(c).push(h),a.get(h).push(c);const i=new Set,u=[];for(const c of e){if(i.has(c))continue;const h=[],y=[c];for(;y.length;){const l=y.pop();if(!i.has(l)){i.add(l),h.push(l);for(const g of a.get(l))y.push(g)}}u.push(h)}for(;u.length>1;){let c=null,h=1/0,y=-1,l=-1;for(let g=0;g.985){t.push(i);continue}const D=Math.min(r,g*.35,p*.35);if(D<.25){t.push(i);continue}const T=[i[0]-v*D,i[1]-d*D],I=[i[0]+m*D,i[1]+f*D];t.push(T);for(let E=1;Es-a);for(let s=1;s.001&&(o=Math.min(o,a))}}return Number.isFinite(o)?o:0}function co(e,n,t){if(e.length<2)return{extraPads:[],extraSegments:[]};const o=e.map(m=>({index:m,pos:n[m]})).filter(m=>Array.isArray(m.pos)&&m.pos.length>=2);if(o.length!==e.length)return{extraPads:[],extraSegments:[]};const r=.001,s=Pt(n);if(s<=r)return{extraPads:[],extraSegments:[]};const a=Math.max(.5,s*.25),i=.001,u=(m,f)=>n.some(_=>{if(Math.abs(_[1]-m.pos[1])>i)return!1;const D=_[0]-m.pos[0];return f==="left"&&D>=-r||f==="right"&&D<=r?!1:Math.abs(Math.abs(D)-s)<=a}),c=new Map;for(const m of o){const f=m.pos[1].toFixed(4);c.has(f)||c.set(f,[]),c.get(f).push(m)}const h=[],y=[];for(const m of c.values())m.sort((f,_)=>f.pos[0]-_.pos[0]),h.push(m[0]),y.push(m[m.length-1]);const l=h.filter(m=>!u(m,"left")),g=y.filter(m=>!u(m,"right")),p=[],v=[],d=(m,f)=>{const _=f==="left"?-1:1,D=m.slice().sort((T,I)=>T.pos[1]-I.pos[1]);for(let T=0;T({index:E,pos:n[E]})).filter(E=>Array.isArray(E.pos)&&E.pos.length>=2);if(r.length!==e.length)return{extraPads:[],extraSegments:[]};const s=.001,a=Pt(n);if(a<=s)return{extraPads:[],extraSegments:[]};const i=Math.max(.5,a*.25),u=.001,c=(E,w)=>n.some(S=>{if(Math.abs(S[1]-E.pos[1])>u)return!1;const k=S[0]-E.pos[0];return w==="left"&&k>=-s||w==="right"&&k<=s?!1:Math.abs(Math.abs(k)-a)<=i}),h=new Map;for(const E of r){const w=E.pos[1].toFixed(4);h.has(w)||h.set(w,[]),h.get(w).push(E)}const y=[],l=[];for(const E of h.values())E.sort((w,S)=>w.pos[0]-S.pos[0]),y.push(E[0]),l.push(E[E.length-1]);const g=y.filter(E=>!c(E,"left")),p=l.filter(E=>!c(E,"right"));let v=null,d=[];if(g.length>p.length&&g.length>0?(v="left",d=g):p.length>g.length&&p.length>0&&(v="right",d=p),!v||d.length===0)return{extraPads:[],extraSegments:[]};const m=Number.isFinite(Number(o))&&Number(o)>0?Number(o):10,f=v==="left"?-1:1,_=t==="honeycomb"?f<0?Math.min(...d.map(E=>E.pos[0]))-m:Math.max(...d.map(E=>E.pos[0]))+m:null,D=[],T=[],I=d.slice().sort((E,w)=>E.pos[1]-w.pos[1]).map((E,w)=>{const S=`edge_overlap_${w}`,k=[_??E.pos[0]+f*m,E.pos[1]];return D.push({key:S,pos:k}),T.push({from:k,to:E.pos,fromKey:S,toKey:`c${E.index}`}),{key:S,pos:k}});for(let E=0;En[o]).filter(o=>Array.isArray(o)&&o.length>=2).map(o=>({center:o.slice(),width:5,height:2}))}function Bt(e,n,t,o,r,s,a=null,i=!0,u="grid",c=10,h=!1){if(e.length===0)return{padIndices:[],edges:[],blocked:null,extraPads:[],extraSegments:[],cutouts:[]};if(e.length===1)return{padIndices:e.slice(),edges:[],blocked:null,extraPads:[],extraSegments:[],cutouts:mt(e,n,h)};const y=no(e,n,t,r),l=new Set(e),g=n.filter((T,I)=>!l.has(I)),p=Math.max(r,.3),v=o,d=[];for(const[T,I]of y){const E=n[T],w=n[I];if(tt(E,w,v,g,s,p))d.push({from:T,to:I,waypoints:[]});else{const S=ro(E,w,v,g,s,p);if(S)d.push({from:T,to:I,waypoints:[S]});else return{padIndices:e.slice(),edges:d,blocked:{from:T,to:I,reason:"no clear route between these cells"},extraPads:[],extraSegments:[],cutouts:[]}}}const m=d.map(T=>{const I=[n[T.from],...T.waypoints,n[T.to]],E=io(I,o);return{from:T.from,to:T.to,waypoints:E.slice(1,-1)}}),f=i?lo(e,n,u,c):{extraPads:[],extraSegments:[]},_=co(e,n,o),D=mt(e,n,h);return{padIndices:e.slice(),edges:m,blocked:null,extraPads:[...f.extraPads,..._.extraPads],extraSegments:[...f.extraSegments,..._.extraSegments],cutouts:D}}function uo(e,n){const t=parseInt(e.slice(1,3),16),o=parseInt(e.slice(3,5),16),r=parseInt(e.slice(5,7),16);return`rgba(${t},${o},${r},${n})`}function pt(e,n,t,o,r,s,a,i="preview",u=!1,c=!1){const h=document.getElementById(i);if(!h)return;const y=B.viewTransform;if(!y||e.length===0)return;const l=h.getContext("2d"),g=o/2,p=I=>(I-y.minX+g+s)*y.scale+y.offsetX,v=I=>(I-y.minY+g+s)*y.scale+y.offsetY,d=I=>(I??r)*y.scale,m=I=>Array.isArray(I==null?void 0:I.extraSegments)?I.extraSegments:[],f=I=>Array.isArray(I==null?void 0:I.extraPads)?I.extraPads:[],_=I=>Array.isArray(I==null?void 0:I.cutouts)?I.cutouts:[];l.save(),l.translate(B.panX,B.panY),l.scale(B.zoom,B.zoom);const D=B.zoom,T=(I,E=1)=>{l.save(),l.setTransform(1,0,0,1,0,0),l.globalAlpha=E,c&&(l.translate(h.width,0),l.scale(-1,1)),l.drawImage(I,0,0),l.restore()};e.forEach((I,E)=>{const w=n[E];if(!w||I.cellIndices.length===0)return;const S=!u&&I.id===a,k=u?.12:S?.45:.3,z=window.devicePixelRatio||1,V=document.createElement("canvas");V.width=h.width,V.height=h.height;const $=V.getContext("2d");$.scale(z,z),$.translate(B.panX,B.panY),$.scale(B.zoom,B.zoom);const M=document.createElement("canvas");M.width=h.width,M.height=h.height;const b=M.getContext("2d");b.scale(z,z),b.translate(B.panX,B.panY),b.scale(B.zoom,B.zoom);const x=I.color,C=`rgb(${parseInt(x.slice(1,3),16)},${parseInt(x.slice(3,5),16)},${parseInt(x.slice(5,7),16)})`,F=I.cellIndices,W=F.length,oe=(o+s)*1.3,j=(o+s)*1.5,J=[],ne=Array.from({length:W},()=>new Set);for(let N=0;N{const se=re[0]-Z[0],A=re[1]-Z[1],K=Math.hypot(se,A);if(K<1e-6||K<=r)return re;const H=r/K;return[re[0]-se*H,re[1]-A*H]});$.beginPath(),$.moveTo(p(Q[0][0]),v(Q[0][1])),$.quadraticCurveTo(p(Z[0]),v(Z[1]),p(Q[1][0]),v(Q[1][1])),$.quadraticCurveTo(p(Z[0]),v(Z[1]),p(Q[2][0]),v(Q[2][1])),$.quadraticCurveTo(p(Z[0]),v(Z[1]),p(Q[0][0]),v(Q[0][1])),$.closePath(),$.fill()}$.strokeStyle=C,$.lineWidth=2*d(),$.lineCap="round";for(const N of w.edges){if(N.waypoints.length===0)continue;const Y=[t[N.from],...N.waypoints,t[N.to]];if(!Y.some(P=>!P)){$.beginPath(),$.moveTo(p(Y[0][0]),v(Y[0][1]));for(let P=1;P"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}let $e="both",ot=null,Ve=null;function Tt(){var t;const e=document.getElementById("busbarList");if(!e)return;const n=((t=document.getElementById("busbarFormat"))==null?void 0:t.value)||"step";e.classList.toggle("busbar-list-dxf",n==="dxf")}const ho='';function fo(e,n){const t=document.createElement("div");return t.className="busbar-row"+(e.id===U.activeId?" active":""),t.dataset.id=e.id,t.innerHTML=` +
+ + + + +
+
+ ${e.cellIndices.length} cell${e.cellIndices.length===1?"":"s"} + + + +
+ ${n[e.id]?`
⚠ ${Ye(n[e.id])}
`:""} + `,t.addEventListener("click",o=>{o.target.closest("input")||o.target.closest("button")||U.setActive(e.id)}),t.querySelector(".busbar-name").addEventListener("change",o=>{U.rename(e.id,o.target.value)}),t.querySelector(".busbar-color").addEventListener("input",o=>{U.setColor(e.id,o.target.value)}),t.querySelector(".busbar-overlap").addEventListener("change",o=>{U.setOverlapEnabled(e.id,o.target.checked)}),t.querySelector(".busbar-overlap-size").addEventListener("change",o=>{const r=parseFloat(o.target.value);r>0&&U.setOverlapSize(e.id,r)}),t.querySelector(".busbar-thickness").addEventListener("change",o=>{const r=parseFloat(o.target.value);r>0&&U.setThickness(e.id,r)}),t.querySelector(".busbar-dl").addEventListener("click",o=>{o.stopPropagation(),ot&&ot(e.id)}),t.querySelector(".busbar-del").addEventListener("click",o=>{o.stopPropagation(),U.remove(e.id)}),t}function Ze(e={}){const n=document.getElementById("busbarList");if(!n)return;if(n.innerHTML="",Tt(),U.list.length===0){const o=document.createElement("div");o.className="busbar-empty",o.textContent="No busbars. Add a Top or Bottom busbar then click cells in the preview.",n.appendChild(o);return}const t=$e==="top"?["top"]:["top","bottom"];for(const o of t){const r=U.list.filter(i=>(i.face||"top")===o),s=document.createElement("div");s.className="busbar-face-section";const a=document.createElement("div");if(a.className="busbar-face-label",a.textContent=o==="top"?"Top Face":"Bottom Face",s.appendChild(a),r.length===0){const i=document.createElement("div");i.className="busbar-face-empty",i.textContent=`No ${o} busbars.`,s.appendChild(i)}else r.forEach(i=>s.appendChild(fo(i,e)));n.appendChild(s)}}function mo({onDownloadSingle:e,onDownloadAll:n,onFaceFilterChange:t}={}){ot=e??null,Ve=n??null;const o=document.getElementById("addTopBusbarBtn");o&&o.addEventListener("click",()=>U.add("top"));const r=document.getElementById("addBottomBusbarBtn");r&&r.addEventListener("click",()=>U.add("bottom"));const s=document.getElementById("bottomFaceWrap");document.querySelectorAll(".face-filter-btn").forEach(c=>{c.addEventListener("click",()=>{$e=c.dataset.filter,document.querySelectorAll(".face-filter-btn").forEach(h=>{h.classList.toggle("active",h.dataset.filter===$e)}),s&&(s.hidden=$e==="top"),t&&t($e),Ze()})});const a=document.getElementById("clearMarkingsBtn");a&&a.addEventListener("click",()=>{U.list.every(c=>c.cellIndices.length===0)||U.clearAllCells()});const i=document.getElementById("downloadAllBusbarsBtn");i&&i.addEventListener("click",()=>{Ve&&Ve()});const u=document.getElementById("busbarFormat");u&&u.addEventListener("change",Tt)}let Te=[],Le=null;function po(e,n,t){if(!Array.isArray(e)||e.length<2)return{top:[],bottom:[]};const o=new Map;for(const[c,h]of e){const y=h.toFixed(4);o.has(y)||o.set(y,[]),o.get(y).push([c,h])}const r=Array.from(o.keys()).sort((c,h)=>Number(c)-Number(h));if(r.length===0)return{top:[],bottom:[]};const s=(o.get(r[0])||[]).slice().sort((c,h)=>c[0]-h[0]),a=(o.get(r[r.length-1])||[]).slice().sort((c,h)=>c[0]-h[0]),i=Math.min(...e.map(([,c])=>c))-n-t,u=Math.max(...e.map(([,c])=>c))+n+t;return{top:s.slice(0,-1).map((c,h)=>({key:`top_${h}`,x:(c[0]+s[h+1][0])/2,y:i})),bottom:a.slice(0,-1).map((c,h)=>({key:`bottom_${h}`,x:(c[0]+a[h+1][0])/2,y:u}))}}function bo(e,n){const t=[],o=new Set,r=(s,a)=>{const i=`${s.toFixed(4)},${a.toFixed(4)}`;o.has(i)||(o.add(i),t.push([s,a]))};for(const s of(e==null?void 0:e.padIndices)||[]){const a=n[s];a&&r(a[0],a[1])}for(const s of(e==null?void 0:e.edges)||[]){const a=[n[s.from],...s.waypoints||[],n[s.to]].filter(Boolean);for(let i=0;iS)):Math.max(...D.map(([,S])=>S)),I=Math.max(s*1.1,a+s*.35),E=D.filter(([,S])=>u==="top"?S<=T+I:S>=T-I);if(E.length===0)continue;let w=null;for(const S of h)for(const k of E){const z=Math.abs(k[0]-S.x),V=Math.abs(S.y-k[1]);if(z>l||V>g)continue;const $=z*3+V,M={busbarIndex:f,geometry:_,anchor:k,score:$,tabKey:S.key,tab:S,deltaX:k[0]-S.x};(!w||M.scoreS.deltaX<-p),T=_.find(S=>S.deltaX>p);if(D&&T)continue;const I=f.anchor.slice(),E=[f.tab.x,f.tab.y],w=[f.tab.x,f.tab.y+y*c];f.geometry.extraPads=Array.isArray(f.geometry.extraPads)?f.geometry.extraPads:[],f.geometry.extraPads.push({key:`bms_tab_anchor_${u}_${f.tabKey}`,pos:I,radius:v}),f.geometry.extraPads.push({key:`bms_tab_edge_${u}_${f.tabKey}`,pos:E,radius:v}),f.geometry.extraPads.push({key:`bms_tab_inner_${u}_${f.tabKey}`,pos:w,radius:v}),f.geometry.extraSegments=Array.isArray(f.geometry.extraSegments)?f.geometry.extraSegments:[],f.geometry.extraSegments.push({from:I,to:w,fromKey:`bms_tab_anchor_${u}_${f.tabKey}`,toKey:`bms_tab_inner_${u}_${f.tabKey}`,radius:v}),f.geometry.extraSegments.push({from:E,to:w,fromKey:`bms_tab_edge_${u}_${f.tabKey}`,toKey:`bms_tab_inner_${u}_${f.tabKey}`,radius:v})}}function Ct(e,n,t,o){zt(e,n);const r=U.list.map((i,u)=>({bb:i,geom:Te[u]})),s=r.filter(i=>(i.bb.face||"top")==="top"),a=r.filter(i=>(i.bb.face||"top")==="bottom");Ot("preview-bottom"),pt(s.map(i=>i.bb),s.map(i=>i.geom),e,n,t,o,U.activeId,"preview"),pt(a.map(i=>i.bb),a.map(i=>i.geom),e,n,t,o,U.activeId,"preview-bottom",!1,!0)}function At(){if(!Le)return;const{positions:e,cellSize:n,padRadius:t,spacing:o}=Le;Ct(e,n,t,o)}function Be(e=!1){var o,r,s,a;e&&(B.zoom=1,B.panX=0,B.panY=0);const n=document.getElementById("previewStats"),t=(i,u)=>{n.textContent=i,n.style.color=u};try{const i=parseFloat(document.getElementById("xDim").value),u=parseFloat(document.getElementById("yDim").value),c=parseFloat(document.getElementById("spacing").value),h=parseFloat(document.getElementById("cellSize").value),y=document.getElementById("layoutType").value,l=parseFloat(document.getElementById("ledgeWidth").value)||0,g=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,p=parseFloat(document.getElementById("coverThickness").value);if(!i||!u||!c||!h){t("Configure settings to see preview","#94a3b8");return}if(l>0&&l>=h){t(`Ledge width (${l}mm) must be less than cell diameter (${h}mm)!`,"#ef4444"),Pe();return}const v=h+c*2;if(ii||h>u){t(`Cell diameter (${h}mm) larger than pack dimensions!`,"#ef4444"),Pe();return}if(c<0){t("Cell spacing cannot be negative!","#ef4444"),Pe();return}const d=Ht(i,u,c,h,y);if(!d||d.length===0){t("No cells fit! Increase pack size or decrease cell size/spacing","#ef4444"),Pe();return}if(document.getElementById("bmsHolesType").value!=="off"){if(g>h){t(`BMS hole (${g}mm) larger than cell (${h}mm)! Reduce hole size.`,"#ef4444"),Pe();return}const b=h/2,x=g/2,C=b,F=Math.min(...d.map(L=>L[1])),oe=Math.max(...d.map(L=>L[1]))+C+c,j=F-C-c,J={};for(const[L,te]of d){const Z=Math.round(te*1e3);J[Z]||(J[Z]=[]),J[Z].push([L,te])}const ne=Object.keys(J).map(Number).sort((L,te)=>L-te),N=ne[ne.length-1],Y=ne[0];J[N].sort((L,te)=>L[0]-te[0]),J[Y].sort((L,te)=>L[0]-te[0]);const P=oe,R=j;for(let L=0;Lh/2&&t(`Cover thickness (${p}mm) very large for cell size (${h}mm)`,"#f59e0b"),c<.5&&c>0&&t("Spacing < 0.5mm may be difficult to 3D print","#f59e0b"),d.length<2?t(`Only ${d.length} cell fits. Increase pack size for practical holder.`,"#f59e0b"):n.style.color="#10b981";const _=h/2,D=Math.max(_-l,1),T=4,I=((o=document.getElementById("busbarCellCutoutEnabled"))==null?void 0:o.checked)===!0,E={left:Math.min(...d.map(b=>b[0]))-_-c,right:Math.max(...d.map(b=>b[0]))+_+c,bottom:Math.min(...d.map(b=>b[1]))-_-c,top:Math.max(...d.map(b=>b[1]))+_+c};Te=U.list.map(b=>Bt(b.cellIndices,d,_,D,c,T,E,b.overlapEnabled!==!1,y,b.overlapSize,I)),kt(U.list,Te,d,{enabled:((r=document.getElementById("bmsHolesType"))==null?void 0:r.value)==="tabs",cellRadius:_,spacing:c,tabWidth:parseFloat((s=document.getElementById("tabWidth"))==null?void 0:s.value)||4,tabOverlapSide:((a=document.getElementById("tabOverlapSide"))==null?void 0:a.value)||"off",overlapLength:28}),Le={positions:d,cellSize:h,padRadius:D,spacing:c},Ct(d,h,D,c);const w={};U.list.forEach((b,x)=>{const C=Te[x];C&&C.blocked&&(w[b.id]=C.blocked.reason)}),Ze(w);const S=Math.min(...d.map(b=>b[0])),k=Math.min(...d.map(b=>b[1])),z=Math.max(...d.map(b=>b[0])),V=Math.max(...d.map(b=>b[1])),$=z-S+h+c*2,M=V-k+h+c*2;d.length>=2&&(n.textContent=`${d.length} cells • ${$.toFixed(0)}×${M.toFixed(0)} mm`)}catch(i){console.error("Preview error:",i),t("Error: "+i.message,"#ef4444")}}async function go(){var n,t,o;const e=document.getElementById("layoutType").value;if(!(!we.initialized&&(ae("3D engine not ready. Please wait...","error"),await wt(),!we.initialized))){be(!0,"Generating 3D Model","Please be patient..."),await new Promise(r=>setTimeout(r,50));try{const r=parseFloat(document.getElementById("xDim").value),s=parseFloat(document.getElementById("yDim").value),a=parseFloat(document.getElementById("spacing").value),i=parseFloat(document.getElementById("cellSize").value),u=parseFloat(document.getElementById("ledgeWidth").value)||0,c=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,h=parseFloat(document.getElementById("coverThickness").value),y=i/2,l=c/2;if(u>0&&u>=i){ae(`Ledge width (${u}mm) must be less than cell diameter (${i}mm)!`,"error"),be(!1);return}const g=i+a*2;if(rr||i>s){ae("Cell diameter is larger than pack dimensions!","error"),be(!1);return}if(a<0){ae("Cell spacing cannot be negative!","error"),be(!1);return}const p=parseFloat(document.getElementById("height").value),v=8,d=1,m=document.getElementById("roundedCorners").checked,f=document.getElementById("bmsHolesType").value,_=f!=="off",D=f==="tabs",T=f==="fullcircles",I=!1,E=!1;let w,S;switch(e){case"grid":w=Et(r,s,a,i),S="Grid Layout";break;case"honeycomb":w=St(r,s,a,i),S="Honeycomb Layout";break;case"vertical":w=Mt(r,s,a,i),S="Vertical Honeycomb";break;default:ae("Invalid layout type","error");return}if(_&&T){const A=(G,ce,de,q)=>{const me=(de+q)/2,pe=ce0?-Math.PI/2:0,Ee=pe>0?0:Math.PI/2;for(let Re=0;Re<80;Re++){const ke=(xe+Ee)/2,Rt=me-(de+y*Math.cos(ke)),dt=(ce+y*Math.sin(ke)-G)*pe-Rt*Math.sqrt(3);if(Math.abs(dt)<1e-8)break;dt<0?pe>0?xe=ke:Ee=ke:pe>0?Ee=ke:xe=ke}const Se=(xe+Ee)/2,Me=ce+y*Math.sin(Se);return(G+2*Me)/3},K=Math.min(...w.map(G=>G[1])),H=Math.max(...w.map(G=>G[1])),ie=K-y-a,le=H+y+a,X={};for(const[G,ce]of w){const de=Math.round(ce*1e3);X[de]||(X[de]=[]),X[de].push([G,ce])}const ue=Object.keys(X).map(Number).sort((G,ce)=>G-ce),ee=ue[0],O=ue[ue.length-1];X[ee].sort((G,ce)=>G[0]-ce[0]),X[O].sort((G,ce)=>G[0]-ce[0]);const fe=[];for(let G=0;Gq[1])),K=Math.max(...w.map(q=>q[1])),H=i/2,ie=A-H-a,le=K+H+a,X={};for(const[q,me]of w){const pe=Math.round(me*1e3);X[pe]||(X[pe]=[]),X[pe].push([q,me])}const ue=Object.keys(X).map(Number).sort((q,me)=>q-me),ee=ue[ue.length-1],O=ue[0];X[ee].sort((q,me)=>q[0]-me[0]),X[O].sort((q,me)=>q[0]-me[0]);const fe=X[ee][0][1],G=X[O][0][1];let ce,de;E||(ce=le,de=ie);for(let q=0;qA[0]))-y-a,right:Math.max(...w.map(A=>A[0]))+y+a,bottom:Math.min(...w.map(A=>A[1]))-y-a,top:Math.max(...w.map(A=>A[1]))+y+a},W=U.list.map(A=>Bt(A.cellIndices,w,y,b,a,x,F,A.overlapEnabled!==!1,e,A.overlapSize,C));kt(U.list,W,w,{enabled:f==="tabs",cellRadius:y,spacing:a,tabWidth:k,tabOverlapSide:V,overlapLength:28});for(let A=0;AA[0]))+Math.max(...w.map(A=>A[0])))/2,j=(Math.min(...w.map(A=>A[1]))+Math.max(...w.map(A=>A[1])))/2,J=w.map(([A,K])=>[A-oe,K-j]),ne=A=>(A||"").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"busbar",N=(A,K)=>{var ee;const H=A.cellIndices,ie=((ee=document.getElementById("busbarCellCutoutEnabled"))==null?void 0:ee.checked)===!0,le=(Array.isArray(K==null?void 0:K.extraSegments)?K.extraSegments:[]).filter(O=>String((O==null?void 0:O.fromKey)||"").startsWith("bms_tab_")||String((O==null?void 0:O.toKey)||"").startsWith("bms_tab_")).map(O=>`${O.from[0].toFixed(3)},${O.from[1].toFixed(3)}>${O.to[0].toFixed(3)},${O.to[1].toFixed(3)}`).sort().join(";");if(H.length===0)return null;if(H.length===1)return`single|${A.thickness.toFixed(2)}|ov:${A.overlapEnabled===!0?1:0}|os:${Number(A.overlapSize??10).toFixed(2)}|cc:${ie?1:0}|tabs:${le}`;const X=H.map(O=>J[O]).filter(Boolean),ue=[];for(let O=0;OO-fe),`${X.length}|${A.thickness.toFixed(2)}|ov:${A.overlapEnabled===!0?1:0}|os:${Number(A.overlapSize??10).toFixed(2)}|cc:${ie?1:0}|tabs:${le}|${ue.map(O=>O.toFixed(3)).join(",")}`},Y=((o=document.getElementById("busbarFormat"))==null?void 0:o.value)||"step",P=[],R=new Map;for(let A=0;Anew Promise(K=>setTimeout(K,A));et(M,`cellholder_${e}.step`);for(let A=0;AA.cellIndices.length>0).length-P.length,Q=P.length>0?`. ${P.length} unique ${Y.toUpperCase()} busbar file${P.length===1?"":"s"}${Z>0?` (${Z} mirrored duplicate${Z===1?"":"s"} skipped)`:""}`:"",re=D?"edge tabs":E?"circle offset":"semicircle offset",se=I&&!D?" with filleted holes":"";ae(`${S} generated. ${w.length} cells (${re}${se})${Q}.`,"success")}catch(r){console.error("Generation error:",r),ae("Error: "+r.message,"error")}finally{be(!1)}}}function _t(){var u;if(!Le||Te.length===0)return null;const{positions:e,padRadius:n}=Le,t=(Math.min(...e.map(c=>c[0]))+Math.max(...e.map(c=>c[0])))/2,o=(Math.min(...e.map(c=>c[1]))+Math.max(...e.map(c=>c[1])))/2,r=e.map(([c,h])=>[c-t,h-o]),s=parseFloat(document.getElementById("height").value),a=((u=document.getElementById("busbarFormat"))==null?void 0:u.value)||"step";return{centeredPositions:r,padRadius:n,height:s,busbarFormat:a,safeName:c=>(c||"").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"busbar"}}async function yo(e){var a;const n=_t();if(!n){ae("Configure the layout first to enable busbar downloads.","error");return}const t=U.list.findIndex(i=>i.id===e);if(t<0)return;const o=U.list[t];if(o.cellIndices.length===0){ae(`${o.name} has no cells assigned.`,"error");return}const r=Te[t];if(!r||r.blocked){ae(`${o.name}: ${((a=r==null?void 0:r.blocked)==null?void 0:a.reason)??"geometry unavailable"}`,"error");return}if(n.busbarFormat==="step"&&!we.initialized){ae("3D engine not ready. Please wait.","error");return}const s=`busbar_${n.safeName(o.name)}`;be(!0,`Exporting ${o.name}`,""),await new Promise(i=>setTimeout(i,20));try{if(n.busbarFormat==="dxf"){const i=at(r,n.centeredPositions,n.padRadius);It(i,`${s}.dxf`)}else{const i=it(r,n.centeredPositions,n.padRadius,n.height,o.thickness);if(!i){ae(`Failed to build 3D shape for ${o.name}.`,"error");return}et(i,`${s}.step`)}}catch(i){ae(`Export error: ${i.message}`,"error")}finally{be(!1)}}async function vo(){const e=_t();if(!e){ae("Configure the layout first to enable busbar downloads.","error");return}const n=U.list.map((t,o)=>({bb:t,geom:Te[o],i:o})).filter(({bb:t,geom:o})=>t.cellIndices.length>0&&o&&!o.blocked);if(n.length===0){ae("No busbars with cells to export.","error");return}if(e.busbarFormat==="step"&&!we.initialized){ae("3D engine not ready. Please wait.","error");return}be(!0,"Building busbar ZIP","Please wait..."),await new Promise(t=>setTimeout(t,50));try{const{default:t}=await qt(async()=>{const{default:i}=await import("./jszip.min-BiHF8TMC.js").then(u=>u.j);return{default:i}},[],import.meta.url),o=new t;for(const{bb:i,geom:u}of n){const c=`busbar_${e.safeName(i.name)}`;if(e.busbarFormat==="dxf"){const h=at(u,e.centeredPositions,e.padRadius);o.file(`${c}.dxf`,h)}else{const h=it(u,e.centeredPositions,e.padRadius,e.height,i.thickness);if(!h)continue;const y=jt(h,`_zip_${c}.step`);y&&o.file(`${c}.step`,y)}}const r=await o.generateAsync({type:"blob",compression:"DEFLATE",compressionOptions:{level:6}}),s=URL.createObjectURL(r),a=document.createElement("a");a.href=s,a.download="busbars.zip",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(s),ae(`Downloaded busbars.zip (${n.length} file${n.length===1?"":"s"}).`,"success")}catch(t){console.error("ZIP export error:",t),ae("ZIP export error: "+t.message,"error")}finally{be(!1)}}const Dt=2,nt="#config=",wo=new Set(["grid","honeycomb","vertical"]),xo=new Set(["off","halfcircles","fullcircles","tabs"]),Eo=new Set(["off","top","bottom","left","right"]);function $t(e){if(e==="top"||e==="bottom"||e==="off")return e;if(e==="left"||e==="right")return"off";throw new Error("Invalid tab overlap side")}const So=new Set(["sp","mm"]),Mo=new Set(["step","dxf"]);function Io(e){const n=new TextEncoder().encode(e);let t="";for(const o of n)t+=String.fromCharCode(o);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Po(e){const n=e.replace(/-/g,"+").replace(/_/g,"/"),t="=".repeat((4-n.length%4)%4),o=atob(n+t),r=new Uint8Array(o.length);for(let s=0;s{const y=Number(h);if(!Number.isInteger(y)||y<0)throw new Error(`Invalid busbar cell index at index ${n}`);return y}),i=e.face==="bottom"?"bottom":"top",u=e.overlapEnabled==null?!1:st(e.overlapEnabled,`busbars.list[${n}].overlapEnabled`),c=e.overlapSize==null?10:ye(Number(e.overlapSize),`busbars.list[${n}].overlapSize`);if(c<=0)throw new Error(`Invalid overlap size at index ${n}`);return{id:t,name:o,color:r,thickness:s,cellIndices:a,face:i,overlapEnabled:u,overlapSize:c}}function ct(e){if(!e||typeof e!="object")throw new Error("Missing config object");const n=Number(e.v);if(!Number.isInteger(n))throw new Error("Missing schema version");if(n!==Dt)throw new Error("Unsupported schema version");const t=e.pack,o=e.cell,r=e.bms,s=e.busbars;if(!t||!o||!r||!s)throw new Error("Missing required sections");const a=Ie(t.mode,"pack.mode");if(!So.has(a))throw new Error("Invalid pack mode");const i=Ie(o.layoutType,"cell.layoutType");if(!wo.has(i))throw new Error("Invalid layout type");const u=Ie(r.type,"bms.type");if(!xo.has(u))throw new Error("Invalid BMS type");const c=Ie(r.tabOverlapSide??"off","bms.tabOverlapSide");if(!Eo.has(c))throw new Error("Invalid tab overlap side");const h=$t(c),y=Ie(s.format,"busbars.format");if(!Mo.has(y))throw new Error("Invalid busbar format");const l=s.cellCutoutEnabled==null?!1:st(s.cellCutoutEnabled,"busbars.cellCutoutEnabled"),g=Array.isArray(s.list)?s.list.map((v,d)=>Bo(v,d)):(()=>{throw new Error("Invalid busbar list")})(),p=s.activeId==null?null:Ie(s.activeId,"busbars.activeId");if(p!==null&&!g.some(v=>v.id===p))throw new Error("Active busbar id not found in list");return{v:n,pack:{mode:a,series:ye(Number(t.series),"pack.series"),parallel:ye(Number(t.parallel),"pack.parallel"),xDim:ye(Number(t.xDim),"pack.xDim"),yDim:ye(Number(t.yDim),"pack.yDim")},cell:{cellSize:ye(Number(o.cellSize),"cell.cellSize"),layoutType:i,spacing:ye(Number(o.spacing),"cell.spacing"),height:ye(Number(o.height),"cell.height"),coverThickness:ye(Number(o.coverThickness),"cell.coverThickness"),ledgeWidth:ye(Number(o.ledgeWidth),"cell.ledgeWidth"),roundedCorners:st(o.roundedCorners,"cell.roundedCorners")},bms:{type:u,holeDiameter:ye(Number(r.holeDiameter),"bms.holeDiameter"),tabWidth:ye(Number(r.tabWidth),"bms.tabWidth"),tabDepth:ye(Number(r.tabDepth),"bms.tabDepth"),tabOverlapSide:h},busbars:{format:y,activeId:p,cellCutoutEnabled:l,list:g}}}function ve(e,n=0){const t=document.getElementById(e);if(!t)return n;const o=Number(t.value);return Number.isFinite(o)?o:n}function Oe(e,n=""){const t=document.getElementById(e);return t&&typeof t.value=="string"?t.value:n}function bt(e,n=!1){const t=document.getElementById(e);return t?!!t.checked:n}async function Ft(e){if(!(crypto!=null&&crypto.subtle))throw new Error("Web Crypto API is unavailable");const n=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),t=new Uint8Array(n);return Array.from(t).map(o=>o.toString(16).padStart(2,"0")).join("")}function To(e,n){const t=typeof e=="function"?e():"sp";return ct({v:Dt,pack:{mode:t,series:ve("series",1),parallel:ve("parallel",1),xDim:ve("xDim",150),yDim:ve("yDim",100)},cell:{cellSize:ve("cellSize",21.35),layoutType:Oe("layoutType","honeycomb"),spacing:ve("spacing",.6),height:ve("height",10),coverThickness:ve("coverThickness",.4),ledgeWidth:ve("ledgeWidth",2.75),roundedCorners:bt("roundedCorners",!0)},bms:{type:Oe("bmsHolesType","fullcircles"),holeDiameter:ve("bmsHoleDiameter",4),tabWidth:ve("tabWidth",4),tabDepth:ve("tabDepth",1),tabOverlapSide:$t(Oe("tabOverlapSide","off"))},busbars:{format:Oe("busbarFormat","step"),activeId:(n==null?void 0:n.activeId)??null,cellCutoutEnabled:bt("busbarCellCutoutEnabled",!1),list:Array.isArray(n==null?void 0:n.list)?n.list.map(o=>({...o,face:o.face==="bottom"?"bottom":"top"})):[]}})}async function ko(e){const n=ct(e),t=JSON.stringify(n),o=Io(t),r=(await Ft(t)).slice(0,16);return`${nt}${o}_${r}`}async function Co(e){if(!e||!e.startsWith(nt))return{ok:!1,reason:"missing"};const n=e.slice(nt.length),t=n.lastIndexOf("_");if(t<=0||t===n.length-1)return{ok:!1,reason:"format"};const o=n.slice(0,t),r=n.slice(t+1);if(!/^[0-9a-f]{16}$/i.test(r))return{ok:!1,reason:"checksum-format"};try{const s=Po(o),a=(await Ft(s)).slice(0,16);if(r.toLowerCase()!==a.toLowerCase())return{ok:!1,reason:"checksum-mismatch"};const i=JSON.parse(s);return{ok:!0,config:ct(i)}}catch(s){return{ok:!1,reason:s instanceof Error?s.message:"decode-failed"}}}const ze=4,Ao=250;let rt=null,Xe=null,He=!1;function Ue(e){const n=document.getElementById(e);if(!n)return;const t=window.devicePixelRatio||1,o=n.getBoundingClientRect();if(o.width===0||o.height===0)return;n.width=o.width*t,n.height=o.height*t,n.getContext("2d").scale(t,t)}function _o(){Ue("preview"),Ue("preview-bottom")}function je(){const e=document.querySelector("[data-pack-mode]");return e&&e.dataset.mode||"sp"}function Do(e){var s;const n=document.getElementById(e);if(!n)return;const t=n.closest(".custom-select");if(!t)return;const o=t.querySelector(".select-selected"),r=t.querySelectorAll(".select-items div");o&&(o.textContent=((s=n.options[n.selectedIndex])==null?void 0:s.text)||""),r.forEach(a=>{a.classList.toggle("same-as-selected",a.dataset.value===n.value)})}function ge(e,n){const t=document.getElementById(e);t&&(t.value=String(n))}function gt(e,n){const t=document.getElementById(e);t&&(t.checked=!!n)}function Ke(e,n){const t=document.getElementById(e);t&&(t.value=String(n),Do(e))}function Lt(e,n={}){const{clearBusbars:t=!0,refresh:o=!0}=n;if(!rt)return;const r=e==="mm"?"mm":"sp",{toggle:s,buttons:a,indicator:i,spFields:u,mmFields:c}=rt;s.dataset.mode=r,a.forEach(h=>{const y=h.dataset.mode===r;h.classList.toggle("active",y),y&&i&&(i.style.left=h.offsetLeft+"px",i.style.width=h.offsetWidth+"px")}),u&&(u.hidden=r!=="sp"),c&&(c.hidden=r!=="mm"),o&&(Ae(),Be(!0)),t&&U.clearAll()}async function lt(){if(!He)try{const e=To(()=>je(),U.getSnapshot()),n=await ko(e);window.location.hash!==n&&window.history.replaceState(null,"",n)}catch(e){console.error("Failed to sync URL hash:",e)}}function Je(){He||(Xe&&clearTimeout(Xe),Xe=setTimeout(()=>{Xe=null,lt()},Ao))}function $o(e){He=!0;try{ge("series",e.pack.series),ge("parallel",e.pack.parallel),ge("xDim",e.pack.xDim),ge("yDim",e.pack.yDim),ge("cellSize",e.cell.cellSize),Ke("layoutType",e.cell.layoutType),ge("spacing",e.cell.spacing),ge("height",e.cell.height),ge("coverThickness",e.cell.coverThickness),ge("ledgeWidth",e.cell.ledgeWidth),gt("roundedCorners",e.cell.roundedCorners),Ke("bmsHolesType",e.bms.type),ge("bmsHoleDiameter",e.bms.holeDiameter),ge("tabWidth",e.bms.tabWidth),ge("tabDepth",e.bms.tabDepth),Ke("tabOverlapSide",e.bms.tabOverlapSide||"off"),Ke("busbarFormat",e.busbars.format),gt("busbarCellCutoutEnabled",e.busbars.cellCutoutEnabled===!0),Lt(e.pack.mode,{clearBusbars:!1,refresh:!1}),Qe(),Ae(),e.pack.mode==="mm"&&(ge("xDim",e.pack.xDim),ge("yDim",e.pack.yDim)),U.replaceFromSnapshot({activeId:e.busbars.activeId,list:e.busbars.list}),Ze()}finally{He=!1}}async function Fo(){if(!window.location.hash||!window.location.hash.startsWith("#config="))return!1;const e=await Co(window.location.hash);return e.ok?($o(e.config),!0):(ae("Shared URL is invalid or corrupted. Loaded default configuration.","error"),!1)}function Lo(){const e=document.getElementById("copyShareUrlBtn");if(!e)return;const n=(e.textContent||"Copy Share URL").trim();let t=null;const o=(r,s)=>{t&&clearTimeout(t),e.classList.remove("is-success","is-error"),r&&e.classList.add(r),e.textContent=s,t=setTimeout(()=>{e.classList.remove("is-success","is-error"),e.textContent=n},2e3)};e.addEventListener("click",async()=>{var r;e.disabled=!0;try{await lt();const s=`${window.location.origin}${window.location.pathname}${window.location.hash}`;if(!((r=navigator.clipboard)!=null&&r.writeText))throw new Error("Clipboard API unavailable");await navigator.clipboard.writeText(s),o("is-success","✓ Copied")}catch(s){console.error("Failed to copy share URL:",s),o("is-error","Copy failed")}finally{e.disabled=!1}})}function Ro(){["series","parallel","xDim","yDim","height","cellSize","layoutType","spacing","coverThickness","ledgeWidth","roundedCorners","bmsHolesType","bmsHoleDiameter","tabWidth","tabDepth","tabOverlapSide","busbarFormat","busbarCellCutoutEnabled"].forEach(n=>{const t=document.getElementById(n);t&&(t.addEventListener("input",Je),t.addEventListener("change",Je))}),U.subscribeMutations(Je)}function Ae(){const e=je(),n=document.getElementById("xDim"),t=document.getElementById("yDim"),o=document.getElementById("packSummary");if(e==="mm"){const m=parseFloat(n.value)||0,f=parseFloat(t.value)||0;o&&(o.innerHTML=`${m.toFixed(0)} × ${f.toFixed(0)} mm footprint. Cells fit automatically.`);return}const r=Math.max(1,Math.round(parseFloat(document.getElementById("series").value)||1)),s=Math.max(1,Math.round(parseFloat(document.getElementById("parallel").value)||1)),a=parseFloat(document.getElementById("cellSize").value)||21.35,i=parseFloat(document.getElementById("spacing").value)||.6,u=document.getElementById("layoutType").value,c=a+i,h=Math.sqrt(3)/2*c,y=.02,l=m=>a+2*i+(m-1)*c+y,g=m=>a+2*i+(m-1)*h+y,p=m=>a+2*i+(m-1)*c+c/2+y;let v,d;if(u==="vertical"?(v=g(r),d=p(s)):u==="honeycomb"?(v=p(r),d=g(s)):(v=l(r),d=l(s)),n.value=v.toFixed(2),t.value=d.toFixed(2),o){const m=r*s;o.innerHTML=`${r}S ${s}P. ${m} cells. Footprint about ${v.toFixed(0)} × ${d.toFixed(0)} mm.`}}function No(){["series","parallel"].forEach(s=>{const a=document.getElementById(s);a&&(a.addEventListener("input",()=>{Ae(),Be(!0)}),a.addEventListener("change",()=>U.clearAll()))}),["xDim","yDim"].forEach(s=>{const a=document.getElementById(s);a&&(a.addEventListener("input",()=>{je()==="mm"&&(Ae(),Be(!0))}),a.addEventListener("change",()=>{je()==="mm"&&U.clearAll()}))}),["spacing","cellSize","layoutType"].forEach(s=>{const a=document.getElementById(s);a&&a.addEventListener("change",()=>U.clearAll())}),["spacing","cellSize","layoutType","height","coverThickness"].forEach(s=>{const a=document.getElementById(s);if(!a)return;const i=()=>{Ae(),Be(!0)};a.addEventListener("input",i),a.addEventListener("change",i)}),["bmsHolesType","roundedCorners","bmsHoleDiameter","ledgeWidth","tabWidth","tabDepth","tabOverlapSide","busbarCellCutoutEnabled"].forEach(s=>{const a=document.getElementById(s);a&&(a.addEventListener("input",()=>Be(!1)),a.addEventListener("change",()=>Be(!1)))})}function Yo(){const e=document.querySelector("[data-pack-mode]");if(!e)return;const n=Array.from(e.querySelectorAll(".seg")),t=e.querySelector(".seg-indicator"),o=document.querySelector(".pack-sp-fields"),r=document.querySelector(".pack-mm-fields"),s=a=>{!t||!a||(t.style.left=a.offsetLeft+"px",t.style.width=a.offsetWidth+"px")};rt={toggle:e,buttons:n,indicator:t,spFields:o,mmFields:r},n.forEach(a=>a.addEventListener("click",()=>Lt(a.dataset.mode))),requestAnimationFrame(()=>{const a=n.find(i=>i.classList.contains("active"))||n[0];a&&s(a)})}function qe(){B.currentPositions.length>0&&At()}function Oo(e,n){const t=B.viewTransform;if(!t)return null;const o=(e-B.panX)/B.zoom,r=(n-B.panY)/B.zoom,s=(o-t.offsetX)/t.scale+t.minX-t.r-t.spacing,a=(r-t.offsetY)/t.scale+t.minY-t.r-t.spacing;return[s,a]}function yt(e,n){if(!U.getActive())return;const o=Oo(e,n);if(!o)return;const r=B.currentCellSize/2;let s=-1,a=r;B.currentPositions.forEach(([i,u],c)=>{const h=Math.hypot(o[0]-i,o[1]-u);h=0&&U.toggleCell(s)}function zo(){function e(t){const o=U.getActive();if(o&&(o.face||"top")!==t){const r=U.list.find(s=>(s.face||"top")===t);r&&U.setActive(r.id)}}function n(t,o){if(!t)return;t.style.cursor="grab",t.addEventListener("wheel",d=>{d.preventDefault();const m=.1,f=d.deltaY>0?-m:m,_=Math.max(.2,Math.min(5,B.zoom+f)),D=t.getBoundingClientRect(),T=d.clientX-D.left,I=d.clientY-D.top,E=_/B.zoom;B.panX=T-(T-B.panX)*E,B.panY=I-(I-B.panY)*E,B.zoom=_,qe()},{passive:!1}),t.addEventListener("mousedown",d=>{e(o),B.isDragging=!0,B.dragStartX=d.clientX,B.dragStartY=d.clientY,B.dragMoved=!1,B.lastMouseX=d.clientX,B.lastMouseY=d.clientY,t.style.cursor="grabbing"}),t.addEventListener("mousemove",d=>{if(!B.isDragging)return;const m=d.clientX-B.dragStartX,f=d.clientY-B.dragStartY;(Math.abs(m)>ze||Math.abs(f)>ze)&&(B.dragMoved=!0),B.panX+=d.clientX-B.lastMouseX,B.panY+=d.clientY-B.lastMouseY,B.lastMouseX=d.clientX,B.lastMouseY=d.clientY,B.currentPositions.length>0&&requestAnimationFrame(()=>qe())}),t.addEventListener("mouseup",d=>{if(B.isDragging&&!B.dragMoved){const m=t.getBoundingClientRect();yt(d.clientX-m.left,d.clientY-m.top)}B.isDragging=!1,B.dragMoved=!1,t.style.cursor="grab"}),t.addEventListener("mouseleave",()=>{t.style.cursor="grab"});let r=0,s=1,a=0,i=0,u=0,c=0,h=0,y=0,l=0,g=0,p=!1,v=!1;t.addEventListener("touchstart",d=>{if(d.preventDefault(),e(o),d.touches.length===1)v=!0,h=d.touches[0].clientX,y=d.touches[0].clientY,l=h,g=y,p=!1;else if(d.touches.length===2){v=!1;const m=d.touches[0],f=d.touches[1];r=Math.hypot(f.clientX-m.clientX,f.clientY-m.clientY),s=B.zoom,a=B.panX,i=B.panY;const _=t.getBoundingClientRect();u=(m.clientX+f.clientX)/2-_.left,c=(m.clientY+f.clientY)/2-_.top}},{passive:!1}),t.addEventListener("touchmove",d=>{if(d.preventDefault(),d.touches.length===1&&v){const m=d.touches[0],f=m.clientX-l,_=m.clientY-g;(Math.abs(f)>ze||Math.abs(_)>ze)&&(p=!0),B.panX+=m.clientX-h,B.panY+=m.clientY-y,h=m.clientX,y=m.clientY,B.currentPositions.length>0&&requestAnimationFrame(()=>qe())}else if(d.touches.length===2){const m=d.touches[0],f=d.touches[1],D=Math.hypot(f.clientX-m.clientX,f.clientY-m.clientY)/r,T=Math.max(.2,Math.min(5,s*D)),I=T/s;B.panX=u-(u-a)*I,B.panY=c-(c-i)*I,B.zoom=T,B.currentPositions.length>0&&requestAnimationFrame(()=>qe())}},{passive:!1}),t.addEventListener("touchend",d=>{if(d.preventDefault(),d.changedTouches.length>0&&v&&!p){const m=d.changedTouches[0],f=t.getBoundingClientRect();yt(m.clientX-f.left,m.clientY-f.top)}d.touches.length===0&&(v=!1,p=!1),d.touches.length<2&&(r=0)},{passive:!1}),t.addEventListener("touchcancel",()=>{v=!1,p=!1,r=0})}window.addEventListener("mouseup",()=>{var t,o;B.isDragging&&(B.isDragging=!1,B.dragMoved=!1,(t=document.getElementById("preview"))!=null&&t.style&&(document.getElementById("preview").style.cursor="grab"),(o=document.getElementById("preview-bottom"))!=null&&o.style&&(document.getElementById("preview-bottom").style.cursor="grab"))}),n(document.getElementById("preview"),"top"),n(document.getElementById("preview-bottom"),"bottom")}function Xo(){const e=document.querySelector("[data-tabs]");if(!e)return;const n=Array.from(e.querySelectorAll(".tab")),t=e.querySelector(".tab-indicator"),o=Array.from(document.querySelectorAll(".tab-panel")),r=a=>{!t||!a||(t.style.left=a.offsetLeft+"px",t.style.width=a.offsetWidth+"px")},s=a=>{for(const i of n){const u=i.dataset.panel===a;i.classList.toggle("active",u),i.setAttribute("aria-selected",u?"true":"false"),u&&r(i)}for(const i of o)i.classList.toggle("active",i.dataset.panel===a)};for(const a of n)a.addEventListener("click",()=>s(a.dataset.panel));requestAnimationFrame(()=>{const a=n.find(i=>i.classList.contains("active"))||n[0];a&&r(a)}),window.addEventListener("resize",()=>{const a=n.find(i=>i.classList.contains("active"));a&&r(a)})}async function vt(){_o(),Nt(),Xo(),Yo();const e=document.getElementById("bmsHolesType");e&&(e.addEventListener("change",Qe),Qe());const n=document.getElementById("generateBtn");n&&n.addEventListener("click",go),mo({onDownloadSingle:yo,onDownloadAll:vo,onFaceFilterChange(o){requestAnimationFrame(()=>requestAnimationFrame(()=>{Ue("preview"),o==="both"&&Ue("preview-bottom"),At()}))}}),Ze(),U.subscribe(()=>Be(!1)),No(),zo(),Lo(),Ro(),await Fo()||Ae(),await wt(),setTimeout(()=>{Be(!0),lt()},100)}document.readyState==="loading"?window.addEventListener("DOMContentLoaded",vt):vt(); diff --git a/dist/assets/index-jBPW89B_.js b/dist/assets/index-jBPW89B_.js deleted file mode 100644 index 8919e78..0000000 --- a/dist/assets/index-jBPW89B_.js +++ /dev/null @@ -1,16 +0,0 @@ -(function(){const o=document.createElement("link").relList;if(o&&o.supports&&o.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))t(c);new MutationObserver(c=>{for(const s of c)if(s.type==="childList")for(const r of s.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&t(r)}).observe(document,{childList:!0,subtree:!0});function e(c){const s={};return c.integrity&&(s.integrity=c.integrity),c.referrerPolicy&&(s.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?s.credentials="include":c.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function t(c){if(c.ep)return;c.ep=!0;const s=e(c);fetch(c.href,s)}})();const M={zoom:1,panX:0,panY:0,isDragging:!1,dragStartX:0,dragStartY:0,dragMoved:!1,lastMouseX:0,lastMouseY:0,currentPositions:[],currentCellSize:18,viewTransform:null},ke={key:null,positions:null};function ae(n,o="success"){const e=document.getElementById("previewStats");e&&(e.textContent=n,o==="error"?e.style.color="#ef4444":o==="success"?e.style.color="#10b981":e.style.color="#94a3b8")}function be(n,o="Generating 3D Model",e="Please be patient..."){const t=document.getElementById("loadingOverlay");if(!t)return;const c=document.getElementById("loadingText"),s=document.getElementById("loadingSubtext");c&&(c.textContent=o),s&&(s.textContent=e),n?(t.classList.add("active"),t.style.display="flex"):(t.classList.remove("active"),t.style.display="none")}function ze(){const n=document.getElementById("bmsHolesType").value,o=document.getElementById("bmsHoleDiameterGroup"),e=document.getElementById("tabDimensionsGroup");o.style.display=n==="halfcircles"||n==="fullcircles"?"block":"none",e.style.display=n==="tabs"?"grid":"none"}function ft(){document.querySelectorAll("select").forEach(o=>{const e=document.createElement("div");e.className="custom-select",o.parentNode.insertBefore(e,o),e.appendChild(o);const t=document.createElement("div");t.className="select-selected",t.textContent=o.options[o.selectedIndex].text,e.appendChild(t);const c=document.createElement("div");c.className="select-items",Array.from(o.options).forEach((s,r)=>{const u=document.createElement("div");u.textContent=s.text,u.dataset.value=s.value,r===o.selectedIndex&&(u.className="same-as-selected"),u.addEventListener("click",function(m){m.stopPropagation(),o.selectedIndex=r,t.textContent=this.textContent;const i=c.querySelector(".same-as-selected");i&&i.classList.remove("same-as-selected"),this.classList.add("same-as-selected"),t.click(),o.dispatchEvent(new Event("change"))}),c.appendChild(u)}),e.appendChild(c),t.addEventListener("click",function(s){s.stopPropagation(),Je(this),c.classList.toggle("show"),this.classList.toggle("select-arrow-active")})}),document.addEventListener("click",Je)}function Je(n){const o=document.querySelectorAll(".select-items"),e=document.querySelectorAll(".select-selected");o.forEach((t,c)=>{n!==e[c]&&(t.classList.remove("show"),e[c].classList.remove("select-arrow-active"))})}const pt={BASE_URL:"./",DEV:!1,MODE:"production",PROD:!0,SSR:!1},Ie={instance:null,initialized:!1};async function ct(){if(!Ie.initialized)try{const o=`${(typeof import.meta<"u"&&pt?"./":"/").replace(/\/?$/,"/")}vendor/opencascade.wasm.wasm`;Ie.instance=await opencascade({locateFile:()=>o}),Ie.initialized=!0,console.log("OpenCascade initialized successfully"),be(!1)}catch(n){console.error("Failed to initialize OpenCascade:",n),ae("Failed to initialize 3D engine. Please ensure opencascade.wasm.js and opencascade.wasm.wasm are in vendor/.","error"),be(!1)}}function ve(){const n=document.getElementById("preview");if(!n)return;const o=n.getContext("2d");o.clearRect(0,0,n.width,n.height),o.fillStyle="#1e293b",o.fillRect(0,0,n.width,n.height)}function Te(n,o){M.currentPositions=n,M.currentCellSize=o;const e=document.getElementById("preview");if(!e){console.error("Canvas element not found!");return}const t=e.getContext("2d");if(t.clearRect(0,0,e.width,e.height),t.fillStyle="#1e293b",t.fillRect(0,0,e.width,e.height),n.length===0)return;t.save(),t.translate(M.panX,M.panY),t.scale(M.zoom,M.zoom);const c=parseFloat(document.getElementById("spacing").value),s=document.getElementById("bmsHolesType").value,r=s!=="off",u=s==="tabs",m=s==="fullcircles",i=document.getElementById("roundedCorners").checked,f=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,v=parseFloat(document.getElementById("ledgeWidth").value)||0,l=o/2,a=Math.min(...n.map(p=>p[0])),h=Math.min(...n.map(p=>p[1])),x=Math.max(...n.map(p=>p[0])),I=Math.max(...n.map(p=>p[1])),b=x-a+o+c*2,S=I-h+o+c*2,V=e.getBoundingClientRect(),H=V.width,K=V.height,T=80,R=(H-T*2)/b,O=(K-T*2)/S,g=Math.min(R,O),E=(H-b*g)/2,_=(K-S*g)/2;M.viewTransform={offsetX:E,offsetY:_,scale:g,minX:a,minY:h,spacing:c,r:l};const Q=M.zoom;if(i){const p=5*g,y=E,d=_,w=b*g,k=S*g;t.fillStyle="rgba(100, 149, 237, 0.15)",t.beginPath(),t.moveTo(y+p,d),t.lineTo(y+w-p,d),t.arcTo(y+w,d,y+w,d+p,p),t.lineTo(y+w,d+k-p),t.arcTo(y+w,d+k,y+w-p,d+k,p),t.lineTo(y+p,d+k),t.arcTo(y,d+k,y,d+k-p,p),t.lineTo(y,d+p),t.arcTo(y,d,y+p,d,p),t.closePath(),t.fill()}else t.fillStyle="rgba(100, 149, 237, 0.15)",t.fillRect(E,_,b*g,S*g);if(t.strokeStyle="#667eea",t.lineWidth=2/Q,i){const p=5*g,y=E,d=_,w=b*g,k=S*g;t.beginPath(),t.moveTo(y+p,d),t.lineTo(y+w-p,d),t.arcTo(y+w,d,y+w,d+p,p),t.lineTo(y+w,d+k-p),t.arcTo(y+w,d+k,y+w-p,d+k,p),t.lineTo(y+p,d+k),t.arcTo(y,d+k,y,d+k-p,p),t.lineTo(y,d+p),t.arcTo(y,d,y+p,d,p),t.closePath(),t.stroke()}else t.strokeRect(E,_,b*g,S*g);const fe=[];if(r){const p=h-l-c,y=I+l+c,d={};for(const[B,A]of n){const N=Math.round(A*1e3);d[N]||(d[N]=[]),d[N].push([B,A])}const w=Object.keys(d).map(Number).sort((B,A)=>B-A),k=w[w.length-1],q=w[0],le=w[0],de=w[w.length-1];d[k].sort((B,A)=>B[0]-A[0]),d[q].sort((B,A)=>B[0]-A[0]),d[k][0][1],d[q][0][1];let $,U;if(m){const B=d[le][0][1],A=d[de][0][1],N=d[le],j=d[de],P=(se,te,Z,ie)=>{const X=(Z+ie)/2,z=te0?G=re:D=re:z>0?D=re:G=re}const ce=(G+D)/2,oe=te+l*Math.sin(ce);return(se+2*oe)/3};N.length>=2&&($=P(p,B,N[0][0],N[1][0])),j.length>=2&&(U=P(y,A,j[0][0],j[1][0]))}else $=y,U=p;const ee=m?le:k,C=m?de:q;for(let B=0;B0?-Math.PI/2:0,X=Z>0?0:Math.PI/2;for(let oe=0;oe<80;oe++){const ne=(ie+X)/2,re=A-(j+l*Math.cos(ne)),ue=(N+l*Math.sin(ne)-se)*Z-re*Math.sqrt(3);if(Math.abs(ue)<1e-8)break;ue<0?Z>0?ie=ne:X=ne:Z>0?X=ne:ie=ne}const z=(ie+X)/2,G={x:j+l*Math.cos(z),y:N+l*Math.sin(z)},D={x:P-l*Math.cos(z),y:N+l*Math.sin(z)},ce=m?{apex:{x:A,y:se},left:G,right:D}:null;fe.push({x:A,y:te,diameter:f,isTab:!1,isFull:m,debugTri:ce})}for(let B=0;B0?-Math.PI/2:0,X=Z>0?0:Math.PI/2;for(let oe=0;oe<80;oe++){const ne=(ie+X)/2,re=A-(j+l*Math.cos(ne)),ue=(N+l*Math.sin(ne)-se)*Z-re*Math.sqrt(3);if(Math.abs(ue)<1e-8)break;ue<0?Z>0?ie=ne:X=ne:Z>0?X=ne:ie=ne}const z=(ie+X)/2,G={x:j+l*Math.cos(z),y:N+l*Math.sin(z)},D={x:P-l*Math.cos(z),y:N+l*Math.sin(z)},ce=m?{apex:{x:A,y:se},left:G,right:D}:null;fe.push({x:A,y:te,diameter:f,isTab:!1,isFull:m,debugTri:ce})}}t.fillStyle="#1e293b",t.strokeStyle="rgba(102, 126, 234, 0.8)",t.lineWidth=1.5/Q;for(const[p,y]of n){const d=(p-a+l+c)*g+E,w=(y-h+l+c)*g+_,k=l*g;if(t.beginPath(),t.arc(d,w,k,0,Math.PI*2),t.fill(),t.stroke(),v>0){const q=(l-v)*g;t.strokeStyle="rgba(255, 193, 7, 0.8)",t.setLineDash([3/Q,3/Q]),t.lineWidth=1/Q,t.beginPath(),t.arc(d,w,q,0,Math.PI*2),t.stroke(),t.setLineDash([]),t.strokeStyle="rgba(102, 126, 234, 0.8)",t.lineWidth=1.5/Q}}if(r&&fe.length>0)if(u){t.fillStyle="rgba(255, 193, 7, 0.5)",t.strokeStyle="rgba(255, 193, 7, 0.9)",t.lineWidth=1.5/Q;const p=parseFloat(document.getElementById("tabWidth").value)||4,y=parseFloat(document.getElementById("tabDepth").value)||1,d=p*g,w=y*g;for(const k of fe){const q=(k.x-a+l+c)*g+E;k.y>I?(t.fillRect(q-d/2,_+S*g-w,d,w),t.strokeRect(q-d/2,_+S*g-w,d,w)):(t.fillRect(q-d/2,_,d,w),t.strokeRect(q-d/2,_,d,w))}}else if(m){let p=null;for(const y of fe){const d=y.diameter/2*g,w=(y.x-a+l+c)*g+E,k=(y.y-h+l+c)*g+_;for(const[q,le]of n){const de=(q-a+l+c)*g+E,$=(le-h+l+c)*g+_,U=Math.sqrt((w-de)**2+(k-$)**2);if(UI){const w=3/Q;t.arc(y,_+S*g,d,Math.PI,0,!1),t.lineTo(y+d,_+S*g+w),t.lineTo(y-d,_+S*g+w)}else{const w=3/Q;t.arc(y,_,d,0,Math.PI,!1),t.lineTo(y-d,_-w),t.lineTo(y+d,_-w)}t.closePath(),t.fill()}t.globalCompositeOperation="source-over";for(const p of fe){const y=(p.x-a+l+c)*g+E,d=p.diameter/2*g;t.strokeStyle="rgba(16, 185, 129, 0.9)",t.lineWidth=1.5/Q,t.beginPath(),p.y>I?t.arc(y,_+S*g,d,Math.PI,0,!1):t.arc(y,_,d,0,Math.PI,!1),t.stroke()}t.restore()}t.strokeStyle="#94a3b8",t.fillStyle="#94a3b8",t.lineWidth=1/Q,t.font=`${12/Q}px Arial`,t.textAlign="center",t.textBaseline="middle";const Y=15/Q,W=5/Q,F=_+S*g+Y;t.setLineDash([2/Q,2/Q]),t.beginPath(),t.moveTo(E,_+S*g),t.lineTo(E,F+Y/2),t.stroke(),t.beginPath(),t.moveTo(E+b*g,_+S*g),t.lineTo(E+b*g,F+Y/2),t.stroke(),t.setLineDash([]),t.beginPath(),t.moveTo(E,F),t.lineTo(E+b*g,F),t.stroke(),t.beginPath(),t.moveTo(E,F),t.lineTo(E+W,F-W/2),t.lineTo(E+W,F+W/2),t.closePath(),t.fill(),t.beginPath(),t.moveTo(E+b*g,F),t.lineTo(E+b*g-W,F-W/2),t.lineTo(E+b*g-W,F+W/2),t.closePath(),t.fill(),t.fillText(`${b.toFixed(1)}mm`,E+b*g/2,F+Y);const L=E+b*g+Y;t.setLineDash([2/Q,2/Q]),t.beginPath(),t.moveTo(E+b*g,_),t.lineTo(L+Y/2,_),t.stroke(),t.beginPath(),t.moveTo(E+b*g,_+S*g),t.lineTo(L+Y/2,_+S*g),t.stroke(),t.setLineDash([]),t.beginPath(),t.moveTo(L,_),t.lineTo(L,_+S*g),t.stroke(),t.beginPath(),t.moveTo(L,_),t.lineTo(L-W/2,_+W),t.lineTo(L+W/2,_+W),t.closePath(),t.fill(),t.beginPath(),t.moveTo(L,_+S*g),t.lineTo(L-W/2,_+S*g-W),t.lineTo(L+W/2,_+S*g-W),t.closePath(),t.fill(),t.save(),t.translate(L+Y,_+S*g/2),t.rotate(-Math.PI/2),t.fillText(`${S.toFixed(1)}mm`,0,0),t.restore(),t.restore()}function rt(n,o,e,t){const c=[],s=t/2,r=s+e,u=s+e;for(let m=u;m+s+e<=o;m+=t+e)for(let i=r;i+s+e<=n;i+=t+e)c.push([i,m]);return c}function it(n,o,e,t){const c=[],s=t/2;let r=s+e,u=0;for(;r+s+e<=o;){const m=u%2===0?0:(t+e)/2;let i=s+e+m;for(;i+s+e<=n;)c.push([i,r]),i+=t+e;r+=Math.sqrt(3)*(s+e/2),u++}return c}function lt(n,o,e,t){const c=[],s=t/2;let r=s+e,u=0;for(;r+s+e<=n;){const m=u%2===0?0:(t+e)/2;let i=s+e+m;for(;i+s+e<=o;)c.push([r,i]),i+=t+e;r+=Math.sqrt(3)*(s+e/2),u++}return c}function gt(n,o,e,t,c){return n==="grid"?rt(o,e,t,c):n==="honeycomb"?it(o,e,t,c):lt(o,e,t,c)}function yt(n,o,e,t,c){const s=`${n}_${o}_${e}_${t}_${c}`;if(ke.key===s&&ke.positions)return ke.positions;const r=gt(c,n,o,e,t);return ke.key=s,ke.positions=r,r}function bt(n,o){const e=Ie.instance;if(!e||n.length===0)return null;const{cellSize:t,spacing:c,height:s,terminalDiameter:r,terminalDepth:u,coverThickness:m,ledgeWidth:i,roundedCorners:f,bmsHoles:v,useTabs:l,useFullCircles:a}=o,h=t/2,x=Math.min(...n.map(Y=>Y[0]))-h-c,I=Math.min(...n.map(Y=>Y[1]))-h-c,b=Math.max(...n.map(Y=>Y[0]))+h+c,S=Math.max(...n.map(Y=>Y[1]))+h+c,V=(x+b)/2,H=(I+S)/2,K=n.map(([Y,W])=>[Y-V,W-H]),T=b-x,R=S-I;performance.now();const O=Y=>{performance.now()};let E=new e.BRepPrimAPI_MakeBox(T,R,s).Shape();const _=new e.gp_Trsf;if(_.SetTranslation(new e.gp_Vec(-T/2,-R/2,0)),E=new e.BRepBuilderAPI_Transform(E,_,!1).Shape(),f)try{const W=(L,p)=>{const y={};let d=0;const w=new e.TopExp_Explorer(L,e.TopAbs_EDGE);for(w.Init(L,e.TopAbs_EDGE);w.More();w.Next()){const k=e.TopoDS.prototype.Edge(w.Current()),q=k.HashCode(1e8);y.hasOwnProperty(q)||(y[q]=d,p(d++,k))}return y},F=[];if(W(E,(L,p)=>{try{const y=new e.Bnd_Box;e.BRepBndLib.prototype.Add(p,y,!1);const d=y.CornerMin(),w=y.CornerMax(),k=Math.abs(w.X()-d.X()),q=Math.abs(w.Y()-d.Y()),le=Math.abs(w.Z()-d.Z());k<1&&q<1&&le>s*.8&&F.push(L)}catch{}}),F.length>0){const L=new e.BRepFilletAPI_MakeFillet(E);let p=0;W(E,(y,d)=>{if(F.includes(y))try{L.Add(5,d),p++}catch(w){console.error(` Failed to add edge ${y}:`,w.message)}}),p>0&&(E=new e.TopoDS_Solid(L.Shape()),console.log(" Applied rounded corners"),O("Rounded corners"))}else console.log(" No vertical edges found to fillet")}catch(Y){console.error(" Fillet operation failed:",Y.message),console.log(" Continuing without rounded corners")}const fe=i>0||m>0;if(fe){const Y=m,W=s-m,F=Math.max(.1,h-i),L=K.map(([w,k])=>{const q=new e.gp_Ax2(new e.gp_Pnt(w,k,Y),e.gp.prototype.DZ());return new e.BRepPrimAPI_MakeCylinder(q,h,W).Shape()});let p=L[0];for(let w=1;w{const q=new e.gp_Ax2(new e.gp_Pnt(w,k,0),e.gp.prototype.DZ());return new e.BRepPrimAPI_MakeCylinder(q,F,s).Shape()});let d=y[0];for(let w=1;w{const p=new e.gp_Ax2(new e.gp_Pnt(F,L,0),e.gp.prototype.DZ());return new e.BRepPrimAPI_MakeCylinder(p,h,s).Shape()});let W=Y[0];for(let F=1;F{const ee=Math.round(U*1e3);L[ee]||(L[ee]=[]),L[ee].push([$,U])});const p=Object.keys(L).map($=>parseInt($)).sort(($,U)=>U-$),y=p[0],d=p[p.length-1];if(a){const $=L[y][0][1],U=L[d][0][1],ee=R/2,C=-R/2,B=(A,N,j,P)=>{const se=(j+P)/2,te=N0?-Math.PI/2:0,ie=te>0?0:Math.PI/2;for(let G=0;G<80;G++){const D=(Z+ie)/2,ce=j+h*Math.cos(D),oe=N+h*Math.sin(D),ne=se-ce,me=(oe-A)*te-ne*Math.sqrt(3);if(Math.abs(me)<1e-8)break;me<0?te>0?Z=D:ie=D:te>0?ie=D:Z=D}const X=(Z+ie)/2,z=N+h*Math.sin(X);return(A+2*z)/3};W=B(ee,$,L[y][0][0],L[y][1][0]),F=B(C,U,L[d][0][0],L[d][1][0])}const w=L[y].sort(($,U)=>$[0]-U[0]),k=L[d].sort(($,U)=>$[0]-U[0]),q=[];for(let $=0;${const j=new e.BRepPrimAPI_MakeBox($,U,s).Shape(),P=new e.gp_Trsf;P.SetTranslation(new e.gp_Vec(A-$/2,ee-U,0));const se=new e.BRepBuilderAPI_Transform(j,P,!1);B.push(se.Shape())}),le.forEach(([A])=>{const j=new e.BRepPrimAPI_MakeBox($,U,s).Shape(),P=new e.gp_Trsf;P.SetTranslation(new e.gp_Vec(A-$/2,C,0));const se=new e.BRepBuilderAPI_Transform(j,P,!1);B.push(se.Shape())}),B.length>0)if(B.length===1)E=new e.BRepAlgoAPI_Cut(E,B[0]).Shape();else{let A=B[0];for(let N=1;N{const ee=new e.gp_Ax2(new e.gp_Pnt($,U,0),e.gp.prototype.DZ()),C=new e.BRepPrimAPI_MakeCylinder(ee,Y/2,s).Shape();E=new e.BRepAlgoAPI_Cut(E,C).Shape()});else{const $=de.map(([ee,C])=>{const B=new e.gp_Ax2(new e.gp_Pnt(ee,C,0),e.gp.prototype.DZ());return new e.BRepPrimAPI_MakeCylinder(B,Y/2,s).Shape()});let U=$[0];for(let ee=1;ee<$.length;ee++)U=new e.BRepAlgoAPI_Fuse(U,$[ee]).Shape();E=new e.BRepAlgoAPI_Cut(E,U).Shape()}O()}}if(K.length<=10)K.forEach(([Y,W])=>{const F=new e.gp_Ax2(new e.gp_Pnt(Y,W,s-u),e.gp.prototype.DZ()),L=new e.BRepPrimAPI_MakeCylinder(F,r/2,u).Shape();E=new e.BRepAlgoAPI_Cut(E,L).Shape()});else{const Y=K.map(([p,y])=>{const d=new e.gp_Ax2(new e.gp_Pnt(p,y,s-u),e.gp.prototype.DZ());return new e.BRepPrimAPI_MakeCylinder(d,r/2,u).Shape()}),W=30,F=[];for(let p=0;pu*180/Math.PI;return["0","ARC","8",s,"10",n.toFixed(4),"20",o.toFixed(4),"30","0.0","40",e.toFixed(4),"50",r(t).toFixed(4),"51",r(c).toFixed(4)]}function Mt(n,o,e,t,c){return["0","LINE","8",c,"10",n.toFixed(4),"20",o.toFixed(4),"30","0.0","11",e.toFixed(4),"21",t.toFixed(4),"31","0.0"]}const Re=2*Math.PI,he=1e-5,Be=n=>{const o=n%Re;return o<0?o+Re:o},Et=(n,o)=>{const e=Math.abs(Be(n)-Be(o));return e>Math.PI?Re-e:e};function St(n,o,e,t){const c=o[0]-n[0],s=o[1]-n[1],r=n[0]-e[0],u=n[1]-e[1],m=c*c+s*s;if(m[(T[0]-s)*l+(T[1]-r)*a,-(T[0]-s)*a+(T[1]-r)*l],x=h(n),I=h(o);let b=0,S=1;const V=I[0]-x[0],H=I[1]-x[1],K=[[-V,x[0]-0],[V,v-x[0]],[-H,x[1]- -c],[H,c-x[1]]];for(const[T,R]of K)if(Math.abs(T)S+he)return[];O>b&&(b=O)}else{if(Os[0]-r[0]),e=[];for(const s of o)e.length&&s[0]<=e[e.length-1][1]+he?e[e.length-1][1]=Math.max(e[e.length-1][1],s[1]):e.push([s[0],s[1]]);const t=[];let c=0;for(const[s,r]of e)s>c+he&&t.push([c,Math.min(s,1)]),c=Math.max(c,r);return c<1-he&&t.push([c,1]),t}function Tt(n,o){for(const e of o)if(Et(n,e)(s.has(i)||s.set(i,{pos:f,dirs:[]}),s.get(i));for(const i of n.padIndices){const f=o[i];f&&u(`c${i}`,f)}n.edges.forEach((i,f)=>{const v=o[i.from],l=o[i.to];if(!v||!l)return;const a=[{key:`c${i.from}`,pos:v},...i.waypoints.map((h,x)=>({key:`w${f}_${x}`,pos:h})),{key:`c${i.to}`,pos:l}];for(let h=1;h({key:i,...f}));for(const i of m){const[f,v]=i.pos;if(i.dirs.length===0){c.push(...xt(f,v,e,t));continue}const l=[];for(const h of i.dirs)l.push(Be(h-Math.PI/2)),l.push(Be(h+Math.PI/2));l.sort((h,x)=>h-x);const a=[];for(const h of l)(a.length===0||Math.abs(h-a[a.length-1])>he)&&a.push(h);for(let h=0;hthis.listeners.delete(n)},subscribeMutations(n){return this.mutationListeners.add(n),()=>this.mutationListeners.delete(n)},_emitMutation(n){this.mutationListeners.forEach(o=>o(n))},_notify(){this.listeners.forEach(n=>n())},getSnapshot(){return{activeId:this.activeId,list:this.list.map(n=>({id:n.id,name:n.name,color:n.color,cellIndices:Array.isArray(n.cellIndices)?[...n.cellIndices]:[],thickness:n.thickness}))}},replaceFromSnapshot(n){const o=n&&Array.isArray(n.list)?n.list:[];this.list=o.map((t,c)=>({id:typeof t.id=="string"&&t.id?t.id:`bb-${c+1}`,name:typeof t.name=="string"&&t.name?t.name:`Busbar ${c+1}`,color:typeof t.color=="string"&&t.color?t.color:Ce[c%Ce.length],cellIndices:Array.isArray(t.cellIndices)?t.cellIndices.map(s=>Number(s)).filter(s=>Number.isInteger(s)&&s>=0):[],thickness:Number.isFinite(Number(t.thickness))&&Number(t.thickness)>0?Number(t.thickness):1})),typeof(n==null?void 0:n.activeId)=="string"&&this.list.some(t=>t.id===n.activeId)?this.activeId=n.activeId:this.activeId=this.list.length?this.list[0].id:null,Xe=this.list.reduce((t,c)=>{const s=parseInt(String(c.id).replace(/^bb-/,""),10);return Number.isFinite(s)?Math.max(t,s):t},0)+1,De=this.list.length,this._emitMutation("replaceFromSnapshot"),this._notify()},add(){const n={id:"bb-"+Xe++,name:`Busbar ${this.list.length+1}`,color:Ce[De%Ce.length],cellIndices:[],thickness:1};return De++,this.list.push(n),this.activeId=n.id,this._emitMutation("add"),this._notify(),n},remove(n){this.list=this.list.filter(o=>o.id!==n),this.activeId===n&&(this.activeId=this.list.length?this.list[0].id:null),this._emitMutation("remove"),this._notify()},rename(n,o){const e=this.list.find(t=>t.id===n);e&&(e.name=o,this._emitMutation("rename"),this._notify())},setColor(n,o){const e=this.list.find(t=>t.id===n);e&&(e.color=o,this._emitMutation("setColor"),this._notify())},setThickness(n,o){const e=this.list.find(t=>t.id===n);e&&(e.thickness=o,this._emitMutation("setThickness"),this._notify())},setActive(n){this.activeId=n,this._emitMutation("setActive"),this._notify()},getActive(){return this.list.find(n=>n.id===this.activeId)||null},toggleCell(n){const o=this.getActive();if(!o)return!1;const e=o.cellIndices.indexOf(n);return e>=0?o.cellIndices.splice(e,1):o.cellIndices.push(n),this._emitMutation("toggleCell"),this._notify(),!0},clearAll(){this.list.length!==0&&(this.list=[],this.activeId=null,De=0,Xe=1,this._emitMutation("clearAll"),this._notify())}};function Ct(n,o,e,t){const c=[],s=(2*e+t)*1.3;for(let i=0;ir.set(i,[]));for(const[i,f]of c)r.get(i).push(f),r.get(f).push(i);const u=new Set,m=[];for(const i of n){if(u.has(i))continue;const f=[],v=[i];for(;v.length;){const l=v.pop();if(!u.has(l)){u.add(l),f.push(l);for(const a of r.get(l))v.push(a)}}m.push(f)}for(;m.length>1;){let i=null,f=1/0,v=-1,l=-1;for(let a=0;a!u.has(a)),i=Math.max(c,.3),f=t,v=[];for(const[l,a]of r){const h=o[l],x=o[a];if(qe(h,x,f,m,s,i))v.push({from:l,to:a,waypoints:[]});else{const I=At(h,x,f,m,s,i);if(I)v.push({from:l,to:a,waypoints:[I]});else return{padIndices:n.slice(),edges:v,blocked:{from:l,to:a,reason:"no clear route between these cells"}}}}return{padIndices:n.slice(),edges:v,blocked:null}}function tt(n,o){const e=parseInt(n.slice(1,3),16),t=parseInt(n.slice(3,5),16),c=parseInt(n.slice(5,7),16);return`rgba(${e},${t},${c},${o})`}function dt(n,o,e,t,c,s,r){const u=document.getElementById("preview");if(!u)return;const m=M.viewTransform;if(!m||n.length===0)return;const i=u.getContext("2d"),f=t/2,v=I=>(I-m.minX+f+s)*m.scale+m.offsetX,l=I=>(I-m.minY+f+s)*m.scale+m.offsetY,a=c*m.scale;i.save(),i.translate(M.panX,M.panY),i.scale(M.zoom,M.zoom);const h=M.zoom,x=2*Math.PI;n.forEach((I,b)=>{const S=o[b];if(!S||I.cellIndices.length===0)return;const V=I.id===r,H=V?.45:.3,K=tt(I.color,H);i.fillStyle=K,i.beginPath();for(const T of I.cellIndices){if(!e[T])continue;const[R,O]=e[T],g=v(R),E=l(O);i.moveTo(g+a,E),i.arc(g,E,a,0,x)}i.fill(),i.strokeStyle=K,i.lineWidth=2*a,i.lineJoin="round",i.lineCap="round";for(const T of S.edges){const R=[e[T.from],...T.waypoints,e[T.to]];for(let O=1;O=2){i.beginPath(),i.moveTo(v(R[0][0]),l(R[0][1]));for(let O=1;O"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}function je(n={}){const o=document.getElementById("busbarList");if(o){if(o.innerHTML="",J.list.length===0){const e=document.createElement("div");e.className="busbar-empty",e.textContent='No busbars. Click "Add Busbar" then click cells in the preview.',o.appendChild(e);return}J.list.forEach(e=>{const t=document.createElement("div");t.className="busbar-row"+(e.id===J.activeId?" active":""),t.dataset.id=e.id,t.innerHTML=` -
-
- - -
-
- ${e.cellIndices.length} cell${e.cellIndices.length===1?"":"s"} - -
- ${n[e.id]?`
⚠ ${nt(n[e.id])}
`:""} - `,t.addEventListener("click",c=>{c.target.closest("input")||c.target.closest("button")||J.setActive(e.id)}),t.querySelector(".busbar-name").addEventListener("change",c=>{J.rename(e.id,c.target.value)}),t.querySelector(".busbar-thickness").addEventListener("change",c=>{const s=parseFloat(c.target.value);s>0&&J.setThickness(e.id,s)}),t.querySelector(".busbar-del").addEventListener("click",c=>{c.stopPropagation(),J.remove(e.id)}),o.appendChild(t)})}}function Lt(){const n=document.getElementById("addBusbarBtn");n&&n.addEventListener("click",()=>J.add())}let Le=[],Oe=null;function Fe(){if(!Oe)return;const{positions:n,cellSize:o,padRadius:e,spacing:t}=Oe;dt(J.list,Le,n,o,e,t,J.activeId)}function we(n=!1){n&&(M.zoom=1,M.panX=0,M.panY=0);const o=document.getElementById("previewStats"),e=(t,c)=>{o.textContent=t,o.style.color=c};try{const t=parseFloat(document.getElementById("xDim").value),c=parseFloat(document.getElementById("yDim").value),s=parseFloat(document.getElementById("spacing").value),r=parseFloat(document.getElementById("cellSize").value),u=document.getElementById("layoutType").value,m=parseFloat(document.getElementById("ledgeWidth").value)||0,i=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,f=parseFloat(document.getElementById("coverThickness").value);if(!t||!c||!s||!r){e("Configure settings to see preview","#94a3b8");return}if(m>0&&m>=r){e(`Ledge width (${m}mm) must be less than cell diameter (${r}mm)!`,"#ef4444"),ve();return}const v=r+s*2;if(tt||r>c){e(`Cell diameter (${r}mm) larger than pack dimensions!`,"#ef4444"),ve();return}if(s<0){e("Cell spacing cannot be negative!","#ef4444"),ve();return}const l=yt(t,c,s,r,u);if(!l||l.length===0){e("No cells fit! Increase pack size or decrease cell size/spacing","#ef4444"),ve();return}if(document.getElementById("bmsHolesType").value!=="off"){if(i>r){e(`BMS hole (${i}mm) larger than cell (${r}mm)! Reduce hole size.`,"#ef4444"),ve();return}const g=r/2,E=i/2,_=g,Q=Math.min(...l.map(k=>k[1])),Y=Math.max(...l.map(k=>k[1]))+_+s,W=Q-_-s,F={};for(const[k,q]of l){const le=Math.round(q*1e3);F[le]||(F[le]=[]),F[le].push([k,q])}const L=Object.keys(F).map(Number).sort((k,q)=>k-q),p=L[L.length-1],y=L[0];F[p].sort((k,q)=>k[0]-q[0]),F[y].sort((k,q)=>k[0]-q[0]);const d=Y,w=W;for(let k=0;kr/2&&e(`Cover thickness (${f}mm) very large for cell size (${r}mm)`,"#f59e0b"),s<.5&&s>0&&e("Spacing < 0.5mm may be difficult to 3D print","#f59e0b"),l.length<2?e(`Only ${l.length} cell fits. Increase pack size for practical holder.`,"#f59e0b"):o.style.color="#10b981",Te(l,r);const x=r/2,I=Math.max(x-m,1),b=4;Le=J.list.map(g=>at(g.cellIndices,l,x,I,s,b)),Oe={positions:l,cellSize:r,padRadius:I,spacing:s},dt(J.list,Le,l,r,I,s,J.activeId);const S={};J.list.forEach((g,E)=>{const _=Le[E];_&&_.blocked&&(S[g.id]=_.blocked.reason)}),je(S);const V=Math.min(...l.map(g=>g[0])),H=Math.min(...l.map(g=>g[1])),K=Math.max(...l.map(g=>g[0])),T=Math.max(...l.map(g=>g[1])),R=K-V+r+s*2,O=T-H+r+s*2;l.length>=2&&(o.textContent=`${l.length} cells • ${R.toFixed(0)}×${O.toFixed(0)} mm`)}catch(t){console.error("Preview error:",t),e("Error: "+t.message,"#ef4444")}}async function Ft(){var o;const n=document.getElementById("layoutType").value;if(!(!Ie.initialized&&(ae("3D engine not ready. Please wait...","error"),await ct(),!Ie.initialized))){be(!0,"Generating 3D Model","Please be patient..."),await new Promise(e=>setTimeout(e,50));try{const e=parseFloat(document.getElementById("xDim").value),t=parseFloat(document.getElementById("yDim").value),c=parseFloat(document.getElementById("spacing").value),s=parseFloat(document.getElementById("cellSize").value),r=parseFloat(document.getElementById("ledgeWidth").value)||0,u=parseFloat(document.getElementById("bmsHoleDiameter").value)||4,m=parseFloat(document.getElementById("coverThickness").value),i=s/2,f=u/2;if(r>0&&r>=s){ae(`Ledge width (${r}mm) must be less than cell diameter (${s}mm)!`,"error"),be(!1);return}const v=s+c*2;if(ee||s>t){ae("Cell diameter is larger than pack dimensions!","error"),be(!1);return}if(c<0){ae("Cell spacing cannot be negative!","error"),be(!1);return}const l=parseFloat(document.getElementById("height").value),a=8,h=1,x=document.getElementById("roundedCorners").checked,I=document.getElementById("bmsHolesType").value,b=I!=="off",S=I==="tabs",V=I==="fullcircles",H=!1,K=!1;let T,R;switch(n){case"grid":T=rt(e,t,c,s),R="Grid Layout";break;case"honeycomb":T=it(e,t,c,s),R="Honeycomb Layout";break;case"vertical":T=lt(e,t,c,s),R="Vertical Honeycomb";break;default:ae("Invalid layout type","error");return}if(b&&V){const C=(X,z,G,D)=>{const ce=(G+D)/2,oe=z0?-Math.PI/2:0,re=oe>0?0:Math.PI/2;for(let Ee=0;Ee<80;Ee++){const Me=(ne+re)/2,Pe=ce-(G+i*Math.cos(Me)),Ve=(z+i*Math.sin(Me)-X)*oe-Pe*Math.sqrt(3);if(Math.abs(Ve)<1e-8)break;Ve<0?oe>0?ne=Me:re=Me:oe>0?re=Me:ne=Me}const me=(ne+re)/2,ue=z+i*Math.sin(me);return(X+2*ue)/3},B=Math.min(...T.map(X=>X[1])),A=Math.max(...T.map(X=>X[1])),N=B-i-c,j=A+i+c,P={};for(const[X,z]of T){const G=Math.round(z*1e3);P[G]||(P[G]=[]),P[G].push([X,z])}const se=Object.keys(P).map(Number).sort((X,z)=>X-z),te=se[0],Z=se[se.length-1];P[te].sort((X,z)=>X[0]-z[0]),P[Z].sort((X,z)=>X[0]-z[0]);const ie=[];for(let X=0;XD[1])),B=Math.max(...T.map(D=>D[1])),A=s/2,N=C-A-c,j=B+A+c,P={};for(const[D,ce]of T){const oe=Math.round(ce*1e3);P[oe]||(P[oe]=[]),P[oe].push([D,ce])}const se=Object.keys(P).map(Number).sort((D,ce)=>D-ce),te=se[se.length-1],Z=se[0];P[te].sort((D,ce)=>D[0]-ce[0]),P[Z].sort((D,ce)=>D[0]-ce[0]);const ie=P[te][0][1],X=P[Z][0][1];let z,G;K||(z=j,G=N);for(let D=0;Dat(C.cellIndices,T,i,Q,c,fe));for(let C=0;CC[0]))+Math.max(...T.map(C=>C[0])))/2,F=(Math.min(...T.map(C=>C[1]))+Math.max(...T.map(C=>C[1])))/2,L=T.map(([C,B])=>[C-W,B-F]),p=C=>(C||"").replace(/[^A-Za-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"busbar",y=C=>{const B=C.cellIndices;if(B.length===0)return null;if(B.length===1)return`single|${C.thickness.toFixed(2)}`;const A=B.map(j=>L[j]).filter(Boolean),N=[];for(let j=0;jj-P),`${A.length}|${C.thickness.toFixed(2)}|${N.map(j=>j.toFixed(3)).join(",")}`},d=((o=document.getElementById("busbarFormat"))==null?void 0:o.value)||"step",w=[],k=new Map;for(let C=0;Cnew Promise(B=>setTimeout(B,C));Qe(_,`cellholder_${n}.step`);for(let C=0;CC.cellIndices.length>0).length-w.length,$=w.length>0?`. ${w.length} unique ${d.toUpperCase()} busbar file${w.length===1?"":"s"}${de>0?` (${de} mirrored duplicate${de===1?"":"s"} skipped)`:""}`:"",U=S?"edge tabs":K?"circle offset":"semicircle offset",ee=H&&!S?" with filleted holes":"";ae(`${R} generated. ${T.length} cells (${U}${ee})${$}.`,"success")}catch(e){console.error("Generation error:",e),ae("Error: "+e.message,"error")}finally{be(!1)}}}const ut=1,Ue="#config=",Rt=new Set(["grid","honeycomb","vertical"]),Yt=new Set(["off","halfcircles","fullcircles","tabs"]),$t=new Set(["sp","mm"]),Xt=new Set(["step","dxf"]);function Nt(n){const o=new TextEncoder().encode(n);let e="";for(const t of o)e+=String.fromCharCode(t);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Ht(n){const o=n.replace(/-/g,"+").replace(/_/g,"/"),e="=".repeat((4-o.length%4)%4),t=atob(o+e),c=new Uint8Array(t.length);for(let s=0;s{const m=Number(u);if(!Number.isInteger(m)||m<0)throw new Error(`Invalid busbar cell index at index ${o}`);return m});return{id:e,name:t,color:c,thickness:s,cellIndices:r}}function Ze(n){if(!n||typeof n!="object")throw new Error("Missing config object");const o=Number(n.v);if(!Number.isInteger(o))throw new Error("Missing schema version");if(o!==ut)throw new Error("Unsupported schema version");const e=n.pack,t=n.cell,c=n.bms,s=n.busbars;if(!e||!t||!c||!s)throw new Error("Missing required sections");const r=xe(e.mode,"pack.mode");if(!$t.has(r))throw new Error("Invalid pack mode");const u=xe(t.layoutType,"cell.layoutType");if(!Rt.has(u))throw new Error("Invalid layout type");const m=xe(c.type,"bms.type");if(!Yt.has(m))throw new Error("Invalid BMS type");const i=xe(s.format,"busbars.format");if(!Xt.has(i))throw new Error("Invalid busbar format");const f=Array.isArray(s.list)?s.list.map((l,a)=>zt(l,a)):(()=>{throw new Error("Invalid busbar list")})(),v=s.activeId==null?null:xe(s.activeId,"busbars.activeId");if(v!==null&&!f.some(l=>l.id===v))throw new Error("Active busbar id not found in list");return{v:o,pack:{mode:r,series:ge(Number(e.series),"pack.series"),parallel:ge(Number(e.parallel),"pack.parallel"),xDim:ge(Number(e.xDim),"pack.xDim"),yDim:ge(Number(e.yDim),"pack.yDim")},cell:{cellSize:ge(Number(t.cellSize),"cell.cellSize"),layoutType:u,spacing:ge(Number(t.spacing),"cell.spacing"),height:ge(Number(t.height),"cell.height"),coverThickness:ge(Number(t.coverThickness),"cell.coverThickness"),ledgeWidth:ge(Number(t.ledgeWidth),"cell.ledgeWidth"),roundedCorners:Wt(t.roundedCorners,"cell.roundedCorners")},bms:{type:m,holeDiameter:ge(Number(c.holeDiameter),"bms.holeDiameter"),tabWidth:ge(Number(c.tabWidth),"bms.tabWidth"),tabDepth:ge(Number(c.tabDepth),"bms.tabDepth")},busbars:{format:i,activeId:v,list:f}}}function ye(n,o=0){const e=document.getElementById(n);if(!e)return o;const t=Number(e.value);return Number.isFinite(t)?t:o}function Ne(n,o=""){const e=document.getElementById(n);return e&&typeof e.value=="string"?e.value:o}function qt(n,o=!1){const e=document.getElementById(n);return e?!!e.checked:o}async function ht(n){if(!(crypto!=null&&crypto.subtle))throw new Error("Web Crypto API is unavailable");const o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n)),e=new Uint8Array(o);return Array.from(e).map(t=>t.toString(16).padStart(2,"0")).join("")}function Ot(n,o){const e=typeof n=="function"?n():"sp";return Ze({v:ut,pack:{mode:e,series:ye("series",1),parallel:ye("parallel",1),xDim:ye("xDim",150),yDim:ye("yDim",100)},cell:{cellSize:ye("cellSize",21.35),layoutType:Ne("layoutType","honeycomb"),spacing:ye("spacing",.6),height:ye("height",10),coverThickness:ye("coverThickness",.4),ledgeWidth:ye("ledgeWidth",2.75),roundedCorners:qt("roundedCorners",!0)},bms:{type:Ne("bmsHolesType","fullcircles"),holeDiameter:ye("bmsHoleDiameter",4),tabWidth:ye("tabWidth",4),tabDepth:ye("tabDepth",1)},busbars:{format:Ne("busbarFormat","step"),activeId:(o==null?void 0:o.activeId)??null,list:Array.isArray(o==null?void 0:o.list)?o.list:[]}})}async function Ut(n){const o=Ze(n),e=JSON.stringify(o),t=Nt(e),c=(await ht(e)).slice(0,16);return`${Ue}${t}_${c}`}async function Kt(n){if(!n||!n.startsWith(Ue))return{ok:!1,reason:"missing"};const o=n.slice(Ue.length),e=o.lastIndexOf("_");if(e<=0||e===o.length-1)return{ok:!1,reason:"format"};const t=o.slice(0,e),c=o.slice(e+1);if(!/^[0-9a-f]{16}$/i.test(c))return{ok:!1,reason:"checksum-format"};try{const s=Ht(t),r=(await ht(s)).slice(0,16);if(c.toLowerCase()!==r.toLowerCase())return{ok:!1,reason:"checksum-mismatch"};const u=JSON.parse(s);return{ok:!0,config:Ze(u)}}catch(s){return{ok:!1,reason:s instanceof Error?s.message:"decode-failed"}}}const Ae=4,jt=250;let Ke=null,_e=null,Ye=!1;function Zt(){const n=document.getElementById("preview");if(!n)return;const o=window.devicePixelRatio||1,e=n.getBoundingClientRect();n.width=e.width*o,n.height=e.height*o,n.getContext("2d").scale(o,o),n.style.width=e.width+"px",n.style.height=e.height+"px"}function $e(){const n=document.querySelector("[data-pack-mode]");return n&&n.dataset.mode||"sp"}function Gt(n){var s;const o=document.getElementById(n);if(!o)return;const e=o.closest(".custom-select");if(!e)return;const t=e.querySelector(".select-selected"),c=e.querySelectorAll(".select-items div");t&&(t.textContent=((s=o.options[o.selectedIndex])==null?void 0:s.text)||""),c.forEach(r=>{r.classList.toggle("same-as-selected",r.dataset.value===o.value)})}function pe(n,o){const e=document.getElementById(n);e&&(e.value=String(o))}function Vt(n,o){const e=document.getElementById(n);e&&(e.checked=!!o)}function He(n,o){const e=document.getElementById(n);e&&(e.value=String(o),Gt(n))}function mt(n,o={}){const{clearBusbars:e=!0,refresh:t=!0}=o;if(!Ke)return;const c=n==="mm"?"mm":"sp",{toggle:s,buttons:r,indicator:u,spFields:m,mmFields:i}=Ke;s.dataset.mode=c,r.forEach(f=>{const v=f.dataset.mode===c;f.classList.toggle("active",v),v&&u&&(u.style.left=f.offsetLeft+"px",u.style.width=f.offsetWidth+"px")}),m&&(m.hidden=c!=="sp"),i&&(i.hidden=c!=="mm"),t&&(Se(),we(!0)),e&&J.clearAll()}async function Ge(){if(!Ye)try{const n=Ot(()=>$e(),J.getSnapshot()),o=await Ut(n);window.location.hash!==o&&window.history.replaceState(null,"",o)}catch(n){console.error("Failed to sync URL hash:",n)}}function We(){Ye||(_e&&clearTimeout(_e),_e=setTimeout(()=>{_e=null,Ge()},jt))}function Jt(n){Ye=!0;try{pe("series",n.pack.series),pe("parallel",n.pack.parallel),pe("xDim",n.pack.xDim),pe("yDim",n.pack.yDim),pe("cellSize",n.cell.cellSize),He("layoutType",n.cell.layoutType),pe("spacing",n.cell.spacing),pe("height",n.cell.height),pe("coverThickness",n.cell.coverThickness),pe("ledgeWidth",n.cell.ledgeWidth),Vt("roundedCorners",n.cell.roundedCorners),He("bmsHolesType",n.bms.type),pe("bmsHoleDiameter",n.bms.holeDiameter),pe("tabWidth",n.bms.tabWidth),pe("tabDepth",n.bms.tabDepth),He("busbarFormat",n.busbars.format),mt(n.pack.mode,{clearBusbars:!1,refresh:!1}),ze(),Se(),n.pack.mode==="mm"&&(pe("xDim",n.pack.xDim),pe("yDim",n.pack.yDim)),J.replaceFromSnapshot({activeId:n.busbars.activeId,list:n.busbars.list}),je()}finally{Ye=!1}}async function Qt(){if(!window.location.hash||!window.location.hash.startsWith("#config="))return!1;const n=await Kt(window.location.hash);return n.ok?(Jt(n.config),!0):(ae("Shared URL is invalid or corrupted. Loaded default configuration.","error"),!1)}function en(){const n=document.getElementById("copyShareUrlBtn");n&&n.addEventListener("click",async()=>{var o;try{await Ge();const e=`${window.location.origin}${window.location.pathname}${window.location.hash}`;if(!((o=navigator.clipboard)!=null&&o.writeText))throw new Error("Clipboard API unavailable");await navigator.clipboard.writeText(e),ae("Share URL copied to clipboard.","success")}catch(e){console.error("Failed to copy share URL:",e),ae("Unable to copy URL automatically. Copy it from the address bar.","error")}})}function tn(){["series","parallel","xDim","yDim","height","cellSize","layoutType","spacing","coverThickness","ledgeWidth","roundedCorners","bmsHolesType","bmsHoleDiameter","tabWidth","tabDepth","busbarFormat"].forEach(o=>{const e=document.getElementById(o);e&&(e.addEventListener("input",We),e.addEventListener("change",We))}),J.subscribeMutations(We)}function Se(){const n=$e(),o=document.getElementById("xDim"),e=document.getElementById("yDim"),t=document.getElementById("packSummary");if(n==="mm"){const b=parseFloat(o.value)||0,S=parseFloat(e.value)||0;t&&(t.innerHTML=`${b.toFixed(0)} × ${S.toFixed(0)} mm footprint. Cells fit automatically.`);return}const c=Math.max(1,Math.round(parseFloat(document.getElementById("series").value)||1)),s=Math.max(1,Math.round(parseFloat(document.getElementById("parallel").value)||1)),r=parseFloat(document.getElementById("cellSize").value)||21.35,u=parseFloat(document.getElementById("spacing").value)||.6,m=document.getElementById("layoutType").value,i=r+u,f=Math.sqrt(3)/2*i,v=.02,l=b=>r+2*u+(b-1)*i+v,a=b=>r+2*u+(b-1)*f+v,h=b=>r+2*u+(b-1)*i+i/2+v;let x,I;if(m==="vertical"?(x=a(c),I=h(s)):m==="honeycomb"?(x=h(c),I=a(s)):(x=l(c),I=l(s)),o.value=x.toFixed(2),e.value=I.toFixed(2),t){const b=c*s;t.innerHTML=`${c}S ${s}P. ${b} cells. Footprint about ${x.toFixed(0)} × ${I.toFixed(0)} mm.`}}function nn(){["series","parallel"].forEach(s=>{const r=document.getElementById(s);r&&(r.addEventListener("input",()=>{Se(),we(!0)}),r.addEventListener("change",()=>J.clearAll()))}),["xDim","yDim"].forEach(s=>{const r=document.getElementById(s);r&&(r.addEventListener("input",()=>{$e()==="mm"&&(Se(),we(!0))}),r.addEventListener("change",()=>{$e()==="mm"&&J.clearAll()}))}),["spacing","cellSize","layoutType"].forEach(s=>{const r=document.getElementById(s);r&&r.addEventListener("change",()=>J.clearAll())}),["spacing","cellSize","layoutType","height","coverThickness"].forEach(s=>{const r=document.getElementById(s);if(!r)return;const u=()=>{Se(),we(!0)};r.addEventListener("input",u),r.addEventListener("change",u)}),["bmsHolesType","roundedCorners","bmsHoleDiameter","ledgeWidth","tabWidth","tabDepth"].forEach(s=>{const r=document.getElementById(s);r&&(r.addEventListener("input",()=>we(!1)),r.addEventListener("change",()=>we(!1)))})}function on(){const n=document.querySelector("[data-pack-mode]");if(!n)return;const o=Array.from(n.querySelectorAll(".seg")),e=n.querySelector(".seg-indicator"),t=document.querySelector(".pack-sp-fields"),c=document.querySelector(".pack-mm-fields"),s=r=>{!e||!r||(e.style.left=r.offsetLeft+"px",e.style.width=r.offsetWidth+"px")};Ke={toggle:n,buttons:o,indicator:e,spFields:t,mmFields:c},o.forEach(r=>r.addEventListener("click",()=>mt(r.dataset.mode))),requestAnimationFrame(()=>{const r=o.find(u=>u.classList.contains("active"))||o[0];r&&s(r)})}function sn(){M.currentPositions.length>0&&(Te(M.currentPositions,M.currentCellSize),Fe())}function cn(n,o){const e=M.viewTransform;if(!e)return null;const t=(n-M.panX)/M.zoom,c=(o-M.panY)/M.zoom,s=(t-e.offsetX)/e.scale+e.minX-e.r-e.spacing,r=(c-e.offsetY)/e.scale+e.minY-e.r-e.spacing;return[s,r]}function ot(n,o){if(!J.getActive())return;const t=cn(n,o);if(!t)return;const c=M.currentCellSize/2;let s=-1,r=c;M.currentPositions.forEach(([u,m],i)=>{const f=Math.hypot(t[0]-u,t[1]-m);f=0&&J.toggleCell(s)}function rn(){const n=document.getElementById("preview");if(!n)return;n.addEventListener("wheel",a=>{a.preventDefault();const h=.1,x=a.deltaY>0?-h:h,I=Math.max(.2,Math.min(5,M.zoom+x)),b=n.getBoundingClientRect(),S=a.clientX-b.left,V=a.clientY-b.top,H=I/M.zoom;M.panX=S-(S-M.panX)*H,M.panY=V-(V-M.panY)*H,M.zoom=I,sn()}),n.addEventListener("mousedown",a=>{M.isDragging=!0,M.dragStartX=a.clientX,M.dragStartY=a.clientY,M.dragMoved=!1,M.lastMouseX=a.clientX,M.lastMouseY=a.clientY,n.style.cursor="grabbing"}),n.addEventListener("mousemove",a=>{if(!M.isDragging)return;const h=a.clientX-M.dragStartX,x=a.clientY-M.dragStartY;(Math.abs(h)>Ae||Math.abs(x)>Ae)&&(M.dragMoved=!0);const I=a.clientX-M.lastMouseX,b=a.clientY-M.lastMouseY;M.panX+=I,M.panY+=b,M.lastMouseX=a.clientX,M.lastMouseY=a.clientY,M.currentPositions.length>0&&requestAnimationFrame(()=>{Te(M.currentPositions,M.currentCellSize),Fe()})}),n.addEventListener("mouseup",a=>{if(M.isDragging&&!M.dragMoved){const h=n.getBoundingClientRect();ot(a.clientX-h.left,a.clientY-h.top)}M.isDragging=!1,M.dragMoved=!1,n.style.cursor="grab"}),n.addEventListener("mouseleave",()=>{M.isDragging=!1,M.dragMoved=!1,n.style.cursor="grab"});let o=0,e=1,t=0,c=0,s=0,r=0,u=0,m=0,i=0,f=0,v=!1,l=!1;n.addEventListener("touchstart",a=>{if(a.preventDefault(),a.touches.length===1)l=!0,u=a.touches[0].clientX,m=a.touches[0].clientY,i=u,f=m,v=!1;else if(a.touches.length===2){l=!1;const h=a.touches[0],x=a.touches[1];o=Math.hypot(x.clientX-h.clientX,x.clientY-h.clientY),e=M.zoom,t=M.panX,c=M.panY;const I=n.getBoundingClientRect();s=(h.clientX+x.clientX)/2-I.left,r=(h.clientY+x.clientY)/2-I.top}}),n.addEventListener("touchmove",a=>{if(a.preventDefault(),a.touches.length===1&&l){const h=a.touches[0],x=h.clientX-i,I=h.clientY-f;(Math.abs(x)>Ae||Math.abs(I)>Ae)&&(v=!0),M.panX+=h.clientX-u,M.panY+=h.clientY-m,u=h.clientX,m=h.clientY,M.currentPositions.length>0&&requestAnimationFrame(()=>{Te(M.currentPositions,M.currentCellSize),Fe()})}else if(a.touches.length===2){const h=a.touches[0],x=a.touches[1],b=Math.hypot(x.clientX-h.clientX,x.clientY-h.clientY)/o,S=Math.max(.2,Math.min(5,e*b)),V=S/e;M.panX=s-(s-t)*V,M.panY=r-(r-c)*V,M.zoom=S,M.currentPositions.length>0&&requestAnimationFrame(()=>{Te(M.currentPositions,M.currentCellSize),Fe()})}}),n.addEventListener("touchend",a=>{if(a.preventDefault(),a.changedTouches.length>0&&l&&!v){const h=a.changedTouches[0],x=n.getBoundingClientRect();ot(h.clientX-x.left,h.clientY-x.top)}a.touches.length===0&&(l=!1,v=!1),a.touches.length<2&&(o=0)}),n.addEventListener("touchcancel",()=>{l=!1,v=!1,o=0}),n.style.cursor="grab"}function ln(){const n=document.querySelector("[data-tabs]");if(!n)return;const o=Array.from(n.querySelectorAll(".tab")),e=n.querySelector(".tab-indicator"),t=Array.from(document.querySelectorAll(".tab-panel")),c=r=>{!e||!r||(e.style.left=r.offsetLeft+"px",e.style.width=r.offsetWidth+"px")},s=r=>{for(const u of o){const m=u.dataset.panel===r;u.classList.toggle("active",m),u.setAttribute("aria-selected",m?"true":"false"),m&&c(u)}for(const u of t)u.classList.toggle("active",u.dataset.panel===r)};for(const r of o)r.addEventListener("click",()=>s(r.dataset.panel));requestAnimationFrame(()=>{const r=o.find(u=>u.classList.contains("active"))||o[0];r&&c(r)}),window.addEventListener("resize",()=>{const r=o.find(u=>u.classList.contains("active"));r&&c(r)})}async function st(){Zt(),ft(),ln(),on();const n=document.getElementById("bmsHolesType");n&&(n.addEventListener("change",ze),ze());const o=document.getElementById("generateBtn");o&&o.addEventListener("click",Ft),Lt(),je(),J.subscribe(()=>we(!1)),nn(),rn(),en(),tn(),await Qt()||Se(),await ct(),setTimeout(()=>{we(!0),Ge()},100)}document.readyState==="loading"?window.addEventListener("DOMContentLoaded",st):st(); diff --git a/dist/assets/jszip.min-BiHF8TMC.js b/dist/assets/jszip.min-BiHF8TMC.js new file mode 100644 index 0000000..3c97a93 --- /dev/null +++ b/dist/assets/jszip.min-BiHF8TMC.js @@ -0,0 +1,12 @@ +function Ot(ut,wt){for(var _=0;_N[w]})}}}return Object.freeze(Object.defineProperty(ut,Symbol.toStringTag,{value:"Module"}))}var vt=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Bt(ut){return ut&&ut.__esModule&&Object.prototype.hasOwnProperty.call(ut,"default")?ut.default:ut}function yt(ut){throw new Error('Could not dynamically require "'+ut+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var zt={exports:{}};/*! + +JSZip v3.10.1 - A JavaScript class for generating and reading zip files + + +(c) 2009-2016 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/main/LICENSE +*/(function(ut,wt){(function(_){ut.exports=_()})(function(){return function _(N,w,u){function o(g,y){if(!w[g]){if(!N[g]){var p=typeof yt=="function"&&yt;if(!y&&p)return p(g,!0);if(n)return n(g,!0);var b=new Error("Cannot find module '"+g+"'");throw b.code="MODULE_NOT_FOUND",b}var i=w[g]={exports:{}};N[g][0].call(i.exports,function(d){var r=N[g][1][d];return o(r||d)},i,i.exports,_,N,w,u)}return w[g].exports}for(var n=typeof yt=="function"&&yt,h=0;h>2,i=(3&g)<<4|y>>4,d=1>6:64,r=2>4,y=(15&b)<<4|(i=n.indexOf(h.charAt(r++)))>>2,p=(3&i)<<6|(d=n.indexOf(h.charAt(r++))),c[l++]=g,i!==64&&(c[l++]=y),d!==64&&(c[l++]=p);return c}},{"./support":30,"./utils":32}],2:[function(_,N,w){var u=_("./external"),o=_("./stream/DataWorker"),n=_("./stream/Crc32Probe"),h=_("./stream/DataLengthProbe");function g(y,p,b,i,d){this.compressedSize=y,this.uncompressedSize=p,this.crc32=b,this.compression=i,this.compressedContent=d}g.prototype={getContentWorker:function(){var y=new o(u.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new h("data_length")),p=this;return y.on("end",function(){if(this.streamInfo.data_length!==p.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),y},getCompressedWorker:function(){return new o(u.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},g.createWorkerFrom=function(y,p,b){return y.pipe(new n).pipe(new h("uncompressedSize")).pipe(p.compressWorker(b)).pipe(new h("compressedSize")).withStreamInfo("compression",p)},N.exports=g},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(_,N,w){var u=_("./stream/GenericWorker");w.STORE={magic:"\0\0",compressWorker:function(){return new u("STORE compression")},uncompressWorker:function(){return new u("STORE decompression")}},w.DEFLATE=_("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(_,N,w){var u=_("./utils"),o=function(){for(var n,h=[],g=0;g<256;g++){n=g;for(var y=0;y<8;y++)n=1&n?3988292384^n>>>1:n>>>1;h[g]=n}return h}();N.exports=function(n,h){return n!==void 0&&n.length?u.getTypeOf(n)!=="string"?function(g,y,p,b){var i=o,d=b+p;g^=-1;for(var r=b;r>>8^i[255&(g^y[r])];return-1^g}(0|h,n,n.length,0):function(g,y,p,b){var i=o,d=b+p;g^=-1;for(var r=b;r>>8^i[255&(g^y.charCodeAt(r))];return-1^g}(0|h,n,n.length,0):0}},{"./utils":32}],5:[function(_,N,w){w.base64=!1,w.binary=!1,w.dir=!1,w.createFolders=!0,w.date=null,w.compression=null,w.compressionOptions=null,w.comment=null,w.unixPermissions=null,w.dosPermissions=null},{}],6:[function(_,N,w){var u=null;u=typeof Promise<"u"?Promise:_("lie"),N.exports={Promise:u}},{lie:37}],7:[function(_,N,w){var u=typeof Uint8Array<"u"&&typeof Uint16Array<"u"&&typeof Uint32Array<"u",o=_("pako"),n=_("./utils"),h=_("./stream/GenericWorker"),g=u?"uint8array":"array";function y(p,b){h.call(this,"FlateWorker/"+p),this._pako=null,this._pakoAction=p,this._pakoOptions=b,this.meta={}}w.magic="\b\0",n.inherits(y,h),y.prototype.processChunk=function(p){this.meta=p.meta,this._pako===null&&this._createPako(),this._pako.push(n.transformTo(g,p.data),!1)},y.prototype.flush=function(){h.prototype.flush.call(this),this._pako===null&&this._createPako(),this._pako.push([],!0)},y.prototype.cleanUp=function(){h.prototype.cleanUp.call(this),this._pako=null},y.prototype._createPako=function(){this._pako=new o[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var p=this;this._pako.onData=function(b){p.push({data:b,meta:p.meta})}},w.compressWorker=function(p){return new y("Deflate",p)},w.uncompressWorker=function(){return new y("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(_,N,w){function u(i,d){var r,l="";for(r=0;r>>=8;return l}function o(i,d,r,l,a,c){var v,S,x=i.file,D=i.compression,O=c!==g.utf8encode,j=n.transformTo("string",c(x.name)),I=n.transformTo("string",g.utf8encode(x.name)),W=x.comment,V=n.transformTo("string",c(W)),m=n.transformTo("string",g.utf8encode(W)),B=I.length!==x.name.length,e=m.length!==W.length,T="",J="",U="",$=x.dir,L=x.date,q={crc32:0,compressedSize:0,uncompressedSize:0};d&&!r||(q.crc32=i.crc32,q.compressedSize=i.compressedSize,q.uncompressedSize=i.uncompressedSize);var E=0;d&&(E|=8),O||!B&&!e||(E|=2048);var C=0,X=0;$&&(C|=16),a==="UNIX"?(X=798,C|=function(H,nt){var ot=H;return H||(ot=nt?16893:33204),(65535&ot)<<16}(x.unixPermissions,$)):(X=20,C|=function(H){return 63&(H||0)}(x.dosPermissions)),v=L.getUTCHours(),v<<=6,v|=L.getUTCMinutes(),v<<=5,v|=L.getUTCSeconds()/2,S=L.getUTCFullYear()-1980,S<<=4,S|=L.getUTCMonth()+1,S<<=5,S|=L.getUTCDate(),B&&(J=u(1,1)+u(y(j),4)+I,T+="up"+u(J.length,2)+J),e&&(U=u(1,1)+u(y(V),4)+m,T+="uc"+u(U.length,2)+U);var G="";return G+=` +\0`,G+=u(E,2),G+=D.magic,G+=u(v,2),G+=u(S,2),G+=u(q.crc32,4),G+=u(q.compressedSize,4),G+=u(q.uncompressedSize,4),G+=u(j.length,2),G+=u(T.length,2),{fileRecord:p.LOCAL_FILE_HEADER+G+j+T,dirRecord:p.CENTRAL_FILE_HEADER+u(X,2)+G+u(V.length,2)+"\0\0\0\0"+u(C,4)+u(l,4)+j+T+V}}var n=_("../utils"),h=_("../stream/GenericWorker"),g=_("../utf8"),y=_("../crc32"),p=_("../signature");function b(i,d,r,l){h.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=d,this.zipPlatform=r,this.encodeFileName=l,this.streamFiles=i,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}n.inherits(b,h),b.prototype.push=function(i){var d=i.meta.percent||0,r=this.entriesCount,l=this._sources.length;this.accumulate?this.contentBuffer.push(i):(this.bytesWritten+=i.data.length,h.prototype.push.call(this,{data:i.data,meta:{currentFile:this.currentFile,percent:r?(d+100*(r-l-1))/r:100}}))},b.prototype.openedSource=function(i){this.currentSourceOffset=this.bytesWritten,this.currentFile=i.file.name;var d=this.streamFiles&&!i.file.dir;if(d){var r=o(i,d,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},b.prototype.closedSource=function(i){this.accumulate=!1;var d=this.streamFiles&&!i.file.dir,r=o(i,d,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),d)this.push({data:function(l){return p.DATA_DESCRIPTOR+u(l.crc32,4)+u(l.compressedSize,4)+u(l.uncompressedSize,4)}(i),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},b.prototype.flush=function(){for(var i=this.bytesWritten,d=0;d=this.index;h--)g=(g<<8)+this.byteAt(h);return this.index+=n,g},readString:function(n){return u.transformTo("string",this.readData(n))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var n=this.readInt(4);return new Date(Date.UTC(1980+(n>>25&127),(n>>21&15)-1,n>>16&31,n>>11&31,n>>5&63,(31&n)<<1))}},N.exports=o},{"../utils":32}],19:[function(_,N,w){var u=_("./Uint8ArrayReader");function o(n){u.call(this,n)}_("../utils").inherits(o,u),o.prototype.readData=function(n){this.checkOffset(n);var h=this.data.slice(this.zero+this.index,this.zero+this.index+n);return this.index+=n,h},N.exports=o},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(_,N,w){var u=_("./DataReader");function o(n){u.call(this,n)}_("../utils").inherits(o,u),o.prototype.byteAt=function(n){return this.data.charCodeAt(this.zero+n)},o.prototype.lastIndexOfSignature=function(n){return this.data.lastIndexOf(n)-this.zero},o.prototype.readAndCheckSignature=function(n){return n===this.readData(4)},o.prototype.readData=function(n){this.checkOffset(n);var h=this.data.slice(this.zero+this.index,this.zero+this.index+n);return this.index+=n,h},N.exports=o},{"../utils":32,"./DataReader":18}],21:[function(_,N,w){var u=_("./ArrayReader");function o(n){u.call(this,n)}_("../utils").inherits(o,u),o.prototype.readData=function(n){if(this.checkOffset(n),n===0)return new Uint8Array(0);var h=this.data.subarray(this.zero+this.index,this.zero+this.index+n);return this.index+=n,h},N.exports=o},{"../utils":32,"./ArrayReader":17}],22:[function(_,N,w){var u=_("../utils"),o=_("../support"),n=_("./ArrayReader"),h=_("./StringReader"),g=_("./NodeBufferReader"),y=_("./Uint8ArrayReader");N.exports=function(p){var b=u.getTypeOf(p);return u.checkSupport(b),b!=="string"||o.uint8array?b==="nodebuffer"?new g(p):o.uint8array?new y(u.transformTo("uint8array",p)):new n(u.transformTo("array",p)):new h(p)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(_,N,w){w.LOCAL_FILE_HEADER="PK",w.CENTRAL_FILE_HEADER="PK",w.CENTRAL_DIRECTORY_END="PK",w.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK\x07",w.ZIP64_CENTRAL_DIRECTORY_END="PK",w.DATA_DESCRIPTOR="PK\x07\b"},{}],24:[function(_,N,w){var u=_("./GenericWorker"),o=_("../utils");function n(h){u.call(this,"ConvertWorker to "+h),this.destType=h}o.inherits(n,u),n.prototype.processChunk=function(h){this.push({data:o.transformTo(this.destType,h.data),meta:h.meta})},N.exports=n},{"../utils":32,"./GenericWorker":28}],25:[function(_,N,w){var u=_("./GenericWorker"),o=_("../crc32");function n(){u.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}_("../utils").inherits(n,u),n.prototype.processChunk=function(h){this.streamInfo.crc32=o(h.data,this.streamInfo.crc32||0),this.push(h)},N.exports=n},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(_,N,w){var u=_("../utils"),o=_("./GenericWorker");function n(h){o.call(this,"DataLengthProbe for "+h),this.propName=h,this.withStreamInfo(h,0)}u.inherits(n,o),n.prototype.processChunk=function(h){if(h){var g=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=g+h.data.length}o.prototype.processChunk.call(this,h)},N.exports=n},{"../utils":32,"./GenericWorker":28}],27:[function(_,N,w){var u=_("../utils"),o=_("./GenericWorker");function n(h){o.call(this,"DataWorker");var g=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,h.then(function(y){g.dataIsReady=!0,g.data=y,g.max=y&&y.length||0,g.type=u.getTypeOf(y),g.isPaused||g._tickAndRepeat()},function(y){g.error(y)})}u.inherits(n,o),n.prototype.cleanUp=function(){o.prototype.cleanUp.call(this),this.data=null},n.prototype.resume=function(){return!!o.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,u.delay(this._tickAndRepeat,[],this)),!0)},n.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(u.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},n.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var h=null,g=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":h=this.data.substring(this.index,g);break;case"uint8array":h=this.data.subarray(this.index,g);break;case"array":case"nodebuffer":h=this.data.slice(this.index,g)}return this.index=g,this.push({data:h,meta:{percent:this.max?this.index/this.max*100:0}})},N.exports=n},{"../utils":32,"./GenericWorker":28}],28:[function(_,N,w){function u(o){this.name=o||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}u.prototype={push:function(o){this.emit("data",o)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(o){this.emit("error",o)}return!0},error:function(o){return!this.isFinished&&(this.isPaused?this.generatedError=o:(this.isFinished=!0,this.emit("error",o),this.previous&&this.previous.error(o),this.cleanUp()),!0)},on:function(o,n){return this._listeners[o].push(n),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(o,n){if(this._listeners[o])for(var h=0;h "+o:o}},N.exports=u},{}],29:[function(_,N,w){var u=_("../utils"),o=_("./ConvertWorker"),n=_("./GenericWorker"),h=_("../base64"),g=_("../support"),y=_("../external"),p=null;if(g.nodestream)try{p=_("../nodejs/NodejsStreamOutputAdapter")}catch{}function b(d,r){return new y.Promise(function(l,a){var c=[],v=d._internalType,S=d._outputType,x=d._mimeType;d.on("data",function(D,O){c.push(D),r&&r(O)}).on("error",function(D){c=[],a(D)}).on("end",function(){try{var D=function(O,j,I){switch(O){case"blob":return u.newBlob(u.transformTo("arraybuffer",j),I);case"base64":return h.encode(j);default:return u.transformTo(O,j)}}(S,function(O,j){var I,W=0,V=null,m=0;for(I=0;I"u")w.blob=!1;else{var u=new ArrayBuffer(0);try{w.blob=new Blob([u],{type:"application/zip"}).size===0}catch{try{var o=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);o.append(u),w.blob=o.getBlob("application/zip").size===0}catch{w.blob=!1}}}try{w.nodestream=!!_("readable-stream").Readable}catch{w.nodestream=!1}},{"readable-stream":16}],31:[function(_,N,w){for(var u=_("./utils"),o=_("./support"),n=_("./nodejsUtils"),h=_("./stream/GenericWorker"),g=new Array(256),y=0;y<256;y++)g[y]=252<=y?6:248<=y?5:240<=y?4:224<=y?3:192<=y?2:1;g[254]=g[254]=1;function p(){h.call(this,"utf-8 decode"),this.leftOver=null}function b(){h.call(this,"utf-8 encode")}w.utf8encode=function(i){return o.nodebuffer?n.newBufferFrom(i,"utf-8"):function(d){var r,l,a,c,v,S=d.length,x=0;for(c=0;c>>6:(l<65536?r[v++]=224|l>>>12:(r[v++]=240|l>>>18,r[v++]=128|l>>>12&63),r[v++]=128|l>>>6&63),r[v++]=128|63&l);return r}(i)},w.utf8decode=function(i){return o.nodebuffer?u.transformTo("nodebuffer",i).toString("utf-8"):function(d){var r,l,a,c,v=d.length,S=new Array(2*v);for(r=l=0;r>10&1023,S[l++]=56320|1023&a)}return S.length!==l&&(S.subarray?S=S.subarray(0,l):S.length=l),u.applyFromCharCode(S)}(i=u.transformTo(o.uint8array?"uint8array":"array",i))},u.inherits(p,h),p.prototype.processChunk=function(i){var d=u.transformTo(o.uint8array?"uint8array":"array",i.data);if(this.leftOver&&this.leftOver.length){if(o.uint8array){var r=d;(d=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),d.set(r,this.leftOver.length)}else d=this.leftOver.concat(d);this.leftOver=null}var l=function(c,v){var S;for((v=v||c.length)>c.length&&(v=c.length),S=v-1;0<=S&&(192&c[S])==128;)S--;return S<0||S===0?v:S+g[c[S]]>v?S:v}(d),a=d;l!==d.length&&(o.uint8array?(a=d.subarray(0,l),this.leftOver=d.subarray(l,d.length)):(a=d.slice(0,l),this.leftOver=d.slice(l,d.length))),this.push({data:w.utf8decode(a),meta:i.meta})},p.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:w.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},w.Utf8DecodeWorker=p,u.inherits(b,h),b.prototype.processChunk=function(i){this.push({data:w.utf8encode(i.data),meta:i.meta})},w.Utf8EncodeWorker=b},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(_,N,w){var u=_("./support"),o=_("./base64"),n=_("./nodejsUtils"),h=_("./external");function g(r){return r}function y(r,l){for(var a=0;a>8;this.dir=!!(16&this.externalFileAttributes),i==0&&(this.dosPermissions=63&this.externalFileAttributes),i==3&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||this.fileNameStr.slice(-1)!=="/"||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var i=u(this.extraFields[1].value);this.uncompressedSize===o.MAX_VALUE_32BITS&&(this.uncompressedSize=i.readInt(8)),this.compressedSize===o.MAX_VALUE_32BITS&&(this.compressedSize=i.readInt(8)),this.localHeaderOffset===o.MAX_VALUE_32BITS&&(this.localHeaderOffset=i.readInt(8)),this.diskNumberStart===o.MAX_VALUE_32BITS&&(this.diskNumberStart=i.readInt(4))}},readExtraFields:function(i){var d,r,l,a=i.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});i.index+4>>6:(i<65536?b[l++]=224|i>>>12:(b[l++]=240|i>>>18,b[l++]=128|i>>>12&63),b[l++]=128|i>>>6&63),b[l++]=128|63&i);return b},w.buf2binstring=function(p){return y(p,p.length)},w.binstring2buf=function(p){for(var b=new u.Buf8(p.length),i=0,d=b.length;i>10&1023,c[d++]=56320|1023&r)}return y(c,d)},w.utf8border=function(p,b){var i;for((b=b||p.length)>p.length&&(b=p.length),i=b-1;0<=i&&(192&p[i])==128;)i--;return i<0||i===0?b:i+h[p[i]]>b?i:b}},{"./common":41}],43:[function(_,N,w){N.exports=function(u,o,n,h){for(var g=65535&u|0,y=u>>>16&65535|0,p=0;n!==0;){for(n-=p=2e3>>1:o>>>1;n[h]=o}return n}();N.exports=function(o,n,h,g){var y=u,p=g+h;o^=-1;for(var b=g;b>>8^y[255&(o^n[b])];return-1^o}},{}],46:[function(_,N,w){var u,o=_("../utils/common"),n=_("./trees"),h=_("./adler32"),g=_("./crc32"),y=_("./messages"),p=0,b=4,i=0,d=-2,r=-1,l=4,a=2,c=8,v=9,S=286,x=30,D=19,O=2*S+1,j=15,I=3,W=258,V=W+I+1,m=42,B=113,e=1,T=2,J=3,U=4;function $(t,R){return t.msg=y[R],R}function L(t){return(t<<1)-(4t.avail_out&&(A=t.avail_out),A!==0&&(o.arraySet(t.output,R.pending_buf,R.pending_out,A,t.next_out),t.next_out+=A,R.pending_out+=A,t.total_out+=A,t.avail_out-=A,R.pending-=A,R.pending===0&&(R.pending_out=0))}function C(t,R){n._tr_flush_block(t,0<=t.block_start?t.block_start:-1,t.strstart-t.block_start,R),t.block_start=t.strstart,E(t.strm)}function X(t,R){t.pending_buf[t.pending++]=R}function G(t,R){t.pending_buf[t.pending++]=R>>>8&255,t.pending_buf[t.pending++]=255&R}function H(t,R){var A,f,s=t.max_chain_length,k=t.strstart,F=t.prev_length,P=t.nice_match,z=t.strstart>t.w_size-V?t.strstart-(t.w_size-V):0,Z=t.window,K=t.w_mask,M=t.prev,Y=t.strstart+W,rt=Z[k+F-1],tt=Z[k+F];t.prev_length>=t.good_match&&(s>>=2),P>t.lookahead&&(P=t.lookahead);do if(Z[(A=R)+F]===tt&&Z[A+F-1]===rt&&Z[A]===Z[k]&&Z[++A]===Z[k+1]){k+=2,A++;do;while(Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&Z[++k]===Z[++A]&&kz&&--s!=0);return F<=t.lookahead?F:t.lookahead}function nt(t){var R,A,f,s,k,F,P,z,Z,K,M=t.w_size;do{if(s=t.window_size-t.lookahead-t.strstart,t.strstart>=M+(M-V)){for(o.arraySet(t.window,t.window,M,M,0),t.match_start-=M,t.strstart-=M,t.block_start-=M,R=A=t.hash_size;f=t.head[--R],t.head[R]=M<=f?f-M:0,--A;);for(R=A=M;f=t.prev[--R],t.prev[R]=M<=f?f-M:0,--A;);s+=M}if(t.strm.avail_in===0)break;if(F=t.strm,P=t.window,z=t.strstart+t.lookahead,Z=s,K=void 0,K=F.avail_in,Z=I)for(k=t.strstart-t.insert,t.ins_h=t.window[k],t.ins_h=(t.ins_h<=I&&(t.ins_h=(t.ins_h<=I)if(f=n._tr_tally(t,t.strstart-t.match_start,t.match_length-I),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=I){for(t.match_length--;t.strstart++,t.ins_h=(t.ins_h<=I&&(t.ins_h=(t.ins_h<=I&&t.match_length<=t.prev_length){for(s=t.strstart+t.lookahead-I,f=n._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-I),t.lookahead-=t.prev_length-1,t.prev_length-=2;++t.strstart<=s&&(t.ins_h=(t.ins_h<t.pending_buf_size-5&&(A=t.pending_buf_size-5);;){if(t.lookahead<=1){if(nt(t),t.lookahead===0&&R===p)return e;if(t.lookahead===0)break}t.strstart+=t.lookahead,t.lookahead=0;var f=t.block_start+A;if((t.strstart===0||t.strstart>=f)&&(t.lookahead=t.strstart-f,t.strstart=f,C(t,!1),t.strm.avail_out===0)||t.strstart-t.block_start>=t.w_size-V&&(C(t,!1),t.strm.avail_out===0))return e}return t.insert=0,R===b?(C(t,!0),t.strm.avail_out===0?J:U):(t.strstart>t.block_start&&(C(t,!1),t.strm.avail_out),e)}),new et(4,4,8,4,ot),new et(4,5,16,8,ot),new et(4,6,32,32,ot),new et(4,4,16,16,Q),new et(8,16,32,32,Q),new et(8,16,128,128,Q),new et(8,32,128,256,Q),new et(32,128,258,1024,Q),new et(32,258,258,4096,Q)],w.deflateInit=function(t,R){return lt(t,R,c,15,8,0)},w.deflateInit2=lt,w.deflateReset=ft,w.deflateResetKeep=it,w.deflateSetHeader=function(t,R){return t&&t.state?t.state.wrap!==2?d:(t.state.gzhead=R,i):d},w.deflate=function(t,R){var A,f,s,k;if(!t||!t.state||5>8&255),X(f,f.gzhead.time>>16&255),X(f,f.gzhead.time>>24&255),X(f,f.level===9?2:2<=f.strategy||f.level<2?4:0),X(f,255&f.gzhead.os),f.gzhead.extra&&f.gzhead.extra.length&&(X(f,255&f.gzhead.extra.length),X(f,f.gzhead.extra.length>>8&255)),f.gzhead.hcrc&&(t.adler=g(t.adler,f.pending_buf,f.pending,0)),f.gzindex=0,f.status=69):(X(f,0),X(f,0),X(f,0),X(f,0),X(f,0),X(f,f.level===9?2:2<=f.strategy||f.level<2?4:0),X(f,3),f.status=B);else{var F=c+(f.w_bits-8<<4)<<8;F|=(2<=f.strategy||f.level<2?0:f.level<6?1:f.level===6?2:3)<<6,f.strstart!==0&&(F|=32),F+=31-F%31,f.status=B,G(f,F),f.strstart!==0&&(G(f,t.adler>>>16),G(f,65535&t.adler)),t.adler=1}if(f.status===69)if(f.gzhead.extra){for(s=f.pending;f.gzindex<(65535&f.gzhead.extra.length)&&(f.pending!==f.pending_buf_size||(f.gzhead.hcrc&&f.pending>s&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),E(t),s=f.pending,f.pending!==f.pending_buf_size));)X(f,255&f.gzhead.extra[f.gzindex]),f.gzindex++;f.gzhead.hcrc&&f.pending>s&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),f.gzindex===f.gzhead.extra.length&&(f.gzindex=0,f.status=73)}else f.status=73;if(f.status===73)if(f.gzhead.name){s=f.pending;do{if(f.pending===f.pending_buf_size&&(f.gzhead.hcrc&&f.pending>s&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),E(t),s=f.pending,f.pending===f.pending_buf_size)){k=1;break}k=f.gzindexs&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),k===0&&(f.gzindex=0,f.status=91)}else f.status=91;if(f.status===91)if(f.gzhead.comment){s=f.pending;do{if(f.pending===f.pending_buf_size&&(f.gzhead.hcrc&&f.pending>s&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),E(t),s=f.pending,f.pending===f.pending_buf_size)){k=1;break}k=f.gzindexs&&(t.adler=g(t.adler,f.pending_buf,f.pending-s,s)),k===0&&(f.status=103)}else f.status=103;if(f.status===103&&(f.gzhead.hcrc?(f.pending+2>f.pending_buf_size&&E(t),f.pending+2<=f.pending_buf_size&&(X(f,255&t.adler),X(f,t.adler>>8&255),t.adler=0,f.status=B)):f.status=B),f.pending!==0){if(E(t),t.avail_out===0)return f.last_flush=-1,i}else if(t.avail_in===0&&L(R)<=L(A)&&R!==b)return $(t,-5);if(f.status===666&&t.avail_in!==0)return $(t,-5);if(t.avail_in!==0||f.lookahead!==0||R!==p&&f.status!==666){var P=f.strategy===2?function(z,Z){for(var K;;){if(z.lookahead===0&&(nt(z),z.lookahead===0)){if(Z===p)return e;break}if(z.match_length=0,K=n._tr_tally(z,0,z.window[z.strstart]),z.lookahead--,z.strstart++,K&&(C(z,!1),z.strm.avail_out===0))return e}return z.insert=0,Z===b?(C(z,!0),z.strm.avail_out===0?J:U):z.last_lit&&(C(z,!1),z.strm.avail_out===0)?e:T}(f,R):f.strategy===3?function(z,Z){for(var K,M,Y,rt,tt=z.window;;){if(z.lookahead<=W){if(nt(z),z.lookahead<=W&&Z===p)return e;if(z.lookahead===0)break}if(z.match_length=0,z.lookahead>=I&&0z.lookahead&&(z.match_length=z.lookahead)}if(z.match_length>=I?(K=n._tr_tally(z,1,z.match_length-I),z.lookahead-=z.match_length,z.strstart+=z.match_length,z.match_length=0):(K=n._tr_tally(z,0,z.window[z.strstart]),z.lookahead--,z.strstart++),K&&(C(z,!1),z.strm.avail_out===0))return e}return z.insert=0,Z===b?(C(z,!0),z.strm.avail_out===0?J:U):z.last_lit&&(C(z,!1),z.strm.avail_out===0)?e:T}(f,R):u[f.level].func(f,R);if(P!==J&&P!==U||(f.status=666),P===e||P===J)return t.avail_out===0&&(f.last_flush=-1),i;if(P===T&&(R===1?n._tr_align(f):R!==5&&(n._tr_stored_block(f,0,0,!1),R===3&&(q(f.head),f.lookahead===0&&(f.strstart=0,f.block_start=0,f.insert=0))),E(t),t.avail_out===0))return f.last_flush=-1,i}return R!==b?i:f.wrap<=0?1:(f.wrap===2?(X(f,255&t.adler),X(f,t.adler>>8&255),X(f,t.adler>>16&255),X(f,t.adler>>24&255),X(f,255&t.total_in),X(f,t.total_in>>8&255),X(f,t.total_in>>16&255),X(f,t.total_in>>24&255)):(G(f,t.adler>>>16),G(f,65535&t.adler)),E(t),0=A.w_size&&(k===0&&(q(A.head),A.strstart=0,A.block_start=0,A.insert=0),Z=new o.Buf8(A.w_size),o.arraySet(Z,R,K-A.w_size,A.w_size,0),R=Z,K=A.w_size),F=t.avail_in,P=t.next_in,z=t.input,t.avail_in=K,t.next_in=0,t.input=R,nt(A);A.lookahead>=I;){for(f=A.strstart,s=A.lookahead-(I-1);A.ins_h=(A.ins_h<>>=I=j>>>24,v-=I,(I=j>>>16&255)===0)T[y++]=65535&j;else{if(!(16&I)){if(!(64&I)){j=S[(65535&j)+(c&(1<>>=I,v-=I),v<15&&(c+=e[h++]<>>=I=j>>>24,v-=I,!(16&(I=j>>>16&255))){if(!(64&I)){j=x[(65535&j)+(c&(1<>>=I,v-=I,(I=y-p)>3,c&=(1<<(v-=W<<3))-1,u.next_in=h,u.next_out=y,u.avail_in=h>>24&255)+(m>>>8&65280)+((65280&m)<<8)+((255&m)<<24)}function c(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new u.Buf16(320),this.work=new u.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function v(m){var B;return m&&m.state?(B=m.state,m.total_in=m.total_out=B.total=0,m.msg="",B.wrap&&(m.adler=1&B.wrap),B.mode=d,B.last=0,B.havedict=0,B.dmax=32768,B.head=null,B.hold=0,B.bits=0,B.lencode=B.lendyn=new u.Buf32(r),B.distcode=B.distdyn=new u.Buf32(l),B.sane=1,B.back=-1,b):i}function S(m){var B;return m&&m.state?((B=m.state).wsize=0,B.whave=0,B.wnext=0,v(m)):i}function x(m,B){var e,T;return m&&m.state?(T=m.state,B<0?(e=0,B=-B):(e=1+(B>>4),B<48&&(B&=15)),B&&(B<8||15=U.wsize?(u.arraySet(U.window,B,e-U.wsize,U.wsize,0),U.wnext=0,U.whave=U.wsize):(T<(J=U.wsize-U.wnext)&&(J=T),u.arraySet(U.window,B,e-T,J,U.wnext),(T-=J)?(u.arraySet(U.window,B,e-T,T,0),U.wnext=T,U.whave=U.wsize):(U.wnext+=J,U.wnext===U.wsize&&(U.wnext=0),U.whave>>8&255,e.check=n(e.check,k,2,0),C=E=0,e.mode=2;break}if(e.flags=0,e.head&&(e.head.done=!1),!(1&e.wrap)||(((255&E)<<8)+(E>>8))%31){m.msg="incorrect header check",e.mode=30;break}if((15&E)!=8){m.msg="unknown compression method",e.mode=30;break}if(C-=4,t=8+(15&(E>>>=4)),e.wbits===0)e.wbits=t;else if(t>e.wbits){m.msg="invalid window size",e.mode=30;break}e.dmax=1<>8&1),512&e.flags&&(k[0]=255&E,k[1]=E>>>8&255,e.check=n(e.check,k,2,0)),C=E=0,e.mode=3;case 3:for(;C<32;){if(L===0)break t;L--,E+=T[U++]<>>8&255,k[2]=E>>>16&255,k[3]=E>>>24&255,e.check=n(e.check,k,4,0)),C=E=0,e.mode=4;case 4:for(;C<16;){if(L===0)break t;L--,E+=T[U++]<>8),512&e.flags&&(k[0]=255&E,k[1]=E>>>8&255,e.check=n(e.check,k,2,0)),C=E=0,e.mode=5;case 5:if(1024&e.flags){for(;C<16;){if(L===0)break t;L--,E+=T[U++]<>>8&255,e.check=n(e.check,k,2,0)),C=E=0}else e.head&&(e.head.extra=null);e.mode=6;case 6:if(1024&e.flags&&(L<(H=e.length)&&(H=L),H&&(e.head&&(t=e.head.extra_len-e.length,e.head.extra||(e.head.extra=new Array(e.head.extra_len)),u.arraySet(e.head.extra,T,U,H,t)),512&e.flags&&(e.check=n(e.check,T,H,U)),L-=H,U+=H,e.length-=H),e.length))break t;e.length=0,e.mode=7;case 7:if(2048&e.flags){if(L===0)break t;for(H=0;t=T[U+H++],e.head&&t&&e.length<65536&&(e.head.name+=String.fromCharCode(t)),t&&H>9&1,e.head.done=!0),m.adler=e.check=0,e.mode=12;break;case 10:for(;C<32;){if(L===0)break t;L--,E+=T[U++]<>>=7&C,C-=7&C,e.mode=27;break}for(;C<3;){if(L===0)break t;L--,E+=T[U++]<>>=1)){case 0:e.mode=14;break;case 1:if(W(e),e.mode=20,B!==6)break;E>>>=2,C-=2;break t;case 2:e.mode=17;break;case 3:m.msg="invalid block type",e.mode=30}E>>>=2,C-=2;break;case 14:for(E>>>=7&C,C-=7&C;C<32;){if(L===0)break t;L--,E+=T[U++]<>>16^65535)){m.msg="invalid stored block lengths",e.mode=30;break}if(e.length=65535&E,C=E=0,e.mode=15,B===6)break t;case 15:e.mode=16;case 16:if(H=e.length){if(L>>=5,C-=5,e.ndist=1+(31&E),E>>>=5,C-=5,e.ncode=4+(15&E),E>>>=4,C-=4,286>>=3,C-=3}for(;e.have<19;)e.lens[F[e.have++]]=0;if(e.lencode=e.lendyn,e.lenbits=7,A={bits:e.lenbits},R=g(0,e.lens,0,19,e.lencode,0,e.work,A),e.lenbits=A.bits,R){m.msg="invalid code lengths set",e.mode=30;break}e.have=0,e.mode=19;case 19:for(;e.have>>16&255,st=65535&s,!((Q=s>>>24)<=C);){if(L===0)break t;L--,E+=T[U++]<>>=Q,C-=Q,e.lens[e.have++]=st;else{if(st===16){for(f=Q+2;C>>=Q,C-=Q,e.have===0){m.msg="invalid bit length repeat",e.mode=30;break}t=e.lens[e.have-1],H=3+(3&E),E>>>=2,C-=2}else if(st===17){for(f=Q+3;C>>=Q)),E>>>=3,C-=3}else{for(f=Q+7;C>>=Q)),E>>>=7,C-=7}if(e.have+H>e.nlen+e.ndist){m.msg="invalid bit length repeat",e.mode=30;break}for(;H--;)e.lens[e.have++]=t}}if(e.mode===30)break;if(e.lens[256]===0){m.msg="invalid code -- missing end-of-block",e.mode=30;break}if(e.lenbits=9,A={bits:e.lenbits},R=g(y,e.lens,0,e.nlen,e.lencode,0,e.work,A),e.lenbits=A.bits,R){m.msg="invalid literal/lengths set",e.mode=30;break}if(e.distbits=6,e.distcode=e.distdyn,A={bits:e.distbits},R=g(p,e.lens,e.nlen,e.ndist,e.distcode,0,e.work,A),e.distbits=A.bits,R){m.msg="invalid distances set",e.mode=30;break}if(e.mode=20,B===6)break t;case 20:e.mode=21;case 21:if(6<=L&&258<=q){m.next_out=$,m.avail_out=q,m.next_in=U,m.avail_in=L,e.hold=E,e.bits=C,h(m,G),$=m.next_out,J=m.output,q=m.avail_out,U=m.next_in,T=m.input,L=m.avail_in,E=e.hold,C=e.bits,e.mode===12&&(e.back=-1);break}for(e.back=0;et=(s=e.lencode[E&(1<>>16&255,st=65535&s,!((Q=s>>>24)<=C);){if(L===0)break t;L--,E+=T[U++]<>it)])>>>16&255,st=65535&s,!(it+(Q=s>>>24)<=C);){if(L===0)break t;L--,E+=T[U++]<>>=it,C-=it,e.back+=it}if(E>>>=Q,C-=Q,e.back+=Q,e.length=st,et===0){e.mode=26;break}if(32&et){e.back=-1,e.mode=12;break}if(64&et){m.msg="invalid literal/length code",e.mode=30;break}e.extra=15&et,e.mode=22;case 22:if(e.extra){for(f=e.extra;C>>=e.extra,C-=e.extra,e.back+=e.extra}e.was=e.length,e.mode=23;case 23:for(;et=(s=e.distcode[E&(1<>>16&255,st=65535&s,!((Q=s>>>24)<=C);){if(L===0)break t;L--,E+=T[U++]<>it)])>>>16&255,st=65535&s,!(it+(Q=s>>>24)<=C);){if(L===0)break t;L--,E+=T[U++]<>>=it,C-=it,e.back+=it}if(E>>>=Q,C-=Q,e.back+=Q,64&et){m.msg="invalid distance code",e.mode=30;break}e.offset=st,e.extra=15&et,e.mode=24;case 24:if(e.extra){for(f=e.extra;C>>=e.extra,C-=e.extra,e.back+=e.extra}if(e.offset>e.dmax){m.msg="invalid distance too far back",e.mode=30;break}e.mode=25;case 25:if(q===0)break t;if(H=G-q,e.offset>H){if((H=e.offset-H)>e.whave&&e.sane){m.msg="invalid distance too far back",e.mode=30;break}nt=H>e.wnext?(H-=e.wnext,e.wsize-H):e.wnext-H,H>e.length&&(H=e.length),ot=e.window}else ot=J,nt=$-e.offset,H=e.length;for(qO?(I=nt[ot+l[B]],C[X+l[B]]):(I=96,0),c=1<>$)+(v-=c)]=j<<24|I<<16|W|0,v!==0;);for(c=1<>=1;if(c!==0?(E&=c-1,E+=c):E=0,B++,--G[m]==0){if(m===T)break;m=p[b+l[B]]}if(J>>7)]}function X(s,k){s.pending_buf[s.pending++]=255&k,s.pending_buf[s.pending++]=k>>>8&255}function G(s,k,F){s.bi_valid>a-F?(s.bi_buf|=k<>a-s.bi_valid,s.bi_valid+=F-a):(s.bi_buf|=k<>>=1,F<<=1,0<--k;);return F>>>1}function ot(s,k,F){var P,z,Z=new Array(l+1),K=0;for(P=1;P<=l;P++)Z[P]=K=K+F[P-1]<<1;for(z=0;z<=k;z++){var M=s[2*z+1];M!==0&&(s[2*z]=nt(Z[M]++,M))}}function Q(s){var k;for(k=0;k>1;1<=F;F--)it(s,Z,F);for(z=Y;F=s.heap[1],s.heap[1]=s.heap[s.heap_len--],it(s,Z,1),P=s.heap[1],s.heap[--s.heap_max]=F,s.heap[--s.heap_max]=P,Z[2*z]=Z[2*F]+Z[2*P],s.depth[z]=(s.depth[F]>=s.depth[P]?s.depth[F]:s.depth[P])+1,Z[2*F+1]=Z[2*P+1]=z,s.heap[1]=z++,it(s,Z,1),2<=s.heap_len;);s.heap[--s.heap_max]=s.heap[1],function(tt,ht){var pt,dt,mt,at,gt,kt,ct=ht.dyn_tree,xt=ht.max_code,Et=ht.stat_desc.static_tree,At=ht.stat_desc.has_stree,It=ht.stat_desc.extra_bits,St=ht.stat_desc.extra_base,_t=ht.stat_desc.max_length,bt=0;for(at=0;at<=l;at++)tt.bl_count[at]=0;for(ct[2*tt.heap[tt.heap_max]+1]=0,pt=tt.heap_max+1;pt>=7;z>>=1)if(1&rt&&M.dyn_ltree[2*Y]!==0)return o;if(M.dyn_ltree[18]!==0||M.dyn_ltree[20]!==0||M.dyn_ltree[26]!==0)return n;for(Y=32;Y>>3,(Z=s.static_len+3+7>>>3)<=z&&(z=Z)):z=Z=F+5,F+4<=z&&k!==-1?f(s,k,F,P):s.strategy===4||Z===z?(G(s,2+(P?1:0),3),ft(s,V,m)):(G(s,4+(P?1:0),3),function(M,Y,rt,tt){var ht;for(G(M,Y-257,5),G(M,rt-1,5),G(M,tt-4,4),ht=0;ht>>8&255,s.pending_buf[s.d_buf+2*s.last_lit+1]=255&k,s.pending_buf[s.l_buf+s.last_lit]=255&F,s.last_lit++,k===0?s.dyn_ltree[2*F]++:(s.matches++,k--,s.dyn_ltree[2*(e[F]+p+1)]++,s.dyn_dtree[2*C(k)]++),s.last_lit===s.lit_bufsize-1},w._tr_align=function(s){G(s,2,3),H(s,v,V),function(k){k.bi_valid===16?(X(k,k.bi_buf),k.bi_buf=0,k.bi_valid=0):8<=k.bi_valid&&(k.pending_buf[k.pending++]=255&k.bi_buf,k.bi_buf>>=8,k.bi_valid-=8)}(s)}},{"../utils/common":41}],53:[function(_,N,w){N.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(_,N,w){(function(u){(function(o,n){if(!o.setImmediate){var h,g,y,p,b=1,i={},d=!1,r=o.document,l=Object.getPrototypeOf&&Object.getPrototypeOf(o);l=l&&l.setTimeout?l:o,h={}.toString.call(o.process)==="[object process]"?function(S){process.nextTick(function(){c(S)})}:function(){if(o.postMessage&&!o.importScripts){var S=!0,x=o.onmessage;return o.onmessage=function(){S=!1},o.postMessage("","*"),o.onmessage=x,S}}()?(p="setImmediate$"+Math.random()+"$",o.addEventListener?o.addEventListener("message",v,!1):o.attachEvent("onmessage",v),function(S){o.postMessage(p+S,"*")}):o.MessageChannel?((y=new MessageChannel).port1.onmessage=function(S){c(S.data)},function(S){y.port2.postMessage(S)}):r&&"onreadystatechange"in r.createElement("script")?(g=r.documentElement,function(S){var x=r.createElement("script");x.onreadystatechange=function(){c(S),x.onreadystatechange=null,g.removeChild(x),x=null},g.appendChild(x)}):function(S){setTimeout(c,0,S)},l.setImmediate=function(S){typeof S!="function"&&(S=new Function(""+S));for(var x=new Array(arguments.length-1),D=0;D"u"?u===void 0?this:u:self)}).call(this,typeof vt<"u"?vt:typeof self<"u"?self:typeof window<"u"?window:{})},{}]},{},[10])(10)})})(zt);var Ct=zt.exports;const Rt=Bt(Ct),Tt=Ot({__proto__:null,default:Rt},[Ct]);export{Tt as j}; diff --git a/dist/index.html b/dist/index.html index f8c5efd..326b258 100644 --- a/dist/index.html +++ b/dist/index.html @@ -4,9 +4,9 @@ - Cell Holder Generator - - + Cell Holder Generator - waak.me + +
@@ -18,7 +18,10 @@
-

Cell Holder Generator

+
+

Cell Holder Generator

+

Forked from waak's Battery Builder
Huge thanks to waak for the original project.

+

Generate custom 3D printable cell holders with STEP export

@@ -160,6 +163,15 @@
+
@@ -171,8 +183,28 @@ STEP exports a 3D solid for CAD or 3D printing. DXF exports a flat 2D outline for laser or plasma cutters. +
+ + +
+
+
+ + +
+
+ + +
+
- +
+ + +
@@ -182,7 +214,16 @@

Preview

- +
+
+
Top
+ +
+
+
Bottom
+ +
+
Configure settings and click Generate to see preview
diff --git a/index.html b/index.html index 96ea537..351740d 100644 --- a/index.html +++ b/index.html @@ -154,14 +154,19 @@ +
@@ -173,6 +178,10 @@ STEP exports a 3D solid for CAD or 3D printing. DXF exports a flat 2D outline for laser or plasma cutters. +
+ + +
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 9e08e00..ab7b9d6 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -29,9 +29,20 @@ "x64" ], "dev": true, - "libc": [ - "glibc" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/node_modules/.vite/deps/_metadata.json b/node_modules/.vite/deps/_metadata.json index 5216615..4cdf495 100644 --- a/node_modules/.vite/deps/_metadata.json +++ b/node_modules/.vite/deps/_metadata.json @@ -1,13 +1,13 @@ { - "hash": "768e60fe", - "configHash": "cddbe005", - "lockfileHash": "6cdf80c6", - "browserHash": "40f95f93", + "hash": "c96d213e", + "configHash": "c79dcb49", + "lockfileHash": "6e69140d", + "browserHash": "2de19426", "optimized": { "jszip": { "src": "../../jszip/dist/jszip.min.js", "file": "jszip.js", - "fileHash": "26c0effe", + "fileHash": "d21eabfe", "needsInterop": true } }, diff --git a/node_modules/@rollup/rollup-linux-x64-musl/README.md b/node_modules/@rollup/rollup-linux-x64-musl/README.md new file mode 100644 index 0000000..5848a6c --- /dev/null +++ b/node_modules/@rollup/rollup-linux-x64-musl/README.md @@ -0,0 +1,3 @@ +# `@rollup/rollup-linux-x64-musl` + +This is the **x86_64-unknown-linux-musl** binary for `rollup` diff --git a/node_modules/@rollup/rollup-linux-x64-musl/package.json b/node_modules/@rollup/rollup-linux-x64-musl/package.json new file mode 100644 index 0000000..4d880a4 --- /dev/null +++ b/node_modules/@rollup/rollup-linux-x64-musl/package.json @@ -0,0 +1,25 @@ +{ + "name": "@rollup/rollup-linux-x64-musl", + "version": "4.60.2", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "files": [ + "rollup.linux-x64-musl.node" + ], + "description": "Native bindings for Rollup", + "author": "Lukas Taegert-Atkinson", + "homepage": "https://rollupjs.org/", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/rollup/rollup.git" + }, + "libc": [ + "musl" + ], + "main": "./rollup.linux-x64-musl.node" +} \ No newline at end of file diff --git a/node_modules/@rollup/rollup-linux-x64-musl/rollup.linux-x64-musl.node b/node_modules/@rollup/rollup-linux-x64-musl/rollup.linux-x64-musl.node new file mode 100644 index 0000000..f865218 Binary files /dev/null and b/node_modules/@rollup/rollup-linux-x64-musl/rollup.linux-x64-musl.node differ diff --git a/package-lock.json b/package-lock.json index 8f671c8..2eb420b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -497,9 +497,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -514,9 +511,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -531,9 +525,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -548,9 +539,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -565,9 +553,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -582,9 +567,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -599,9 +581,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -616,9 +595,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -633,9 +609,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -650,9 +623,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -667,9 +637,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -684,9 +651,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -701,9 +665,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/src/app.js b/src/app.js index d603b92..6e0738a 100644 --- a/src/app.js +++ b/src/app.js @@ -23,6 +23,262 @@ export function getLastBusbarGeometries() { return lastComputedGeometries; } +function getEdgeTabCenters(positions, cellRadius, spacing, layoutType) { + if (!Array.isArray(positions) || positions.length < 2) { + return { top: [], bottom: [] }; + } + + const rows = new Map(); + for (const [x, y] of positions) { + const key = y.toFixed(4); + if (!rows.has(key)) rows.set(key, []); + rows.get(key).push([x, y]); + } + + const rowKeys = Array.from(rows.keys()).sort((a, b) => Number(a) - Number(b)); + if (rowKeys.length === 0) return { top: [], bottom: [] }; + + const topRow = (rows.get(rowKeys[0]) || []).slice().sort((a, b) => a[0] - b[0]); + const bottomRow = (rows.get(rowKeys[rowKeys.length - 1]) || []).slice().sort((a, b) => a[0] - b[0]); + const topY = Math.min(...positions.map(([, y]) => y)) - cellRadius - spacing; + const bottomY = Math.max(...positions.map(([, y]) => y)) + cellRadius + spacing; + const minAllX = Math.min(...positions.map(([x]) => x)); + + const topMidpoints = topRow.slice(0, -1).map((cell, index) => ({ + key: `top_${index}`, + x: (cell[0] + topRow[index + 1][0]) / 2, + y: topY, + })); + const bottomMidpoints = bottomRow.slice(0, -1).map((cell, index) => ({ + key: `bottom_${index}`, + x: (cell[0] + bottomRow[index + 1][0]) / 2, + y: bottomY, + })); + + // Grid: no extra tab on either edge + if (layoutType === 'grid') { + return { top: topMidpoints, bottom: bottomMidpoints }; + } + + // Vertical honeycomb: column pitch = min X delta between any two cells + if (layoutType === 'vertical') { + const allXSorted = [...new Set(positions.map(([x]) => Math.round(x * 1000)))] + .sort((a, b) => a - b).map(v => v / 1000); + const colPitch = allXSorted.length >= 2 ? allXSorted[1] - allXSorted[0] : 0; + // Even-column rows start at minAllX; odd-column rows are offset by colPitch + const topIsEven = colPitch === 0 || (topRow[0][0] - minAllX) < colPitch / 2; + const bottomIsEven = colPitch === 0 || (bottomRow[0][0] - minAllX) < colPitch / 2; + return { + top: topIsEven + ? topMidpoints + : [{ key: 'top_extra_left', x: topRow[0][0] - colPitch / 2, y: topY }, + ...topMidpoints, + { key: 'top_extra_right', x: topRow[topRow.length - 1][0] + colPitch / 2, y: topY }], + bottom: bottomIsEven + ? bottomMidpoints + : [{ key: 'bottom_extra_left', x: bottomRow[0][0] - colPitch / 2, y: bottomY }, + ...bottomMidpoints, + { key: 'bottom_extra_right', x: bottomRow[bottomRow.length - 1][0] + colPitch / 2, y: bottomY }], + }; + } + + // Horizontal honeycomb: 1 extra tab on the side that has a gap to the wall + const topPitch = topRow.length >= 2 ? topRow[topRow.length - 1][0] - topRow[topRow.length - 2][0] : 0; + const bottomPitch = bottomRow.length >= 2 ? bottomRow[bottomRow.length - 1][0] - bottomRow[bottomRow.length - 2][0] : 0; + const topExtraRight = topRow.length < 2 || (topRow[0][0] - minAllX) < topPitch / 4; + const bottomExtraRight = bottomRow.length < 2 || (bottomRow[0][0] - minAllX) < bottomPitch / 4; + return { + top: topExtraRight + ? [...topMidpoints, { key: 'top_extra', x: topRow[topRow.length - 1][0] + topPitch / 2, y: topY }] + : [{ key: 'top_extra', x: topRow[0][0] - topPitch / 2, y: topY }, ...topMidpoints], + bottom: bottomExtraRight + ? [...bottomMidpoints, { key: 'bottom_extra', x: bottomRow[bottomRow.length - 1][0] + bottomPitch / 2, y: bottomY }] + : [{ key: 'bottom_extra', x: bottomRow[0][0] - bottomPitch / 2, y: bottomY }, ...bottomMidpoints], + }; +} + +function buildBusbarAnchorCandidates(geometry, positions) { + const candidates = []; + const seen = new Set(); + const addPoint = (x, y) => { + const key = `${x.toFixed(4)},${y.toFixed(4)}`; + if (seen.has(key)) return; + seen.add(key); + candidates.push([x, y]); + }; + + for (const index of geometry?.padIndices || []) { + const point = positions[index]; + if (!point) continue; + addPoint(point[0], point[1]); + } + + for (const edge of geometry?.edges || []) { + const stops = [positions[edge.from], ...(edge.waypoints || []), positions[edge.to]].filter(Boolean); + for (let i = 0; i < stops.length - 1; i++) { + const a = stops[i]; + const b = stops[i + 1]; + addPoint((a[0] + b[0]) / 2, (a[1] + b[1]) / 2); + } + } + + for (const pad of geometry?.extraPads || []) { + if (!Array.isArray(pad?.pos)) continue; + addPoint(pad.pos[0], pad.pos[1]); + } + + for (const segment of geometry?.extraSegments || []) { + if (!Array.isArray(segment?.from) || !Array.isArray(segment?.to)) continue; + addPoint(segment.from[0], segment.from[1]); + addPoint(segment.to[0], segment.to[1]); + addPoint((segment.from[0] + segment.to[0]) / 2, (segment.from[1] + segment.to[1]) / 2); + } + + return candidates; +} + +function attachEdgeTabsToNearestBusbars(busbars, geometries, positions, options) { + const { + enabled = false, + cellRadius, + spacing, + tabWidth, + tabOverlapSide, + overlapLength = 28, + layoutType = 'honeycomb', + } = options; + + if (enabled !== true) return; + if (tabOverlapSide !== 'top' && tabOverlapSide !== 'bottom') return; + + const tabCenters = getEdgeTabCenters(positions, cellRadius, spacing, layoutType)[tabOverlapSide]; + if (tabCenters.length === 0) return; + + const inwardDirection = tabOverlapSide === 'top' ? 1 : -1; + const maxHorizontalGap = Math.max((tabWidth || 0), cellRadius * 2 + spacing * 2); + const maxVerticalGap = overlapLength + cellRadius * 2 + spacing * 2; + const betweenBusbarThreshold = Math.max((tabWidth || 0) * 0.6, cellRadius * 0.75); + const connectorRadius = Math.max(0.05, ((tabWidth || 0) - 0.5) / 2); + + const selectedByBusbar = []; + + for (let i = 0; i < busbars.length; i++) { + const busbar = busbars[i]; + const geometry = geometries[i]; + if (!geometry || geometry.blocked) continue; + + const anchors = buildBusbarAnchorCandidates(geometry, positions); + if (anchors.length === 0) continue; + + const extremeY = tabOverlapSide === 'top' + ? Math.min(...anchors.map(([, y]) => y)) + : Math.max(...anchors.map(([, y]) => y)); + const edgeBand = Math.max(cellRadius * 1.1, spacing + cellRadius * 0.35); + const edgeAnchors = anchors.filter(([, y]) => ( + tabOverlapSide === 'top' ? y <= extremeY + edgeBand : y >= extremeY - edgeBand + )); + if (edgeAnchors.length === 0) continue; + + let best = null; + for (const tab of tabCenters) { + for (const anchor of edgeAnchors) { + const dx = Math.abs(anchor[0] - tab.x); + const dy = Math.abs(tab.y - anchor[1]); + if (dx > maxHorizontalGap || dy > maxVerticalGap) continue; + const score = dx * 3 + dy; + const candidate = { + busbarIndex: i, + geometry, + anchor, + score, + tabKey: tab.key, + tab, + deltaX: anchor[0] - tab.x, + }; + if (!best || candidate.score < best.score) { + best = candidate; + } + } + } + if (best) selectedByBusbar.push(best); + } + + const conflictsByTab = new Map(); + for (const candidate of selectedByBusbar) { + if (!conflictsByTab.has(candidate.tabKey)) conflictsByTab.set(candidate.tabKey, []); + conflictsByTab.get(candidate.tabKey).push(candidate); + } + + for (const best of selectedByBusbar) { + const sameTabCandidates = conflictsByTab.get(best.tabKey) || []; + const leftCandidate = sameTabCandidates.find((candidate) => candidate.deltaX < -betweenBusbarThreshold); + const rightCandidate = sameTabCandidates.find((candidate) => candidate.deltaX > betweenBusbarThreshold); + if (leftCandidate && rightCandidate) continue; + + const anchorPoint = best.anchor.slice(); + const edgePoint = [best.tab.x, best.tab.y]; + const innerPoint = [best.tab.x, best.tab.y + inwardDirection * overlapLength]; + + const busbar = busbars[best.busbarIndex]; + const overlapOutward = Number(busbar.overlapSize) > 0 ? Number(busbar.overlapSize) : 0; + const outerPoint = overlapOutward > 0 + ? [best.tab.x, best.tab.y - inwardDirection * overlapOutward] + : null; + + best.geometry.extraPads = Array.isArray(best.geometry.extraPads) + ? best.geometry.extraPads + : []; + best.geometry.extraPads.push({ + key: `bms_tab_anchor_${tabOverlapSide}_${best.tabKey}`, + pos: anchorPoint, + radius: connectorRadius, + }); + best.geometry.extraPads.push({ + key: `bms_tab_edge_${tabOverlapSide}_${best.tabKey}`, + pos: edgePoint, + radius: connectorRadius, + }); + best.geometry.extraPads.push({ + key: `bms_tab_inner_${tabOverlapSide}_${best.tabKey}`, + pos: innerPoint, + radius: connectorRadius, + }); + if (outerPoint) { + best.geometry.extraPads.push({ + key: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`, + pos: outerPoint, + radius: connectorRadius, + }); + } + best.geometry.extraSegments = Array.isArray(best.geometry.extraSegments) + ? best.geometry.extraSegments + : []; + best.geometry.extraSegments.push({ + from: anchorPoint, + to: innerPoint, + fromKey: `bms_tab_anchor_${tabOverlapSide}_${best.tabKey}`, + toKey: `bms_tab_inner_${tabOverlapSide}_${best.tabKey}`, + radius: connectorRadius, + }); + best.geometry.extraSegments.push({ + from: edgePoint, + to: innerPoint, + fromKey: `bms_tab_edge_${tabOverlapSide}_${best.tabKey}`, + toKey: `bms_tab_inner_${tabOverlapSide}_${best.tabKey}`, + radius: connectorRadius, + }); + if (outerPoint) { + best.geometry.extraSegments.push({ + from: edgePoint, + to: outerPoint, + fromKey: `bms_tab_edge_${tabOverlapSide}_${best.tabKey}`, + toKey: `bms_tab_outer_${tabOverlapSide}_${best.tabKey}`, + radius: connectorRadius, + }); + } + } +} + function drawBothCanvases(positions, cellSize, padRadius, spacing) { drawPreview(positions, cellSize); @@ -202,9 +458,37 @@ export function updatePreview(resetView = false) { const cellRadius = cellSize / 2; const busbarPadRadius = Math.max(cellRadius - ledgeWidth, 1.0); const busbarKeepoutRadius = 4.0; + const busbarCellCutoutEnabled = document.getElementById('busbarCellCutoutEnabled')?.checked === true; + const packBounds = { + left: Math.min(...positions.map(p => p[0])) - cellRadius - spacing, + right: Math.max(...positions.map(p => p[0])) + cellRadius + spacing, + bottom: Math.min(...positions.map(p => p[1])) - cellRadius - spacing, + top: Math.max(...positions.map(p => p[1])) + cellRadius + spacing, + }; lastComputedGeometries = busbarStore.list.map(bb => - computeBusbarGeometry(bb.cellIndices, positions, cellRadius, busbarPadRadius, spacing, busbarKeepoutRadius) + computeBusbarGeometry( + bb.cellIndices, + positions, + cellRadius, + busbarPadRadius, + spacing, + busbarKeepoutRadius, + packBounds, + bb.overlapEnabled !== false, + layoutType, + bb.overlapSize, + busbarCellCutoutEnabled, + ) ); + attachEdgeTabsToNearestBusbars(busbarStore.list, lastComputedGeometries, positions, { + enabled: document.getElementById('bmsHolesType')?.value === 'tabs', + cellRadius, + spacing, + tabWidth: parseFloat(document.getElementById('height')?.value) || 10.0, + tabOverlapSide: document.getElementById('tabOverlapSide')?.value || 'off', + overlapLength: parseFloat(document.getElementById('height')?.value) || 10.0, + layoutType, + }); lastBusbarDrawArgs = { positions, cellSize, padRadius: busbarPadRadius, spacing }; drawBothCanvases(positions, cellSize, busbarPadRadius, spacing); @@ -448,14 +732,15 @@ export async function generateLayout() { } } - const tabWidth = parseFloat(document.getElementById('tabWidth').value) || 4.0; - const tabDepth = parseFloat(document.getElementById('tabDepth').value) || 1.0; + const tabWidth = parseFloat(document.getElementById('height').value) || 10.0; + const tabLength = parseFloat(document.getElementById('tabLength')?.value) || 10.0; + const tabOverlapSide = document.getElementById('tabOverlapSide')?.value || 'off'; const config = { cellSize, spacing, height, terminalDiameter, terminalDepth, coverThickness, roundedCorners, bmsHoles, ledgeWidth, filletBms, circleHoleOffset, useTabs, useFullCircles, bmsHoleDiameter, - tabWidth, tabDepth, + tabWidth, tabLength, tabDepth: 1.0, tabOverlapSide, layoutType, }; const holderShape = create3DModel(positions, config); @@ -467,9 +752,37 @@ export async function generateLayout() { const busbarPadRadius = Math.max(cellRadius - ledgeWidth, 1.0); const busbarKeepoutRadius = terminalDiameter / 2; + const busbarCellCutoutEnabled = document.getElementById('busbarCellCutoutEnabled')?.checked === true; + const packBounds = { + left: Math.min(...positions.map(p => p[0])) - cellRadius - spacing, + right: Math.max(...positions.map(p => p[0])) + cellRadius + spacing, + bottom: Math.min(...positions.map(p => p[1])) - cellRadius - spacing, + top: Math.max(...positions.map(p => p[1])) + cellRadius + spacing, + }; const busbarGeometries = busbarStore.list.map(bb => - computeBusbarGeometry(bb.cellIndices, positions, cellRadius, busbarPadRadius, spacing, busbarKeepoutRadius) + computeBusbarGeometry( + bb.cellIndices, + positions, + cellRadius, + busbarPadRadius, + spacing, + busbarKeepoutRadius, + packBounds, + bb.overlapEnabled !== false, + layoutType, + bb.overlapSize, + busbarCellCutoutEnabled, + ) ); + attachEdgeTabsToNearestBusbars(busbarStore.list, busbarGeometries, positions, { + enabled: bmsHolesType === 'tabs', + cellRadius, + spacing, + tabWidth, + tabOverlapSide, + overlapLength: height, + layoutType, + }); for (let i = 0; i < busbarStore.list.length; i++) { const bb = busbarStore.list[i]; @@ -492,10 +805,18 @@ export async function generateLayout() { // signatures are congruent, so we only need to print one copy. Uses sorted // pairwise cell distances; pairwise distance sets are the same under // translation, rotation, and mirror. - const busbarSignature = (bb) => { + const busbarSignature = (bb, geom) => { const idxs = bb.cellIndices; + const cellCutoutEnabled = document.getElementById('busbarCellCutoutEnabled')?.checked === true; + const tabSegments = (Array.isArray(geom?.extraSegments) ? geom.extraSegments : []) + .filter((segment) => String(segment?.fromKey || '').startsWith('bms_tab_') || String(segment?.toKey || '').startsWith('bms_tab_')) + .map((segment) => `${segment.from[0].toFixed(3)},${segment.from[1].toFixed(3)}>${segment.to[0].toFixed(3)},${segment.to[1].toFixed(3)}`) + .sort() + .join(';'); if (idxs.length === 0) return null; - if (idxs.length === 1) return `single|${bb.thickness.toFixed(2)}`; + if (idxs.length === 1) { + return `single|${bb.thickness.toFixed(2)}|ov:${bb.overlapEnabled === true ? 1 : 0}|os:${Number(bb.overlapSize ?? 10).toFixed(2)}|cc:${cellCutoutEnabled ? 1 : 0}|tabs:${tabSegments}`; + } const pts = idxs.map(i => centeredPositions[i]).filter(Boolean); const dists = []; for (let a = 0; a < pts.length; a++) { @@ -504,7 +825,7 @@ export async function generateLayout() { } } dists.sort((x, y) => x - y); - return `${pts.length}|${bb.thickness.toFixed(2)}|${dists.map(d => d.toFixed(3)).join(',')}`; + return `${pts.length}|${bb.thickness.toFixed(2)}|ov:${bb.overlapEnabled === true ? 1 : 0}|os:${Number(bb.overlapSize ?? 10).toFixed(2)}|cc:${cellCutoutEnabled ? 1 : 0}|tabs:${tabSegments}|${dists.map(d => d.toFixed(3)).join(',')}`; }; // Export format for busbars: STEP solid or DXF flat pattern. The cellholder @@ -518,14 +839,14 @@ export async function generateLayout() { for (let i = 0; i < busbarStore.list.length; i++) { const bb = busbarStore.list[i]; if (bb.cellIndices.length === 0) continue; - const sig = busbarSignature(bb); + const sig = busbarSignature(bb, busbarGeometries[i]); if (sig && sigSeen.has(sig)) { sigSeen.get(sig).copies.push(bb.name); continue; } const entry = { bb, geom: busbarGeometries[i], copies: [bb.name], shape: null }; if (busbarFormat === 'step') { - entry.shape = build3DBusbar(busbarGeometries[i], centeredPositions, busbarPadRadius, height, bb.thickness); + entry.shape = build3DBusbar(centeredGeom(busbarGeometries[i], holderCenterX, holderCenterY), centeredPositions, busbarPadRadius, height, bb.thickness); if (!entry.shape) continue; } uniqueBusbars.push(entry); @@ -540,7 +861,7 @@ export async function generateLayout() { const { bb, geom, shape } = uniqueBusbars[i]; const base = `busbar_${safeName(bb.name)}`; if (busbarFormat === 'dxf') { - const content = buildBusbarDXF(geom, centeredPositions, busbarPadRadius); + const content = buildBusbarDXF(centeredGeom(geom, holderCenterX, holderCenterY), centeredPositions, busbarPadRadius); downloadDXF(content, `${base}.dxf`); } else { downloadSTEP(shape, `${base}.step`); @@ -570,6 +891,21 @@ export async function generateLayout() { // ── Per-busbar download helpers ──────────────────────────────────────────────── +// Return a copy of geom with extraPads, extraSegments, and cutouts shifted by (-cx, -cy). +function centeredGeom(geom, cx, cy) { + if (!geom) return geom; + const cutouts = Array.isArray(geom.cutouts) + ? geom.cutouts.map(c => ({ ...c, center: [c.center[0] - cx, c.center[1] - cy] })) + : geom.cutouts; + const extraPads = Array.isArray(geom.extraPads) + ? geom.extraPads.map(p => ({ ...p, pos: [p.pos[0] - cx, p.pos[1] - cy] })) + : geom.extraPads; + const extraSegments = Array.isArray(geom.extraSegments) + ? geom.extraSegments.map(s => ({ ...s, from: [s.from[0] - cx, s.from[1] - cy], to: [s.to[0] - cx, s.to[1] - cy] })) + : geom.extraSegments; + return { ...geom, cutouts, extraPads, extraSegments }; +} + function getBusbarExportContext() { if (!lastBusbarDrawArgs || lastComputedGeometries.length === 0) return null; const { positions, padRadius } = lastBusbarDrawArgs; @@ -579,7 +915,7 @@ function getBusbarExportContext() { const height = parseFloat(document.getElementById('height').value); const busbarFormat = document.getElementById('busbarFormat')?.value || 'step'; const safeName = (name) => (name || '').replace(/[^A-Za-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'busbar'; - return { centeredPositions, padRadius, height, busbarFormat, safeName }; + return { centeredPositions, centerX: cx, centerY: cy, padRadius, height, busbarFormat, safeName }; } export async function downloadSingleBusbar(busbarId) { @@ -609,10 +945,10 @@ export async function downloadSingleBusbar(busbarId) { await new Promise(r => setTimeout(r, 20)); try { if (ctx.busbarFormat === 'dxf') { - const content = buildBusbarDXF(geom, ctx.centeredPositions, ctx.padRadius); + const content = buildBusbarDXF(centeredGeom(geom, ctx.centerX, ctx.centerY), ctx.centeredPositions, ctx.padRadius); downloadDXF(content, `${base}.dxf`); } else { - const shape = build3DBusbar(geom, ctx.centeredPositions, ctx.padRadius, ctx.height, bb.thickness); + const shape = build3DBusbar(centeredGeom(geom, ctx.centerX, ctx.centerY), ctx.centeredPositions, ctx.padRadius, ctx.height, bb.thickness); if (!shape) { showStatus(`Failed to build 3D shape for ${bb.name}.`, 'error'); return; } downloadSTEP(shape, `${base}.step`); } @@ -648,10 +984,10 @@ export async function downloadAllBusbarsZip() { for (const { bb, geom } of eligible) { const base = `busbar_${ctx.safeName(bb.name)}`; if (ctx.busbarFormat === 'dxf') { - const content = buildBusbarDXF(geom, ctx.centeredPositions, ctx.padRadius); + const content = buildBusbarDXF(centeredGeom(geom, ctx.centerX, ctx.centerY), ctx.centeredPositions, ctx.padRadius); zip.file(`${base}.dxf`, content); } else { - const shape = build3DBusbar(geom, ctx.centeredPositions, ctx.padRadius, ctx.height, bb.thickness); + const shape = build3DBusbar(centeredGeom(geom, ctx.centerX, ctx.centerY), ctx.centeredPositions, ctx.padRadius, ctx.height, bb.thickness); if (!shape) continue; const bytes = buildSTEPBytes(shape, `_zip_${base}.step`); if (bytes) zip.file(`${base}.step`, bytes); diff --git a/src/busbar-geometry.js b/src/busbar-geometry.js index 30b78e3..3fa10a9 100644 --- a/src/busbar-geometry.js +++ b/src/busbar-geometry.js @@ -155,12 +155,238 @@ function smoothPolylinePoints(points, padRadius) { return out; } -export function computeBusbarGeometry(cellIndices, positions, cellRadius, padRadius, spacing, keepoutRadius) { +function inferHorizontalPitch(positions) { + const epsilon = 1e-3; + const rows = new Map(); + for (const pos of positions) { + const key = pos[1].toFixed(4); + if (!rows.has(key)) rows.set(key, []); + rows.get(key).push(pos[0]); + } + + let pitch = Infinity; + for (const rowXs of rows.values()) { + rowXs.sort((a, b) => a - b); + for (let index = 1; index < rowXs.length; index++) { + const delta = rowXs[index] - rowXs[index - 1]; + if (delta > epsilon) pitch = Math.min(pitch, delta); + } + } + + return Number.isFinite(pitch) ? pitch : 0; +} + +function computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius) { + if (cellIndices.length < 2) { + return { extraPads: [], extraSegments: [] }; + } + + const selected = cellIndices + .map((index) => ({ index, pos: positions[index] })) + .filter((entry) => Array.isArray(entry.pos) && entry.pos.length >= 2); + if (selected.length !== cellIndices.length) { + return { extraPads: [], extraSegments: [] }; + } + + const epsilon = 1e-3; + const horizontalPitch = inferHorizontalPitch(positions); + if (horizontalPitch <= epsilon) { + return { extraPads: [], extraSegments: [] }; + } + + const xTolerance = Math.max(0.5, horizontalPitch * 0.25); + const yTolerance = 1e-3; + const hasHorizontalNeighbor = (entry, direction) => positions.some((pos) => { + if (Math.abs(pos[1] - entry.pos[1]) > yTolerance) return false; + const deltaX = pos[0] - entry.pos[0]; + if (direction === 'left' && deltaX >= -epsilon) return false; + if (direction === 'right' && deltaX <= epsilon) return false; + return Math.abs(Math.abs(deltaX) - horizontalPitch) <= xTolerance; + }); + + const rows = new Map(); + for (const entry of selected) { + const key = entry.pos[1].toFixed(4); + if (!rows.has(key)) rows.set(key, []); + rows.get(key).push(entry); + } + + const leftBoundary = []; + const rightBoundary = []; + for (const rowEntries of rows.values()) { + rowEntries.sort((a, b) => a.pos[0] - b.pos[0]); + leftBoundary.push(rowEntries[0]); + rightBoundary.push(rowEntries[rowEntries.length - 1]); + } + + const leftExposed = leftBoundary.filter((entry) => !hasHorizontalNeighbor(entry, 'left')); + const rightExposed = rightBoundary.filter((entry) => !hasHorizontalNeighbor(entry, 'right')); + const extraPads = []; + const extraSegments = []; + const fillRadius = Math.max(0.2, padRadius * 0.55); + const outwardShift = Math.max(0.2, padRadius * 0.22); + + const addSideRoundovers = (entries, side) => { + const direction = side === 'left' ? -1 : 1; + const ordered = entries.slice().sort((a, b) => a.pos[1] - b.pos[1]); + for (let index = 0; index < ordered.length - 1; index++) { + const top = ordered[index]; + const bottom = ordered[index + 1]; + const fillKey = `boundary_round_${side}_${index}`; + const fillPos = [ + (top.pos[0] + bottom.pos[0]) / 2 + direction * outwardShift, + (top.pos[1] + bottom.pos[1]) / 2, + ]; + extraPads.push({ key: fillKey, pos: fillPos, radius: fillRadius }); + extraSegments.push({ from: fillPos, to: top.pos, fromKey: fillKey, toKey: `c${top.index}`, radius: fillRadius }); + extraSegments.push({ from: fillPos, to: bottom.pos, fromKey: fillKey, toKey: `c${bottom.index}`, radius: fillRadius }); + } + }; + + addSideRoundovers(leftExposed, 'left'); + addSideRoundovers(rightExposed, 'right'); + + return { extraPads, extraSegments }; +} + +function computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapLength) { + if (cellIndices.length < 2) { + return { extraPads: [], extraSegments: [] }; + } + + const selected = cellIndices + .map((index) => ({ index, pos: positions[index] })) + .filter((entry) => Array.isArray(entry.pos) && entry.pos.length >= 2); + if (selected.length !== cellIndices.length) { + return { extraPads: [], extraSegments: [] }; + } + + const epsilon = 1e-3; + const horizontalPitch = inferHorizontalPitch(positions); + if (horizontalPitch <= epsilon) { + return { extraPads: [], extraSegments: [] }; + } + + const xTolerance = Math.max(0.5, horizontalPitch * 0.25); + const yTolerance = 1e-3; + const hasHorizontalNeighbor = (entry, direction) => positions.some((pos) => { + if (Math.abs(pos[1] - entry.pos[1]) > yTolerance) return false; + const deltaX = pos[0] - entry.pos[0]; + if (direction === 'left' && deltaX >= -epsilon) return false; + if (direction === 'right' && deltaX <= epsilon) return false; + return Math.abs(Math.abs(deltaX) - horizontalPitch) <= xTolerance; + }); + + const rows = new Map(); + for (const entry of selected) { + const key = entry.pos[1].toFixed(4); + if (!rows.has(key)) rows.set(key, []); + rows.get(key).push(entry); + } + + const leftBoundary = []; + const rightBoundary = []; + for (const rowEntries of rows.values()) { + rowEntries.sort((a, b) => a.pos[0] - b.pos[0]); + leftBoundary.push(rowEntries[0]); + rightBoundary.push(rowEntries[rowEntries.length - 1]); + } + + const leftExposed = leftBoundary.filter((entry) => !hasHorizontalNeighbor(entry, 'left')); + const rightExposed = rightBoundary.filter((entry) => !hasHorizontalNeighbor(entry, 'right')); + + let chosenSide = null; + let boundaryEntries = []; + if (leftExposed.length > rightExposed.length && leftExposed.length > 0) { + chosenSide = 'left'; + boundaryEntries = leftExposed; + } else if (rightExposed.length > leftExposed.length && rightExposed.length > 0) { + chosenSide = 'right'; + boundaryEntries = rightExposed; + } + + if (!chosenSide || boundaryEntries.length === 0) { + return { extraPads: [], extraSegments: [] }; + } + + const extension = Number.isFinite(Number(overlapLength)) && Number(overlapLength) > 0 + ? Number(overlapLength) + : 10; + const direction = chosenSide === 'left' ? -1 : 1; + const straightHoneycombX = layoutType === 'honeycomb' + ? (direction < 0 + ? Math.min(...boundaryEntries.map((entry) => entry.pos[0])) - extension + : Math.max(...boundaryEntries.map((entry) => entry.pos[0])) + extension) + : null; + const extraPads = []; + const extraSegments = []; + const overlapPads = boundaryEntries + .slice() + .sort((a, b) => a.pos[1] - b.pos[1]) + .map((entry, index) => { + const key = `edge_overlap_${index}`; + const overlapPos = [ + straightHoneycombX ?? (entry.pos[0] + direction * extension), + entry.pos[1], + ]; + extraPads.push({ key, pos: overlapPos }); + extraSegments.push({ from: overlapPos, to: entry.pos, fromKey: key, toKey: `c${entry.index}` }); + return { key, pos: overlapPos }; + }); + + for (let index = 0; index < overlapPads.length - 1; index++) { + const topOverlapPad = overlapPads[index]; + const bottomOverlapPad = overlapPads[index + 1]; + const topCell = boundaryEntries[index]; + const bottomCell = boundaryEntries[index + 1]; + + extraSegments.push({ + from: topOverlapPad.pos, + to: bottomOverlapPad.pos, + fromKey: topOverlapPad.key, + toKey: bottomOverlapPad.key, + }); + + // Large overlap offsets open a trapezoid between two neighboring connector + // legs and the outer spine. Add one interior hub and connect it to the + // bay corners so the shared pad/capsule geometry stays fully filled. + const fillKey = `edge_overlap_fill_${index}`; + const fillPos = [ + (topOverlapPad.pos[0] + bottomOverlapPad.pos[0] + topCell.pos[0] + bottomCell.pos[0]) / 4, + (topOverlapPad.pos[1] + bottomOverlapPad.pos[1] + topCell.pos[1] + bottomCell.pos[1]) / 4, + ]; + extraPads.push({ key: fillKey, pos: fillPos }); + extraSegments.push({ from: fillPos, to: topOverlapPad.pos, fromKey: fillKey, toKey: topOverlapPad.key }); + extraSegments.push({ from: fillPos, to: bottomOverlapPad.pos, fromKey: fillKey, toKey: bottomOverlapPad.key }); + extraSegments.push({ from: fillPos, to: topCell.pos, fromKey: fillKey, toKey: `c${topCell.index}` }); + extraSegments.push({ from: fillPos, to: bottomCell.pos, fromKey: fillKey, toKey: `c${bottomCell.index}` }); + } + + return { extraPads, extraSegments }; +} + +function computeCellCutouts(cellIndices, positions, cellCutoutEnabled) { + if (cellCutoutEnabled !== true) return []; + + return cellIndices + .map((index) => positions[index]) + .filter((pos) => Array.isArray(pos) && pos.length >= 2) + .map((pos) => ({ center: pos.slice(), width: 5, height: 2 })); +} + +export function computeBusbarGeometry(cellIndices, positions, cellRadius, padRadius, spacing, keepoutRadius, packBounds = null, overlapEnabled = true, layoutType = 'grid', overlapSize = 10, cellCutoutEnabled = false) { if (cellIndices.length === 0) { - return { padIndices: [], edges: [], blocked: null }; + return { padIndices: [], edges: [], blocked: null, extraPads: [], extraSegments: [], cutouts: [] }; } if (cellIndices.length === 1) { - return { padIndices: cellIndices.slice(), edges: [], blocked: null }; + return { + padIndices: cellIndices.slice(), + edges: [], + blocked: null, + extraPads: [], + extraSegments: [], + cutouts: computeCellCutouts(cellIndices, positions, cellCutoutEnabled), + }; } const edgePairs = buildEdgePairs(cellIndices, positions, cellRadius, spacing); @@ -185,6 +411,9 @@ export function computeBusbarGeometry(cellIndices, positions, cellRadius, padRad padIndices: cellIndices.slice(), edges, blocked: { from: i, to: j, reason: 'no clear route between these cells' }, + extraPads: [], + extraSegments: [], + cutouts: [], }; } } @@ -200,5 +429,18 @@ export function computeBusbarGeometry(cellIndices, positions, cellRadius, padRad }; }); - return { padIndices: cellIndices.slice(), edges: roundedEdges, blocked: null }; + const overlapFeatures = overlapEnabled + ? computeEdgeOverlapFeatures(cellIndices, positions, layoutType, overlapSize) + : { extraPads: [], extraSegments: [] }; + const roundoverFeatures = computeBoundaryRoundoverFeatures(cellIndices, positions, padRadius); + const cutouts = computeCellCutouts(cellIndices, positions, cellCutoutEnabled); + + return { + padIndices: cellIndices.slice(), + edges: roundedEdges, + blocked: null, + extraPads: [...overlapFeatures.extraPads, ...roundoverFeatures.extraPads], + extraSegments: [...overlapFeatures.extraSegments, ...roundoverFeatures.extraSegments], + cutouts, + }; } diff --git a/src/busbar-model.js b/src/busbar-model.js index c378767..121456e 100644 --- a/src/busbar-model.js +++ b/src/busbar-model.js @@ -5,6 +5,9 @@ export function build3DBusbar(geometry, positions, padRadius, zBase, thickness) if (!oc || geometry.padIndices.length === 0) return null; const shapes = []; + const extraPads = Array.isArray(geometry.extraPads) ? geometry.extraPads : []; + const extraSegments = Array.isArray(geometry.extraSegments) ? geometry.extraSegments : []; + const cutouts = Array.isArray(geometry.cutouts) ? geometry.cutouts : []; for (const i of geometry.padIndices) { if (!positions[i]) continue; @@ -13,6 +16,13 @@ export function build3DBusbar(geometry, positions, padRadius, zBase, thickness) shapes.push(new oc.BRepPrimAPI_MakeCylinder(ax, padRadius, thickness).Shape()); } + for (const pad of extraPads) { + const [x, y] = pad.pos; + const radius = pad.radius ?? padRadius; + const ax = new oc.gp_Ax2(new oc.gp_Pnt(x, y, zBase), oc.gp.prototype.DZ()); + shapes.push(new oc.BRepPrimAPI_MakeCylinder(ax, radius, thickness).Shape()); + } + for (const edge of geometry.edges) { const pts = [positions[edge.from], ...edge.waypoints, positions[edge.to]]; @@ -48,10 +58,49 @@ export function build3DBusbar(geometry, positions, padRadius, zBase, thickness) } } + for (const segment of extraSegments) { + const [x1, y1] = segment.from; + const [x2, y2] = segment.to; + const segmentRadius = segment.radius ?? padRadius; + const dx = x2 - x1, dy = y2 - y1; + const len = Math.hypot(dx, dy); + if (len < 1e-6) continue; + const angle = Math.atan2(dy, dx); + + const box = new oc.BRepPrimAPI_MakeBox(len, 2 * segmentRadius, thickness).Shape(); + + const trans1 = new oc.gp_Trsf(); + trans1.SetTranslation(new oc.gp_Vec(0, -segmentRadius, 0)); + let shape = new oc.BRepBuilderAPI_Transform(box, trans1, false).Shape(); + + const rot = new oc.gp_Trsf(); + rot.SetRotation(new oc.gp_Ax1(new oc.gp_Pnt(0, 0, 0), oc.gp.prototype.DZ()), angle); + shape = new oc.BRepBuilderAPI_Transform(shape, rot, false).Shape(); + + const trans2 = new oc.gp_Trsf(); + trans2.SetTranslation(new oc.gp_Vec(x1, y1, zBase)); + shape = new oc.BRepBuilderAPI_Transform(shape, trans2, false).Shape(); + + shapes.push(shape); + } + if (shapes.length === 0) return null; let combined = shapes[0]; for (let i = 1; i < shapes.length; i++) { combined = new oc.BRepAlgoAPI_Fuse(combined, shapes[i]).Shape(); } + + for (const cutout of cutouts) { + const [cx, cy] = cutout.center; + const width = cutout.width; + const height = cutout.height; + const cutter = new oc.BRepPrimAPI_MakeBox(width, height, thickness).Shape(); + + const trans = new oc.gp_Trsf(); + trans.SetTranslation(new oc.gp_Vec(cx - width / 2, cy - height / 2, zBase)); + const placedCutter = new oc.BRepBuilderAPI_Transform(cutter, trans, false).Shape(); + combined = new oc.BRepAlgoAPI_Cut(combined, placedCutter).Shape(); + } + return combined; } diff --git a/src/busbar-preview.js b/src/busbar-preview.js index 7fe322f..8530d22 100644 --- a/src/busbar-preview.js +++ b/src/busbar-preview.js @@ -20,7 +20,10 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad const cellR = cellSize / 2; const toScreenX = (wx) => (wx - t.minX + cellR + spacing) * t.scale + t.offsetX; const toScreenY = (wy) => (wy - t.minY + cellR + spacing) * t.scale + t.offsetY; - const padRadiusScreen = padRadius * t.scale; + const padRadiusScreen = (radius) => (radius ?? padRadius) * t.scale; + const extraSegments = (geom) => Array.isArray(geom?.extraSegments) ? geom.extraSegments : []; + const extraPads = (geom) => Array.isArray(geom?.extraPads) ? geom.extraPads : []; + const cutouts = (geom) => Array.isArray(geom?.cutouts) ? geom.cutouts : []; ctx.save(); ctx.translate(canvasState.panX, canvasState.panY); @@ -81,7 +84,7 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad // 1. Capsule strokes between direct neighbours (lineCap:round = semicircle // at each end, so two meeting capsules form a full circle at every junction). off.strokeStyle = opaqueColor; - off.lineWidth = 2 * padRadiusScreen; + off.lineWidth = 2 * padRadiusScreen(); off.lineCap = 'round'; off.lineJoin = 'round'; for (const [a, b] of adjCaps) { @@ -91,22 +94,36 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad off.lineTo(toScreenX(pb[0]), toScreenY(pb[1])); off.stroke(); } + for (const segment of extraSegments(geom)) { + off.lineWidth = 2 * padRadiusScreen(segment.radius); + off.beginPath(); + off.moveTo(toScreenX(segment.from[0]), toScreenY(segment.from[1])); + off.lineTo(toScreenX(segment.to[0]), toScreenY(segment.to[1])); + off.stroke(); + } // 2. Circle fills at every cell pad. off.fillStyle = opaqueColor; off.beginPath(); for (const ci of cellIndices) { const p = positions[ci]; if (!p) continue; + const radius = padRadiusScreen(); const sx = toScreenX(p[0]), sy = toScreenY(p[1]); - off.moveTo(sx + padRadiusScreen, sy); - off.arc(sx, sy, padRadiusScreen, 0, Math.PI * 2); + off.moveTo(sx + radius, sy); + off.arc(sx, sy, radius, 0, Math.PI * 2); + } + for (const pad of extraPads(geom)) { + const radius = padRadiusScreen(pad.radius); + const sx = toScreenX(pad.pos[0]); + const sy = toScreenY(pad.pos[1]); + off.moveTo(sx + radius, sy); + off.arc(sx, sy, radius, 0, Math.PI * 2); } off.fill(); - // 3. Triangle fills for every mutually-adjacent triplet. - // The centre of three packed circles (the "interstice") lies outside all - // three circles AND all three capsules, so without this step those points - // remain unfilled and appear as sharp V-notches. + // 3. Fill the three-circle interstice without extending to the cell centers. + // Use curved joins through the centroid so boundary bays stay rounded + // instead of forming straight-sided inward peaks. off.fillStyle = opaqueColor; for (let a = 0; a < nCells; a++) { for (const b of triAdj[a]) { @@ -118,10 +135,43 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad const pb = positions[cellIndices[b]]; const pc = positions[cellIndices[c]]; if (!pa || !pb || !pc) continue; + + const centroid = [ + (pa[0] + pb[0] + pc[0]) / 3, + (pa[1] + pb[1] + pc[1]) / 3, + ]; + const tangentPoints = [pa, pb, pc].map((point) => { + const dx = point[0] - centroid[0]; + const dy = point[1] - centroid[1]; + const len = Math.hypot(dx, dy); + if (len < 1e-6 || len <= padRadius) return point; + const inset = padRadius / len; + return [ + point[0] - dx * inset, + point[1] - dy * inset, + ]; + }); + off.beginPath(); - off.moveTo(toScreenX(pa[0]), toScreenY(pa[1])); - off.lineTo(toScreenX(pb[0]), toScreenY(pb[1])); - off.lineTo(toScreenX(pc[0]), toScreenY(pc[1])); + off.moveTo(toScreenX(tangentPoints[0][0]), toScreenY(tangentPoints[0][1])); + off.quadraticCurveTo( + toScreenX(centroid[0]), + toScreenY(centroid[1]), + toScreenX(tangentPoints[1][0]), + toScreenY(tangentPoints[1][1]), + ); + off.quadraticCurveTo( + toScreenX(centroid[0]), + toScreenY(centroid[1]), + toScreenX(tangentPoints[2][0]), + toScreenY(tangentPoints[2][1]), + ); + off.quadraticCurveTo( + toScreenX(centroid[0]), + toScreenY(centroid[1]), + toScreenX(tangentPoints[0][0]), + toScreenY(tangentPoints[0][1]), + ); off.closePath(); off.fill(); } @@ -130,7 +180,7 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad // 4. Obstacle-avoidance detour waypoints (spanning-tree edges with bends). off.strokeStyle = opaqueColor; - off.lineWidth = 2 * padRadiusScreen; + off.lineWidth = 2 * padRadiusScreen(); off.lineCap = 'round'; for (const edge of geom.edges) { if (edge.waypoints.length === 0) continue; @@ -142,6 +192,17 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad off.stroke(); } + off.save(); + off.globalCompositeOperation = 'destination-out'; + for (const cutout of cutouts(geom)) { + const width = cutout.width * t.scale; + const height = cutout.height * t.scale; + const cx = toScreenX(cutout.center[0]); + const cy = toScreenY(cutout.center[1]); + off.fillRect(cx - width / 2, cy - height / 2, width, height); + } + off.restore(); + // Composite offscreen onto main canvas at fillAlpha (reset transform for pixel-exact drawImage) ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); @@ -158,7 +219,7 @@ export function drawBusbarsOverlay(busbars, geometries, positions, cellSize, pad const [x, y] = positions[i]; const sx = toScreenX(x), sy = toScreenY(y); ctx.beginPath(); - ctx.arc(sx, sy, padRadiusScreen, 0, Math.PI * 2); + ctx.arc(sx, sy, padRadiusScreen(), 0, Math.PI * 2); ctx.stroke(); } } diff --git a/src/busbar-ui.js b/src/busbar-ui.js index edbb27c..4c5bb8b 100644 --- a/src/busbar-ui.js +++ b/src/busbar-ui.js @@ -31,13 +31,22 @@ function buildBusbarRow(bb, blockedByBusbarId) { row.innerHTML = `
-
+
${bb.cellIndices.length} cell${bb.cellIndices.length === 1 ? '' : 's'} + + @@ -52,6 +61,16 @@ function buildBusbarRow(bb, blockedByBusbarId) { row.querySelector('.busbar-name').addEventListener('change', (e) => { busbarStore.rename(bb.id, e.target.value); }); + row.querySelector('.busbar-color').addEventListener('input', (e) => { + busbarStore.setColor(bb.id, e.target.value); + }); + row.querySelector('.busbar-overlap').addEventListener('change', (e) => { + busbarStore.setOverlapEnabled(bb.id, e.target.checked); + }); + row.querySelector('.busbar-overlap-size').addEventListener('change', (e) => { + const v = parseFloat(e.target.value); + if (v > 0) busbarStore.setOverlapSize(bb.id, v); + }); row.querySelector('.busbar-thickness').addEventListener('change', (e) => { const v = parseFloat(e.target.value); if (v > 0) busbarStore.setThickness(bb.id, v); diff --git a/src/busbars.js b/src/busbars.js index 2241773..ff565e2 100644 --- a/src/busbars.js +++ b/src/busbars.js @@ -39,6 +39,10 @@ export const busbarStore = { color: b.color, cellIndices: Array.isArray(b.cellIndices) ? [...b.cellIndices] : [], thickness: b.thickness, + overlapEnabled: b.overlapEnabled === true, + overlapSize: Number.isFinite(Number(b.overlapSize)) && Number(b.overlapSize) > 0 + ? Number(b.overlapSize) + : 10, face: b.face === 'bottom' ? 'bottom' : 'top', })), }; @@ -58,6 +62,10 @@ export const busbarStore = { thickness: Number.isFinite(Number(b.thickness)) && Number(b.thickness) > 0 ? Number(b.thickness) : 1.0, + overlapEnabled: b.overlapEnabled === true, + overlapSize: Number.isFinite(Number(b.overlapSize)) && Number(b.overlapSize) > 0 + ? Number(b.overlapSize) + : 10, face: b.face === 'bottom' ? 'bottom' : 'top', })); @@ -92,6 +100,8 @@ export const busbarStore = { color, cellIndices: [], thickness: 1.0, + overlapEnabled: false, + overlapSize: 10, face: face === 'bottom' ? 'bottom' : 'top', }; paletteIdx++; @@ -138,6 +148,24 @@ export const busbarStore = { } }, + setOverlapEnabled(id, overlapEnabled) { + const b = this.list.find(b => b.id === id); + if (b) { + b.overlapEnabled = overlapEnabled === true; + this._emitMutation('setOverlapEnabled'); + this._notify(); + } + }, + + setOverlapSize(id, overlapSize) { + const b = this.list.find(b => b.id === id); + if (b && Number.isFinite(Number(overlapSize)) && Number(overlapSize) > 0) { + b.overlapSize = Number(overlapSize); + this._emitMutation('setOverlapSize'); + this._notify(); + } + }, + setFace(id, face) { const b = this.list.find(b => b.id === id); if (b) { diff --git a/src/dxf-export.js b/src/dxf-export.js index 02b000e..0c328d3 100644 --- a/src/dxf-export.js +++ b/src/dxf-export.js @@ -148,21 +148,30 @@ function angleCovered(theta, dirs) { export function buildBusbarDXF(geometry, positions, padRadius) { const layer = 'busbar'; const tokens = [...dxfHeader(), ...dxfTables(), '0', 'SECTION', '2', 'ENTITIES']; + const extraPads = Array.isArray(geometry.extraPads) ? geometry.extraPads : []; + const extraSegments = Array.isArray(geometry.extraSegments) ? geometry.extraSegments : []; + const cutouts = Array.isArray(geometry.cutouts) ? geometry.cutouts : []; // Enumerate pads (cells + waypoints) and capsule segments with shared keys so we // can tell which shapes are "self" vs "other" when clipping. const pads = new Map(); - const caps = []; // { a, b, padKeyA, padKeyB } + const caps = []; // { a, b, padKeyA, padKeyB, radius } - const ensurePad = (key, pos) => { - if (!pads.has(key)) pads.set(key, { pos, dirs: [] }); + const ensurePad = (key, pos, radius = padRadius) => { + if (!pads.has(key)) pads.set(key, { pos, dirs: [], radius }); + else pads.get(key).radius = Math.max(pads.get(key).radius, radius); return pads.get(key); }; for (const idx of geometry.padIndices) { const p = positions[idx]; if (!p) continue; - ensurePad(`c${idx}`, p); + ensurePad(`c${idx}`, p, padRadius); + } + + for (const pad of extraPads) { + if (!pad?.key || !Array.isArray(pad.pos)) continue; + ensurePad(pad.key, pad.pos, pad.radius ?? padRadius); } geometry.edges.forEach((edge, ei) => { @@ -174,7 +183,7 @@ export function buildBusbarDXF(geometry, positions, padRadius) { ...edge.waypoints.map((wp, wi) => ({ key: `w${ei}_${wi}`, pos: wp })), { key: `c${edge.to}`, pos: to }, ]; - for (let i = 1; i < stops.length - 1; i++) ensurePad(stops[i].key, stops[i].pos); + for (let i = 1; i < stops.length - 1; i++) ensurePad(stops[i].key, stops[i].pos, padRadius); for (let i = 0; i < stops.length - 1; i++) { const a = stops[i], b = stops[i + 1]; const dx = b.pos[0] - a.pos[0]; @@ -182,19 +191,39 @@ export function buildBusbarDXF(geometry, positions, padRadius) { const len = Math.hypot(dx, dy); if (len < EPS) continue; const ang = Math.atan2(dy, dx); - ensurePad(a.key, a.pos).dirs.push(ang); - ensurePad(b.key, b.pos).dirs.push(ang + Math.PI); - caps.push({ a: a.pos.slice(), b: b.pos.slice(), padKeyA: a.key, padKeyB: b.key }); + ensurePad(a.key, a.pos, padRadius).dirs.push(ang); + ensurePad(b.key, b.pos, padRadius).dirs.push(ang + Math.PI); + caps.push({ a: a.pos.slice(), b: b.pos.slice(), padKeyA: a.key, padKeyB: b.key, radius: padRadius }); } }); + extraSegments.forEach((segment, index) => { + if (!Array.isArray(segment?.from) || !Array.isArray(segment?.to)) return; + const fromKey = segment.fromKey || `extra_from_${index}`; + const toKey = segment.toKey || `extra_to_${index}`; + const radius = segment.radius ?? padRadius; + ensurePad(fromKey, segment.from, radius); + ensurePad(toKey, segment.to, radius); + + const dx = segment.to[0] - segment.from[0]; + const dy = segment.to[1] - segment.from[1]; + const len = Math.hypot(dx, dy); + if (len < EPS) return; + + const ang = Math.atan2(dy, dx); + ensurePad(fromKey, segment.from, radius).dirs.push(ang); + ensurePad(toKey, segment.to, radius).dirs.push(ang + Math.PI); + caps.push({ a: segment.from.slice(), b: segment.to.slice(), padKeyA: fromKey, padKeyB: toKey, radius }); + }); + // Pad arcs: parts of each circle not covered by any connected capsule half-disc // AND not interior to any other capsule rectangle or pad circle. const padList = Array.from(pads.entries()).map(([key, data]) => ({ key, ...data })); for (const pad of padList) { const [cx, cy] = pad.pos; + const localRadius = pad.radius ?? padRadius; if (pad.dirs.length === 0) { - tokens.push(...circleEntity(cx, cy, padRadius, layer)); + tokens.push(...circleEntity(cx, cy, localRadius, layer)); continue; } const tangents = []; @@ -217,21 +246,22 @@ export function buildBusbarDXF(geometry, positions, padRadius) { // Also require the arc's midpoint to be outside all other shapes so it's // genuinely on the outline — an arc that enters a neighbour's capsule is // interior and must be dropped. - const mx = cx + padRadius * Math.cos(mid); - const my = cy + padRadius * Math.sin(mid); + const mx = cx + localRadius * Math.cos(mid); + const my = cy + localRadius * Math.sin(mid); let buried = false; for (const cap of caps) { if (cap.padKeyA === pad.key || cap.padKeyB === pad.key) continue; - if (segInsideCapsule([mx, my], [mx, my], cap.a, cap.b, padRadius).length) { buried = true; break; } + if (segInsideCapsule([mx, my], [mx, my], cap.a, cap.b, cap.radius ?? padRadius).length) { buried = true; break; } } if (!buried) { for (const other of padList) { if (other.key === pad.key) continue; const dx = mx - other.pos[0], dy = my - other.pos[1]; - if (dx * dx + dy * dy < padRadius * padRadius - EPS) { buried = true; break; } + const otherRadius = other.radius ?? padRadius; + if (dx * dx + dy * dy < otherRadius * otherRadius - EPS) { buried = true; break; } } } - if (!buried) tokens.push(...arcEntity(cx, cy, padRadius, t1, t2, layer)); + if (!buried) tokens.push(...arcEntity(cx, cy, localRadius, t1, t2, layer)); } } @@ -244,8 +274,9 @@ export function buildBusbarDXF(geometry, positions, padRadius) { const dy = cap.b[1] - cap.a[1]; const len = Math.hypot(dx, dy); if (len < EPS) continue; - const nx = -dy / len * padRadius; - const ny = dx / len * padRadius; + const localRadius = cap.radius ?? padRadius; + const nx = -dy / len * localRadius; + const ny = dx / len * localRadius; const sides = [ { p: [cap.a[0] + nx, cap.a[1] + ny], q: [cap.b[0] + nx, cap.b[1] + ny] }, { p: [cap.a[0] - nx, cap.a[1] - ny], q: [cap.b[0] - nx, cap.b[1] - ny] }, @@ -254,11 +285,11 @@ export function buildBusbarDXF(geometry, positions, padRadius) { const insides = []; for (let j = 0; j < caps.length; j++) { if (j === ci) continue; - insides.push(...segInsideCapsule(side.p, side.q, caps[j].a, caps[j].b, padRadius)); + insides.push(...segInsideCapsule(side.p, side.q, caps[j].a, caps[j].b, caps[j].radius ?? padRadius)); } for (const pad of padList) { if (pad.key === cap.padKeyA || pad.key === cap.padKeyB) continue; - insides.push(...segInsideCircle(side.p, side.q, pad.pos, padRadius)); + insides.push(...segInsideCircle(side.p, side.q, pad.pos, pad.radius ?? padRadius)); } const outside = subtractIntervals(insides); for (const [t0, t1] of outside) { @@ -272,6 +303,20 @@ export function buildBusbarDXF(geometry, positions, padRadius) { } } + for (const cutout of cutouts) { + const [cx, cy] = cutout.center; + const halfWidth = cutout.width / 2; + const halfHeight = cutout.height / 2; + const x1 = cx - halfWidth; + const x2 = cx + halfWidth; + const y1 = cy - halfHeight; + const y2 = cy + halfHeight; + tokens.push(...lineEntity(x1, y1, x2, y1, layer)); + tokens.push(...lineEntity(x2, y1, x2, y2, layer)); + tokens.push(...lineEntity(x2, y2, x1, y2, layer)); + tokens.push(...lineEntity(x1, y2, x1, y1, layer)); + } + tokens.push('0', 'ENDSEC', '0', 'EOF'); return tokens.join('\n') + '\n'; } diff --git a/src/main.js b/src/main.js index a3c23ab..e2e5898 100644 --- a/src/main.js +++ b/src/main.js @@ -144,9 +144,11 @@ function applyConfigToUi(config) { setSelectInput('bmsHolesType', config.bms.type); setNumberInput('bmsHoleDiameter', config.bms.holeDiameter); setNumberInput('tabWidth', config.bms.tabWidth); - setNumberInput('tabDepth', config.bms.tabDepth); + setNumberInput('tabLength', config.bms.tabLength ?? 10.0); + setSelectInput('tabOverlapSide', config.bms.tabOverlapSide || 'off'); setSelectInput('busbarFormat', config.busbars.format); + setCheckboxInput('busbarCellCutoutEnabled', config.busbars.cellCutoutEnabled === true); setPackMode(config.pack.mode, { clearBusbars: false, refresh: false }); toggleBmsDiameter(); @@ -222,8 +224,8 @@ function wireUrlSyncListeners() { const ids = [ 'series', 'parallel', 'xDim', 'yDim', 'height', 'cellSize', 'layoutType', 'spacing', 'coverThickness', 'ledgeWidth', - 'roundedCorners', 'bmsHolesType', 'bmsHoleDiameter', 'tabWidth', 'tabDepth', - 'busbarFormat', + 'roundedCorners', 'bmsHolesType', 'bmsHoleDiameter', 'tabWidth', + 'tabOverlapSide', 'busbarFormat', 'busbarCellCutoutEnabled', ]; ids.forEach((id) => { @@ -338,7 +340,7 @@ function wireInputs() { element.addEventListener('change', handler); }); - const visualInputs = ['bmsHolesType', 'roundedCorners', 'bmsHoleDiameter', 'ledgeWidth', 'tabWidth', 'tabDepth']; + const visualInputs = ['bmsHolesType', 'roundedCorners', 'bmsHoleDiameter', 'ledgeWidth', 'tabWidth', 'tabLength', 'tabOverlapSide', 'busbarCellCutoutEnabled']; visualInputs.forEach(id => { const element = document.getElementById(id); if (!element) return; diff --git a/src/model.js b/src/model.js index 4f9e8a3..0ff9bf2 100644 --- a/src/model.js +++ b/src/model.js @@ -17,6 +17,8 @@ export function create3DModel(positions, config) { useTabs, useFullCircles, filletBms, + tabOverlapSide, + layoutType = 'honeycomb', } = config; const r = cellSize / 2; @@ -212,16 +214,51 @@ export function create3DModel(positions, config) { const topRow = rows[topYKey].sort((a, b) => a[0] - b[0]); const bottomRow = rows[bottomYKey].sort((a, b) => a[0] - b[0]); + const minAllX = Math.min(...positions.map(([x]) => x)); + + // Vertical column pitch: minimum X delta between any two cells + const _allXSorted = [...new Set(positions.map(([x]) => Math.round(x * 1000)))] + .sort((a, b) => a - b).map(v => v / 1000); + const vertColPitch = _allXSorted.length >= 2 ? _allXSorted[1] - _allXSorted[0] : 0; + + const topPitch = topRow.length >= 2 ? topRow[topRow.length - 1][0] - topRow[topRow.length - 2][0] : 0; const topHoles = []; for (let i = 0; i < topRow.length - 1; i++) { - const xMid = (topRow[i][0] + topRow[i + 1][0]) / 2; - topHoles.push([xMid, holeYTop]); + topHoles.push([(topRow[i][0] + topRow[i + 1][0]) / 2, holeYTop]); + } + if (layoutType === 'vertical') { + const topIsEven = vertColPitch === 0 || (topRow[0][0] - minAllX) < vertColPitch / 2; + if (!topIsEven) { + topHoles.unshift([topRow[0][0] - vertColPitch / 2, holeYTop]); + topHoles.push([topRow[topRow.length - 1][0] + vertColPitch / 2, holeYTop]); + } + } else if (layoutType !== 'grid') { + const topExtraRight = topRow.length >= 2 && (topRow[0][0] - minAllX) < topPitch / 4; + if (topExtraRight) { + topHoles.push([topRow[topRow.length - 1][0] + topPitch / 2, holeYTop]); + } else { + topHoles.unshift([topRow[0][0] - topPitch / 2, holeYTop]); + } } + const bottomPitch = bottomRow.length >= 2 ? bottomRow[bottomRow.length - 1][0] - bottomRow[bottomRow.length - 2][0] : 0; const bottomHoles = []; for (let i = 0; i < bottomRow.length - 1; i++) { - const xMid = (bottomRow[i][0] + bottomRow[i + 1][0]) / 2; - bottomHoles.push([xMid, holeYBottom]); + bottomHoles.push([(bottomRow[i][0] + bottomRow[i + 1][0]) / 2, holeYBottom]); + } + if (layoutType === 'vertical') { + const bottomIsEven = vertColPitch === 0 || (bottomRow[0][0] - minAllX) < vertColPitch / 2; + if (!bottomIsEven) { + bottomHoles.unshift([bottomRow[0][0] - vertColPitch / 2, holeYBottom]); + bottomHoles.push([bottomRow[bottomRow.length - 1][0] + vertColPitch / 2, holeYBottom]); + } + } else if (layoutType !== 'grid') { + const bottomExtraRight = bottomRow.length >= 2 && (bottomRow[0][0] - minAllX) < bottomPitch / 4; + if (bottomExtraRight) { + bottomHoles.push([bottomRow[bottomRow.length - 1][0] + bottomPitch / 2, holeYBottom]); + } else { + bottomHoles.unshift([bottomRow[0][0] - bottomPitch / 2, holeYBottom]); + } } const allBmsHoles = [...topHoles, ...bottomHoles]; @@ -229,25 +266,28 @@ export function create3DModel(positions, config) { if (useTabs) { const slotWidth = config.tabWidth || holeDiameter; const slotInset = config.tabDepth || 1.0; + const slotHeight = Number.isFinite(config.tabLength) && config.tabLength > 0 + ? Math.min(config.tabLength, height) + : height; const topEdgeY = length / 2; const bottomEdgeY = -length / 2; const allSlots = []; topHoles.forEach(([xPos]) => { - const slotBox = new oc.BRepPrimAPI_MakeBox(slotWidth, slotInset, height); + const slotBox = new oc.BRepPrimAPI_MakeBox(slotWidth, slotInset, slotHeight); const slot = slotBox.Shape(); const trans = new oc.gp_Trsf(); - trans.SetTranslation(new oc.gp_Vec(xPos - slotWidth / 2, topEdgeY - slotInset, 0)); + trans.SetTranslation(new oc.gp_Vec(xPos - slotWidth / 2, topEdgeY - slotInset, height - slotHeight)); const slotTransform = new oc.BRepBuilderAPI_Transform(slot, trans, false); allSlots.push(slotTransform.Shape()); }); bottomHoles.forEach(([xPos]) => { - const slotBox = new oc.BRepPrimAPI_MakeBox(slotWidth, slotInset, height); + const slotBox = new oc.BRepPrimAPI_MakeBox(slotWidth, slotInset, slotHeight); const slot = slotBox.Shape(); const trans = new oc.gp_Trsf(); - trans.SetTranslation(new oc.gp_Vec(xPos - slotWidth / 2, bottomEdgeY, 0)); + trans.SetTranslation(new oc.gp_Vec(xPos - slotWidth / 2, bottomEdgeY, height - slotHeight)); const slotTransform = new oc.BRepBuilderAPI_Transform(slot, trans, false); allSlots.push(slotTransform.Shape()); }); diff --git a/src/preview.js b/src/preview.js index 8d66d95..abab840 100644 --- a/src/preview.js +++ b/src/preview.js @@ -75,6 +75,7 @@ export function drawPreview(positions, cellSize) { const useTabs = bmsHolesType === 'tabs'; const useFullCircles = bmsHolesType === 'fullcircles'; const circleHoleOffset = false; + const layoutType = document.getElementById('layoutType').value; const roundedCorners = document.getElementById('roundedCorners').checked; const bmsHoleDiameter = parseFloat(document.getElementById('bmsHoleDiameter').value) || 4.0; const ledgeWidth = parseFloat(document.getElementById('ledgeWidth').value) || 0; @@ -238,11 +239,18 @@ export function drawPreview(positions, cellSize) { const holeTopRowKey = useFullCircles ? visualTopRowKey : topYKey; const holeBottomRowKey = useFullCircles ? visualBottomRowKey : bottomYKey; - for (let i = 0; i < rows[holeTopRowKey].length - 1; i++) { - const x = (rows[holeTopRowKey][i][0] + rows[holeTopRowKey][i + 1][0]) / 2; - const cellY = rows[holeTopRowKey][i][1]; - const x1 = rows[holeTopRowKey][i][0]; - const x2 = rows[holeTopRowKey][i + 1][0]; + // Vertical column pitch: minimum X delta between any two cells + const _allXSorted = [...new Set(positions.map(([x]) => Math.round(x * 1000)))] + .sort((a, b) => a - b).map(v => v / 1000); + const vertColPitch = _allXSorted.length >= 2 ? _allXSorted[1] - _allXSorted[0] : 0; + + const topTabRow = rows[holeTopRowKey]; + const topTabPitch = topTabRow.length >= 2 ? topTabRow[topTabRow.length - 1][0] - topTabRow[topTabRow.length - 2][0] : 0; + for (let i = 0; i < topTabRow.length - 1; i++) { + const x = (topTabRow[i][0] + topTabRow[i + 1][0]) / 2; + const cellY = topTabRow[i][1]; + const x1 = topTabRow[i][0]; + const x2 = topTabRow[i + 1][0]; const wallY = packMinY; const y = topEdge; const flip = cellY < wallY ? -1 : 1; @@ -261,12 +269,42 @@ export function drawPreview(positions, cellSize) { const debugTri = useFullCircles ? { apex: { x, y: wallY }, left, right } : null; bmsHolePositions.push({ x, y, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri }); } + if (layoutType === 'vertical') { + if (!useFullCircles) { + const topIsEven = vertColPitch === 0 || (topTabRow[0][0] - minX) < vertColPitch / 2; + if (!topIsEven) { + const topExtY = topEdge; + // Left corner: continue interior hole pattern (tabPitch/2 = one colPitch to the left) + bmsHolePositions.push({ x: topTabRow[0][0] - topTabPitch / 2, y: topExtY, diameter: bmsHoleDiameter, isTab: false, isFull: false, debugTri: null }); + // Right corner only if an even-col neighbor exists to the right + const topRightNeighX = topTabRow[topTabRow.length - 1][0] + vertColPitch; + if (_allXSorted.some(x => Math.abs(x - topRightNeighX) < 0.5)) { + bmsHolePositions.push({ x: topTabRow[topTabRow.length - 1][0] + topTabPitch / 2, y: topExtY, diameter: bmsHoleDiameter, isTab: false, isFull: false, debugTri: null }); + } + } + } else { + // Full circles: top-right corner when an odd col exists beyond the last even col (even S) + const topRightNeighX = topTabRow[topTabRow.length - 1][0] + vertColPitch; + if (_allXSorted.some(x => Math.abs(x - topRightNeighX) < 0.5)) { + // Continue the interior hole pattern: offset by topTabPitch/2 (= one colPitch) + bmsHolePositions.push({ x: topTabRow[topTabRow.length - 1][0] + topTabPitch / 2, y: topEdge, diameter: bmsHoleDiameter, isTab: false, isFull: true, debugTri: null }); + } + } + } else if (layoutType !== 'grid') { + const topTabExtraRight = topTabRow.length < 2 || (topTabRow[0][0] - minX) < topTabPitch / 4; + bmsHolePositions.push(topTabExtraRight + ? { x: topTabRow[topTabRow.length - 1][0] + topTabPitch / 2, y: topEdge, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null } + : { x: topTabRow[0][0] - topTabPitch / 2, y: topEdge, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null }); + } + + const botTabRow = rows[holeBottomRowKey]; + const botTabPitch = botTabRow.length >= 2 ? botTabRow[botTabRow.length - 1][0] - botTabRow[botTabRow.length - 2][0] : 0; + for (let i = 0; i < botTabRow.length - 1; i++) { + const x = (botTabRow[i][0] + botTabRow[i + 1][0]) / 2; + const cellY = botTabRow[i][1]; + const x1 = botTabRow[i][0]; + const x2 = botTabRow[i + 1][0]; - for (let i = 0; i < rows[holeBottomRowKey].length - 1; i++) { - const x = (rows[holeBottomRowKey][i][0] + rows[holeBottomRowKey][i + 1][0]) / 2; - const cellY = rows[holeBottomRowKey][i][1]; - const x1 = rows[holeBottomRowKey][i][0]; - const x2 = rows[holeBottomRowKey][i + 1][0]; const wallY = packMaxY; const y = bottomEdge; const flip = cellY < wallY ? -1 : 1; @@ -285,6 +323,31 @@ export function drawPreview(positions, cellSize) { const debugTri = useFullCircles ? { apex: { x, y: wallY }, left, right } : null; bmsHolePositions.push({ x, y, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri }); } + if (layoutType === 'vertical') { + const botIsEven = vertColPitch === 0 || (botTabRow[0][0] - minX) < vertColPitch / 2; + if (!botIsEven) { + // Use bottomEdge for all types so corner aligns with interior bottom holes + const botExtY = bottomEdge; + // Left corner: continue interior hole pattern (botTabPitch/2 = one colPitch to the left) + bmsHolePositions.push({ x: botTabRow[0][0] - botTabPitch / 2, y: botExtY, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null }); + // Right corner only if an even-col neighbor exists to the right (odd S = yes, even S = no) + const botRightNeighX = botTabRow[botTabRow.length - 1][0] + vertColPitch; + if (_allXSorted.some(x => Math.abs(x - botRightNeighX) < 0.5)) { + bmsHolePositions.push({ x: botTabRow[botTabRow.length - 1][0] + botTabPitch / 2, y: botExtY, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null }); + } + } else if (vertColPitch > 0 && botTabRow.length >= 2) { + // Even-col row (visual top for non-FC): add right corner when an odd col extends beyond the last even col + const botRightNeighX = botTabRow[botTabRow.length - 1][0] + vertColPitch; + if (_allXSorted.some(x => Math.abs(x - botRightNeighX) < 0.5)) { + bmsHolePositions.push({ x: botTabRow[botTabRow.length - 1][0] + botTabPitch / 2, y: bottomEdge, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null }); + } + } + } else if (layoutType !== 'grid') { + const botTabExtraRight = botTabRow.length < 2 || (botTabRow[0][0] - minX) < botTabPitch / 4; + bmsHolePositions.push(botTabExtraRight + ? { x: botTabRow[botTabRow.length - 1][0] + botTabPitch / 2, y: bottomEdge, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null } + : { x: botTabRow[0][0] - botTabPitch / 2, y: bottomEdge, diameter: bmsHoleDiameter, isTab: false, isFull: useFullCircles, debugTri: null }); + } } ctx.fillStyle = '#1e293b'; @@ -321,20 +384,19 @@ export function drawPreview(positions, cellSize) { ctx.strokeStyle = 'rgba(255, 193, 7, 0.9)'; ctx.lineWidth = 1.5 / zoom; - const tabWidthMm = parseFloat(document.getElementById('tabWidth').value) || 4.0; - const tabDepthMm = parseFloat(document.getElementById('tabDepth').value) || 1.0; + const tabWidthMm = parseFloat(document.getElementById('height').value) || 10.0; const tabWidth = tabWidthMm * scale; - const tabHeight = tabDepthMm * scale; + const tabHeight = 1.0 * scale; for (const hole of bmsHolePositions) { const cx = (hole.x - minX + r + spacing) * scale + offsetX; - if (hole.y > maxY) { - ctx.fillRect(cx - tabWidth / 2, offsetY + packHeight * scale - tabHeight, tabWidth, tabHeight); - ctx.strokeRect(cx - tabWidth / 2, offsetY + packHeight * scale - tabHeight, tabWidth, tabHeight); - } else { + if (hole.y < minY) { ctx.fillRect(cx - tabWidth / 2, offsetY, tabWidth, tabHeight); ctx.strokeRect(cx - tabWidth / 2, offsetY, tabWidth, tabHeight); + } else { + ctx.fillRect(cx - tabWidth / 2, offsetY + packHeight * scale - tabHeight, tabWidth, tabHeight); + ctx.strokeRect(cx - tabWidth / 2, offsetY + packHeight * scale - tabHeight, tabWidth, tabHeight); } } } else if (useFullCircles) { diff --git a/src/ui.js b/src/ui.js index f30d5b9..f67e213 100644 --- a/src/ui.js +++ b/src/ui.js @@ -33,9 +33,11 @@ export function toggleBmsDiameter() { const bmsType = document.getElementById('bmsHolesType').value; const diameterGroup = document.getElementById('bmsHoleDiameterGroup'); const tabDimsGroup = document.getElementById('tabDimensionsGroup'); + const tabOverlapSideGroup = document.getElementById('tabOverlapSideGroup'); diameterGroup.style.display = (bmsType === 'halfcircles' || bmsType === 'fullcircles') ? 'block' : 'none'; tabDimsGroup.style.display = (bmsType === 'tabs') ? 'grid' : 'none'; + if (tabOverlapSideGroup) tabOverlapSideGroup.style.display = (bmsType === 'tabs') ? 'block' : 'none'; } export function initCustomSelects() { diff --git a/src/url-config.js b/src/url-config.js index d727695..848999f 100644 --- a/src/url-config.js +++ b/src/url-config.js @@ -3,6 +3,13 @@ const HASH_PREFIX = '#config='; const ALLOWED_LAYOUTS = new Set(['grid', 'honeycomb', 'vertical']); const ALLOWED_BMS_TYPES = new Set(['off', 'halfcircles', 'fullcircles', 'tabs']); +const ALLOWED_TAB_OVERLAP_SIDES = new Set(['off', 'top', 'bottom', 'left', 'right']); + +function normalizeTabOverlapSide(value) { + if (value === 'top' || value === 'bottom' || value === 'off') return value; + if (value === 'left' || value === 'right') return 'off'; + throw new Error('Invalid tab overlap side'); +} const ALLOWED_PACK_MODES = new Set(['sp', 'mm']); const ALLOWED_BUSBAR_FORMATS = new Set(['step', 'dxf']); @@ -58,8 +65,12 @@ function normalizeBusbar(raw, index) { }); const face = raw.face === 'bottom' ? 'bottom' : 'top'; - - return { id, name, color, thickness, cellIndices, face }; + const overlapEnabled = raw.overlapEnabled == null ? false : requireBoolean(raw.overlapEnabled, `busbars.list[${index}].overlapEnabled`); + const overlapSize = raw.overlapSize == null + ? 10 + : requireFiniteNumber(Number(raw.overlapSize), `busbars.list[${index}].overlapSize`); + if (overlapSize <= 0) throw new Error(`Invalid overlap size at index ${index}`); + return { id, name, color, thickness, cellIndices, face, overlapEnabled, overlapSize }; } function normalizeConfig(raw) { @@ -86,9 +97,15 @@ function normalizeConfig(raw) { const bmsType = requireString(bms.type, 'bms.type'); if (!ALLOWED_BMS_TYPES.has(bmsType)) throw new Error('Invalid BMS type'); + const rawTabOverlapSide = requireString(bms.tabOverlapSide ?? 'off', 'bms.tabOverlapSide'); + if (!ALLOWED_TAB_OVERLAP_SIDES.has(rawTabOverlapSide)) throw new Error('Invalid tab overlap side'); + const tabOverlapSide = normalizeTabOverlapSide(rawTabOverlapSide); const busbarFormat = requireString(busbars.format, 'busbars.format'); if (!ALLOWED_BUSBAR_FORMATS.has(busbarFormat)) throw new Error('Invalid busbar format'); + const cellCutoutEnabled = busbars.cellCutoutEnabled == null + ? false + : requireBoolean(busbars.cellCutoutEnabled, 'busbars.cellCutoutEnabled'); const list = Array.isArray(busbars.list) ? busbars.list.map((bb, i) => normalizeBusbar(bb, i)) @@ -122,11 +139,14 @@ function normalizeConfig(raw) { type: bmsType, holeDiameter: requireFiniteNumber(Number(bms.holeDiameter), 'bms.holeDiameter'), tabWidth: requireFiniteNumber(Number(bms.tabWidth), 'bms.tabWidth'), + tabLength: requireFiniteNumber(Number(bms.tabLength ?? 10), 'bms.tabLength'), tabDepth: requireFiniteNumber(Number(bms.tabDepth), 'bms.tabDepth'), + tabOverlapSide, }, busbars: { format: busbarFormat, activeId, + cellCutoutEnabled, list, }, }; @@ -187,11 +207,14 @@ export function captureConfig(getPackMode, busbarSnapshot) { type: readString('bmsHolesType', 'fullcircles'), holeDiameter: readNumber('bmsHoleDiameter', 4.0), tabWidth: readNumber('tabWidth', 4.0), + tabLength: readNumber('tabLength', 10.0), tabDepth: readNumber('tabDepth', 1.0), + tabOverlapSide: normalizeTabOverlapSide(readString('tabOverlapSide', 'off')), }, busbars: { format: readString('busbarFormat', 'step'), activeId: busbarSnapshot?.activeId ?? null, + cellCutoutEnabled: readBool('busbarCellCutoutEnabled', false), list: Array.isArray(busbarSnapshot?.list) ? busbarSnapshot.list.map(bb => ({ ...bb, face: bb.face === 'bottom' ? 'bottom' : 'top' })) : [], diff --git a/styles/main.css b/styles/main.css index 8ac8e92..c5d76c9 100644 --- a/styles/main.css +++ b/styles/main.css @@ -1260,12 +1260,40 @@ input[type="checkbox"]:focus { gap: 8px; } -.busbar-swatch { - width: 16px; - height: 16px; - border-radius: 4px; +.busbar-color-wrap { + width: 20px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; flex-shrink: 0; + border-radius: 6px; + overflow: hidden; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.04); +} + +.busbar-color { + width: 100%; + height: 100%; + border: none; + padding: 0; + background: transparent; + cursor: pointer; +} + +.busbar-color::-webkit-color-swatch-wrapper { + padding: 0; +} + +.busbar-color::-webkit-color-swatch { + border: none; + border-radius: 0; +} + +.busbar-color::-moz-color-swatch { + border: none; + border-radius: 0; } .busbar-name { @@ -1310,6 +1338,7 @@ input[type="checkbox"]:focus { display: flex; align-items: center; justify-content: space-between; + flex-wrap: wrap; gap: 8px; margin-top: 6px; padding-left: 24px; @@ -1317,6 +1346,47 @@ input[type="checkbox"]:focus { color: #94a3b8; } +.busbar-overlap-label { + display: inline-flex; + align-items: center; + gap: 6px; + color: #cbd5e1; + cursor: pointer; + user-select: none; +} + +.busbar-overlap { + accent-color: #6495ed; +} + +.busbar-overlap-size-label { + display: inline-flex; + align-items: center; + gap: 6px; + color: #94a3b8; + font-size: 0.95em; +} + +.busbar-overlap-size { + width: 64px; + padding: 3px 6px; + font-size: 0.9em; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(100, 149, 237, 0.2); + border-radius: 4px; + color: #e2e8f0; +} + +.busbar-overlap-size:focus { + outline: none; + border-color: rgba(100, 149, 237, 0.6); +} + +.busbar-overlap-size:disabled { + opacity: 0.45; + cursor: not-allowed; +} + .busbar-thickness-label { display: flex; align-items: center;