1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.io.euclidean.threed.stl;
18
19 import java.io.InputStream;
20 import java.nio.ByteBuffer;
21 import java.nio.charset.Charset;
22 import java.util.Arrays;
23
24 import org.apache.commons.geometry.euclidean.threed.Vector3D;
25 import org.apache.commons.geometry.io.core.internal.GeometryIOUtils;
26 import org.apache.commons.geometry.io.euclidean.threed.FacetDefinitionReader;
27
28
29
30
31 public class BinaryStlFacetDefinitionReader implements FacetDefinitionReader {
32
33
34 private final InputStream in;
35
36
37 private final ByteBuffer triangleBuffer = StlUtils.byteBuffer(StlConstants.BINARY_TRIANGLE_BYTES);
38
39
40 private ByteBuffer header = StlUtils.byteBuffer(StlConstants.BINARY_HEADER_BYTES);
41
42
43 private long triangleTotal;
44
45
46 private long trianglesRead;
47
48
49 private boolean hasReadHeader;
50
51
52
53
54 public BinaryStlFacetDefinitionReader(final InputStream in) {
55 this.in = in;
56 }
57
58
59
60
61
62
63 public ByteBuffer getHeader() {
64 beginRead();
65 return ByteBuffer.wrap(header.array().clone());
66 }
67
68
69
70
71
72
73 public String getHeaderAsString() {
74 return getHeaderAsString(StlConstants.DEFAULT_CHARSET);
75 }
76
77
78
79
80
81
82
83 public String getHeaderAsString(final Charset charset) {
84
85 final String raw = charset.decode(getHeader()).toString();
86
87
88 final StringBuilder sb = new StringBuilder();
89 for (char c : raw.toCharArray()) {
90 if (!Character.isISOControl(c)) {
91 sb.append(c);
92 }
93 }
94
95 return sb.toString();
96 }
97
98
99
100
101
102 public long getNumTriangles() {
103 beginRead();
104 return triangleTotal;
105 }
106
107
108 @Override
109 public BinaryStlFacetDefinition readFacet() {
110 beginRead();
111
112 BinaryStlFacetDefinition facet = null;
113
114 if (trianglesRead < triangleTotal) {
115 facet = readFacetInternal();
116
117 ++trianglesRead;
118 }
119
120 return facet;
121 }
122
123
124 @Override
125 public void close() {
126 GeometryIOUtils.closeUnchecked(in);
127 }
128
129
130
131
132
133 private void beginRead() {
134 if (!hasReadHeader) {
135
136 final int headerBytesRead = GeometryIOUtils.applyAsIntUnchecked(in::read, header.array());
137 if (headerBytesRead < StlConstants.BINARY_HEADER_BYTES) {
138 throw dataNotAvailable("header");
139 }
140
141 header.rewind();
142
143
144 final ByteBuffer triangleBuf = StlUtils.byteBuffer(Integer.BYTES);
145
146 if (fill(triangleBuf) < triangleBuf.capacity()) {
147 throw dataNotAvailable("triangle count");
148 }
149
150 triangleTotal = Integer.toUnsignedLong(triangleBuf.getInt());
151
152 hasReadHeader = true;
153 }
154 }
155
156
157
158
159 private BinaryStlFacetDefinition readFacetInternal() {
160 if (fill(triangleBuffer) < triangleBuffer.capacity()) {
161 throw dataNotAvailable("triangle at index " + trianglesRead);
162 }
163
164 final Vector3D normal = readVector(triangleBuffer);
165 final Vector3D p1 = readVector(triangleBuffer);
166 final Vector3D p2 = readVector(triangleBuffer);
167 final Vector3D p3 = readVector(triangleBuffer);
168
169 final int attr = Short.toUnsignedInt(triangleBuffer.getShort());
170
171 return new BinaryStlFacetDefinition(Arrays.asList(p1, p2, p3), normal, attr);
172 }
173
174
175
176
177
178
179
180 private int fill(final ByteBuffer buf) {
181 int read = GeometryIOUtils.applyAsIntUnchecked(in::read, buf.array());
182 buf.rewind();
183
184 return read;
185 }
186
187
188
189
190
191
192 private Vector3D readVector(final ByteBuffer buf) {
193 final double x = buf.getFloat();
194 final double y = buf.getFloat();
195 final double z = buf.getFloat();
196
197 return Vector3D.of(x, y, z);
198 }
199
200
201
202
203
204
205 private static IllegalStateException dataNotAvailable(final String name) {
206 return GeometryIOUtils.parseError("Failed to read STL " + name + ": data not available");
207 }
208 }