/** * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Sukolsak Sakshuwong */ import { asciiWrite } from '../../mol-io/common/ascii'; import { Box3D } from '../../mol-math/geometry'; import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; import { PLUGIN_VERSION } from '../../mol-plugin/version'; import { RuntimeContext } from '../../mol-task'; import { MeshExporter, AddMeshInput } from './mesh-exporter'; // avoiding namespace lookup improved performance in Chrome (Aug 2020) const v3fromArray = Vec3.fromArray; const v3transformMat4 = Vec3.transformMat4; const v3triangleNormal = Vec3.triangleNormal; const v3toArray = Vec3.toArray; // https://www.fabbers.com/tech/STL_Format export type StlData = { stl: Uint8Array } export class StlExporter extends MeshExporter { readonly fileExtension = 'stl'; private triangleBuffers: ArrayBuffer[] = []; private triangleCount = 0; private centerTransform: Mat4; protected async addMeshWithColors(input: AddMeshInput) { const { values, isGeoTexture, ctx } = input; const t = Mat4(); const tmpV = Vec3(); const v1 = Vec3(); const v2 = Vec3(); const v3 = Vec3(); const stride = isGeoTexture ? 4 : 3; const aTransform = values.aTransform.ref.value; const instanceCount = values.uInstanceCount.ref.value; for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 }); const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex); Mat4.fromArray(t, aTransform, instanceIndex * 16); Mat4.mul(t, this.centerTransform, t); // position const vertexArray = new Float32Array(vertexCount * 3); for (let i = 0; i < vertexCount; ++i) { v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t); v3toArray(tmpV, vertexArray, i * 3); } // face const triangleBuffer = new ArrayBuffer(50 * drawCount); const dataView = new DataView(triangleBuffer); for (let i = 0; i < drawCount; i += 3) { v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3); v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3); v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3); v3triangleNormal(tmpV, v1, v2, v3); const byteOffset = 50 * i; dataView.setFloat32(byteOffset, tmpV[0], true); dataView.setFloat32(byteOffset + 4, tmpV[1], true); dataView.setFloat32(byteOffset + 8, tmpV[2], true); dataView.setFloat32(byteOffset + 12, v1[0], true); dataView.setFloat32(byteOffset + 16, v1[1], true); dataView.setFloat32(byteOffset + 20, v1[2], true); dataView.setFloat32(byteOffset + 24, v2[0], true); dataView.setFloat32(byteOffset + 28, v2[1], true); dataView.setFloat32(byteOffset + 32, v2[2], true); dataView.setFloat32(byteOffset + 36, v3[0], true); dataView.setFloat32(byteOffset + 40, v3[1], true); dataView.setFloat32(byteOffset + 44, v3[2], true); } this.triangleBuffers.push(triangleBuffer); this.triangleCount += drawCount; } } async getData() { const stl = new Uint8Array(84 + 50 * this.triangleCount); asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`); const dataView = new DataView(stl.buffer); dataView.setUint32(80, this.triangleCount, true); let byteOffset = 84; for (const buffer of this.triangleBuffers) { stl.set(new Uint8Array(buffer), byteOffset); byteOffset += buffer.byteLength; } return { stl }; } async getBlob(ctx: RuntimeContext) { return new Blob([(await this.getData()).stl], { type: 'model/stl' }); } constructor(boundingBox: Box3D) { super(); const tmpV = Vec3(); Vec3.add(tmpV, boundingBox.min, boundingBox.max); Vec3.scale(tmpV, tmpV, -0.5); this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV); } }