😸

StLファイルをC++で読み込んでグラフィック表示させたい

2024/01/14に公開

ここでのStLは標準ライブラリではなく,3Dオブジェクトを三角形と法線の集合で表したファイル形式のことを言うの注意.

StLファイルについて

StLファイルには,asciiフォーマットとバイナリフォーマットの形式がある

ASCIIフォーマット

ASCIIフォーマットはファイルの中身を見てもらえばわかる通り,一行目にsolid ASCIIという文字列があり,二行目から法線,三角形の頂点座標が羅列されている.

ASCIIフォーマットのファイルの中身の例↓

solid ASCII
  facet normal 1.000000e+00 0.000000e+00 0.000000e+00
    outer loop
      vertex   -2.000000e+00 1.748350e+00 3.398087e+00
      vertex   -2.000000e+00 1.606920e+00 3.500886e+00
      vertex   -2.000000e+00 1.696376e+00 3.330702e+00
    endloop
  endfacet
  facet normal 1.000000e+00 0.000000e+00 0.000000e+00
    outer loop
      vertex   -2.000000e+00 1.696376e+00 3.330702e+00
      vertex   -2.000000e+00 1.606920e+00 3.500886e+00
      vertex   -2.000000e+00 1.569699e+00 3.453079e+00
    endloop
  endfacet
...(これの繰り返し)

となっている."vertex"とかのキーワードとかをもとに読み取ってけばいい.以下がそのコード例

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
struct Triangle3D {
    float normal[3];
    float first_vertex[3];
    float second_vertex[3];
    float third_vertex[3];
};
std::vector<Triangle3D> triangles;
void LoadStLAscii(const std::string& filename) {
    std::ifstream file(filename);
    std::string line;
    if(!file.is_open()) {
        std::cerr << "Error: could not open file " << filename << std::endl;
        std::exit(1);
    }
    while (std::getline(file, line)) {
        Triangle3D triangle;
        if (line.find("facet normal") != std::string::npos) {
            std::sscanf(line.c_str(), "  facet normal %f %f %f", &triangle.normal[0], &triangle.normal[1], &triangle.normal[2]);
            std::getline(file, line); // skip "outer loop"
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.first_vertex[0], &triangle.first_vertex[1], &triangle.first_vertex[2]);
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.second_vertex[0], &triangle.second_vertex[1], &triangle.second_vertex[2]);
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.third_vertex[0], &triangle.third_vertex[1], &triangle.third_vertex[2]);
            std::getline(file, line); // skip "endloop"
            std::getline(file, line); // skip "endfacet"
            triangles.push_back(triangle);
        }  
    }
}

int main(int argc,char *argv[]){
    LoadStLAscii("sample.stl");
    return 0;
}

バイナリ―フォーマット

バイナリフォーマットは,以下のようなフォーマットになっている(この表は[1]を引用)

バイト数 データ型 内容
80 char[] ヘッダー(任意の文字列)
4 unsigned int ポリゴン数
4 float ポリゴン1つ目の法線ベクトルx方向
4 float ポリゴン1つ目の法線ベクトルy方向
4 float ポリゴン1つ目の法線ベクトルz方向
4 float ポリゴン1つ目の頂点1つ目のx座標
4 float ポリゴン1つ目の頂点1つ目のy座標
4 float ポリゴン1つ目の頂点1つ目のz座標
4 float ポリゴン1つ目の頂点2つ目のx座標
4 float ポリゴン1つ目の頂点2つ目のy座標
4 float ポリゴン1つ目の頂点2つ目のz座標
4 float ポリゴン1つ目の頂点3つ目のx座標
4 float ポリゴン1つ目の頂点3つ目のy座標
4 float ポリゴン1つ目の頂点3つ目のz座標
2 -- 未使用データ
4 float ポリゴン2つ目の法線ベクトルx方向
... ... ...

これをもとに読み込めばいいので,以下がコード例

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
struct Triangle3D {
    float normal[3];
    float first_vertex[3];
    float second_vertex[3];
    float third_vertex[3];
};
std::vector<Triangle3D> triangles;
void LoadStLBin(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if(!file.is_open()) {
        std::cerr << "Error: could not open file " << filename << std::endl;
        std::exit(1);
    }
    char header_info[80] ="";
    char trash[2]="";
    unsigned int polygon_num=0;
    Triangle3D triangle;
    file.read(header_info,80);//ヘッダー情報の読み込み
    file.read((char*)&polygon_num, sizeof(unsigned int));//三角形の数の読み込み
    for (int i=0;i<polygon_num;i++){
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.normal[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.first_vertex[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.second_vertex[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.third_vertex[i], sizeof(float));
        }
        file.read(trash,2);
        triangles.push_back(triangle);
    }
}

int main(int argc,char *argv[]){
    LoadStLBin("sample.stl");
    return 0;
}

(おまけ)読み込んだファイルをグラフィック表示(OpenGL)

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <vector>
#include<gl/glut.h>

struct Triangle3D {
    float normal[3];
    float first_vertex[3];
    float second_vertex[3];
    float third_vertex[3];
};

struct WindowInfo{
    int width;
    int height;
    float left;
    float right=left+width;
    float bottom;
    float top=bottom+height;
};

std::vector<Triangle3D> triangles;
WindowInfo window_info;

void LoadStLAscii(const std::string& filename);
void LoadStLBin(const std::string& filename);
void DrawShapes();
void Init(void);
void Display();
void Resize(int w,int h);

void LoadStLAscii(const std::string& filename) {
    std::ifstream file(filename);
    std::string line;
    if(!file.is_open()) {
        std::cerr << "Error: could not open file " << filename << std::endl;
        std::exit(1);
    }
    while (std::getline(file, line)) {
        Triangle3D triangle;
        if (line.find("facet normal") != std::string::npos) {
            std::sscanf(line.c_str(), "  facet normal %f %f %f", &triangle.normal[0], &triangle.normal[1], &triangle.normal[2]);
            std::getline(file, line); // skip "outer loop"
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.first_vertex[0], &triangle.first_vertex[1], &triangle.first_vertex[2]);
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.second_vertex[0], &triangle.second_vertex[1], &triangle.second_vertex[2]);
            std::getline(file, line);
            std::sscanf(line.c_str(), "      vertex   %f %f %f", &triangle.third_vertex[0], &triangle.third_vertex[1], &triangle.third_vertex[2]);
            std::getline(file, line); // skip "endloop"
            std::getline(file, line); // skip "endfacet"
            triangles.push_back(triangle);
        }  
    }
}

void LoadStLBin(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if(!file.is_open()) {
        std::cerr << "Error: could not open file " << filename << std::endl;
        std::exit(1);
    }
    char header_info[80] ="";
    char trash[2]="";
    unsigned int polygon_num=0;
    Triangle3D triangle;
    file.read(header_info,80);//ヘッダー情報の読み込み
    file.read((char*)&polygon_num, sizeof(unsigned int));//三角形の数の読み込み
    for (int i=0;i<polygon_num;i++){
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.normal[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.first_vertex[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.second_vertex[i], sizeof(float));
        }
        for (int i=0;i<3;i++){
            file.read((char*)&triangle.third_vertex[i], sizeof(float));
        }
        file.read(trash,2);
        triangles.push_back(triangle);
    }
}

//stlを描画する関数
void DrawShapes()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glBegin(GL_TRIANGLES);
    for(auto& triangle : triangles){
        glNormal3f(triangle.normal[0], triangle.normal[1], triangle.normal[2]);
        glVertex3f(triangle.first_vertex[0], triangle.first_vertex[1], triangle.first_vertex[2]);
        glVertex3f(triangle.second_vertex[0], triangle.second_vertex[1], triangle.second_vertex[2]);
        glVertex3f(triangle.third_vertex[0], triangle.third_vertex[1], triangle.third_vertex[2]);
    }
    glEnd(); 
}

void Init(void){
    glutInitWindowPosition(100, 100); /* ウィンドウの左上の位置*/
    glutInitWindowSize(500, 500); 
    glutInitDisplayMode(GLUT_RGBA); /* 色の指定に RGBA モードを用いる */
    glutCreateWindow("viewer"); /* winname で指定された名前でウィンドウを開く */
	glClearColor(1.0, 1.0, 1.0, 1.0);
	glMatrixMode(GL_PROJECTION);//透視変換
    // glMatrixMode(GL_MODELVIEW);//モデルビュー変換
    glEnable(GL_DEPTH_TEST);
    //ライトの有効化
    GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
    GLfloat white_light[] = { 1.0, 1.0, 1.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
    glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glEnable(GL_COLOR_MATERIAL);

    glShadeModel(GL_SMOOTH);
	glLoadIdentity();//変換行列を計算
}

void Display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    DrawShapes();
    glutSwapBuffers();
}

void Resize(int w, int h) {
    float aspectRatio, left, right, bottom, top, nearVal, farVal,fovy;

    aspectRatio = (float)w / (float)h;

    // ビューポートをウィンドウ全体に設定
    glViewport(0, 0, w, h);

    // 投影行列の設定
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // glOrtho(left, right, bottom, top, nearVal, farVal);
    fovy=80.0;
    gluPerspective(fovy,aspectRatio,10,100.0);
    
    // モデルビュー行列に戻す
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(30.0,30.0,30.0,0.0,0.0,0.0,0.0,0.0,1.0);//視点の設定
}

int main(int argc,char *argv[]){
    // LoadStLAscii("inputfiles/nihi_ascii.stl");//rocket_ascii-nihi_ascii
    LoadStLBin("inputfiles/bin_1.stl");
    glutInit(&argc, argv);
    Init();
    glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
    glutDisplayFunc(Display);
    glutReshapeFunc(Resize); 
    glutMainLoop();
    return 0;
}

参考文献

以下のサイト等を参照しました.
[1]バイナリフォーマットstlファイルをC++で読み込む @JmpM
(まるや)(閲覧日2024/01/14)
https://qiita.com/JmpM/items/9355c655614d47b5d67b

Discussion