news 2026/6/13 23:35:07

three教学 3d资产拼接源代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
three教学 3d资产拼接源代码

pinjie.html

拼接后还需要偏移量,不然3d打印Bambu Studio拆分成零件还是独立物体。

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>GLB 模型拼接 - Three.js 0.162.0 增强版</title> <style> body { margin: 0; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } canvas { display: block; } #info { position: absolute; top: 20px; left: 20px; background: rgba(0,0,0,0.75); color: white; padding: 12px 20px; border-radius: 8px; backdrop-filter: blur(8px); pointer-events: none; z-index: 10; font-size: 14px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); border-left: 4px solid #4caf50; font-family: monospace; } #controls-panel { position: absolute; bottom: 20px; left: 20px; right: 20px; background: rgba(30,30,40,0.95); backdrop-filter: blur(8px); border-radius: 12px; padding: 15px 20px; display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; align-items: center; z-index: 20; pointer-events: auto; border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 15px rgba(0,0,0,0.3); color: #eee; } .btn-group { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; } button { background: #3a6ea5; border: none; color: white; padding: 8px 18px; border-radius: 40px; cursor: pointer; font-weight: bold; font-size: 14px; transition: all 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.3); letter-spacing: 1px; } button:hover { background: #2c4e7a; transform: scale(1.02); } button.active { background: #ff9800; color: #1e1e2a; box-shadow: 0 0 8px rgba(255,152,0,0.5); } button.primary { background: #4caf50; color: white; } button.primary:hover { background: #3e8e41; } button.warning { background: #ff9800; color: #1e1e2a; } button.danger { background: #f44336; } button.danger:hover { background: #d32f2f; } button.merge-btn { background: #9c27b0; font-size: 16px; padding: 10px 24px; } button.merge-btn:hover { background: #7b1fa2; } .file-label { background: #9c27b0; padding: 8px 18px; border-radius: 40px; cursor: pointer; font-weight: bold; font-size: 14px; transition: 0.2s; display: inline-block; } .file-label:hover { background: #7b1fa2; } .file-label.upper { background: #e91e63; } .file-label.upper:hover { background: #c2185b; } .file-label.lower { background: #2196f3; } .file-label.lower:hover { background: #1976d2; } input[type="file"] { display: none; } .status { background: #000000aa; padding: 5px 12px; border-radius: 20px; font-size: 12px; font-family: monospace; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .model-indicator { padding: 4px 10px; border-radius: 20px; font-size: 11px; font-family: monospace; font-weight: bold; } .model-indicator.upper-loaded { background: #e91e63; color: white; } .model-indicator.lower-loaded { background: #2196f3; color: white; } .model-indicator.not-loaded { background: #555; color: #999; } .model-indicator.selected { box-shadow: 0 0 8px rgba(255,255,255,0.6); } @media (max-width: 700px) { .btn-group { gap: 6px; } button, .file-label { padding: 5px 12px; font-size: 12px; } #controls-panel { flex-direction: column; align-items: stretch; bottom: 10px; left: 10px; right: 10px; } .status { white-space: normal; max-width: none; text-align: center; } } .loading-overlay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.9); color: white; padding: 20px 40px; border-radius: 12px; z-index: 100; display: none; font-size: 16px; pointer-events: none; font-family: monospace; text-align: center; } .gap-slider { display: flex; align-items: center; gap: 8px; background: rgba(0,0,0,0.3); padding: 4px 12px; border-radius: 20px; } .gap-slider label { font-size: 12px; color: #aaa; } .gap-slider input { width: 100px; } .gap-value { font-size: 12px; color: #4caf50; font-weight: bold; min-width: 50px; } </style> </head> <body> <div id="info"> 🔗 双模型拼接 | 移动/旋转/缩放 | 合并导出 </div> <div id="controls-panel"> <div class="btn-group"> <span class="model-indicator not-loaded" id="upperIndicator">⬆ 上半部: 未加载</span> <label class="file-label upper" for="upperFile">🔴 加载上半部</label> <input type="file" id="upperFile" accept=".glb,.gltf,.GLB,.GLTF"> <span class="model-indicator not-loaded" id="lowerIndicator">⬇ 下半部: 未加载</span> <label class="file-label lower" for="lowerFile">🔵 加载下半部</label> <input type="file" id="lowerFile" accept=".glb,.gltf,.GLB,.GLTF"> </div> <div class="btn-group"> <button id="selectUpperBtn">⬆ 选上半部</button> <button id="selectLowerBtn">⬇ 选下半部</button> <button id="selectBothBtn">🔗 选整体</button> </div> <div class="btn-group"> <button id="modeTranslate" class="active">↔ 移动</button> <button id="modeRotate">🔄 旋转</button> <button id="modeScale">📐 缩放</button> </div> <div class="btn-group"> <div class="gap-slider"> <label>📏 间隙</label> <input type="range" id="gapSlider" min="0" max="2" step="0.01" value="0.05"> <span class="gap-value" id="gapValue">0.05</span> </div> <button id="alignBtn" class="primary">🔗 自动拼接</button> <button id="mergeBtn" class="merge-btn">🧩 合并成一个</button> <button id="exportBtn" class="warning">📦 导出GLB</button> <button id="resetViewBtn">🎥 重置</button> </div> <div class="status" id="loadStatus"> ⚡ 就绪 | 加载模型 → 调整 → 合并导出 </div> </div> <div class="loading-overlay" id="loadingOverlay"> <div>⏳ 正在处理...</div> </div> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.162.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.162.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from "three"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { TransformControls } from "three/addons/controls/TransformControls.js"; import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js"; // --- 场景 --- const scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a2e); scene.fog = new THREE.FogExp2(0x1a1a2e, 0.006); const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000); camera.position.set(5, 3, 6); const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(devicePixelRatio); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); // OrbitControls const orbitControls = new OrbitControls(camera, renderer.domElement); orbitControls.enableDamping = true; orbitControls.dampingFactor = 0.05; orbitControls.target.set(0, 0.5, 0); orbitControls.update(); // TransformControls const transformControls = new TransformControls(camera, renderer.domElement); transformControls.addEventListener('dragging-changed', (event) => { orbitControls.enabled = !event.value; }); scene.add(transformControls); // 光照 scene.add(new THREE.AmbientLight(0x404060, 0.8)); const mainLight = new THREE.DirectionalLight(0xffffff, 1.5); mainLight.position.set(3, 6, 4); mainLight.castShadow = true; mainLight.shadow.mapSize.set(2048, 2048); mainLight.shadow.camera.near = 0.1; mainLight.shadow.camera.far = 50; mainLight.shadow.bias = -0.0001; scene.add(mainLight); scene.add(new THREE.PointLight(0x6688cc, 0.6, 20, 2)); scene.add(new THREE.PointLight(0xff9966, 0.4, 20, 2)); scene.add(new THREE.PointLight(0x8866ff, 0.5, 20, 2)); // 辅助 const grid = new THREE.GridHelper(8, 20, 0x88aaff, 0x335588); grid.position.y = -1; scene.add(grid); const jointPlaneGeo = new THREE.PlaneGeometry(4, 4); const jointPlaneMat = new THREE.MeshPhongMaterial({ color: 0x4caf50, side: THREE.DoubleSide, transparent: true, opacity: 0.3, emissive: 0x1a3a1a }); const jointPlane = new THREE.Mesh(jointPlaneGeo, jointPlaneMat); jointPlane.rotation.x = -Math.PI / 2; scene.add(jointPlane); // --- 状态 --- let upperModel = null; let lowerModel = null; let upperBounds = null; let lowerBounds = null; let currentGap = 0.05; let selectedTarget = 'both'; // 'upper' | 'lower' | 'both' let mergedGroup = null; // --- 工具 --- function disposeModel(model) { if (!model) return; model.traverse(c => { if (c.isMesh) { c.geometry?.dispose(); if (Array.isArray(c.material)) c.material.forEach(m => m.dispose()); else c.material?.dispose(); } }); model.parent?.remove(model); } function getBounds(model) { const box = new THREE.Box3().setFromObject(model); return { box, min: box.min.clone(), max: box.max.clone(), center: box.getCenter(new THREE.Vector3()), size: box.getSize(new THREE.Vector3()) }; } function setTransformTarget(target) { selectedTarget = target; // 更新按钮状态 document.getElementById('selectUpperBtn').classList.toggle('active', target === 'upper'); document.getElementById('selectLowerBtn').classList.toggle('active', target === 'lower'); document.getElementById('selectBothBtn').classList.toggle('active', target === 'both'); // 更新指示器 document.getElementById('upperIndicator').classList.toggle('selected', target === 'upper'); document.getElementById('lowerIndicator').classList.toggle('selected', target === 'lower'); // 附加 TransformControls if (target === 'upper' && upperModel) { transformControls.attach(upperModel); } else if (target === 'lower' && lowerModel) { transformControls.attach(lowerModel); } else if (target === 'both') { // 如果有合并的组,附加到组;否则分离 if (mergedGroup) { transformControls.attach(mergedGroup); } else { transformControls.detach(); // 两个都选时,分别操作 if (upperModel && lowerModel) { // 创建一个临时父节点 const tempParent = new THREE.Group(); const upperWorldPos = upperModel.position.clone(); const lowerWorldPos = lowerModel.position.clone(); scene.remove(upperModel); scene.remove(lowerModel); tempParent.add(upperModel); tempParent.add(lowerModel); upperModel.position.copy(upperWorldPos); lowerModel.position.copy(lowerWorldPos); scene.add(tempParent); mergedGroup = tempParent; transformControls.attach(mergedGroup); } } } } function updateIndicators() { const upEl = document.getElementById('upperIndicator'); const lowEl = document.getElementById('lowerIndicator'); if (upperModel) { upEl.textContent = '⬆ 上半部: 已加载'; upEl.className = 'model-indicator upper-loaded'; } if (lowerModel) { lowEl.textContent = '⬇ 下半部: 已加载'; lowEl.className = 'model-indicator lower-loaded'; } if (selectedTarget === 'upper') upEl.classList.add('selected'); if (selectedTarget === 'lower') lowEl.classList.add('selected'); } // --- 自动拼接 --- function autoAlign() { if (!upperModel || !lowerModel) { alert("请先加载上下两个模型"); return; } // 如果有合并组,先解散 if (mergedGroup) { while (mergedGroup.children.length) { scene.add(mergedGroup.children[0]); } scene.remove(mergedGroup); mergedGroup = null; } upperBounds = getBounds(upperModel); lowerBounds = getBounds(lowerModel); // 居中对齐 X/Z const cx = (upperBounds.center.x + lowerBounds.center.x) / 2; const cz = (upperBounds.center.z + lowerBounds.center.z) / 2; // 上半部底面在 y=0 upperModel.position.set( upperModel.position.x - upperBounds.center.x, -upperBounds.min.y, upperModel.position.z - upperBounds.center.z ); // 下半部顶面在 y=-gap lowerModel.position.set( lowerModel.position.x - lowerBounds.center.x, -lowerBounds.max.y - currentGap, lowerModel.position.z - lowerBounds.center.z ); upperBounds = getBounds(upperModel); lowerBounds = getBounds(lowerModel); // 更新平面 jointPlane.position.y = -currentGap / 2; const allBounds = new THREE.Box3(); allBounds.expandByObject(upperModel); allBounds.expandByObject(lowerModel); const size = allBounds.getSize(new THREE.Vector3()); jointPlane.scale.set(Math.max(size.x, size.z) * 1.5 / 4, Math.max(size.x, size.z) * 1.5 / 4, 1); // 相机 const center = allBounds.getCenter(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); orbitControls.target.copy(center); camera.position.set(center.x + maxDim * 1.2, center.y + maxDim * 0.8, center.z + maxDim * 1.5); orbitControls.update(); document.getElementById('loadStatus').innerHTML = `✅ 拼接完成 | 上:${upperBounds.size.x.toFixed(1)}x${upperBounds.size.y.toFixed(1)} | 下:${lowerBounds.size.x.toFixed(1)}x${lowerBounds.size.y.toFixed(1)} | 间隙:${currentGap.toFixed(2)}`; } // --- 合并成一个 --- function mergeIntoOne() { if (!upperModel || !lowerModel) { alert("请先加载两个模型"); return; } // 解散合并组 if (mergedGroup) { while (mergedGroup.children.length) { scene.add(mergedGroup.children[0]); } scene.remove(mergedGroup); mergedGroup = null; } // 创建新合并组 const combinedGroup = new THREE.Group(); combinedGroup.name = "CombinedModel"; // 克隆上半部 const upperClone = upperModel.clone(true); upperClone.position.copy(upperModel.position); upperClone.quaternion.copy(upperModel.quaternion); upperClone.scale.copy(upperModel.scale); upperClone.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; } }); // 克隆下半部 const lowerClone = lowerModel.clone(true); lowerClone.position.copy(lowerModel.position); lowerClone.quaternion.copy(lowerModel.quaternion); lowerClone.scale.copy(lowerModel.scale); lowerClone.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; } }); combinedGroup.add(upperClone); combinedGroup.add(lowerClone); // 隐藏原始模型 upperModel.visible = false; lowerModel.visible = false; // 添加到场景 scene.add(combinedGroup); mergedGroup = combinedGroup; // 附加TransformControls transformControls.attach(mergedGroup); selectedTarget = 'both'; setTransformTarget('both'); document.getElementById('loadStatus').innerHTML = "🧩 已合并为一个物体!可用移动/旋转/缩放整体调整,点击导出GLB保存"; } // --- 导出 --- function exportGLB() { const target = mergedGroup || (upperModel && lowerModel ? (() => { // 临时合并导出 const temp = new THREE.Group(); temp.add(upperModel.clone(true)); temp.add(lowerModel.clone(true)); return temp; })() : null); if (!target && upperModel) { // 只有一个模型 exportSingle(upperModel); return; } if (!target && lowerModel) { exportSingle(lowerModel); return; } if (!target) { alert("没有模型可导出"); return; } document.getElementById('loadingOverlay').style.display = 'flex'; const exporter = new GLTFExporter(); exporter.parse(target, (result) => { const blob = result instanceof ArrayBuffer ? new Blob([result], {type: 'application/octet-stream'}) : new Blob([JSON.stringify(result)], {type: 'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `combined_model_${Date.now()}.glb`; a.click(); document.getElementById('loadingOverlay').style.display = 'none'; document.getElementById('loadStatus').innerHTML = "📦 导出成功!"; }, (err) => { alert("导出失败: " + err); document.getElementById('loadingOverlay').style.display = 'none'; }, { binary: true, onlyVisible: false, trs: true }); } function exportSingle(model) { document.getElementById('loadingOverlay').style.display = 'flex'; const exporter = new GLTFExporter(); exporter.parse(model, (result) => { const blob = result instanceof ArrayBuffer ? new Blob([result], {type: 'application/octet-stream'}) : new Blob([JSON.stringify(result)], {type: 'application/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `model_${Date.now()}.glb`; a.click(); document.getElementById('loadingOverlay').style.display = 'none'; }, { binary: true, trs: true }); } // --- 加载 --- async function loadModel(file, type) { return new Promise((resolve, reject) => { const loader = new GLTFLoader(); const url = URL.createObjectURL(file); loader.load(url, (gltf) => { URL.revokeObjectURL(url); gltf.scene.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; } }); resolve(gltf.scene); }, undefined, reject); }); } // --- 事件 --- document.getElementById('upperFile').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; document.getElementById('loadingOverlay').style.display = 'flex'; try { if (upperModel) { disposeModel(upperModel); upperModel = null; } if (mergedGroup) { disposeModel(mergedGroup); mergedGroup = null; } upperModel = await loadModel(file, 'upper'); scene.add(upperModel); updateIndicators(); if (upperModel && lowerModel) autoAlign(); else setTransformTarget('upper'); document.getElementById('loadStatus').innerHTML = `✅ 上半部: ${file.name}`; } catch(err) { document.getElementById('loadStatus').innerHTML = `❌ ${err.message}`; } document.getElementById('loadingOverlay').style.display = 'none'; e.target.value = ''; }); document.getElementById('lowerFile').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; document.getElementById('loadingOverlay').style.display = 'flex'; try { if (lowerModel) { disposeModel(lowerModel); lowerModel = null; } if (mergedGroup) { disposeModel(mergedGroup); mergedGroup = null; } lowerModel = await loadModel(file, 'lower'); scene.add(lowerModel); updateIndicators(); if (upperModel && lowerModel) autoAlign(); else setTransformTarget('lower'); document.getElementById('loadStatus').innerHTML = `✅ 下半部: ${file.name}`; } catch(err) { document.getElementById('loadStatus').innerHTML = `❌ ${err.message}`; } document.getElementById('loadingOverlay').style.display = 'none'; e.target.value = ''; }); document.getElementById('selectUpperBtn').addEventListener('click', () => setTransformTarget('upper')); document.getElementById('selectLowerBtn').addEventListener('click', () => setTransformTarget('lower')); document.getElementById('selectBothBtn').addEventListener('click', () => { if (mergedGroup) { setTransformTarget('both'); } else if (upperModel && lowerModel) { // 自动创建合并组用于操作 const tempGroup = new THREE.Group(); const upPos = upperModel.position.clone(); const lowPos = lowerModel.position.clone(); const upQuat = upperModel.quaternion.clone(); const lowQuat = lowerModel.quaternion.clone(); scene.remove(upperModel); scene.remove(lowerModel); tempGroup.add(upperModel); tempGroup.add(lowerModel); upperModel.position.copy(upPos); lowerModel.position.copy(lowPos); upperModel.quaternion.copy(upQuat); lowerModel.quaternion.copy(lowQuat); scene.add(tempGroup); mergedGroup = tempGroup; setTransformTarget('both'); } }); document.getElementById('modeTranslate').addEventListener('click', () => { transformControls.setMode('translate'); document.getElementById('modeTranslate').classList.add('active'); document.getElementById('modeRotate').classList.remove('active'); document.getElementById('modeScale').classList.remove('active'); }); document.getElementById('modeRotate').addEventListener('click', () => { transformControls.setMode('rotate'); document.getElementById('modeTranslate').classList.remove('active'); document.getElementById('modeRotate').classList.add('active'); document.getElementById('modeScale').classList.remove('active'); }); document.getElementById('modeScale').addEventListener('click', () => { transformControls.setMode('scale'); document.getElementById('modeTranslate').classList.remove('active'); document.getElementById('modeRotate').classList.remove('active'); document.getElementById('modeScale').classList.add('active'); }); document.getElementById('gapSlider').addEventListener('input', (e) => { currentGap = parseFloat(e.target.value); document.getElementById('gapValue').textContent = currentGap.toFixed(2); if (upperModel && lowerModel) { lowerBounds = getBounds(lowerModel); const lowerHeight = lowerBounds.size.y; lowerModel.position.y = -lowerHeight / 2 - currentGap; jointPlane.position.y = -currentGap / 2; } }); document.getElementById('alignBtn').addEventListener('click', autoAlign); document.getElementById('mergeBtn').addEventListener('click', mergeIntoOne); document.getElementById('exportBtn').addEventListener('click', exportGLB); document.getElementById('resetViewBtn').addEventListener('click', () => { const target = mergedGroup || (() => { const g = new THREE.Group(); if (upperModel) g.add(upperModel); if (lowerModel) g.add(lowerModel); return g; })(); const box = new THREE.Box3().setFromObject(target); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); orbitControls.target.copy(center); camera.position.set(center.x + maxDim*1.2, center.y + maxDim*0.8, center.z + maxDim*1.5); orbitControls.update(); }); // 键盘快捷键 window.addEventListener('keydown', (e) => { switch(e.key.toLowerCase()) { case 'w': transformControls.setMode('translate'); break; case 'e': transformControls.setMode('rotate'); break; case 'r': transformControls.setMode('scale'); break; case 'g': if (upperModel && lowerModel) mergeIntoOne(); break; case 'escape': transformControls.detach(); break; } }); window.addEventListener('resize', () => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); // --- 动画 --- function animate() { requestAnimationFrame(animate); orbitControls.update(); const t = Date.now() * 0.002; jointPlane.material.opacity = 0.25 + Math.sin(t) * 0.1; renderer.render(scene, camera); } animate(); console.log("✅ 增强版拼接系统 | 移动W 旋转E 缩放R 合并G"); console.log("📦 支持 GLB/GLTF | 合并导出功能已就绪"); </script> </body> </html>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 23:33:10

同样是花钱,为什么对穷人讲道德,对富人讲能力? 你付出劳动拿薪水,本是天经地义的契约,可一旦套上“感恩”的道德外衣,你谈加薪就是“忘恩负义”,提权益就是“不知好歹”

全是权力用道德包装的隐形规训 目录 全是权力用道德包装的隐形规训 一、守时与考勤:把你的时间拆成商品的温柔驯化 二、安贫与节俭:给底层量身定做的美德安慰剂 三、感恩文化:用情感道德消解你的议价权 四、体面与身份:维护阶层秩序的无形围栏 所有规训,都逃不开这三套统…

作者头像 李华
网站建设 2026/6/13 23:22:08

KMS_VL_ALL_AIO:3分钟搞定Windows和Office智能激活的终极方案

KMS_VL_ALL_AIO&#xff1a;3分钟搞定Windows和Office智能激活的终极方案 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows激活烦恼吗&#xff1f;每次重装系统都要四处寻找激活工具…

作者头像 李华
网站建设 2026/6/13 23:17:15

鼠标平滑滚动革命:让Mac外接鼠标体验超越触控板的终极方案

鼠标平滑滚动革命&#xff1a;让Mac外接鼠标体验超越触控板的终极方案 【免费下载链接】Mos 一个用于在 macOS 上平滑你的鼠标滚动效果或单独设置滚动方向的小工具, 让你的滚轮爽如触控板 | A lightweight tool used to smooth scrolling and set scroll direction independent…

作者头像 李华
网站建设 2026/6/13 23:16:17

嵌入式看门狗原理与应用:从WDOG到EWM的安全设计实战

1. 嵌入式看门狗&#xff1a;守护系统运行的“沉默哨兵”在嵌入式系统的世界里&#xff0c;代码并非总是按部就班地运行。一个意料之外的死循环、一次被错误指针覆盖的跳转、甚至是一段因电磁干扰而“跑飞”的程序&#xff0c;都可能导致整个系统“僵死”。对于工业控制、汽车电…

作者头像 李华
网站建设 2026/6/13 23:15:03

Phoenix钱包部署指南:从测试网到主网的完整迁移流程

Phoenix钱包部署指南&#xff1a;从测试网到主网的完整迁移流程 【免费下载链接】phoenix Phoenix is a self-custodial Bitcoin wallet using Lightning to send/receive payments. 项目地址: https://gitcode.com/gh_mirrors/phoenix2/phoenix Phoenix钱包是一款自托管…

作者头像 李华