feat(arm): 线束走线可视化落地,并修复承压面盒切误伤底壁

- 查看器新增线束/走线指示系统(4芯主干、电机出线分支、过关节缓冲环、T接小板),
  配套《线束走线与总线拓扑设计方案》文档与 src/test_harness_collision.py 干涉校验
- 取消 P04/P06/P07/P08 的敞口侧定位环盒切(俯仰关节局部坐标使切刀上侵底壁 3mm,
  平白造出 3x6.1mm 缺角),底壁补齐;盒切仅保留原本专用的 P05(M3),开关改名 M3_FENCE_OPEN_CUT
- 3MF 基准 profile(537 键)从手作原件抽出固化到 docs/bambu_profile_reference.json,
  导出链路不再依赖 out/ 二进制;P01_A 也改由当前模型生成,原件备份进 .bak/
- strength.json、BOM、screws、截图与三份查看器 HTML 同步重生成
This commit is contained in:
cbb
2026-10-07 19:08:47 +08:00
parent d8f6c69406
commit c754521bde
175 changed files with 5719 additions and 332 deletions
+368 -6
View File
@@ -20,6 +20,7 @@ button,select{background:var(--pan2);color:var(--tx);border:1px solid var(--line
border-radius:5px;padding:4px 9px;font:inherit;cursor:pointer;white-space:nowrap;flex:none}
button:hover{border-color:var(--ac);color:#fff}
button.on{background:#22406b;border-color:var(--ac)}
#bHarn.on{background:#5c3b1e;border-color:#f59e0b;color:#fbbf24}
aside{background:var(--pan);overflow:auto;padding:8px;min-width:0}
#left{border-right:1px solid var(--line)}
#app.l{grid-template-columns:322px minmax(430px,1fr)}
@@ -217,6 +218,7 @@ body.motion #bomDock{top:124px} body.motion #bomTab{top:140px}
<button id="bWire">线框</button>
<button id="bXRay">电机半透</button>
<button id="bScr" class="on" title="显示/隐藏全部螺钉(S01~S10);点右侧 BOM 螺丝行可在模型中高亮定位">螺钉</button>
<button id="bHarn" class="on" title="显示/隐藏线束走线指示(主干4芯总线、电机出线分支、过关节U型/螺旋缓冲环、T接小板)">线束</button>
<button id="bEnv" title="显示/隐藏可达外包络(运动学边界)">可达包络</button>
<button id="bFit">复位</button>
<button id="bL" title="显示/隐藏左栏">左栏</button>
@@ -508,6 +510,9 @@ function applyJoint(id) {
.multiply(_jmTi.makeTranslation(-P.x, -P.y, -P.z));
JG[id].matrix.copy(m);
JG[id].matrixWorldNeedsUpdate = true;
if (window.updateHarnessBridges && window.isHarnessVisible && window.isHarnessVisible()) {
updateHarnessBridges();
}
requestRender();
}
@@ -575,6 +580,328 @@ for (const p of DATA.parts) {
JG[p.grp || 'root'].add(ms);
meshes.push(ms); byName[p.name] = ms;
}
/* ===================== 线束与走线指示系统 (Wire Harness System) ===================== */
const harnessGroup = new THREE.Group();
harnessGroup.name = 'HARNESS_ROOT';
scene.add(harnessGroup);
const matTrunk = new THREE.MeshStandardMaterial({
color: 0xf59e0b, roughness: 0.35, metalness: 0.25
});
const matDrop = new THREE.MeshStandardMaterial({
color: 0x10b981, roughness: 0.3, metalness: 0.2
});
const matTap = new THREE.MeshStandardMaterial({
color: 0x8b5cf6, roughness: 0.4, metalness: 0.3
});
const matTerm = new THREE.MeshStandardMaterial({
color: 0xef4444, roughness: 0.2, metalness: 0.5
});
const matClip = new THREE.MeshStandardMaterial({
color: 0x1e293b, roughness: 0.5, metalness: 0.2
});
const harnessMeshes = [];
function makeHarnTube(ptsCAD, r, mat, name, cn, parentGrp, segs = 18, radSegs = 8) {
const pts = ptsCAD.map(p => new THREE.Vector3(...VC(p)));
const curve = new THREE.CatmullRomCurve3(pts);
const geo = new THREE.TubeGeometry(curve, segs, r, radSegs, false);
const mesh = new THREE.Mesh(geo, mat);
mesh.userData = {
idx: 'H-CABLE', name: name || '4芯高柔拖链总线 (CAN+24V)',
cn: cn || '主干总线', vol: 8000, printed: false, harness: true
};
(parentGrp || harnessGroup).add(mesh);
harnessMeshes.push(mesh);
return mesh;
}
function makeHarnTap(pCAD, label, parentGrp) {
const geo = new THREE.BoxGeometry(10, 8, 14);
const mesh = new THREE.Mesh(geo, matTap);
mesh.position.set(...VC(pCAD));
mesh.userData = {
idx: 'H-TAP', name: label || 'T接分线小板',
cn: '微型T型总线分线节点', vol: 1100, printed: false, harness: true
};
const ledGeo = new THREE.SphereGeometry(1.2, 8, 8);
const ledMat = new THREE.MeshBasicMaterial({ color: 0x34d399 });
const led = new THREE.Mesh(ledGeo, ledMat);
led.position.set(0, 4.5, 0);
mesh.add(led);
(parentGrp || harnessGroup).add(mesh);
harnessMeshes.push(mesh);
return mesh;
}
function makeHarnClip(pCAD, parentGrp) {
const geo = new THREE.CylinderGeometry(3.0, 3.0, 4.0, 12, 1, true);
const mesh = new THREE.Mesh(geo, matClip);
mesh.position.set(...VC(pCAD));
(parentGrp || harnessGroup).add(mesh);
harnessMeshes.push(mesh);
return mesh;
}
function makeHarnTerm(pCAD, parentGrp) {
const geo = new THREE.CylinderGeometry(2.5, 2.5, 8.0, 12);
const mesh = new THREE.Mesh(geo, matTerm);
mesh.position.set(...VC(pCAD));
mesh.rotation.z = Math.PI / 2;
mesh.userData = {
idx: 'H-TERM', name: '120Ω 终端电阻 (总线末端阻抗匹配)',
cn: 'CAN总线终端匹配电阻', vol: 500, printed: false, harness: true
};
(parentGrp || harnessGroup).add(mesh);
harnessMeshes.push(mesh);
return mesh;
}
// 1. Root group harness (P01 立柱、P03 吊臂、M1 电机)
const hRoot = new THREE.Group();
hRoot.name = 'HARN_ROOT_SUB';
JG['root'].add(hRoot);
makeHarnTube([
[-115.0, 128.0, 20.0], [-115.0, 128.0, 250.0], [-115.0, 128.0, 470.0],
[-115.0, 128.0, 580.0], [-80.0, 128.0, 605.0], [-50.0, 122.0, 608.0]
], 2.0, matTrunk, 'P01立柱内穿线管主干 (24V+CAN,穿44x44内腔与P03 22x22过线孔)', '基座主干线束', hRoot);
makeHarnTap([-50.0, 122.0, 608.0], 'T接小板① (J1/M1 分线节点)', hRoot);
// M1 后端盖缺口出线 (CAD: [-48.8, 110.0, 610.1])
makeHarnTube([
[-48.8, 110.0, 610.1], [-49.4, 116.0, 609.0], [-50.0, 122.0, 608.0]
], 1.3, matDrop, 'M1 电机4芯引出线 (2xCAN + 2x电源)', 'M1 出线分支', hRoot, 10, 6);
makeHarnClip([-115.0, 128.0, 300.0], hRoot);
makeHarnClip([-115.0, 128.0, 520.0], hRoot);
makeHarnClip([-80.0, 128.0, 605.0], hRoot);
const anchorA0 = new THREE.Object3D();
anchorA0.position.set(...VC([-15.0, 128.0, 610.0]));
hRoot.add(anchorA0);
makeHarnTube([
[-50.0, 122.0, 608.0], [-30.0, 126.0, 609.0], [-15.0, 128.0, 610.0]
], 1.8, matTrunk, 'J1入线段 (P03肩吊臂中央轴孔引出)', 'J1入线', hRoot, 8, 6);
// 2. J1 group harness (P04 肩叉、M2 电机)
const hJ1 = new THREE.Group();
hJ1.name = 'HARN_J1';
JG['J1'].add(hJ1);
const anchorB1 = new THREE.Object3D();
anchorB1.position.set(...VC([15.0, 128.0, 610.0]));
hJ1.add(anchorB1);
makeHarnTube([
[15.0, 128.0, 610.0], [26.0, 128.0, 610.0], [26.0, 128.0, 580.0], [28.0, 115.0, 530.0], [28.0, 115.0, 510.0]
], 1.8, matTrunk, 'P04肩叉立柱减重槽暗管走线', 'P04 走线槽暗管', hJ1);
makeHarnTap([28.0, 115.0, 510.0], 'T接小板② (J2/M2 分线节点)', hJ1);
// M2 圈出缺口出线 (CAD: [39.5, 104.0, 506.5],位于电机后端盖圆形PCBA侧向出线槽)
makeHarnTube([
[39.5, 104.0, 506.5], [34.0, 110.0, 508.0], [28.0, 115.0, 510.0]
], 1.3, matDrop, 'M2 电机4芯引出线 (2xCAN + 2x电源)', 'M2 出线分支', hJ1, 10, 6);
makeHarnClip([26.0, 128.0, 580.0], hJ1);
makeHarnClip([28.0, 115.0, 530.0], hJ1);
makeHarnTube([
[28.0, 115.0, 510.0], [28.0, 115.0, 480.0]
], 1.8, matTrunk, 'P04底板贯通引出段 (穿22x22mm过线方孔)', 'P04下行', hJ1, 8, 6);
const anchorA1 = new THREE.Object3D();
anchorA1.position.set(...VC([28.0, 115.0, 480.0]));
hJ1.add(anchorA1);
// 3. J2 group harness (P05 大臂旋转座、M3 电机)
const hJ2 = new THREE.Group();
hJ2.name = 'HARN_J2';
JG['J2'].add(hJ2);
const anchorB2 = new THREE.Object3D();
anchorB2.position.set(...VC([25.0, 115.0, 455.0]));
hJ2.add(anchorB2);
makeHarnTube([
[25.0, 115.0, 455.0], [25.0, 115.0, 442.0]
], 1.8, matTrunk, 'P05顶板22x22mm过线孔贯穿走线', 'P05 内部走线', hJ2, 6, 6);
makeHarnTap([25.0, 115.0, 442.0], 'T接小板③ (J3/M3 分线节点)', hJ2);
// M3 顶端缺口出线 (CAD: [34.0, 109.6, 442.7])
makeHarnTube([
[34.0, 109.6, 442.7], [29.5, 112.3, 442.3], [25.0, 115.0, 442.0]
], 1.3, matDrop, 'M3 电机4芯引出线 (2xCAN + 2x电源)', 'M3 出线分支', hJ2, 10, 6);
const anchorA2 = new THREE.Object3D();
anchorA2.position.set(...VC([25.0, 90.0, 390.0]));
hJ2.add(anchorA2);
makeHarnTube([
[25.0, 115.0, 442.0], [25.0, 105.0, 410.0], [25.0, 90.0, 390.0]
], 1.8, matTrunk, 'P05转接段 (自半包敞口向下引出)', 'P05转接', hJ2, 8, 6);
// 4. J3 group harness (P06 大臂骨架、M4 电机)
const hJ3 = new THREE.Group();
hJ3.name = 'HARN_J3';
JG['J3'].add(hJ3);
const anchorB3 = new THREE.Object3D();
anchorB3.position.set(...VC([25.0, 90.0, 372.0]));
hJ3.add(anchorB3);
makeHarnTube([
[25.0, 90.0, 372.0], [25.0, 105.0, 365.0], [30.0, 110.0, 350.0]
], 1.8, matTrunk, 'P06大臂过渡块22x20mm过线通窗走线', 'P06 过线窗走线', hJ3, 8, 6);
makeHarnTap([30.0, 110.0, 350.0], 'T接小板④ (J4/M4 肘部分线节点)', hJ3);
// M4 肘电机缺口出线 (CAD: [39.5, 104.0, 348.4])
makeHarnTube([
[39.5, 104.0, 348.4], [35.0, 107.0, 349.2], [30.0, 110.0, 350.0]
], 1.3, matDrop, 'M4 肘电机4芯引出线 (2xCAN + 2x电源)', 'M4 出线分支', hJ3, 10, 6);
makeHarnClip([30.0, 110.0, 350.0], hJ3);
makeHarnTube([
[30.0, 110.0, 350.0], [28.0, 112.0, 332.0], [25.0, 115.0, 310.0], [25.0, 115.0, 294.0]
], 1.8, matTrunk, 'P06背脊暗槽向下引出段', 'P06引出', hJ3, 10, 6);
const anchorA3 = new THREE.Object3D();
anchorA3.position.set(...VC([25.0, 115.0, 294.0]));
hJ3.add(anchorA3);
// 5. J4 group harness (P07 小臂骨架、M5 电机)
const hJ4 = new THREE.Group();
hJ4.name = 'HARN_J4';
JG['J4'].add(hJ4);
const anchorB4 = new THREE.Object3D();
anchorB4.position.set(...VC([19.5, 160.0, 296.5]));
hJ4.add(anchorB4);
makeHarnTube([
[19.5, 160.0, 296.5], [19.5, 168.0, 296.5], [25.0, 135.0, 280.0],
[28.0, 115.0, 260.0], [28.0, 115.0, 240.0]
], 1.8, matTrunk, 'P07小臂工艺走线孔(22x22mm)与底板通孔(22x22mm)走线', 'P07 顶板线仓', hJ4, 12, 6);
makeHarnTap([28.0, 115.0, 240.0], 'T接小板⑤ (J5/M5 分线节点)', hJ4);
// M5 腕仰俯电机缺口出线 (CAD: [39.5, 104.0, 238.4])
makeHarnTube([
[39.5, 104.0, 238.4], [34.0, 109.0, 239.0], [28.0, 115.0, 240.0]
], 1.3, matDrop, 'M5 电机4芯引出线 (2xCAN + 2x电源)', 'M5 出线分支', hJ4, 10, 6);
makeHarnClip([28.0, 115.0, 260.0], hJ4);
makeHarnTube([
[28.0, 115.0, 240.0], [26.0, 115.0, 215.0], [25.0, 115.0, 195.0], [25.0, 115.0, 190.0]
], 1.8, matTrunk, 'P07下行引出段 (直达腕部接口)', 'P07引出', hJ4, 8, 6);
const anchorA4 = new THREE.Object3D();
anchorA4.position.set(...VC([25.0, 115.0, 190.0]));
hJ4.add(anchorA4);
// 6. J5 group harness (P08 腕偏转座、M6 电机)
const hJ5 = new THREE.Group();
hJ5.name = 'HARN_J5';
JG['J5'].add(hJ5);
const anchorB5 = new THREE.Object3D();
anchorB5.position.set(...VC([25.0, 115.0, 185.0]));
hJ5.add(anchorB5);
makeHarnTube([
[25.0, 115.0, 185.0], [25.0, 115.0, 175.0]
], 1.8, matTrunk, 'P08腕偏转座22x22mm过线孔走线', 'P08 手腕内腔', hJ5, 6, 6);
makeHarnTap([25.0, 115.0, 175.0], 'T接小板⑥ (J6/M6 终端分线节点)', hJ5);
// M6 夹爪电机缺口出线 (CAD: [34.0, 109.6, 173.7])
makeHarnTube([
[34.0, 109.6, 173.7], [29.5, 112.3, 174.3], [25.0, 115.0, 175.0]
], 1.3, matDrop, 'M6 腕电机4芯引出线 (2xCAN + 2x电源)', 'M6 出线分支', hJ5, 10, 6);
makeHarnTerm([25.0, 115.0, 168.0], hJ5);
// 7. Dynamic Flexible Bridges across J1~J5
const bridgeMeshes = [];
for (let i = 0; i < 5; i++) {
const geo = new THREE.BufferGeometry();
const mesh = new THREE.Mesh(geo, matTrunk);
mesh.userData = {
idx: 'H-BRIDGE-' + (i + 1),
name: 'J' + (i + 1) + ' 跨关节柔性缓冲环 (Service Loop / 螺旋盘簧)',
cn: 'J' + (i + 1) + ' 跨关节柔性段', vol: 2500, printed: false, harness: true
};
harnessGroup.add(mesh);
bridgeMeshes.push(mesh);
harnessMeshes.push(mesh);
}
const allHarnessContainers = [hRoot, hJ1, hJ2, hJ3, hJ4, hJ5, harnessGroup];
let harnessVisibleState = true;
function setHarnessVisible(v) {
harnessVisibleState = v;
allHarnessContainers.forEach(g => g.visible = v);
if (v) updateHarnessBridges();
requestRender();
}
window.setHarnessVisible = setHarnessVisible;
window.isHarnessVisible = () => harnessVisibleState;
const _pA = new THREE.Vector3(), _pB = new THREE.Vector3();
function updateHarnessBridges() {
if (!harnessVisibleState) return;
scene.updateMatrixWorld();
const pairs = [
{ a: anchorA0, b: anchorB1, type: 'roll1' }, // J1 roll
{ a: anchorA1, b: anchorB2, type: 'pitch2' }, // J2 pitch
{ a: anchorA2, b: anchorB3, type: 'roll3' }, // J3 roll
{ a: anchorA3, b: anchorB4, type: 'pitch4' }, // J4 pitch
{ a: anchorA4, b: anchorB5, type: 'pitch5' }, // J5 pitch
];
for (let i = 0; i < 5; i++) {
const pr = pairs[i];
pr.a.getWorldPosition(_pA);
pr.b.getWorldPosition(_pB);
const m = bridgeMeshes[i];
let pts = [];
if (pr.type === 'roll1') {
const mid = new THREE.Vector3().lerpVectors(_pA, _pB, 0.5);
pts = [_pA.clone(), mid, _pB.clone()];
} else if (pr.type === 'pitch2') {
const mid = new THREE.Vector3().lerpVectors(_pA, _pB, 0.5);
pts = [_pA.clone(), mid, _pB.clone()];
} else if (pr.type === 'roll3') {
const mid = new THREE.Vector3().lerpVectors(_pA, _pB, 0.5);
pts = [_pA.clone(), mid, _pB.clone()];
} else if (pr.type === 'pitch4') {
const mid = new THREE.Vector3().lerpVectors(_pA, _pB, 0.5);
pts = [_pA.clone(), mid, _pB.clone()];
} else if (pr.type === 'pitch5') {
const mid = new THREE.Vector3().lerpVectors(_pA, _pB, 0.5);
pts = [_pA.clone(), mid, _pB.clone()];
}
const curve = new THREE.CatmullRomCurve3(pts);
if (m.geometry) m.geometry.dispose();
m.geometry = new THREE.TubeGeometry(curve, 16, 1.8, 8, false);
}
}
window.updateHarnessBridges = updateHarnessBridges;
window.harnessGroup = harnessGroup;
window.harnessMeshes = harnessMeshes;
updateHarnessBridges();
const J6j = JO.find(j => j.id === 'J6');
// the jaws slide along the gripper motor's own local X, so that axis must come from J6
const JAWD = J6j ? new V3(...VC(J6j.xdir)).normalize() : new V3(0, 0, 1);
@@ -587,6 +914,11 @@ function updXform() {
m.position.copy(m.base).multiplyScalar(expl);
if (m.userData.jaw) m.position.addScaledVector(JAWD, m.userData.jaw * jawMM);
}
if (window.setHarnessVisible) {
const bH = document.getElementById('bHarn');
const on = bH ? bH.classList.contains('on') : true;
setHarnessVisible(on && (expl < 0.05));
}
requestRender();
}
function setJaw(t) {
@@ -686,6 +1018,9 @@ function pick(e) {
ray.setFromCamera(ndcPick, cam);
pickList.length = 0;
for (const m of meshes) if (m.visible) pickList.push(m);
if (window.isHarnessVisible && isHarnessVisible() && window.harnessMeshes) {
for (const m of harnessMeshes) if (m.visible) pickList.push(m);
}
const hit = ray.intersectObjects(pickList, false)[0];
return hit ? hit.object : null;
}
@@ -704,9 +1039,13 @@ function hover(e) {
if (!o) { n.style.display = 'none'; return; }
const p = o.userData;
n.style.display = 'block';
n.innerHTML = '<b>#' + p.idx + ' ' + p.cn + '</b><br><small>' + p.name +
' · ' + (p.printed ? '3D打印件' : '电机/已有件') +
(dimsStr(p) ? '<br>' + dimsStr(p) : '') + '</small>';
if (p.harness) {
n.innerHTML = '<b>' + p.cn + '</b><br><small style="color:#fbbf24">' + p.name + '</small>';
} else {
n.innerHTML = '<b>#' + p.idx + ' ' + p.cn + '</b><br><small>' + p.name +
' · ' + (p.printed ? '3D打印件' : '电机/已有件') +
(dimsStr(p) ? '<br>' + dimsStr(p) : '') + '</small>';
}
}
function hidePart(m) {
if (!m) return;
@@ -766,9 +1105,9 @@ cv.addEventListener('click', e => {
requestRender();
return;
}
if (sel && !sel.userData.screw) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; }
if (sel && !sel.userData.screw && !sel.userData.harness) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; }
sel = o;
if (sel && !sel.userData.screw) { sel.material.emissive.setHex(0x2f6bff); sel.material.emissiveIntensity = .55; }
if (sel && !sel.userData.screw && !sel.userData.harness) { sel.material.emissive.setHex(0x2f6bff); sel.material.emissiveIntensity = .55; }
const tb = document.querySelector('#bom tbody');
bomHlClear();
const hud = document.getElementById('hud');
@@ -777,6 +1116,12 @@ cv.addEventListener('click', e => {
if (sel) {
const u = sel.userData;
if (u.screw) { highlightSpec(u.spec, false); requestRender(); return; } // 点中螺钉 -> 反查 BOM 行并定位
if (u.harness) {
hud.innerHTML = '<b>' + u.cn + '</b> · <span style="color:#f59e0b">' + u.name +
'</span> · 2×CAN通信 + 2×24V供电 · <span style="color:var(--ok)">4芯高柔拖链电缆/T型分接</span>';
requestRender();
return;
}
hud.innerHTML = '<b>#' + u.idx + ' ' + u.cn + '</b> · ' + (u.vol / 1000).toFixed(1) +
' cm3 · ' + (u.printed ? '打印件' : '电机/已有件') +
' <span style="margin-left:8px;color:var(--warn);font-size:11px">[右键单击或按H键隐藏此部件]</span>';
@@ -1044,6 +1389,7 @@ const bExp = document.getElementById('bExp');
const bWire = document.getElementById('bWire');
const bXRay = document.getElementById('bXRay');
const bScr = document.getElementById('bScr');
const bHarn = document.getElementById('bHarn');
const bEnv = document.getElementById('bEnv');
const bL = document.getElementById('bL');
const sheet = document.getElementById('sheet');
@@ -1065,6 +1411,9 @@ on('bScr', () => { bScr.classList.toggle('on'); const o = bScr.classList.contain
meshes.forEach(m => { if (m.userData.screw) { m.visible = o; if (m.chk) m.chk.checked = o; } });
if (!o) clearScrewHl();
requestRender(); });
on('bHarn', () => { bHarn.classList.toggle('on'); const o = bHarn.classList.contains('on');
if (window.setHarnessVisible) setHarnessVisible(o);
requestRender(); });
on('bFit', () => { fitTo(BB); az = -0.9; po = 1.15; camUp.copy(UP_Y); requestRender(); });
const app = document.getElementById('app');
on('bL', () => { app.classList.toggle('l'); bL.classList.toggle('on'); });
@@ -1164,7 +1513,20 @@ requestAnimationFrame(loop);
if (q.has('tz')) tz = +q.get('tz');
if (q.get('env') === '1') { const b = document.getElementById('bEnv');
if (b && !b.classList.contains('on')) b.onclick(); }
if (q.get('xray') === '0') { const b = document.getElementById('bXRay'); if (b.classList.contains('on')) b.onclick(); }
if (q.get('xray') === '0') { const b = document.getElementById('bXRay'); if (b && b.classList.contains('on')) b.onclick(); }
if (q.get('xray') === '1') { const b = document.getElementById('bXRay'); if (b && !b.classList.contains('on')) b.onclick(); }
if (q.get('wire') === '1') { const b = document.getElementById('bWire'); if (b && !b.classList.contains('on')) b.onclick(); }
if (q.has('alpha')) {
const a = +q.get('alpha');
meshes.forEach(m => {
if (m.userData.printed) {
m.material.transparent = true;
m.material.opacity = a;
m.material.depthWrite = a > 0.8;
}
});
}
if (q.get('harn') === '0') { const b = document.getElementById('bHarn'); if (b && b.classList.contains('on')) b.onclick(); }
if (q.has('j6')) { const el = document.getElementById('sJ6');
if (el) { el.value = q.get('j6'); el.dispatchEvent(new Event('input')); } }
if (q.has('hide')) {