stl-exporter.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
  5. */
  6. import { asciiWrite } from '../../mol-io/common/ascii';
  7. import { Box3D } from '../../mol-math/geometry';
  8. import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
  9. import { PLUGIN_VERSION } from '../../mol-plugin/version';
  10. import { RuntimeContext } from '../../mol-task';
  11. import { MeshExporter, AddMeshInput } from './mesh-exporter';
  12. // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  13. const v3fromArray = Vec3.fromArray;
  14. const v3transformMat4 = Vec3.transformMat4;
  15. const v3triangleNormal = Vec3.triangleNormal;
  16. const v3toArray = Vec3.toArray;
  17. // https://www.fabbers.com/tech/STL_Format
  18. export type StlData = {
  19. stl: Uint8Array
  20. }
  21. export class StlExporter extends MeshExporter<StlData> {
  22. readonly fileExtension = 'stl';
  23. private triangleBuffers: ArrayBuffer[] = [];
  24. private triangleCount = 0;
  25. private centerTransform: Mat4;
  26. protected async addMeshWithColors(input: AddMeshInput) {
  27. const { values, isGeoTexture, mode, ctx } = input;
  28. if (mode !== 'triangles') return;
  29. const t = Mat4();
  30. const tmpV = Vec3();
  31. const v1 = Vec3();
  32. const v2 = Vec3();
  33. const v3 = Vec3();
  34. const stride = isGeoTexture ? 4 : 3;
  35. const aTransform = values.aTransform.ref.value;
  36. const instanceCount = values.uInstanceCount.ref.value;
  37. for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
  38. if (ctx.shouldUpdate) await ctx.update({ current: instanceIndex + 1 });
  39. const { vertices, indices, vertexCount, drawCount } = StlExporter.getInstance(input, instanceIndex);
  40. Mat4.fromArray(t, aTransform, instanceIndex * 16);
  41. Mat4.mul(t, this.centerTransform, t);
  42. // position
  43. const vertexArray = new Float32Array(vertexCount * 3);
  44. for (let i = 0; i < vertexCount; ++i) {
  45. v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
  46. v3toArray(tmpV, vertexArray, i * 3);
  47. }
  48. // face
  49. const triangleBuffer = new ArrayBuffer(50 * drawCount);
  50. const dataView = new DataView(triangleBuffer);
  51. for (let i = 0; i < drawCount; i += 3) {
  52. v3fromArray(v1, vertexArray, (isGeoTexture ? i : indices![i]) * 3);
  53. v3fromArray(v2, vertexArray, (isGeoTexture ? i + 1 : indices![i + 1]) * 3);
  54. v3fromArray(v3, vertexArray, (isGeoTexture ? i + 2 : indices![i + 2]) * 3);
  55. v3triangleNormal(tmpV, v1, v2, v3);
  56. const byteOffset = 50 * i;
  57. dataView.setFloat32(byteOffset, tmpV[0], true);
  58. dataView.setFloat32(byteOffset + 4, tmpV[1], true);
  59. dataView.setFloat32(byteOffset + 8, tmpV[2], true);
  60. dataView.setFloat32(byteOffset + 12, v1[0], true);
  61. dataView.setFloat32(byteOffset + 16, v1[1], true);
  62. dataView.setFloat32(byteOffset + 20, v1[2], true);
  63. dataView.setFloat32(byteOffset + 24, v2[0], true);
  64. dataView.setFloat32(byteOffset + 28, v2[1], true);
  65. dataView.setFloat32(byteOffset + 32, v2[2], true);
  66. dataView.setFloat32(byteOffset + 36, v3[0], true);
  67. dataView.setFloat32(byteOffset + 40, v3[1], true);
  68. dataView.setFloat32(byteOffset + 44, v3[2], true);
  69. }
  70. this.triangleBuffers.push(triangleBuffer);
  71. this.triangleCount += drawCount;
  72. }
  73. }
  74. async getData() {
  75. const stl = new Uint8Array(84 + 50 * this.triangleCount);
  76. asciiWrite(stl, `Exported from Mol* ${PLUGIN_VERSION}`);
  77. const dataView = new DataView(stl.buffer);
  78. dataView.setUint32(80, this.triangleCount, true);
  79. let byteOffset = 84;
  80. for (const buffer of this.triangleBuffers) {
  81. stl.set(new Uint8Array(buffer), byteOffset);
  82. byteOffset += buffer.byteLength;
  83. }
  84. return { stl };
  85. }
  86. async getBlob(ctx: RuntimeContext) {
  87. return new Blob([(await this.getData()).stl], { type: 'model/stl' });
  88. }
  89. constructor(boundingBox: Box3D) {
  90. super();
  91. const tmpV = Vec3();
  92. Vec3.add(tmpV, boundingBox.min, boundingBox.max);
  93. Vec3.scale(tmpV, tmpV, -0.5);
  94. this.centerTransform = Mat4.fromTranslation(Mat4(), tmpV);
  95. }
  96. }