Skip to content

Commit a960357

Browse files
author
Michael Hemingway
authored
fix(STLExporter): no positionAttributes + TS (#326)
1 parent fd81ff3 commit a960357

File tree

3 files changed

+198
-187
lines changed

3 files changed

+198
-187
lines changed

src/exporters/STLExporter.d.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/exporters/STLExporter.js

Lines changed: 0 additions & 166 deletions
This file was deleted.

src/exporters/STLExporter.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import {
2+
BufferAttribute,
3+
BufferGeometry,
4+
InterleavedBufferAttribute,
5+
Mesh,
6+
Object3D,
7+
SkinnedMesh,
8+
Vector3,
9+
} from 'three'
10+
11+
export interface STLExporterOptionsBinary {
12+
binary: true
13+
}
14+
15+
export interface STLExporterOptionsString {
16+
binary?: false
17+
}
18+
19+
export interface STLExporterOptions {
20+
binary?: boolean
21+
}
22+
23+
const isMesh = (object: unknown): object is Mesh => (object as any).isMesh
24+
25+
export class STLExporter {
26+
private binary = false
27+
28+
private output: string | DataView = ''
29+
private offset: number = 80 // skip header
30+
31+
private objects: { object3d: Object3D; geometry: BufferGeometry }[] = []
32+
private triangles: number = 0
33+
34+
private vA = new Vector3()
35+
private vB = new Vector3()
36+
private vC = new Vector3()
37+
private cb = new Vector3()
38+
private ab = new Vector3()
39+
private normal = new Vector3()
40+
41+
parse(scene: Object3D, options: STLExporterOptionsBinary): DataView
42+
parse(scene: Object3D, options?: STLExporterOptionsString): string
43+
parse(scene: Object3D, options?: STLExporterOptions): string | DataView {
44+
this.binary = options?.binary !== undefined ? options?.binary : false
45+
46+
scene.traverse((object: Object3D) => {
47+
if (isMesh(object)) {
48+
const geometry = object.geometry
49+
50+
if (!geometry.isBufferGeometry) {
51+
throw new Error('THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.')
52+
}
53+
54+
const index = geometry.index
55+
const positionAttribute = geometry.getAttribute('position') || null
56+
if (!positionAttribute) return
57+
58+
this.triangles += index !== null ? index.count / 3 : positionAttribute.count / 3
59+
60+
this.objects.push({
61+
object3d: object,
62+
geometry: geometry,
63+
})
64+
}
65+
})
66+
67+
if (this.binary) {
68+
const bufferLength = this.triangles * 2 + this.triangles * 3 * 4 * 4 + 80 + 4
69+
const arrayBuffer = new ArrayBuffer(bufferLength)
70+
this.output = new DataView(arrayBuffer)
71+
this.output.setUint32(this.offset, this.triangles, true)
72+
this.offset += 4
73+
} else {
74+
this.output = ''
75+
this.output += 'solid exported\n'
76+
}
77+
78+
for (let i = 0, il = this.objects.length; i < il; i++) {
79+
const object = this.objects[i].object3d
80+
const geometry = this.objects[i].geometry
81+
82+
const index = geometry.index
83+
const positionAttribute = geometry.getAttribute('position')
84+
85+
if (index !== null) {
86+
// indexed geometry
87+
for (let j = 0; j < index.count; j += 3) {
88+
const a = index.getX(j + 0)
89+
const b = index.getX(j + 1)
90+
const c = index.getX(j + 2)
91+
92+
this.writeFace(a, b, c, positionAttribute, object as SkinnedMesh)
93+
}
94+
} else {
95+
// non-indexed geometry
96+
for (let j = 0; j < positionAttribute.count; j += 3) {
97+
const a = j + 0
98+
const b = j + 1
99+
const c = j + 2
100+
101+
this.writeFace(a, b, c, positionAttribute, object as SkinnedMesh)
102+
}
103+
}
104+
}
105+
106+
if (!this.binary) {
107+
this.output += 'endsolid exported\n'
108+
}
109+
110+
return this.output
111+
}
112+
113+
private writeFace(
114+
a: number,
115+
b: number,
116+
c: number,
117+
positionAttribute: BufferAttribute | InterleavedBufferAttribute,
118+
object: SkinnedMesh,
119+
): void {
120+
this.vA.fromBufferAttribute(positionAttribute, a)
121+
this.vB.fromBufferAttribute(positionAttribute, b)
122+
this.vC.fromBufferAttribute(positionAttribute, c)
123+
124+
if (object.isSkinnedMesh) {
125+
const mesh = object as Omit<SkinnedMesh, 'boneTransform' | 'applyBoneTransform'> &
126+
(
127+
| {
128+
boneTransform(index: number, vector: Vector3): Vector3
129+
}
130+
| {
131+
applyBoneTransform(index: number, vector: Vector3): Vector3
132+
}
133+
)
134+
135+
// r151 https://github.com/mrdoob/three.js/pull/25586
136+
if ('applyBoneTransform' in mesh) {
137+
mesh.applyBoneTransform(a, this.vA)
138+
mesh.applyBoneTransform(b, this.vB)
139+
mesh.applyBoneTransform(c, this.vC)
140+
} else {
141+
mesh.boneTransform(a, this.vA)
142+
mesh.boneTransform(b, this.vB)
143+
mesh.boneTransform(c, this.vC)
144+
}
145+
}
146+
147+
this.vA.applyMatrix4(object.matrixWorld)
148+
this.vB.applyMatrix4(object.matrixWorld)
149+
this.vC.applyMatrix4(object.matrixWorld)
150+
151+
this.writeNormal(this.vA, this.vB, this.vC)
152+
153+
this.writeVertex(this.vA)
154+
this.writeVertex(this.vB)
155+
this.writeVertex(this.vC)
156+
157+
if (this.binary && this.output instanceof DataView) {
158+
this.output.setUint16(this.offset, 0, true)
159+
this.offset += 2
160+
} else {
161+
this.output += '\t\tendloop\n'
162+
this.output += '\tendfacet\n'
163+
}
164+
}
165+
166+
private writeNormal(vA: Vector3, vB: Vector3, vC: Vector3): void {
167+
this.cb.subVectors(vC, vB)
168+
this.ab.subVectors(vA, vB)
169+
this.cb.cross(this.ab).normalize()
170+
171+
this.normal.copy(this.cb).normalize()
172+
173+
if (this.binary && this.output instanceof DataView) {
174+
this.output.setFloat32(this.offset, this.normal.x, true)
175+
this.offset += 4
176+
this.output.setFloat32(this.offset, this.normal.y, true)
177+
this.offset += 4
178+
this.output.setFloat32(this.offset, this.normal.z, true)
179+
this.offset += 4
180+
} else {
181+
this.output += `\tfacet normal ${this.normal.x} ${this.normal.y} ${this.normal.z}\n`
182+
this.output += '\t\touter loop\n'
183+
}
184+
}
185+
186+
private writeVertex(vertex: Vector3): void {
187+
if (this.binary && this.output instanceof DataView) {
188+
this.output.setFloat32(this.offset, vertex.x, true)
189+
this.offset += 4
190+
this.output.setFloat32(this.offset, vertex.y, true)
191+
this.offset += 4
192+
this.output.setFloat32(this.offset, vertex.z, true)
193+
this.offset += 4
194+
} else {
195+
this.output += `\t\t\tvertex ${vertex.x} ${vertex.y} ${vertex.z}\n`
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)