今回は、Pythonライブラリshapelyを使用して図形データの幾何学計算に挑戦します。
57-1. 幾何学計算ライブラリ shapely
Vectorworks開発者サイトのPython Package Catalogのページで紹介されている、動作確認済みのPythonライブラリにshapelyが追加されています。
shapelyはpythonで幾何学計算の処理を可能にするライブラリです。GISデータの処理に使われることが多いですが、GISに限らず図形データの操作を多様するVectorworksとの親和性は高いです。
shapelyでは主に次のような機能が提供されています。
-
- 図形データの生成(座標, 直線, ポリゴン, 座標群, 直線群, ポリゴンの集合など)
- 図形データの操作(交差判定、包含判定、距離計算など)
- 図形データの変換(回転、平行移動、スケーリングなど)
- 図形データの分析(面積計算、長さ計算、重心計算など)
57-2. shapelyのインストール
まずは、Vectorworksでshapelyを使用できるようにセットアップします。Pythonスクリプトで使用することを想定していますが、マリオネットツールにPythonライブラリを簡単にインストール仕組みがありますので、今回はこれを利用します。
マリオネットツールのツールバーからマリオネット設定を開きます。
マリオネット設定ではマリオネットの実行時の設定のほか、開発情報へのアクセス、Pythonライブラリのインストールが行えます。
PythonライブラリリポジトリのリンクからPython Package Catalogのサイトにアクセスします。
Python Package Catalogに追加されたshapelyの欄を見ると、ライブラリの名前に続けて、使用するバージョンやos等の情報が記載されています。
Windowsの場合
shapely-2.0.4-cp39-cp39-win_amd64.whl
Macの場合
shapely-2.0.4-cp39-cp39-macosx_10_9_universal2.whl
ライブラリのダウンロードリンクをPyPIサイトから取得します。
PyPIの検索窓から「shapely」で検索してshapelyのページを開きます。
記事執筆時点ではshapelyの最新バージョンは2.0.6ですね。
今回使用するのはカタログに記載されている2.0.4ですので、リリース履歴から2.0.4バージョンのリンクを探します。
バージョン2.0.4のページのファイルをダウンロードから、カタログに記載されているものと同じ名前のリンクを探します。
リンクを右クリックしてリンクのアドレスをコピーからダウンロードリンクのURLをコピーします(ブラウザによって方法は異なります)。
Vectorworksに戻り、マリオネット設定のPythonライブラリのインストールをクリックします。
コピーしたダウンロードリンクを貼り付けてOKをクリックします。
完了画面が表示されたらインストール完了です。Vectorworksを再起動してライブラリを有効化しましょう。
57-3. 幾何学計算の準備
今回はshapelyを使用して幾何学計算から生成できる図形をVectorworksに描画します。その準備として、乱数を用いた関数を2つ作成します。
57-3-1. 座標値のランダム生成
x座標、y座標それぞれにランダムに値を設定して、無作為な座標値を1つ作成する関数を作ります。無作為と言いつつも値が発生する範囲はその都度設定できるように設計します。
ここではrandomモジュールのuniformを使用して乱数値を生成します。
import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y )
基準点をプロットして関数の動きをテストします。座標値をランダムに50個生成します。
import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) for ii in range(50): x,y = CreateRandomPoint() vs.Locus( (x,y) )
57-3-2. 色属性値のランダム生成
もう一つ、今度はRGB値をランダムに生成して色属性を作成する関数です。RGB値はR、G、Bそれぞれの値を0〜65535(255*257)の間で設定します。
import random def CreateRandomColor(): r = random.uniform( 0, 255*257 ) g = random.uniform( 0, 255*257 ) b = random.uniform( 0, 255*257 ) return ( r,g,b )
関数のテストとして基準点にランダムに着色します。
import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) def CreateRandomColor(): r = random.uniform( 0, 255*257 ) g = random.uniform( 0, 255*257 ) b = random.uniform( 0, 255*257 ) return ( r,g,b ) for ii in range(50): x,y = CreateRandomPoint() vs.Locus( (x,y) ) vs.SetPenFore( vs.LNewObj(), CreateRandomColor() )
57-4. 凸包
shapelyに用意されているメソッドを利用して図形を作ります。まずはランダムにプロットした50点の座標を凸包する図形を生成します。
ランダム座標生成関数で作成した座標値はshapleyで使用できるPointオブジェクトに変換してリスト化しておきます。
pointlist = [] for ii in range(50): x,y = CreateRandomPoint() vs.Locus( (x,y) ) shpt = shapely.Point( x,y ) pointlist.append( shpt )
凸包の計算にはshapely.convex_hullを使用します。
polyobj = shapely.convex_hull( shapely.MultiPoint(pointlist) )
計算結果としてshapelyのPolygonオブジェクトが返ります。Vectorworksで扱えるデータに変換して図面上に描画します。Polygonオブジェクトからはpolyobj.boundary.xyを実行して、座標データに分解することができます。
import shapely import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) pointlist = [] for ii in range(50): x,y = CreateRandomPoint() vs.Locus( (x,y) ) shpt = shapely.Point( x,y ) pointlist.append( shpt ) polyobj = shapely.convex_hull( shapely.MultiPoint(pointlist) ) xlist,ylist = polyobj.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints ) vs.SetFPat( vs.LNewObj(), 0 )
プロットされた点の外側を囲むように凸包の図形を取得できました。
57-5. 凹包
shapely.concave_hullを使用して凹包を計算できます。
olyobj = shapely.concave_hull( shapely.MultiPoint(pointlist) )
concave_hullの計算結果もPolygonオブジェクトです。同様にデータを変換して図面上にプロットします。凹角を含む凸凹な形状の凹包の図形を取得できました。
import shapely import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) pointlist = [] for ii in range(50): x,y = CreateRandomPoint() vs.Locus( (x,y) ) shpt = shapely.Point( x,y ) pointlist.append( shpt ) polyobj = shapely.concave_hull( shapely.MultiPoint(pointlist) ) xlist,ylist = polyobj.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints ) vs.SetFPat( vs.LNewObj(), 0 )
57-6. ドロネー
次にドロネー図です。shapely.delaunay_trianglesメソッドを使用します。
delaunay = shapely.delaunay_triangles(shapely.MultiPoint(pointlist))
delaunay_trianglesの計算結果はGeometryCollectionオブジェクトです。GeometryCollectionはget_partsを使用して、Polygonオブジェクトに分解することができます。
delaunay = shapely.delaunay_triangles(shapely.MultiPoint(pointlist)) for geom in shapely.get_parts(delaunay): if shapely.get_type_id(geom) == 3: xlist,ylist = geom.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints )
分解したポリゴンのリストをそれぞれ多角形化して、ドロネー図を描画します。
描画したドロネー図をランダムにカラーリングします。色属性値のランダム生成の関数を使用します。(コード全体を示します。)
import shapely import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) def CreateRandomColor(): r = random.uniform( 0, 255*257 ) g = random.uniform( 0, 255*257 ) b = random.uniform( 0, 255*257 ) return ( r,g,b ) pointlist = [] for ii in range(50): x,y = CreateRandomPoint() shpt = shapely.Point( x,y ) pointlist.append( shpt ) delaunay = shapely.delaunay_triangles(shapely.MultiPoint(pointlist)) for geom in shapely.get_parts(delaunay): if shapely.get_type_id(geom) == 3: xlist,ylist = geom.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints ) vs.SetFillBack( vs.LNewObj(), CreateRandomColor() ) vs.SetOpacity( vs.LNewObj(), 60 )
座標も色属性もランダムですので、実行のたびに違う絵が描画されます。
57-7. ボロノイ
最後にボロノイ図です。shapely.voronoi_polygonsメソッドを使用します。
voronoi = shapely.voronoi_polygons( shapely.MultiPoint(pointlist) )
voronoi_polygonsの計算結果もGeometryCollectionオブジェクトです。分解して描画します。
voronoi = shapely.voronoi_polygons( shapely.MultiPoint(pointlist) ) for geom in shapely.get_parts(voronoi): if shapely.get_type_id(geom) == 3: xlist,ylist = geom.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints )
プロットされた座標のボロノイ領域を計算できていますが、境界線が設定されていない状態です。
凸包の図形を境界線としてプログラムに組み込んでみましょう。ボロノイの計算後の各ポリゴンに対して、intersectionメソッドを使用して境界線との共通部分を抽出します。
boundary = shapely.convex_hull( shapely.MultiPoint(pointlist) ) voronoi = shapely.voronoi_polygons( shapely.MultiPoint(pointlist) ) for geom in shapely.get_parts(voronoi): if shapely.get_type_id(geom) == 3: geom = geom.intersection(boundary) xlist,ylist = geom.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints )
最後にランダムにカラーリングします。(コード全体を示します。)
import shapely import random def CreateRandomPoint( xmin=0, xmax=100, ymin=0, ymax=100 ): x = random.uniform( xmin, xmax ) y = random.uniform( ymin, ymax ) return ( x,y ) def CreateRandomColor(): r = random.uniform( 0, 255*257 ) g = random.uniform( 0, 255*257 ) b = random.uniform( 0, 255*257 ) return ( r,g,b ) pointlist = [] for ii in range(50): x,y = CreateRandomPoint() #vs.Locus( (x,y) ) shpt = shapely.Point( x,y ) pointlist.append( shpt ) boundary = shapely.convex_hull( shapely.MultiPoint(pointlist) ) voronoi = shapely.voronoi_polygons( shapely.MultiPoint(pointlist) ) for geom in shapely.get_parts(voronoi): if shapely.get_type_id(geom) == 3: geom = geom.intersection(boundary) xlist,ylist = geom.boundary.xy polypoints = zip( xlist,ylist ) vs.Poly( *polypoints ) vs.SetFillBack( vs.LNewObj(), CreateRandomColor() ) vs.SetOpacity( vs.LNewObj(), 60 )
今回紹介したのは一部の計算に特化したメソッドです。shapelyでは図形の計算に活用できる汎用的なAPIが多数用意されていますので、またご紹介できればと思います。
この機能を利用できる製品
Vectorworks Architect建築設計や内装、ディスプレイデザインに対応した先進的なBIM・インテリア設計支援機能、拡張機能、さらには豊富な建築向けのデータライブラリを搭載した建築/内装業界向け製品 |
|
Vectorworks Landmark地形モデルや多彩な植栽、灌水設備計画等に対応するランドスケープデザイン機能、さらには豊富な造園向けのデータライブラリを搭載した都市計画/造園業界向け製品 |
|
Vectorworks Spotlightステージプランニングやライティング計画に対応した先進的な舞台照明計画支援機能、さらには各種メーカー製のトラスや照明機材、音響機器等の豊富なデータライブラリを搭載したエンタテインメント業界向け製品 |
|
Vectorworks Design Suite専門分野別(建築設計/ディスプレイデザイン、ランドスケープデザイン、ステージデザイン&スポットライトプランニング)の設計支援機能、拡張機能、さらには豊富なデータライブラリを搭載した最上位の製品 |