STL ファイル バイナリ形式読み込み
【概要】
STL(Stereolithography)は,3次元形状を三角形ポリゴンの集まりで表現するファイル形式である。テキスト形式(ASCII STL)とバイナリ形式(バイナリ STL)がある。 このページでは,STL ファイルのバイナリ形式を読み込む C プログラムを示し,読み込んだ三角形ポリゴンのデータから CSV ファイルと SQLite 3 データベースを生成する体験を行う。
バイナリ STL のファイル構造は次のとおりである。
- ヘッダ: 80 バイト
- 三角形の個数: 4 バイト(符号なし整数)
- 三角形データ: 1 個あたり 50 バイト(法線ベクトル nx, ny, nz と 3 頂点の座標 x1, y1, z1, x2, y2, z2, x3, y3, z3 が各 4 バイトの浮動小数点数で 48 バイト,属性が 2 バイト)
◆ 作成するSQLite 3 データベース: stldb(テーブル名: stl)
【目次】
【サイト内の関連情報】
第1章 STL ファイルの準備
バイナリ形式の STL ファイルを 1 つ用意する。 利用条件(ライセンス)を必ず確認すること。利用条件に同意できない場合は,利用(ダウンロード)しないこと。
- このページの実行例では,かつて Capture 3D 社のサイト(http://www.capture3d.com/products-ATOS-download.html)で公開されていた atos-beetle-500000_stl.zip(展開すると beetle_500000.stl になる)を用いた。 現在は同ページでの公開が終了しているため,手元にあるバイナリ STL ファイル, あるいは Thingiverse などのモデル共有サイトで公開されているバイナリ STL ファイルで代用できる。
- ファイル名は自由である。以下では,ファイル名を beetle_500000.stl と書く。別のファイル名を使う場合は読み替えること。
- ヒント: 手元の STL ファイルがテキスト形式(先頭が「solid」で始まり,人間が読める文字列が続く)の場合は, このページのプログラムでは読み込めない。MeshLab などのツールでバイナリ形式に変換してから使うこと。
第2章 CSV ファイル生成手順
◆ ソースコードのダウンロードとコンパイル手順
cd /tmp
rm -f readstl.c
wget https://www.kkaneko.jp/tools/od/readstl.c
g++ -o a.out readstl.c
◆ 上記の手順でダウンロードされるソースコード
#include <stdio.h>
const int BUFSIZE = 10000;
int main(int argc, char** argv)
{
FILE *fp;
char *fname = argv[1];
unsigned char buf[BUFSIZE];
unsigned int size;
unsigned int i;
unsigned int num;
float nx;
float ny;
float nz;
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
float x3;
float y3;
float z3;
if ( argc < 2 ) {
printf( "usage: %s filename.stl\n", argv[0] );
return -1;
}
fp = fopen( fname, "rb" );
if ( fp == NULL ) {
printf( "cannot open %s \n", fname );
return -1;
}
/* header (80 バイト) */
size = fread( buf, sizeof( unsigned char ), 80, fp );
/* 三角形の個数 (4 バイト) */
size = fread( buf, sizeof( unsigned char ), 4, fp );
num = *( (unsigned int*) buf );
/* CSV のヘッダ行 */
printf( "seq, nx, ny, nz, x1, y1, z1, x2, y2, z2, x3, y3, z3\n" );
/* body (1 個あたり 50 バイト) */
i = 1;
while (1) {
size = fread( buf, sizeof( unsigned char ), 50, fp );
if ( size < 50 ) {
break;
}
nx = *( (float*) (&(buf[0])) );
ny = *( (float*) (&(buf[4])) );
nz = *( (float*) (&(buf[8])) );
x1 = *( (float*) (&(buf[12])) );
y1 = *( (float*) (&(buf[16])) );
z1 = *( (float*) (&(buf[20])) );
x2 = *( (float*) (&(buf[24])) );
y2 = *( (float*) (&(buf[28])) );
z2 = *( (float*) (&(buf[32])) );
x3 = *( (float*) (&(buf[36])) );
y3 = *( (float*) (&(buf[40])) );
z3 = *( (float*) (&(buf[44])) );
printf( "%d, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f\n",
i, nx, ny, nz, x1, y1, z1, x2, y2, z2, x3, y3, z3 );
i++;
}
fclose( fp );
return 0;
}
◆ 実行手順と実行結果の例
標準出力に CSV が書き出されるので,リダイレクトでファイルに保存する。
./a.out beetle_500000.stl > beetle_500000.stl.csv
◆ 確認表示
R プログラム(scatterplot3d を使用)
X <- read.table("/tmp/beetle_500000.stl.csv", header=TRUE, sep=",", na.strings="NA", dec=".", strip.white=TRUE);
library(scatterplot3d)
scatterplot3d( x = X$x1, y = X$y1, z = X$z1 )
R プログラム(rgl を使用)
X <- read.table("/tmp/beetle_500000.stl.csv", header=TRUE, sep=",", na.strings="NA", dec=".", strip.white=TRUE);
library(rgl)
plot3d( x = X$x1, y = X$y1, z = X$z1 )
SQLite 3 データベースの生成
SQLite 3
あらかじめ決めておく事項
このページでは,SQLite 3 データベースの生成を行う。 生成するSQLite 3 データベースのデータベース名を決めておくこと。 このページでは,次のように書く。
- データベース名: stldb
データベース名は,自由に決めてよいが,半角文字(つまり英字と英記号)を使い,スペースを含まないこと。
テーブルの作成
- テーブル定義
作業用テーブル T と,本体のテーブル stl の 2 つを定義する。 CSV ファイルをいったん T に取り込み,モデル名(name)を付けて stl に挿入する,という流れである。
- T(seq, nx, ny, nz, x1, y1, z1, x2, y2, z2, x3, y3, z3)
- stl(name, seq, nx, ny, nz, x1, y1, z1, x2, y2, z2, x3, y3, z3)
◆ bash プログラム
#!/bin/bash rm -f /tmp/stldb # cat >/tmp/a.$$.sql <<-SQL drop table if exists T; drop table if exists stl; SQL cat /tmp/a.$$.sql | sqlite3 /tmp/stldb # cat >/tmp/a.$$.sql <<-SQL create table T ( seq integer primary key not null, nx real, ny real, nz real, x1 real, y1 real, z1 real, x2 real, y2 real, z2 real, x3 real, y3 real, z3 real ); SQL cat /tmp/a.$$.sql | sqlite3 /tmp/stldb # cat >/tmp/a.$$.sql <<-SQL create table stl ( name TEXT not null, seq integer not null, nx real, ny real, nz real, x1 real, y1 real, z1 real, x2 real, y2 real, z2 real, x3 real, y3 real, z3 real ); SQL cat /tmp/a.$$.sql | sqlite3 /tmp/stldb
- テーブルへのデータの取り込み
CSV ファイルの先頭 1 行(ヘッダ行)を取り除いたうえで .import で T に取り込み,name 列を付けて stl に挿入する。
◆ bash プログラム
#!/bin/bash cat >/tmp/a.$$.sql <<-SQL .mode csv .import /tmp/a.$$.csv T insert into stl select 'beetle_500000' as name, T.seq as seq, T.nx as nx, T.ny as ny, T.nz as nz, T.x1 as x1, T.y1 as y1, T.z1 as z1, T.x2 as x2, T.y2 as y2, T.z2 as z2, T.x3 as x3, T.y3 as y3, T.z3 as z3 from T; drop table T; vacuum; SQL # cd /tmp rm -f readstl.c wget https://www.kkaneko.jp/tools/od/readstl.c g++ -o a.out readstl.c # # STL ファイル(バイナリ形式)を /tmp/beetle_500000.stl として置いておくこと # ./a.out /tmp/beetle_500000.stl > /tmp/beetle_500000.stl.csv tail -n +2 /tmp/beetle_500000.stl.csv > /tmp/a.$$.csv cat /tmp/a.$$.sql | sqlite3 /tmp/stldb # echo 'select * from stl limit 10;' | sqlite3 /tmp/stldb echo 'select count(*) from stl;' | sqlite3 /tmp/stldb
- (オプション) データベースの再構成
.dump でデータベース全体を SQL 文としてダンプし,.read で読み込み直すことで,データベースを再構成できる。
◆ bash プログラム
#!/bin/bash cd /tmp ls -la stldb # dump cat >/tmp/a.$$.sql <<-SQL .output /tmp/stldb.sql .dump .exit SQL # cat /tmp/a.$$.sql | sqlite3 /tmp/stldb # rm /tmp/stldb # restore cat >/tmp/a.$$.sql <<-SQL .read /tmp/stldb.sql .exit SQL # cat /tmp/a.$$.sql | sqlite3 /tmp/stldb # cd /tmp ls -la stldb
SQLiteman で確認
- R で使ってみる
◆ bash プログラム
#!/bin/bash cat >/tmp/a.$$.r <<-RCOMMAND library(RSQLite) library(sqldf) library(rgl) dbname="/tmp/stldb" driver=dbDriver("SQLite") conn=dbConnect(driver,dbname) # T <- dbGetQuery(conn, "SELECT * from stl"); # D <- sqldf("select x1, y1, z1 from T") plot3d( x = D\$x1, y = D\$y1, z = D\$z1 ) Sys.sleep(100) RCOMMAND cat /tmp/a.$$.r | r
考察ポイント
- バイナリ STL のファイルサイズは「84 + 50 × 三角形数」バイトになるはずである。手元のファイルのサイズと三角形数から,この関係が成り立つことを確かめてみること。
- 頂点は三角形ごとに独立して格納されるため,隣接する三角形で同じ頂点が重複して現れる。stl テーブルに対して SQL の distinct を使い,異なり頂点数を数えてみること。
- CSV ファイル,SQLite 3 データベースそれぞれのファイルサイズを比較し,格納形式による違いを考えてみること。