Meshlab スクリプトによるメッシュフィルター操作を自動化する方法

# はじめに

MeshLabにおいて、スクリプトで自動で操作をする記事です。例として、メッシュフィルター（quadric edge collapse decimation）を用いた、remeshingを行います。

### 作るもの

![meshlab.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100466/c8f907f0-346e-4d8f-0ce8-64be4bde94e1.gif)

今回はこの操作をスクリプト（python）で行います。

### 対象読者

Meshlabの操作を自動化したい人

# create python script

開発環境は、下記の通りです。
Ubuntu 16.04 
python 3.6 

まずは、meshlabをインストールします。

```console:
$ sudo apt-get update
$ sudo apt-get install meshlab
```
meshlabのスクリプトは、meshlabを実際に操作した際に保存されます。そのため、まずは上記gifと同じ操作をmeshlabで直接行います。　
その後、メニュータブのFilters->Show current filter scriptを選択します。
![tem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100466/ec76522c-5057-9b19-3a5f-21034ff56b27.png)

そして、先ほど操作したフィルター名が表示されているのを確認して、Save Scriptでどこか適当な場所に保存します。
![tem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100466/c2cf5f87-e55c-b0dd-acd8-19cd0093d91a.png)

保存したmlxファイルを見ると、先ほどのフィルタのパラメータ等が記載されています。

```temp.mxl
<!DOCTYPE FilterScript>
<FilterScript>
 <filter name="Quadric Edge Collapse Decimation">
  <Param type="RichInt" value="100" name="TargetFaceNum"/>
  <Param type="RichFloat" value="0" name="TargetPerc"/>
  <Param type="RichFloat" value="0.3" name="QualityThr"/>
  <Param type="RichBool" value="false" name="PreserveBoundary"/>
  <Param type="RichFloat" value="1" name="BoundaryWeight"/>
  <Param type="RichBool" value="false" name="PreserveNormal"/>
  <Param type="RichBool" value="false" name="PreserveTopology"/>
  <Param type="RichBool" value="true" name="OptimalPlacement"/>
  <Param type="RichBool" value="false" name="PlanarQuadric"/>
  <Param type="RichBool" value="false" name="QualityWeight"/>
  <Param type="RichBool" value="true" name="AutoClean"/>
  <Param type="RichBool" value="false" name="Selected"/>
 </filter>
</FilterScript>
```

meshlabではmeshlabserverというコマンドでmeshlabを操作する。
このコマンドの引数で最低限使うのは、
-i [filename…] 開くパス＋ファイル名 　
-o [filename…] 書き出すパス＋ファイル名 　
-s filename 適用するスクリプトのパス＋ファイル名　 
であり、コマンドプロンプトから下記のようなコマンドで実施できる。

```console
meshlabserver -i input.obj -o output.ply -s temp.mlx
```
なので、複数ファイルを実施する際には、これをpythonスクリプトでfor文等で実施する。　
pythonスクリプトは https://gist.github.com/tylerlindell/7435ca2261e7c404ccc1241f18e483aa に先人が作成してくれているので、これを修正して使用してください。

```
#!/usr/bin/env python

import sys
import os
import subprocess

# Script taken from doing the needed operation
# (Filters > Remeshing, Simplification and Reconstruction >
# Quadric Edge Collapse Decimation, with parameters:
# 0.9 percentage reduction (10%), 0.3 Quality threshold (70%)
# Target number of faces is ignored with those parameters
# conserving face normals, planar simplification and
# post-simplimfication cleaning)
# And going to Filter > Show current filter script
filter_script_mlx = """<!DOCTYPE FilterScript>
<FilterScript>
 <filter name="Simplification: Quadric Edge Collapse Decimation (with texture)">
  <Param type="RichInt" value="60000" name="TargetFaceNum"/>
  <Param type="RichFloat" value="0" name="TargetPerc"/>
  <Param type="RichFloat" value="1" name="QualityThr"/>
  <Param type="RichBool" value="true" name="PreserveBoundary"/>
  <Param type="RichFloat" value="1" name="BoundaryWeight"/>
  <Param type="RichBool" value="true" name="OptimalPlacement"/>
  <Param type="RichBool" value="true" name="PreserveNormal"/>
  <Param type="RichBool" value="true" name="PlanarSimplification"/>
 </filter>
</FilterScript>
"""

cwd = os.getcwd()

def create_tmp_filter_file(filename='filter_file_tmp.mlx'):
    with open(cwd + 'tmp_' + filename, 'w') as f:
        f.write(filter_script_mlx)
    return cwd + 'tmp_' + filename


def reduce_faces(in_file, out_file,
                 filter_script_path=create_tmp_filter_file()):
    # Add input mesh
    command = "meshlabserver -i " + in_file
    # Add the filter script
    command += " -s " + filter_script_path
    # Add the output filename and output flags
    command += " -o " + out_file
    # Execute command
    print("Going to execute: " + command)
    output = subprocess.call(command, shell=True)
    last_line = output
    print()
    print("Done:")
    print(in_file + " > " + out_file + ": " + str(last_line))


if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage:")
        print(sys.argv[0] + " /path/to/input_mesh num_iterations")
        print("For example, reduce 10 times:")
        print(sys.argv[0] + " /home/myuser/mymesh.dae 10")
        exit(0)

    in_mesh = sys.argv[1]
    filename = in_mesh.split('\\')[-1]
    num_iterations = int(sys.argv[2])

    folder_name = filename.replace('.', '_')
    tmp_folder_name = cwd + '\\tmp\\' + folder_name + '_meshes\\'

    print("Input mesh: " + in_mesh + " (filename: " + filename + ")")
    print("Num iterations: " + str(num_iterations))
    print("Output folder: " + tmp_folder_name)
    try:
        os.mkdir(tmp_folder_name)
    except OSError as e:
        print(sys.stderr, "Exception creating folder for meshes: " + str(e))
    for it in range(num_iterations):
        if num_iterations == 1:
            out_mesh = tmp_folder_name + folder_name + "_it" + str(it) + ".obj"
            reduce_faces(in_mesh, out_mesh)
        else:
            out_mesh = tmp_folder_name + folder_name + "_it" + str(it) + ".obj"
            reduce_faces(last_out_mesh, out_mesh)
        last_out_mesh = out_mesh

    print()
    print("Done reducing, find the files at: " + tmp_folder_name)
```

# おわりに

簡単にでしたが、meshlabにてメッシュのフィルター操作を自動化しました。blenderやその他3DソフトもAPIは豊富にあるので、出来るだけ自動化しましょう。


# 参考サイト一覧
https://gist.github.com/tylerlindell/7435ca2261e7c404ccc1241f18e483aa