Maya + pyshp でシェープファイル (.shp)を取り込む

#CGソフトにShapefileを取り込みたい
建築系CGプロダクションで働く3DCGモデラーです。

建築CGを制作していると、時々、[シェープファイル(.shp)](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%BC%E3%83%97%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)をCGソフトに取り込みたい時があります。
地図なんかを取り込みたいときです。
[GIS](https://ja.wikipedia.org/wiki/%E5%9C%B0%E7%90%86%E6%83%85%E5%A0%B1%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0)系のソフトは持ってないけど、何かお手軽に取り込むことは出来ないか？
というときに使える方法を紹介します。



##Python + pyshp
CG系のソフトはスクリプティング環境としてPythonを使えることが多いです。
Pythonには **pyshp** というシェープファイルを扱うライブラリがあるので、それを使って.shpファイルを取り込めます。
[pyshp](https://pypi.org/project/pyshp/#reading-shapefiles)


#Maya
この記事ではMayaに.shpファイルを取り込み、Curveとして描画する方法について書きます。


###pyshpをMayaへインストール

まず、Maya上のPythonである、"**mayapy**"にpyshpをインストールします。

```console:install.bat
cd "C:\Program Files\Autodesk\Maya2018\bin"
"mayapy.exe" -m pip install "C:\Users\Takeharu\Documents\maya\3rdParty\pyshp-2.1.0.tar.gz"

```


###スクリプティング
そしてMaya用のPythonスクリプト。

```c:import_shapefile.py

# -*- coding: utf-8 -*-
import pymel.core as pm
import shapefile
import os.path

#  ファイルオープンダイアログ
def get_file():
    startdir = ''
    if pm.optionVar( exists='defaultStarDirectoryOfImportShapefile' ):
        startdir = pm.optionVar( q='defaultStarDirectoryOfImportShapefile' )
    else:
        startdir = 'C:/Users/Takeharu/Documents'
    fn = pm.fileDialog2(fileMode=1, caption="Open Shape File...", fileFilter="*.shp", startingDirectory=startdir, okCaption='Open')
    pm.optionVar( sv=('defaultStarDirectoryOfImportShapefile', os.path.dirname(fn[0])) )
    return fn[0]



file_path = get_file()  # ファイルパス取得
with open(file_path, "rb") as myshp:  #  ファイルを開く
    r = shapefile.Reader(shp = myshp)  # リーダー
    if len(r.shapes()):
        pm.refresh(suspend=True)
        grp = pm.group(empty=1,name='shp_group0')
        crvs = []
        for s in r.shapes():
            t = s.shapeType
            if t == 3 or t == 5:  # 今回は地図をCurveで描画したいだけなので、シェイプのタイプを 3:Polyline、5:Polygonに限定します。
                mapped = map(lambda x: x+(0.0,), s.points)  # 元が2D座標データなので、Z軸座標を追加して3次元座標に変換します。
                crv = None
                if len(s.parts) == 1:
                    crv = pm.curve(p=mapped,d=1)
                    crvs.append(crv)
                else:  # ひとつのシェイプに複数のPolygonが含まれることがあります。それ切り分けます。
                       # parts`が[0,256,150] みたいなリストなら、複数のオブジェクトを内包しています。
                       # 数字は座標リストの区切りです。
                    for i in range(0,len(s.parts)-1):  
                        pnts = []
                        pnts = mapped[s.parts[i]:s.parts[i+1]]
                        crv = pm.curve(p=pnts,d=1)
                        crvs.append(crv)
                    crv = pm.curve(p=mapped[s.parts[-1]:],d=1)
                    crvs.append(crv)

        pm.parent(crvs,grp)
        g = grp.getParent()
        pm.select(grp,r=1)
        pm.rotate(g, [-90.0,0,0]) # MayaのY-Upに合わせて全体を回転します。
        pm.viewFit( pm.selected())
        pm.refresh(suspend=False)
        
    for c in pm.ls(cameras=1): # 地図の場合、巨大な図形となるので、カメラのクリッピングプレーンを調整します。
        c.setFarClipPlane(1000000)
        c.setNearClipPlane(1)
        

# ※コピペするときは、使う前にコメントを全部削除してください。
```


```python:シェルフボタン

import sys
if 'terrain.import_shapefile' in sys.modules:
    reload(terrain.import_shapefile)
else:
    import terrain.import_shapefile
    
```




道路のデータを取り込んでみた状態
![sct20190524091323.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66381/b8690bdb-ae7f-5ac3-1c62-29deeb89729a.png)
元データが大きいと、結構時間がかかります。時に数時間かかることもあります。
一見、フリーズしたように見えても放っておけばそのうち終わります。
それがMaya。






###Mayaの単位の話
Mayaの単位について知っておくといいと思います。
MayaはCADソフトなどに比べて内部精度が低いです。
そのため、原点から遠く離れた位置に図形を描画するのが苦手です。

GIS系のデータであるシェープファイルは、この "**原点から遠く離れた位置**" に座標を持つことが珍しくありません。
これをMayaのデフォルト単位であるcmに変換して取り込もうとすると、描画エラーになることがあります。

詳しいことはわかりませんが、"桁落ち"とかそういう類のエラーだと思われます。
DXF/DWGを取り込もうとして、なぜか図が出てこないようなときはこれかもしれません。

面倒を避けたいなら、1ユニット = 1cm ではなく、**1ユニット = 1m** に読み換えて作業するといいです。
取り込み後に、原点の近くにオブジェクトを移動させてしまえば、その後はスケールを修正しても大丈夫です。