feat: 新增螺丝网格生成与强度校核功能,重建模型数据与查看器,补充定位图与BOM文档
新增 screw_mesh.py 螺丝网格划分模块与 strength_check.py 强度校核逻辑,arm_model.py 同步集成网格生成与校核流程;viewer_tpl.html、web/index.html 与 web/机器人手臂3D查看器.html 更新查看器模板以支持新数据展示;docs/bom.json 与 docs/travel_detail.json 调整 BOM 清单与行程参数;docs/print_settings.md 新增打印设置说明,新增螺丝采购清单 HTML;补充大量定位图与验证截图。
This commit is contained in:
+165
-18
@@ -91,6 +91,10 @@ small{color:var(--dim)}
|
||||
.dk-note{color:var(--dim);font-size:11px;margin:1px 0 6px}
|
||||
#screws td{white-space:normal}
|
||||
#screws td:first-child{white-space:nowrap}
|
||||
#screws tbody tr{cursor:pointer;transition:background .12s}
|
||||
#screws tbody tr:hover{background:rgba(76,154,255,.14)}
|
||||
#screws tbody tr.scrHl{background:#1d3f74;box-shadow:inset 3px 0 0 var(--warn)}
|
||||
.hudlink{pointer-events:auto;cursor:pointer;color:var(--ac);text-decoration:underline;margin-left:8px}
|
||||
@media (max-width:760px){#bomTab{top:auto;bottom:36px}#bomDock{top:46px;bottom:26px}}
|
||||
</style>
|
||||
</head>
|
||||
@@ -111,7 +115,9 @@ small{color:var(--dim)}
|
||||
<option value="wrist">腕部特写</option><option value="joint">关节特写</option><option value="elbow">肘部特写</option><option value="hand">手部特写</option>
|
||||
</select>
|
||||
<button id="bWire">线框</button>
|
||||
<button id="bXRay" class="on">电机半透</button>
|
||||
<button id="bXRay">电机半透</button>
|
||||
<button id="bScr" class="on" title="显示/隐藏全部螺钉(S01~S10);点右侧 BOM 螺丝行可在模型中高亮定位">螺钉</button>
|
||||
<button id="bEnv" title="显示/隐藏可达外包络(运动学边界)">可达包络</button>
|
||||
<button id="bFit">复位</button>
|
||||
<button id="bL" title="显示/隐藏左栏">左栏</button>
|
||||
</header>
|
||||
@@ -138,7 +144,7 @@ small{color:var(--dim)}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>左键旋转 · 滚轮缩放 · 右键平移(拖动) · 单击选中高亮 · <b>右键单击隐藏部件(或按H键)</b> · 左侧全选/勾选恢复</span>
|
||||
<span>左键旋转 · 滚轮缩放 · 右键平移(拖动) · 单击选中高亮 · <b>BOM 点螺丝行 = 高亮定位该规格螺钉(穿透显示)</b> · <b>右键单击隐藏部件(或按H键)</b> · 左侧全选/勾选恢复</span>
|
||||
<span class="sp"></span>
|
||||
<span id="stat"></span>
|
||||
</footer>
|
||||
@@ -212,6 +218,25 @@ for (const p of DATA.parts) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
const pos = new Float32Array(p.v.length * 3);
|
||||
for (let i = 0; i < p.v.length; i++) { pos[i*3] = p.v[i][0]; pos[i*3+1] = p.v[i][2]; pos[i*3+2] = -p.v[i][1]; }
|
||||
p.triCount = p.t ? p.t.length : 0;
|
||||
p.vertCount = p.v.length;
|
||||
const isEnv = p.name.slice(0, 4) === 'ENV_'; // 可达包络:运动学边界,不随关节动
|
||||
if (p.kind === 'line') { // 经纬线:LineSegments,无三角面
|
||||
const sIdx = new Uint32Array(p.s);
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
||||
geo.setIndex(new THREE.BufferAttribute(sIdx, 1));
|
||||
geo.computeBoundingSphere(); geo.computeBoundingBox();
|
||||
const lm = new THREE.LineBasicMaterial({color: p.color, transparent: true, opacity: 0.8});
|
||||
const ls = new THREE.LineSegments(geo, lm);
|
||||
ls.userData = p;
|
||||
ls.position.set(p.ex[0], p.ex[2], -p.ex[1]);
|
||||
ls.base = ls.position.clone();
|
||||
ls.visible = !isEnv; // 包络默认隐藏,走开关显示
|
||||
JG[p.grp || 'root'].add(ls);
|
||||
meshes.push(ls); byName[p.name] = ls;
|
||||
delete p.v; delete p.s;
|
||||
continue;
|
||||
}
|
||||
const idx = new Uint32Array(p.t.length * 3);
|
||||
// the vertex map (x,y,z)->(x,z,-y) has determinant +1, so triangle winding must be kept
|
||||
// as-is; swapping two indices here inverted every face and made the normals point inward
|
||||
@@ -221,19 +246,22 @@ for (const p of DATA.parts) {
|
||||
geo.computeVertexNormals();
|
||||
geo.computeBoundingSphere();
|
||||
geo.computeBoundingBox();
|
||||
p.triCount = p.t.length;
|
||||
p.vertCount = p.v.length;
|
||||
delete p.v;
|
||||
delete p.t;
|
||||
const isMo = !p.printed && p.name[0] === 'M';
|
||||
const isScr = !!p.screw; // 紧固件(螺钉):金属亮色,可被 BOM 行高亮
|
||||
const mat = new THREE.MeshStandardMaterial({
|
||||
color: p.color, metalness: isMo ? .55 : .05, roughness: isMo ? .42 : .78,
|
||||
transparent: isMo, opacity: isMo ? .34 : 1, flatShading: false, side: THREE.FrontSide});
|
||||
color: p.color, metalness: isScr ? .78 : (isMo ? .55 : .05),
|
||||
roughness: isScr ? .30 : (isMo ? .42 : .78),
|
||||
transparent: isEnv, opacity: isEnv ? .06 : 1,
|
||||
depthWrite: !isEnv,
|
||||
flatShading: false, side: isEnv ? THREE.DoubleSide : THREE.FrontSide});
|
||||
const ms = new THREE.Mesh(geo, mat);
|
||||
ms.userData = p;
|
||||
ms.position.set(p.ex[0], p.ex[2], -p.ex[1]);
|
||||
ms.base = ms.position.clone();
|
||||
ms.zero = [0,0,0];
|
||||
if (isEnv) ms.visible = false; // 包络默认隐藏,走开关显示
|
||||
JG[p.grp || 'root'].add(ms);
|
||||
meshes.push(ms); byName[p.name] = ms;
|
||||
}
|
||||
@@ -286,7 +314,8 @@ let BB = [[1e9,1e9,1e9],[-1e9,-1e9,-1e9]];
|
||||
/* robust global bounds in the viewer frame (x, z, -y) */
|
||||
(function(){
|
||||
let lo=[1e9,1e9,1e9], hi=[-1e9,-1e9,-1e9];
|
||||
for (const m of meshes) { const b=m.geometry.boundingBox;
|
||||
for (const m of meshes) { if (m.userData.name.slice(0,4) === 'ENV_') continue; // 取景只按手臂本体,包络不入包围盒
|
||||
const b=m.geometry.boundingBox;
|
||||
const a=[b.min.x+m.base.x,b.min.y+m.base.y,b.min.z+m.base.z];
|
||||
const c=[b.max.x+m.base.x,b.max.y+m.base.y,b.max.z+m.base.z];
|
||||
for(let i=0;i<3;i++){lo[i]=Math.min(lo[i],a[i]);hi[i]=Math.max(hi[i],c[i]);} }
|
||||
@@ -381,6 +410,7 @@ window.addEventListener('keydown', e => {
|
||||
if (e.key === 'h' || e.key === 'H' || e.key === 'Delete') {
|
||||
if (sel) hidePart(sel);
|
||||
} else if (e.key === 'Escape') {
|
||||
clearScrewHl();
|
||||
if (sel) {
|
||||
sel.material.emissive.setHex(0);
|
||||
sel.material.emissiveIntensity = 0;
|
||||
@@ -398,18 +428,21 @@ cv.addEventListener('click', e => {
|
||||
if (o === sel) {
|
||||
if (sel) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; sel = null; }
|
||||
const tb = document.querySelector('#bom tbody'); if (tb) for (const tr of tb.children) tr.style.background = '';
|
||||
const hud = document.getElementById('hud'); if (hud) hud.textContent = '';
|
||||
clearScrewHl();
|
||||
const hud = document.getElementById('hud'); if (hud) { hud.textContent = ''; delete hud.dataset.scrHl; }
|
||||
requestRender();
|
||||
return;
|
||||
}
|
||||
if (sel) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; }
|
||||
if (sel && !sel.userData.screw) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; }
|
||||
sel = o;
|
||||
if (sel) { sel.material.emissive.setHex(0x2f6bff); sel.material.emissiveIntensity = .55; }
|
||||
if (sel && !sel.userData.screw) { sel.material.emissive.setHex(0x2f6bff); sel.material.emissiveIntensity = .55; }
|
||||
const tb = document.querySelector('#bom tbody'); for (const tr of tb.children) tr.style.background = '';
|
||||
const hud = document.getElementById('hud');
|
||||
hud.textContent = '';
|
||||
delete hud.dataset.scrHl;
|
||||
if (sel) {
|
||||
const u = sel.userData;
|
||||
if (u.screw) { highlightSpec(u.spec, false); requestRender(); return; } // 点中螺钉 -> 反查 BOM 行并定位
|
||||
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>';
|
||||
@@ -450,9 +483,10 @@ if (bRstJ) bRstJ.onclick = () => {
|
||||
};
|
||||
/* ---- UI: part tree ---- */
|
||||
const th = document.getElementById('tree');
|
||||
const groups = [['打印件(新建)', m => m.userData.printed],
|
||||
const groups = [['可达包络(运动学)', m => m.userData.name.slice(0,4) === 'ENV_'],
|
||||
['打印件(新建)', m => m.userData.printed],
|
||||
['电机模组(复用)', m => !m.userData.printed && m.userData.name[0]==='M'],
|
||||
['端面齿联轴器/抱箍(已有)', m => !m.userData.printed && m.userData.name[0]!=='M']];
|
||||
['端面齿联轴器/抱箍(已有)', m => !m.userData.printed && !m.userData.screw && m.userData.name[0]!=='M' && m.userData.name.slice(0,4) !== 'ENV_']];
|
||||
for (const [gt, test] of groups) {
|
||||
const ms = meshes.filter(test); if (!ms.length) continue;
|
||||
const h = document.createElement('div'); h.className = 'gr'; h.textContent = gt + ' · ' + ms.length;
|
||||
@@ -480,11 +514,42 @@ for (const [gt, test] of groups) {
|
||||
th.appendChild(r);
|
||||
}
|
||||
}
|
||||
/* ---- 紧固件(螺钉)分组:按规格聚合,一行 = 一种规格,勾选/点击作用于该规格全部实例 ---- */
|
||||
const scrAll = meshes.filter(m => m.userData.screw);
|
||||
const bySpec = {}, specChk = {};
|
||||
for (const m of scrAll) (bySpec[m.userData.spec] = bySpec[m.userData.spec] || []).push(m);
|
||||
if (scrAll.length) {
|
||||
const h = document.createElement('div'); h.className = 'gr';
|
||||
h.textContent = '紧固件 / 螺钉(按规格聚合) · ' + Object.keys(bySpec).length + ' 种 ' +
|
||||
scrAll.length + ' 颗(点击名称定位)';
|
||||
th.appendChild(h);
|
||||
for (const sid of Object.keys(bySpec).sort()) {
|
||||
const ms = bySpec[sid], p0 = ms[0].userData;
|
||||
const r = document.createElement('div'); r.className = 'row';
|
||||
r.innerHTML = '<input type="checkbox" checked><span class="chip" style="background:#' +
|
||||
p0.color.toString(16).padStart(6,'0') + '"></span><label title="' + p0.note + '">' +
|
||||
'<span class="ex">' + sid + ' ' + p0.cn.split(' · ')[0] + '</span></label>' +
|
||||
'<span class="q">' + ms.length + ' 颗</span>';
|
||||
const chk = r.querySelector('input');
|
||||
specChk[sid] = chk;
|
||||
chk.onchange = e => {
|
||||
for (const m of ms) {
|
||||
m.visible = e.target.checked;
|
||||
if (!m.visible && m === sel) { sel.material.emissive.setHex(0); sel = null; }
|
||||
}
|
||||
requestRender();
|
||||
};
|
||||
r.querySelector('label').onclick = () => highlightSpec(sid);
|
||||
for (const m of ms) m.chk = chk;
|
||||
th.appendChild(r);
|
||||
}
|
||||
}
|
||||
document.getElementById('allOn').onclick = () => {
|
||||
meshes.forEach(m => {
|
||||
m.visible = true;
|
||||
if (m.chk) m.chk.checked = true;
|
||||
});
|
||||
bEnv.classList.toggle('on', meshes.some(m => m.userData.name.slice(0,4) === 'ENV_' && m.visible));
|
||||
requestRender();
|
||||
};
|
||||
document.getElementById('allOff').onclick = () => {
|
||||
@@ -493,6 +558,9 @@ document.getElementById('allOff').onclick = () => {
|
||||
if (m.chk) m.chk.checked = false;
|
||||
if (sel) { sel.material.emissive.setHex(0); sel = null; }
|
||||
});
|
||||
bEnv.classList.remove('on');
|
||||
bScr.classList.remove('on');
|
||||
clearScrewHl();
|
||||
requestRender();
|
||||
};
|
||||
document.getElementById('onlyPrint').onclick = () => {
|
||||
@@ -501,6 +569,9 @@ document.getElementById('onlyPrint').onclick = () => {
|
||||
if (m.chk) m.chk.checked = m.visible;
|
||||
if (!m.visible && m === sel) { sel.material.emissive.setHex(0); sel = null; }
|
||||
});
|
||||
bEnv.classList.remove('on');
|
||||
bScr.classList.remove('on');
|
||||
clearScrewHl();
|
||||
requestRender();
|
||||
};
|
||||
/* ---- BOM ---- */
|
||||
@@ -532,16 +603,79 @@ for (const s of SCREWS) {
|
||||
nTot += s.数量;
|
||||
bySize[s.螺纹] = (bySize[s.螺纹] || 0) + s.数量;
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = '<td><b>' + s.规格 + '</b><br><small>' + s.头型 + '</small></td>' +
|
||||
tr.innerHTML = '<td><b>' + s.id + '</b> ' + s.规格 + '<br><small>' + s.头型 + '</small></td>' +
|
||||
'<td>' + s.长度mm + '</td><td>' + s.数量 + '</td>' +
|
||||
'<td>' + s.位置 + '<br><small>' + s.备注 + '</small></td>';
|
||||
tr.dataset.spec = s.id;
|
||||
tr.title = '点击:在三维模型中高亮该规格全部 ' + s.数量 + ' 颗并定位(再次点击切换到下一处)';
|
||||
tr.onclick = () => highlightSpec(s.id);
|
||||
skb.appendChild(tr);
|
||||
}
|
||||
const tally = Object.keys(bySize).sort().map(k => k + 'x' + bySize[k]).join(' / ');
|
||||
document.getElementById('scrSum').textContent =
|
||||
'共 ' + SCREWS.length + ' 种 ' + nTot + ' 颗(' + tally + ');另需 M4 螺母 4、M5 螺母 4、M6 螺母+平垫圈各 6,见各行备注';
|
||||
'共 ' + SCREWS.length + ' 种 ' + nTot + ' 颗(' + tally + ');另需 M4 螺母 4、M5 螺母 4、M6 螺母+平垫圈各 6,见各行备注' +
|
||||
' —— 点击任一行可在模型中高亮定位该行螺钉(再次点击切换下一处,Esc 清除)';
|
||||
document.getElementById('bomStat').textContent =
|
||||
'紧固件 ' + nTot + ' 颗 / 打印件 ' + DATA.parts.filter(p=>p.printed).length + ' 种';
|
||||
/* ---- 紧固件高亮定位(BOM 螺丝行 / 零件树 / 三维点选 三处共用)----
|
||||
螺钉多数埋在沉孔或螺丝刀通道里,因此高亮时同时关闭深度测试做"穿透"显示,
|
||||
让位置直接透过零件亮出来;相机再对该孔组自动对焦。 */
|
||||
let hlSpec = null, hlIdx = 0;
|
||||
function specMeshes(sid) { return meshes.filter(m => m.userData.screw && m.userData.spec === sid); }
|
||||
function clearScrewHl() {
|
||||
if (!hlSpec) return;
|
||||
for (const m of specMeshes(hlSpec)) {
|
||||
const mt = m.material;
|
||||
mt.emissive.setHex(0); mt.emissiveIntensity = 0;
|
||||
mt.transparent = false; mt.opacity = 1;
|
||||
mt.depthTest = true; mt.depthWrite = true;
|
||||
mt.needsUpdate = true; m.renderOrder = 0;
|
||||
}
|
||||
hlSpec = null;
|
||||
for (const tr of document.querySelectorAll('#screws tbody tr')) tr.classList.remove('scrHl');
|
||||
const hud = document.getElementById('hud');
|
||||
if (hud && hud.dataset.scrHl) { hud.textContent = ''; delete hud.dataset.scrHl; }
|
||||
requestRender();
|
||||
}
|
||||
function highlightSpec(sid, step) {
|
||||
if (hlSpec !== sid) { clearScrewHl(); hlSpec = sid; hlIdx = 0; }
|
||||
else if (step !== false) hlIdx++; // 再次点击同一行 -> 切换到下一处
|
||||
const ms = specMeshes(sid);
|
||||
if (!ms.length) return;
|
||||
const inf = SCREWS.find(s => s.id === sid) || {规格: sid, 头型: '', 数量: ms.length, 位置: '', 备注: ''};
|
||||
for (const m of ms) {
|
||||
const mt = m.material;
|
||||
if (!m.visible) { m.visible = true; if (m.chk) m.chk.checked = true; }
|
||||
mt.emissive.setHex(0xff8a1e); mt.emissiveIntensity = 1.0;
|
||||
mt.transparent = true; mt.opacity = .96;
|
||||
mt.depthTest = false; mt.depthWrite = false; mt.needsUpdate = true;
|
||||
m.renderOrder = 999;
|
||||
}
|
||||
scene.updateMatrixWorld(true);
|
||||
const pts = ms.map(m => new V3(...m.userData.hl).applyMatrix4(m.parent.matrixWorld));
|
||||
hlIdx = ((hlIdx % pts.length) + pts.length) % pts.length;
|
||||
const c0 = pts[hlIdx];
|
||||
const cl = pts.filter(p => p.distanceTo(c0) < 40.0); // 同一孔组(如某关节 3 颗)一同入框
|
||||
const lo = [1e9,1e9,1e9], hi = [-1e9,-1e9,-1e9];
|
||||
for (const p of cl) for (let i = 0; i < 3; i++) {
|
||||
lo[i] = Math.min(lo[i], p.getComponent(i)); hi[i] = Math.max(hi[i], p.getComponent(i));
|
||||
}
|
||||
tx = (lo[0]+hi[0])/2; ty = (lo[1]+hi[1])/2; tz = (lo[2]+hi[2])/2;
|
||||
const sp = Math.max(hi[0]-lo[0], hi[1]-lo[1], hi[2]-lo[2]);
|
||||
rd = Math.max(150, sp * 2.6 + 130);
|
||||
for (const tr of document.querySelectorAll('#screws tbody tr'))
|
||||
tr.classList.toggle('scrHl', tr.dataset.spec === sid);
|
||||
const tr0 = document.querySelector('#screws tbody tr[data-spec="' + sid + '"]');
|
||||
if (tr0) tr0.scrollIntoView({block: 'nearest'});
|
||||
const hud = document.getElementById('hud');
|
||||
hud.dataset.scrHl = '1';
|
||||
hud.innerHTML = '<b>' + sid + ' ' + inf.规格 + ' ' + inf.头型 + '</b> · ' + inf.数量 +
|
||||
' 颗 —— 第 ' + (hlIdx + 1) + '/' + pts.length + ' 处:' + ms[hlIdx].userData.cn.split(' · ')[1] +
|
||||
'<span class="hudlink" id="scrClr">清除高亮</span><br><small>' + inf.位置 + ';' + inf.备注 + '</small>';
|
||||
const scl = document.getElementById('scrClr');
|
||||
if (scl) scl.onclick = () => clearScrewHl();
|
||||
requestRender();
|
||||
}
|
||||
const dock = document.getElementById('bomDock'), bTab = document.getElementById('bomTab');
|
||||
function dockSet(open) {
|
||||
dock.classList.toggle('open', open);
|
||||
@@ -550,12 +684,14 @@ function dockSet(open) {
|
||||
}
|
||||
bTab.onclick = () => dockSet(!dock.classList.contains('open'));
|
||||
document.getElementById('bomHide').onclick = () => dockSet(false);
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') dockSet(false); });
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !hlSpec) dockSet(false); });
|
||||
/* ---- toolbar ---- */
|
||||
const bAsm = document.getElementById('bAsm');
|
||||
const bExp = document.getElementById('bExp');
|
||||
const bWire = document.getElementById('bWire');
|
||||
const bXRay = document.getElementById('bXRay');
|
||||
const bScr = document.getElementById('bScr');
|
||||
const bEnv = document.getElementById('bEnv');
|
||||
const bL = document.getElementById('bL');
|
||||
const sheet = document.getElementById('sheet');
|
||||
const stage = document.getElementById('stage');
|
||||
@@ -566,9 +702,16 @@ function tog(a, b) { bAsm.classList.toggle('on', a); bExp.classList.toggle('on',
|
||||
const exp = document.getElementById('exp');
|
||||
exp.oninput = () => setExpl(exp.value/100);
|
||||
on('bWire', () => { bWire.classList.toggle('on'); const w = bWire.classList.contains('on');
|
||||
meshes.forEach(m => { m.material.wireframe = w; }); requestRender(); });
|
||||
meshes.forEach(m => { if (m.userData.name.slice(0,4) !== 'ENV_') m.material.wireframe = w; }); requestRender(); });
|
||||
on('bXRay', () => { bXRay.classList.toggle('on'); const o = bXRay.classList.contains('on');
|
||||
meshes.forEach(m => { if (!m.userData.printed && m.userData.name[0]==='M') { m.material.opacity = o ? .34 : 1; m.material.transparent = o; } }); requestRender(); });
|
||||
on('bEnv', () => { bEnv.classList.toggle('on'); const o = bEnv.classList.contains('on');
|
||||
meshes.forEach(m => { if (m.userData.name.slice(0,4) === 'ENV_') { m.visible = o; if (m.chk) m.chk.checked = o; } });
|
||||
requestRender(); });
|
||||
on('bScr', () => { bScr.classList.toggle('on'); const o = bScr.classList.contains('on');
|
||||
meshes.forEach(m => { if (m.userData.screw) { m.visible = o; if (m.chk) m.chk.checked = o; } });
|
||||
if (!o) clearScrewHl();
|
||||
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'); });
|
||||
@@ -628,8 +771,10 @@ document.getElementById('ver').textContent = DATA.layout.ver + ' · 迭代 ' + D
|
||||
function updStat() {
|
||||
const el = document.getElementById('stat');
|
||||
const tri = (typeof meshes !== 'undefined' && meshes) ? meshes.reduce((a, m) => a + (m.userData.triCount || 0), 0) : 0;
|
||||
if (el) el.textContent = '零件 ' + (typeof meshes !== 'undefined' ? meshes.length : 0) + ' · 三角面 ' + (tri/1000).toFixed(0) + 'k · ' +
|
||||
'爆炸 ' + Math.round(expl*100) + '%';
|
||||
const nScr = (typeof meshes !== 'undefined' && meshes) ? meshes.filter(m => m.userData.screw).length : 0;
|
||||
const nTot = (typeof meshes !== 'undefined' && meshes) ? meshes.length : 0;
|
||||
if (el) el.textContent = '零件 ' + (nTot - nScr) + (nScr ? ' + 螺钉 ' + nScr : '') +
|
||||
' · 三角面 ' + (tri/1000).toFixed(0) + 'k · ' + '爆炸 ' + Math.round(expl*100) + '%';
|
||||
}
|
||||
updStat();
|
||||
|
||||
@@ -659,6 +804,8 @@ requestAnimationFrame(loop);
|
||||
if (q.has('tx')) tx = +q.get('tx');
|
||||
if (q.has('ty')) ty = +q.get('ty');
|
||||
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.has('j6')) { const el = document.getElementById('sJ6');
|
||||
if (el) { el.value = q.get('j6'); el.dispatchEvent(new Event('input')); } }
|
||||
|
||||
Reference in New Issue
Block a user