ラテアート用ステンシルの作成
ステンシル本体はサイズをきっちり決めて造形したかったのでFusion 360で円部分の直径やもち手の長さを指定してモデリングを行いました。
直径85mmの円と10mm×45mmの長方形を組み合わせたスケッチを厚さ2.5mmで押出ています。強度的には厚さ1mmちょっとでもいいはずですが、今回はナイロン(磨きあり)で造形するつもり(以前に行った地図の造形は磨き無し)だったので磨き処理で薄くなる部分を考慮して気持ち厚めにしました。このモデルをOBJ形式でエクスポートしてからZBrushに取り込んで犬の顔のかたちに穴を開けていきます。
元になる犬の顔の絵は写真をもとに奥さんに描いてもらいました。
ZBrushで下絵を表示する方法はいくつもあって選択に迷うのですが、今回はDrawメニューの中のUp-Down->Map1に画像ファイルを指定する方法をとりました。Divideを何回か繰り返した面(写真ではステンシル本体)とこの絵を重ねて絵の線の部分をマスクしていきます。Divideの回数は線を滑らかにマスクできるように試行しながら決めています。マスクで線を描くときステンシル本体の内側の島を線で完全に囲ってしまうとその部分を支えることができなくなります。絵として不自然にならない範囲で線を分断しておきます。マスクした部分をSubtool->Extractで線画部分を押し出した(両面指定、Thickの値は最大にしておく)立体を作ります。
絵柄の部分はラテアート以外にも使える可能性があるので独立したパーツとして保存しておきます。
ステンシル本体を改めてZBrushに読み込み、上述の彫り込み用パーツの位置や大きさを調整したうえでBool演算を実行すれば完成です。
立体地図用テクスチャ画像作成に関する備忘録
カラー造形のためのテクスチャ画像を作成する考え方は前回述べたとおりなのですがもう少し実装よりというか、泥臭い作業上の注意点なども記録しておきたいと思います。
テクスチャ画像の生成スクリプトのソースコードを下記に置きました。
このPythonスクリプトは画像編集ソフトGimpのプラグインとして動作します。単独では動きませんのでご注意ください。GimpはフリーのソフトウェアでWindowsとMac両方で利用可能ですが、私のPythonスクリプトはWindows環境でのみテストしています。
PythonスクリプトのファイルをWindowsのユーザフォルダ下の".gimp-2.8\plug-ins"フォルダ(私はGimp2.8を使っているのでこの名前になります)にコピーしておいてGimpを起動するとテクスチャ画像生成をGimpのコマンドとして利用できるようになります。
フィルター(R)->Python-fu->Map->Gen Texture
で以下のようなダイアログが表示されます。
File NameにはUVマッピングの結果を含んだOBJファイルを指定します(重要!! ファイルパスの文字列にダブルバイト文字が含まれていると異常終了します。OBJファイルを置いておくフォルダ名には半角文字を使ってください)。Image Sizeにはテクスチャ画像の縦と横(正方形なので同じ値になります)の画素数を指定します。一般に大きな値を指定したほうが精密な絵が描けますが、頂点間の間隔があいているところを塗りきりなかったり(後述)します。形状のポリゴンの数や大きさも影響しますので、私は512から2048ぐらいの値で悩みながら決めています。BaseとVer.Scaleはプリント用の3Dモデルの高さから元の標高を逆算するのに必要なパラメータです。Excelで直方体のデータを出力するとき、標高の値を縦方向に70万分の1に縮尺してから台の部分を5mm分足しているのでこの逆を行うわけです。得られた標高値に応じて色を決めているのですがその部分は現時点ではハードコーディングされていて以下のような感じです。
height = ((z - base)*ver_scale)/1000.0 thr = - ((base/3.0)*ver_scale)/1000.0 if height < thr : color = (230,230,230,1.0) elif height < 0.1 : color = (43,131,186,1.0) elif height < 10.0 : color = (100,171,176,1.0) elif height < 200.0 : color = (157,211,167,1.0) elif height < 300.0 : color = (199,233,173,1.0) elif height < 500.0 : color = (237,248,185,1.0) elif height < 1000.0 : color = (255,237,170,1.0) elif height < 1500.0 : color = (254,201,128,1.0) elif height < 2000.0 : color = (249,158,89,1.0) elif height < 2500.0 : color = (232,91,58,1.0) else : color = (215,25,28,1.0)
実は、あともう1つこのダイアログには含まれないのですがテクスチャ画像の出来栄えに影響を与えるパラメータがあります。下の図で青枠で囲った数字、指定された色で点を打つときのブラシサイズです。現状のスクリプトでは実行時に設定されていたサイズで点を打っていきます。
現状のスクリプトでは点のサイズを小さくすると精密な着色ができるのですが大きなポリゴンを塗り切れないことがあります(下の図の赤枠部分)。点を打つのがポリゴンの頂点の位置で中を塗りつぶすようなロジックになっていないからです。
ZBrushのDynaMeshの機能でポリゴンの大きさを均質かつ十分小さくしておけばこういうことはおこらないのですがテクスチャ生成に非常に時間がかかってしまうことがあります。ブラシのサイズを大きめに設定してテクスチャ画像を生成すると 粗くはなるが塗り残しをなくすことができます。
2つの画像を重ね、塗り残し部分は粗い画像の情報で補うという手もあります。
ここらへんはどんなやりかたが良いのかいまだ試行錯誤を続けている状況です。
画像が生成できたらZBrushにテクスチャ画像に取り込んでもう一度OBJ形式でエクスポートを行って以下の3つのファイルを作成します。
XXX.OBJ
XXX.mtl
XXX.BMP
OBJファイルににmtlファイルへのパスを記述し、mtlファイルの中にBMPファイルへのパスを記述するというのがカラー造形を依頼するときのお作法になっているためです。
立体地図を作る -補足2 標高で色分けする-
先日投稿した標高で色分けした立体地図の作り方の説明です。
業者に色付きで造形してもらうために渡すデータの形式はいくつかありますが、私はOBJ形式ファイル+テクスチャ画像を渡して依頼しました。ここでいうテクスチャ画像とは立体の表面にシーム(切れ目)を入れて平面に展開した画像です。立体のどこにどうシームをいれて展開するかということを規定する情報はUVマッピングと呼ばれていてその記述方法はOBJ形式の仕様で決められています。前の投稿で説明した”v”と”f”によるOBJ形式の記述はUVマッピングを持たない形状だけの情報でしたが、これにUVマッピングの情報が追加されます。したがって、色付きの造形データを準備するためには
という2ステップの作業を行うことになります。
1. UBマッピング付きのOBJファイルを作成する
よほど単純な形状でない限り手作業でUVマッピングを行うのは無理なので専用のソフトウェアの手助けが必要になります。私の場合、ZBrushのUVマスターというプラグインを使いました。UVマスターでは直感的にわかりやすい展開画像が得られるようシームを入れてほしくない場所(赤)、逆にここに切り分けて展開してほしいという場所(青)を人手で指定したうえで展開処理(Unwrap)を起動します。
展開結果は下の図のようになります。
2.UVマッピングに沿った色付きのテクスチャ画像を作成する
展開画像に対してPhotshopなどの画像編集ソフトウェアを使って手作業で色を塗ることもできますが、今回は標高の数値に応じてきちんと塗り分けたいのでそのためのスクリプトを作成しました。
ZBrushでUVマッピングを行った後でOBJ形式でモデルをエクスポートすると
v 133.550 47.474 9.898 v 133.710 47.439 9.912 v 133.529 47.291 9.871 v 133.723 47.288 9.936 : vt 0.66436 0.38941 vt 0.66386 0.38996 vt 0.66326 0.38942 vt 0.66425 0.38883 : f 2/1 1/2 3/3 4/4 f 5/5 2/1 4/4 f 3/3 24/6 8/7 4/4 f 5/5 4/4 8/7 9/8
:
といったデータが出力されます。形状の情報のみのを保管した場合と比べると"vt"とい文字列で始まる行が追加され、面を表現する”f”で始まる行の記法が変わっています。"vt"で始まる行の2つの数字は展開後の画像のX座標、Y座標です(3次元形状のX,Y,Z座標と区別するためにUV座標と呼ばれます)。この座標の値は0から1の間の値に正規化されているので、テクスチャ画像を作成する側で決めた画像の大きさに応じて実際の点の位置が決まります。
”f”で始まる行で面を構成する頂点を記述していくときには
3次元の頂点のインデックス/UV座標の点のインデックス
という組で記述されます。上の例で2/1という記述はインデックス2の頂点(133.710 47.439 9.912 )はインデックス1のUV座標( 0.66436 0.38941 )と対応していることを示しています。
以上から、標高で塗り分けられたテクスチャ画像を作成するには
面を構成する(”f”で始まる行にでてくる)すべてのインデックスの組に対して
1 3次元頂点のインデックス番号をもとに頂点のZ座標(高さ)をもとめる
2 Z座標から何色(RGBの値)で塗るかを決定する(例えば標高5m以下なら水色)
3 UVのインデックス番号からUV座標を取得、画像上の座標に換算する
4 3でもとめた画像上の座標に2でもとめた色の点をうつ
という処理を 繰り返せばよいことがわかります。
上記のアルゴリズムをPythonスクリプトで実装し実行した結果です。
作成したテクスチャをZBrushに取り込んでみました。この状態でもう一度OBJファイルをエクスポートすると、OBJ、mtl、BMPの3つのファイルが作成されるのでこれをZipに固めて造形を依頼します。
立体地図を作る -標高メッシュから直接OBJ形式データを生成する-
なんとか立体地図が作成できるようにはなったものの、いくつかのの県についてパズルのピースを作ってみると手順にいろいろ不具合があることもわかってきました。今回は今まで汎用のソフトウェアを使って行っていたOBJ形式データの生成処理を専用のプログラムで置き換えることで作業の効率化とモデル品質の向上を図ります。
これまでQGISで作成した標高メッシュの情報から3Dモデルのデータ(OBJファイル)を生成するためにOpenSCADというプログラムを利用していましたが、これには2つ大きな問題がありました。
問題1 処理できるデータ量に制限がある
立体地図作成のためにはメッシュごとに単純な直方体の3Dデータを生成していけばよいのですがOpenSCADでは生成する直方体の数(あるいはプログラムの行数)が19,000を超えたあたりでメモリー不足で異常終了してしまいます。PCのメモリ使用状況としては余裕があるので、OpenSCADの処理系内部のどこかで固定でワーク領域を確保しているのではないかと疑っています。異常終了を避けるためにはメッシュデータを分割して3Dデータを生成してから、ZBrushなどの3Dモデリングソフトを使って統合する必要があります。手間がかかることに加えて、(問題2とも関係しているのですが)なかなかきれいに統合できないでいました。
問題2 STL形式のデータしか出力できない
OpenSCADが出力できるのはSTLという3Dプリント出力用のデータのみです。STL形式では三角形の集合で面を表現しています。ZBrushはSTL形式を読み込めないので、いったんMeshLabで読み込んでOBJ形式(三角形を含む多角形の集合で面を表現している)に変換してからZBrushに読み込んで前出の統合処理やスムージングなどを行なっていました。3Dプリントのためには最終的にはSTL形式を出力するので、STLをOBJに変換、OBJ形式を読み込んで編集、再びSTL形式で出力という段階をふむ必要があります。くわえて、もともとの地図データは直方体なので四角形の集合で面をきれいに表現できていたのが一度三角形の集合になったものを読み込むと元の四角形の集まりとは微妙に異なってしまいます。問題1の対応のために静岡県のモデルを分割生成してとりこんだときは接合面の頂点ががぴったり合わず形状にスジが残ってしまいました。
こういった問題を解決するため標高メッシュのデータからOBJ形式を生成するプログラムをつくることにしました。OpenSCADは3Dモデル作成のためにさまざまな基本形状や形状に対する操作をサポートしていますが、立体地図作成に必要なのは単純な直方体形状をグリッド上に配置していく操作のみです。この部分だけなら比較的簡単に実装することができます。
出力するOBJファイルのフォーマットについてもインターネット上で情報を見つけることができます。改行で区切られたテキスト形式のファイルで"v"で始まる行は頂点、"f"で始まる行が1つの面(多角形)を表すというのが基本です。 頂点を表す行は"v"の後にXYZの座標を示す3つの数字が空白で区切られて続きます。面は多角形の頂点を指すインデックス番号を空白で区切って記述します。下の例で”f 1 4 3 2”の1は1番目の頂点(48.750,100.250,0.000)を示しています。直方体の面はいずれも四角形ですから"f"の後には整数値を4つ記述することになります。面には表と裏があって正式には"vn"で始まる行で法線ベクトルを記述するようですが、1->4->3->2といった頂点を記述する順番(表面からみて反時計回りになるような順番)で裏表を指定できるという仕様(慣習?)がありZBrushも”vn”無しのデータを読み込んでくれました。
OBJ形式ファイルの例
v 48.750 100.250 0.000
v 49.000 100.250 0.000
v 49.000 100.500 0.000
v 48.750 100.500 0.000
v 48.750 100.250 5.000
v 49.000 100.250 5.000
v 49.000 100.500 5.000
v 48.750 100.500 5.000
:
f 1 4 3 2
f 8 5 6 7
f 1 5 8 4
f 3 4 8 7
:
下のような1個の直方体の情報を表すのには8個の頂点と6個の面の情報を出力すればよいわけです。
実際には隣合う直方体の間では頂点を共有したり、面が別の面で覆われたりすることを考慮しないといけません。下の図で青丸で示すような2つ以上の面で共有されている頂点は各面で同一のインデックス番号で指定されないといけません。同じ座標を持った頂点を複数個出力するのはNGです。面についても互いに接している部分は除いて重なっていない部分(下図の青枠部分)だけの面の情報を出力します。
上記のような点を考慮してOBJファイルを生成するスクリプトをPythonで記述しました(末尾に添付)。
入力は基本、OpenSCADに与えていたのと同じ情報ですが数値の部分だけをカンマで区切ったCSV形式で用意します。
48.75,100.25,0,0.25,5
49,100.25,0,0.25,5
49.25,100.25,0,0.25,5
49.5,100.25,0,0.25,5
各行、5つの数字で直方体を表しています。最初の3つは左下角の頂点を示すX,Y,Z座標値、4つ目は底面の正方形の1辺の長さ、最後の数字は直方体の高さになります(単位はmmです)。スクリプトにはこのCSVファイルと出力するOBJファイルのパスを引数として指定します。
Python GenObj.py I:\local\3D-Work\Shizuoka.csv I:\local\3D-Work\Shizuoka.obj
厳密なOBJ形式の仕様に則しているかは若干怪しいですがZBrushは出力されたファイルを問題なく読み込んでくれています。
この静岡県は30,000弱 の直方体で構成されておりOpenSCADではいちどに3Dモデルを生成できなかったのですが作成したスクリプトでは問題なく処理できました。また、処理時間も数秒でOpenSCADでの処理に比べて大幅に向上しました。
以下にスクリプトのソースを開示します。自分で使っている範囲内でしかテストしていないので、利用する場合は自己責任でお願いします。
立体地図を作る -標高で色分け試作-
DMM.makeに造形を依頼していた静岡県の立体地図が届きました。最初アップロードしたデータにはちょっと不備があったのですが、担当者のかたがメールでの問い合わせにも迅速に応えてくれて修正することができました。
今回は縦方向の縮尺比率を横方向の3倍におさえています。前回、近畿地方中心の7県をナイロン製で造形したときは10倍にしていたのですが、10倍だと富士山が異様にとんがってしまい美しいフォルムにならなかったです。
実は今回の造形では真ん中あたりに水平方向のスジがはいってしまったのですがこれは色付けとは関係なくて、静岡県は以前の7県と違って3Dモデルを2つに分けて作成したことが原因です。OpenSCADで処理できるデータの量に制限があり2つのSTLファイルに分けて生成、それらをOBJ形式に変換し、(大きなデータを処理できる)ZBrushに読み込んで統合と編集・改めてSTLファイルを出力、といった手順をとったらこんなスジがついてしまいました。その後、OpenSCADを使わずに大きめのデータでもいっきょにOBJ形式データを生成する方法にめどがたったので機会があれば紹介したいと思います。