import { Application } from '../../src' import { ExampleSystem } from './example-system' import pako from 'pako' import vcControl from './vcControl' import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js' import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js' import * as THREE from 'three' const el: any = document.getElementById('app') const app = new Application(el) app.start() const system = new ExampleSystem() app.addSystem(system) // const init = async () => { vcControl.proxyUrl = '/api' // } document.getElementById('rotate1')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 0) }) document.getElementById('rotate2')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 1) }) document.getElementById('rotate3')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 2) }) document.getElementById('rotate4')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 3) }) document.getElementById('rotate5')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 4) }) document.getElementById('rotate6')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('rotate', 5) }) document.getElementById('reset1')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 0) }) document.getElementById('reset2')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 1) }) document.getElementById('reset3')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 2) }) document.getElementById('reset4')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 3) }) document.getElementById('reset5')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 4) }) document.getElementById('reset6')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('reset', 5) }) document.getElementById('fix')?.addEventListener('click', () => { const { EventManager } = app EventManager.publish('fix') }) function arrayBufferToBase64(arrayBuffer) { var binaryString = '' for (var i = 0; i < arrayBuffer.length; i++) { binaryString += String.fromCharCode(arrayBuffer[i]) } var base64String = btoa(binaryString) return base64String } const saveModeToPako = async (model, name) => { const compressed = pako.deflate(JSON.stringify(model)) // const str = arrayBufferToBase64(compressed) // 将Uint8Array数据写入Blob对象 const blob = new Blob([compressed]) // 生成下载链接 const url = URL.createObjectURL(blob) // 创建一个隐藏的a标签 const a = document.createElement('a') a.style.display = 'none' a.href = url a.download = `${name}.pako` // 设置下载文件的名称 // 将a标签添加到页面中 document.body.appendChild(a) // 模拟点击a标签进行下载 a.click() // 释放URL对象 URL.revokeObjectURL(url) } const saveModeToBase64 = async (model, name) => { const compressed = pako.deflate(JSON.stringify(model)) const str = arrayBufferToBase64(compressed) // 将Uint8Array数据写入Blob对象 const blob = new Blob([str]) // 生成下载链接 const url = URL.createObjectURL(blob) // 创建一个隐藏的a标签 const a = document.createElement('a') a.style.display = 'none' a.href = url a.download = `${name}.base64` // 设置下载文件的名称 // 将a标签添加到页面中 document.body.appendChild(a) // 模拟点击a标签进行下载 a.click() // 释放URL对象 URL.revokeObjectURL(url) } async function downloadGLB(model, name = 'model', directoryHandle = null) { const exporter = new GLTFExporter() const options = { trs: true, // 是否保留位置、旋转、缩放信息 animations: [], // 导出的动画 binary: true, // 是否以二进制格式输出 embedImages: true, //是否嵌入贴图 onlyVisible: true, //是否只导出可见物体 forcePowerOfTwoTextures: true, includeCustomMaterials: true, //指定是否包含自定义材质 includeCustomAttributes: true, // 指定是否包含自定义属性 includeCustomTextures: true, // 指定是否包含自定义纹理 includeCustomSamplers: true, // 指定是否包含自定义采样器 includeCustomImages: true, // 指定是否包含自定义图像 includeCustomTechniques: true, // 指定是否包含自定义技术 includeCustomMaterialsCommon: true, //指定是否包含自定义 MaterialsCommon includeCustomMeshes: true, //指定是否包含自定义网格 includeCustomSkins: true, //指定是否包含自定义皮肤 includeCustomNodes: true, // 指定是否包含自定义节点 includeCustomGeometries: true, //指定是否包含自定义几何体 includeCustomPrograms: true, // 指定是否包含自定义程序 includeCustomShaders: true, //指定是否包含自定义着色器 includeCustomExtensions: true //指定是否包含自定义扩展。如果设置为true,则会包含在导出中定义的自定义GLTF扩展 } return new Promise((resolve, reject) => { exporter.parse( model, async (result) => { try { if (directoryHandle && 'showDirectoryPicker' in window) { // 使用 File System Access API 保存到选定文件夹 const fileHandle = await directoryHandle.getFileHandle(`${name}.glb`, { create: true }) const writable = await fileHandle.createWritable() await writable.write(result) await writable.close() } else { // 回退到传统下载方式 const blob = new Blob([result], { type: 'application/octet-stream' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `${name}.glb` document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } resolve(result) } catch (error) { reject(error) } }, (err) => { reject(err) }, options ) }) } document.getElementById('generate')?.addEventListener('click', () => { const { SystemManager, EntityManager } = app const model = (SystemManager.getSystem(ExampleSystem) as any).fixObject.clone() model.position.set(0, 0, 0) saveModeToPako(model, 'cr20') saveModeToBase64(model, 'cr20') downloadGLB(model) }) export const saveModelToStl = async (object: THREE.Object3D, name = 'model', directoryHandle = null) => { const group = new THREE.Group() object.updateMatrixWorld(true) object.traverse((obj: any) => { if (obj.isMesh && obj.geometry) { const cloneGeometry = obj.geometry.clone() cloneGeometry.applyMatrix4(obj.matrixWorld) cloneGeometry.applyMatrix4(new THREE.Matrix4().makeScale(1 / 1000, 1 / 1000, 1 / 1000)) group.add(new THREE.Mesh(cloneGeometry)) } }) const exporter = new STLExporter() const result = exporter.parse(group, { binary: true }) // 导出二进制格式 if (directoryHandle && 'showDirectoryPicker' in window) { // 使用 File System Access API 保存到选定文件夹 const fileHandle = await directoryHandle.getFileHandle(`${name}.stl`, { create: true }) const writable = await fileHandle.createWritable() await writable.write(result as any) await writable.close() } else { // 回退到传统下载方式 const blob = new Blob([result as any], { type: 'application/octet-stream' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `${name}.stl` document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } } document.getElementById('generateSimModels')?.addEventListener('click', async () => { const { SystemManager, EntityManager, scene } = app const model = (SystemManager.getSystem(ExampleSystem) as any).fixObject.clone() model.position.set(0, 0, 0) console.log(model) // 选择文件夹 let directoryHandle = null if ('showDirectoryPicker' in window) { try { directoryHandle = await (window as any).showDirectoryPicker() console.log('选择的文件夹:', directoryHandle.name) } catch (error) { console.log('用户取消了文件夹选择或浏览器不支持') // 如果用户取消选择或浏览器不支持,继续使用传统下载方式 } } const names = ['base', 'J1', 'J2', 'J3', 'J4', 'J5', 'J6', 'TOOL'].reverse() const exportNames = ['base', 'joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6', 'tool'].reverse() for (let i = 0; i < names.length; i++) { const name = names[i] let group = model.getObjectByName(name) group.parent.remove(group) group.updateMatrixWorld(true) group.applyMatrix4(group.matrixWorld.clone().invert()) group.position.set(0, 0, 0) group.traverse((obj: any) => { if (obj.name.startsWith(name)) { obj.name = obj.name.replace(name, exportNames[i]) } }) if (i === 0) { continue } else { await downloadGLB(group, exportNames[i], directoryHandle) await new Promise(resolve => setTimeout(resolve, 500)) await saveModelToStl(group, exportNames[i], directoryHandle) await new Promise(resolve => setTimeout(resolve, 500)) } } })