Task 693: .STL File Format
Task 693: .STL File Format
STL File Format Specifications
The .STL (STereoLithography) file format is a standard for representing 3D surface geometry using triangular meshes, commonly used in 3D printing, CAD, and rapid prototyping. It was developed by 3D Systems in 1987. The format has two variants: ASCII (text-based) and binary (more compact and common). There is no official MIME type, but it's often "model/stl". Files use the .stl extension (case-insensitive).
To distinguish variants:
- ASCII: Starts with the ASCII string "solid" (case-sensitive, followed by space).
- Binary: Does not start with "solid"; instead, has an 80-byte header followed by a 4-byte little-endian unsigned integer for triangle count.
ASCII Structure:
- Header line: "solid [name]" (name optional).
- For each triangle: "facet normal nx ny nz" / "outer loop" / "vertex v1x v1y v1z" / "vertex v2x v2y v2z" / "vertex v3x v3y v3z" / "endloop" / "endfacet" (all floats in scientific notation).
- Footer: "endsolid [name]".
Binary Structure:
- Header: 80 bytes (UINT8[80], often ASCII text but ignored; should not start with "solid").
- Triangle count: 4 bytes (UINT32, little-endian).
- For each triangle (50 bytes): Normal (3 x FLOAT32), Vertex1 (3 x FLOAT32), Vertex2 (3 x FLOAT32), Vertex3 (3 x FLOAT32), Attribute (UINT16, usually 0; non-standard for color).
Coordinates are arbitrary units, no scale/color/texture in standard. Vertices ordered counter-clockwise (right-hand rule). Normals point outward.
- List of all the properties of this file format intrinsic to its file system:
- File extension: .stl (or .STL)
- MIME type: model/stl (unofficial)
- File signature/magic: None strict; ASCII starts with "solid ", binary has 80-byte header + UINT32 triangle count
- Variant: ASCII (text) or Binary
- Header: ASCII - model name string; Binary - 80-byte data (often descriptive text)
- Number of triangles/facets: Unsigned integer (ASCII: counted; Binary: explicit UINT32)
- Facet normals: 3 floats per triangle (unit vector)
- Vertices: 3 sets of 3 floats per triangle
- Attribute byte count: Binary only, UINT16 per triangle (usually 0)
- End marker: ASCII - "endsolid [name]"; Binary - none (ends after last triangle)
- Byte order: Binary - little-endian
- Data types: Floats - IEEE 754 single-precision (32-bit); Integers - unsigned
- File size calculation: Binary - 84 + (50 * num_triangles) bytes; ASCII - variable (text)
- Units/scale: Arbitrary/none specified
- Color/texture support: None in standard (non-standard extensions in attribute/header)
These are the core properties derivable from the format structure without computation (e.g., no derived bounding box or volume).
Two direct download links for .STL files:
- https://ozeki.hu/attachments/116/Cube_3d_printing_sample.stl
- https://ozeki.hu/attachments/116/Menger_sponge_sample.stl
Ghost blog embedded HTML JavaScript for drag-and-drop .STL file to dump properties:
Drag and Drop .STL File
- Python class for .STL (handles binary; ASCII noted but simplified as text parse):
import struct
import os
class STLHandler:
def __init__(self, filepath):
self.filepath = filepath
self.variant = None
self.header = None
self.num_triangles = 0
self.triangles = [] # list of dicts: {'normal': (x,y,z), 'vertices': [(x,y,z), (x,y,z), (x,y,z)], 'attr': int or None}
def read(self):
with open(self.filepath, 'rb') as f:
data = f.read()
if data[:5] == b'solid':
self.variant = 'ASCII'
text = data.decode('utf-8', errors='ignore')
lines = text.splitlines()
self.header = lines[0][5:].strip() or '(none)'
# Count facets
self.num_triangles = text.count('facet normal')
# Parse triangles (simple, assume well-formed)
i = 1
while i < len(lines):
if lines[i].strip().startswith('facet normal'):
normal = tuple(map(float, lines[i].split()[2:5]))
v1 = tuple(map(float, lines[i+2].split()[1:4]))
v2 = tuple(map(float, lines[i+3].split()[1:4]))
v3 = tuple(map(float, lines[i+4].split()[1:4]))
self.triangles.append({'normal': normal, 'vertices': [v1, v2, v3], 'attr': None})
i += 7
else:
i += 1
else:
self.variant = 'Binary'
self.header = data[:80].decode('utf-8', errors='ignore').replace('\x00', '')
self.num_triangles = struct.unpack('<I', data[80:84])[0]
offset = 84
for _ in range(self.num_triangles):
normal = struct.unpack('<fff', data[offset:offset+12])
v1 = struct.unpack('<fff', data[offset+12:offset+24])
v2 = struct.unpack('<fff', data[offset+24:offset+36])
v3 = struct.unpack('<fff', data[offset+36:offset+48])
attr = struct.unpack('<H', data[offset+48:offset+50])[0]
self.triangles.append({'normal': normal, 'vertices': [v1, v2, v3], 'attr': attr})
offset += 50
def print_properties(self):
print(f'File extension: .stl')
print(f'MIME type: model/stl')
print(f'File signature/magic: {"solid " if self.variant == "ASCII" else "None (binary)"}')
print(f'Variant: {self.variant}')
print(f'Header/Model Name: {self.header}')
print(f'Number of triangles: {self.num_triangles}')
print(f'Attribute byte count: {"N/A (ASCII)" if self.variant == "ASCII" else "UINT16 per triangle (usually 0)"}')
print(f'End marker: {"endsolid [name]" if self.variant == "ASCII" else "None"}')
print(f'Byte order: {"N/A (text)" if self.variant == "ASCII" else "Little-endian"}')
print(f'Data types: Floats - IEEE 754 single; Integers - unsigned')
print(f'File size calculation: {os.path.getsize(self.filepath)} bytes (actual)')
print(f'Units/scale: Arbitrary/None')
print(f'Color/texture support: None')
print('Facet data:')
for i, tri in enumerate(self.triangles):
print(f'Triangle {i+1}: Normal={tri["normal"]}, Vertices={tri["vertices"]}, Attr={tri["attr"]}')
def write(self, output_path):
if self.variant == 'ASCII':
with open(output_path, 'w') as f:
f.write(f'solid {self.header}\n')
for tri in self.triangles:
n = tri['normal']
v = tri['vertices']
f.write(f'facet normal {n[0]:e} {n[1]:e} {n[2]:e}\n')
f.write('outer loop\n')
f.write(f'vertex {v[0][0]:e} {v[0][1]:e} {v[0][2]:e}\n')
f.write(f'vertex {v[1][0]:e} {v[1][1]:e} {v[1][2]:e}\n')
f.write(f'vertex {v[2][0]:e} {v[2][1]:e} {v[2][2]:e}\n')
f.write('endloop\n')
f.write('endfacet\n')
f.write(f'endsolid {self.header}\n')
else:
with open(output_path, 'wb') as f:
header_bytes = self.header.encode('utf-8')[:80].ljust(80, b'\x00')
f.write(header_bytes)
f.write(struct.pack('<I', self.num_triangles))
for tri in self.triangles:
n = tri['normal']
v = tri['vertices']
attr = tri['attr'] or 0
f.write(struct.pack('<ffffffffffffH', n[0], n[1], n[2], v[0][0], v[0][1], v[0][2], v[1][0], v[1][1], v[1][2], v[2][0], v[2][1], v[2][2], attr))
# Example usage:
# stl = STLHandler('path/to/file.stl')
# stl.read()
# stl.print_properties()
# stl.write('path/to/output.stl')
- Java class for .STL (handles binary; ASCII simplified):
import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;
public class STLHandler {
private String filepath;
private String variant;
private String header;
private int numTriangles;
private List<Triangle> triangles = new ArrayList<>();
static class Triangle {
float[] normal = new float[3];
float[][] vertices = new float[3][3];
Integer attr; // null for ASCII
}
public STLHandler(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filepath));
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
if (new String(data, 0, 5).equals("solid")) {
variant = "ASCII";
String text = new String(data);
String[] lines = text.split("\n");
header = lines[0].substring(5).trim().isEmpty() ? "(none)" : lines[0].substring(5).trim();
numTriangles = (int) Arrays.stream(lines).filter(l -> l.trim().startsWith("facet normal")).count();
// Parse triangles
for (int i = 1; i < lines.length; i++) {
if (lines[i].trim().startsWith("facet normal")) {
Triangle tri = new Triangle();
String[] parts = lines[i].split("\\s+");
tri.normal[0] = Float.parseFloat(parts[2]);
tri.normal[1] = Float.parseFloat(parts[3]);
tri.normal[2] = Float.parseFloat(parts[4]);
parts = lines[++i + 1].split("\\s+"); // vertex 1
tri.vertices[0][0] = Float.parseFloat(parts[1]);
tri.vertices[0][1] = Float.parseFloat(parts[2]);
tri.vertices[0][2] = Float.parseFloat(parts[3]);
parts = lines[++i].split("\\s+"); // vertex 2
tri.vertices[1][0] = Float.parseFloat(parts[1]);
tri.vertices[1][1] = Float.parseFloat(parts[2]);
tri.vertices[1][2] = Float.parseFloat(parts[3]);
parts = lines[++i].split("\\s+"); // vertex 3
tri.vertices[2][0] = Float.parseFloat(parts[1]);
tri.vertices[2][1] = Float.parseFloat(parts[2]);
tri.vertices[2][2] = Float.parseFloat(parts[3]);
triangles.add(tri);
i += 3; // skip endloop, endfacet
}
}
} else {
variant = "Binary";
header = new String(data, 0, 80).replace("\0", "");
buffer.position(80);
numTriangles = buffer.getInt();
for (int i = 0; i < numTriangles; i++) {
Triangle tri = new Triangle();
tri.normal[0] = buffer.getFloat();
tri.normal[1] = buffer.getFloat();
tri.normal[2] = buffer.getFloat();
tri.vertices[0][0] = buffer.getFloat();
tri.vertices[0][1] = buffer.getFloat();
tri.vertices[0][2] = buffer.getFloat();
tri.vertices[1][0] = buffer.getFloat();
tri.vertices[1][1] = buffer.getFloat();
tri.vertices[1][2] = buffer.getFloat();
tri.vertices[2][0] = buffer.getFloat();
tri.vertices[2][1] = buffer.getFloat();
tri.vertices[2][2] = buffer.getFloat();
tri.attr = (int) buffer.getShort();
triangles.add(tri);
}
}
}
public void printProperties() {
System.out.println("File extension: .stl");
System.out.println("MIME type: model/stl");
System.out.println("File signature/magic: " + (variant.equals("ASCII") ? "solid " : "None (binary)"));
System.out.println("Variant: " + variant);
System.out.println("Header/Model Name: " + header);
System.out.println("Number of triangles: " + numTriangles);
System.out.println("Attribute byte count: " + (variant.equals("ASCII") ? "N/A (ASCII)" : "UINT16 per triangle (usually 0)"));
System.out.println("End marker: " + (variant.equals("ASCII") ? "endsolid [name]" : "None"));
System.out.println("Byte order: " + (variant.equals("ASCII") ? "N/A (text)" : "Little-endian"));
System.out.println("Data types: Floats - IEEE 754 single; Integers - unsigned");
try {
System.out.println("File size calculation: " + Files.size(Paths.get(filepath)) + " bytes (actual)");
} catch (IOException e) {
System.out.println("File size: Unknown");
}
System.out.println("Units/scale: Arbitrary/None");
System.out.println("Color/texture support: None");
System.out.println("Facet data:");
for (int i = 0; i < triangles.size(); i++) {
Triangle tri = triangles.get(i);
System.out.printf("Triangle %d: Normal=[%.6f, %.6f, %.6f], Vertices=[[%.6f, %.6f, %.6f], [%.6f, %.6f, %.6f], [%.6f, %.6f, %.6f]], Attr=%s%n",
i+1, tri.normal[0], tri.normal[1], tri.normal[2],
tri.vertices[0][0], tri.vertices[0][1], tri.vertices[0][2],
tri.vertices[1][0], tri.vertices[1][1], tri.vertices[1][2],
tri.vertices[2][0], tri.vertices[2][1], tri.vertices[2][2],
tri.attr == null ? "None" : tri.attr);
}
}
public void write(String outputPath) throws IOException {
if (variant.equals("ASCII")) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) {
writer.write("solid " + header + "\n");
for (Triangle tri : triangles) {
writer.write(String.format("facet normal %e %e %e\n", tri.normal[0], tri.normal[1], tri.normal[2]));
writer.write("outer loop\n");
writer.write(String.format("vertex %e %e %e\n", tri.vertices[0][0], tri.vertices[0][1], tri.vertices[0][2]));
writer.write(String.format("vertex %e %e %e\n", tri.vertices[1][0], tri.vertices[1][1], tri.vertices[1][2]));
writer.write(String.format("vertex %e %e %e\n", tri.vertices[2][0], tri.vertices[2][1], tri.vertices[2][2]));
writer.write("endloop\n");
writer.write("endfacet\n");
}
writer.write("endsolid " + header + "\n");
}
} else {
ByteBuffer buffer = ByteBuffer.allocate(84 + 50 * numTriangles).order(ByteOrder.LITTLE_ENDIAN);
byte[] headerBytes = header.getBytes("UTF-8");
buffer.put(headerBytes, 0, Math.min(80, headerBytes.length));
buffer.position(80);
buffer.putInt(numTriangles);
for (Triangle tri : triangles) {
buffer.putFloat(tri.normal[0]);
buffer.putFloat(tri.normal[1]);
buffer.putFloat(tri.normal[2]);
buffer.putFloat(tri.vertices[0][0]);
buffer.putFloat(tri.vertices[0][1]);
buffer.putFloat(tri.vertices[0][2]);
buffer.putFloat(tri.vertices[1][0]);
buffer.putFloat(tri.vertices[1][1]);
buffer.putFloat(tri.vertices[1][2]);
buffer.putFloat(tri.vertices[2][0]);
buffer.putFloat(tri.vertices[2][1]);
buffer.putFloat(tri.vertices[2][2]);
buffer.putShort(tri.attr.shortValue());
}
try (FileChannel channel = new FileOutputStream(outputPath).getChannel()) {
channel.write(buffer.flip());
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// STLHandler stl = new STLHandler("path/to/file.stl");
// stl.read();
// stl.printProperties();
// stl.write("path/to/output.stl");
// }
}
- JavaScript class for .STL (Node.js style, handles binary; ASCII simplified; use fs module):
const fs = require('fs');
class STLHandler {
constructor(filepath) {
this.filepath = filepath;
this.variant = null;
this.header = null;
this.numTriangles = 0;
this.triangles = []; // array of {normal: [x,y,z], vertices: [[x,y,z],[x,y,z],[x,y,z]], attr: number or null}
}
read() {
const data = fs.readFileSync(this.filepath);
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const first5 = new TextDecoder().decode(data.slice(0, 5));
if (first5 === 'solid') {
this.variant = 'ASCII';
const text = data.toString('utf-8');
const lines = text.split('\n');
this.header = lines[0].substring(5).trim() || '(none)';
this.numTriangles = (text.match(/facet normal/g) || []).length;
// Parse triangles
let i = 1;
while (i < lines.length) {
if (lines[i].trim().startsWith('facet normal')) {
const normal = lines[i].split(/\s+/).slice(2,5).map(parseFloat);
const v1 = lines[i+2].split(/\s+/).slice(1,4).map(parseFloat);
const v2 = lines[i+3].split(/\s+/).slice(1,4).map(parseFloat);
const v3 = lines[i+4].split(/\s+/).slice(1,4).map(parseFloat);
this.triangles.push({normal, vertices: [v1, v2, v3], attr: null});
i += 7;
} else {
i++;
}
}
} else {
this.variant = 'Binary';
this.header = new TextDecoder().decode(data.slice(0,80)).replace(/\0/g, '');
this.numTriangles = view.getUint32(80, true);
let offset = 84;
for (let i = 0; i < this.numTriangles; i++) {
const normal = [view.getFloat32(offset, true), view.getFloat32(offset+4, true), view.getFloat32(offset+8, true)];
const v1 = [view.getFloat32(offset+12, true), view.getFloat32(offset+16, true), view.getFloat32(offset+20, true)];
const v2 = [view.getFloat32(offset+24, true), view.getFloat32(offset+28, true), view.getFloat32(offset+32, true)];
const v3 = [view.getFloat32(offset+36, true), view.getFloat32(offset+40, true), view.getFloat32(offset+44, true)];
const attr = view.getUint16(offset+48, true);
this.triangles.push({normal, vertices: [v1, v2, v3], attr});
offset += 50;
}
}
}
printProperties() {
console.log(`File extension: .stl`);
console.log(`MIME type: model/stl`);
console.log(`File signature/magic: ${this.variant === 'ASCII' ? 'solid ' : 'None (binary)'}`);
console.log(`Variant: ${this.variant}`);
console.log(`Header/Model Name: ${this.header}`);
console.log(`Number of triangles: ${this.numTriangles}`);
console.log(`Attribute byte count: ${this.variant === 'ASCII' ? 'N/A (ASCII)' : 'UINT16 per triangle (usually 0)'}`);
console.log(`End marker: ${this.variant === 'ASCII' ? 'endsolid [name]' : 'None'}`);
console.log(`Byte order: ${this.variant === 'ASCII' ? 'N/A (text)' : 'Little-endian'}`);
console.log(`Data types: Floats - IEEE 754 single; Integers - unsigned`);
console.log(`File size calculation: ${fs.statSync(this.filepath).size} bytes (actual)`);
console.log(`Units/scale: Arbitrary/None`);
console.log(`Color/texture support: None`);
console.log('Facet data:');
this.triangles.forEach((tri, i) => {
console.log(`Triangle ${i+1}: Normal=${JSON.stringify(tri.normal)}, Vertices=${JSON.stringify(tri.vertices)}, Attr=${tri.attr}`);
});
}
write(outputPath) {
if (this.variant === 'ASCII') {
let content = `solid ${this.header}\n`;
this.triangles.forEach(tri => {
const n = tri.normal;
const v = tri.vertices;
content += `facet normal ${n[0].toExponential()} ${n[1].toExponential()} ${n[2].toExponential()}\n`;
content += 'outer loop\n';
content += `vertex ${v[0][0].toExponential()} ${v[0][1].toExponential()} ${v[0][2].toExponential()}\n`;
content += `vertex ${v[1][0].toExponential()} ${v[1][1].toExponential()} ${v[1][2].toExponential()}\n`;
content += `vertex ${v[2][0].toExponential()} ${v[2][1].toExponential()} ${v[2][2].toExponential()}\n`;
content += 'endloop\n';
content += 'endfacet\n';
});
content += `endsolid ${this.header}\n`;
fs.writeFileSync(outputPath, content);
} else {
const buffer = Buffer.alloc(84 + 50 * this.numTriangles);
const headerBytes = Buffer.from(this.header, 'utf-8').slice(0, 80);
headerBytes.copy(buffer, 0);
buffer.writeUint32LE(this.numTriangles, 80);
let offset = 84;
this.triangles.forEach(tri => {
const n = tri.normal;
const v = tri.vertices;
const attr = tri.attr || 0;
buffer.writeFloatLE(n[0], offset); offset += 4;
buffer.writeFloatLE(n[1], offset); offset += 4;
buffer.writeFloatLE(n[2], offset); offset += 4;
buffer.writeFloatLE(v[0][0], offset); offset += 4;
buffer.writeFloatLE(v[0][1], offset); offset += 4;
buffer.writeFloatLE(v[0][2], offset); offset += 4;
buffer.writeFloatLE(v[1][0], offset); offset += 4;
buffer.writeFloatLE(v[1][1], offset); offset += 4;
buffer.writeFloatLE(v[1][2], offset); offset += 4;
buffer.writeFloatLE(v[2][0], offset); offset += 4;
buffer.writeFloatLE(v[2][1], offset); offset += 4;
buffer.writeFloatLE(v[2][2], offset); offset += 4;
buffer.writeUint16LE(attr, offset); offset += 2;
});
fs.writeFileSync(outputPath, buffer);
}
}
}
// Example usage:
// const stl = new STLHandler('path/to/file.stl');
// stl.read();
// stl.printProperties();
// stl.write('path/to/output.stl');
- C++ class for .STL (handles binary; ASCII simplified using strings):
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstring>
#include <iomanip>
struct Triangle {
float normal[3];
float vertices[3][3];
unsigned short attr; // 0 for ASCII (not used)
};
class STLHandler {
private:
std::string filepath;
std::string variant;
std::string header;
unsigned int numTriangles;
std::vector<Triangle> triangles;
bool isAscii; // Helper
public:
STLHandler(const std::string& fp) : filepath(fp), numTriangles(0), isAscii(false) {}
void read() {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error opening file\n";
return;
}
size_t size = file.tellg();
file.seekg(0);
std::vector<char> data(size);
file.read(data.data(), size);
file.close();
if (std::strncmp(data.data(), "solid", 5) == 0) {
variant = "ASCII";
isAscii = true;
std::string text(data.begin(), data.end());
size_t pos = text.find(' ', 0);
header = (pos != std::string::npos) ? text.substr(pos + 1, text.find('\n') - pos - 1) : "(none)";
// Count and parse triangles
pos = 0;
while ((pos = text.find("facet normal", pos)) != std::string::npos) {
Triangle tri;
std::sscanf(text.c_str() + pos + 12, "%f %f %f", &tri.normal[0], &tri.normal[1], &tri.normal[2]);
pos = text.find("vertex", pos);
std::sscanf(text.c_str() + pos + 6, "%f %f %f", &tri.vertices[0][0], &tri.vertices[0][1], &tri.vertices[0][2]);
pos = text.find("vertex", pos + 6);
std::sscanf(text.c_str() + pos + 6, "%f %f %f", &tri.vertices[1][0], &tri.vertices[1][1], &tri.vertices[1][2]);
pos = text.find("vertex", pos + 6);
std::sscanf(text.c_str() + pos + 6, "%f %f %f", &tri.vertices[2][0], &tri.vertices[2][1], &tri.vertices[2][2]);
tri.attr = 0; // unused
triangles.push_back(tri);
pos = text.find("endfacet", pos) + 8;
}
numTriangles = triangles.size();
} else {
variant = "Binary";
isAscii = false;
header.assign(data.data(), 80);
header.erase(std::remove(header.begin(), header.end(), '\0'), header.end());
memcpy(&numTriangles, data.data() + 80, 4); // little-endian assumed
size_t offset = 84;
for (unsigned int i = 0; i < numTriangles; ++i) {
Triangle tri;
memcpy(tri.normal, data.data() + offset, 12);
offset += 12;
memcpy(tri.vertices[0], data.data() + offset, 12);
offset += 12;
memcpy(tri.vertices[1], data.data() + offset, 12);
offset += 12;
memcpy(tri.vertices[2], data.data() + offset, 12);
offset += 12;
memcpy(&tri.attr, data.data() + offset, 2);
offset += 2;
triangles.push_back(tri);
}
}
}
void printProperties() {
std::cout << "File extension: .stl" << std::endl;
std::cout << "MIME type: model/stl" << std::endl;
std::cout << "File signature/magic: " << (isAscii ? "solid " : "None (binary)") << std::endl;
std::cout << "Variant: " << variant << std::endl;
std::cout << "Header/Model Name: " << header << std::endl;
std::cout << "Number of triangles: " << numTriangles << std::endl;
std::cout << "Attribute byte count: " << (isAscii ? "N/A (ASCII)" : "UINT16 per triangle (usually 0)") << std::endl;
std::cout << "End marker: " << (isAscii ? "endsolid [name]" : "None") << std::endl;
std::cout << "Byte order: " << (isAscii ? "N/A (text)" : "Little-endian") << std::endl;
std::cout << "Data types: Floats - IEEE 754 single; Integers - unsigned" << std::endl;
std::ifstream file(filepath, std::ios::ate);
std::cout << "File size calculation: " << file.tellg() << " bytes (actual)" << std::endl;
file.close();
std::cout << "Units/scale: Arbitrary/None" << std::endl;
std::cout << "Color/texture support: None" << std::endl;
std::cout << "Facet data:" << std::endl;
for (size_t i = 0; i < triangles.size(); ++i) {
auto& tri = triangles[i];
std::cout << "Triangle " << i+1 << ": Normal=["
<< tri.normal[0] << ", " << tri.normal[1] << ", " << tri.normal[2] << "], Vertices=[["
<< tri.vertices[0][0] << ", " << tri.vertices[0][1] << ", " << tri.vertices[0][2] << "], ["
<< tri.vertices[1][0] << ", " << tri.vertices[1][1] << ", " << tri.vertices[1][2] << "], ["
<< tri.vertices[2][0] << ", " << tri.vertices[2][1] << ", " << tri.vertices[2][2] << "]], Attr="
<< (isAscii ? "None" : std::to_string(tri.attr)) << std::endl;
}
}
void write(const std::string& outputPath) {
std::ofstream file(outputPath, std::ios::binary);
if (!file) {
std::cerr << "Error writing file\n";
return;
}
if (isAscii) {
file << "solid " << header << "\n";
for (const auto& tri : triangles) {
file << "facet normal " << std::scientific << tri.normal[0] << " " << tri.normal[1] << " " << tri.normal[2] << "\n";
file << "outer loop\n";
file << "vertex " << tri.vertices[0][0] << " " << tri.vertices[0][1] << " " << tri.vertices[0][2] << "\n";
file << "vertex " << tri.vertices[1][0] << " " << tri.vertices[1][1] << " " << tri.vertices[1][2] << "\n";
file << "vertex " << tri.vertices[2][0] << " " << tri.vertices[2][1] << " " << tri.vertices[2][2] << "\n";
file << "endloop\n";
file << "endfacet\n";
}
file << "endsolid " << header << "\n";
} else {
char headerBuf[80] = {0};
std::strncpy(headerBuf, header.c_str(), 79);
file.write(headerBuf, 80);
file.write(reinterpret_cast<const char*>(&numTriangles), 4);
for (const auto& tri : triangles) {
file.write(reinterpret_cast<const char*>(tri.normal), 12);
file.write(reinterpret_cast<const char*>(tri.vertices[0]), 12);
file.write(reinterpret_cast<const char*>(tri.vertices[1]), 12);
file.write(reinterpret_cast<const char*>(tri.vertices[2]), 12);
file.write(reinterpret_cast<const char*>(&tri.attr), 2);
}
}
file.close();
}
};
// Example usage:
// int main() {
// STLHandler stl("path/to/file.stl");
// stl.read();
// stl.printProperties();
// stl.write("path/to/output.stl");
// return 0;
// }