PythonでRhinocerosのコマンドを作成する。

#はじめに
Qitaはプログラムを書く際に参考にさせて頂いていましたが投稿自体は初です。
Rhinoceros上でのPython使用に関する記事はあまりないのと自身のメモ代わりとして今後は投稿していこうと思います。

いまは学生でして、普段はRhinoとPythonでろくでもないものを制作しています。そのへんのことも後々記事にできればと。

#概要
さて今回はPythonを使用してRhinoで使用できるコマンドを作成していく方法です。
作成する機能は同一のCurveの位置を合わせるコマンド。（機能はサンプルなので。。。必要にかられて僕が作成しているものです。）
OSはwindows10 
RhinoのバージョンはRhino6です。


#EditPythonScriptについて
まずはRhinoのコマンドラインにEditPythonScriptと打ち込みEnterを押します。

すると以下のような画面が表示されるかと思います。
![スクリーンショット (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400162/14268a22-be83-54ba-651e-c046d67ed332.png)

Editer上部のタブのファイルマークをクリック。
どの種類のFileを作成するか聞かれますのでCommandをクリックします。
![スクリーンショット (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400162/947eb904-57f3-3e38-debe-74f9a3031964.png)

Command NameとPlug-Inの名前を聞かれますので作成する機能に合わせたコマンドの名前と作成するコマンドを格納するプラグインの名前を入力。
今回はCommandNameとしてCrvFitMove、PlugInとしてSampleと入力します。

余談ですが、コマンドを作成するのではなく、Pythonコードを実行する形でRhinoになにか作成したい場合はEmpty Scriptを選択します。（他にもPycharmなどで作成したPythonファイルを開いて実行することももちろん可能です。この辺も後々記事に書いていこうと思います。）

さて、ここまでがEditPythonScriptのEditorの設定の話です。
続いて実際にコードを記述しコマンドを制作していこうと思います。

#Pythonによるコードの記述。rhinoscriptsyntaxについて。
現在うまく行っていれば画面に次のようなコードが表示されていると思います。
![スクリーンショット (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400162/4bb41fbe-e6c4-1098-c84e-2f80b0169346.png)

順にコードの説明をしていきます。
1行目の
import rhinoscriptsyntx as rs
はrhinoscriptsyntaxというパッケージをrsという収縮名でインポートしますと宣言しています。（この辺はPython側のはなしなので。。。わからない方はPythonを少し学習しましょう。）

ちなみにrhinoscriptsyntaxの中にはRhinoのオブジェクトをいろいろと操作できるメソッドが詰まっています。
基本的にはRhinoのコマンドで実行できることはほぼrhinoscriptsyntaxで可能と考えてもらって大丈夫です。（rhinoscriptsyntaxに関しましても時間があれば記事にしますね。）
ほんとに色々なことをしようと思ったり、プログラムの実行速度のことを考えたりするとimport RhinoでインポートできるRhinoCommonを使用するほうが効率いいです。

話がそれました。
3行目のダブルクオーテーションで囲まれている部分がコマンドの名前になります。

7行目から20行目は関数定義部分です。
ここに実装したいコマンドの機能を記述していくことになります。

さて、次は実際にコードを書いていきます。

#コマンド作成！
今回の記事はRhinoで呼び出せるコマンドの実装が目的ですのでコマンドの実際の機能について、またコードの中身に関しての説明は省かさせていただきます。ご了承ください。
今回、def内は次のように記述しました。

```python

# -*- coding:utf-8 -*-

import rhinoscriptsyntax as rs
import Rhino
import scriptcontext
import math

__commandname__ = "CrvFitMove"

def RunCommand( is_interactive ):
    print  __commandname__
    
    # 一致させたいcrvを取得
    base_crv = rs.GetObject('select base crv', rs.filter.curve)
    if not base_crv:
        return 1
        
    # 一致させるcrvを取得
    ori_fit_crv = rs.GetObject('select fit crv', rs.filter.curve)
    if not ori_fit_crv:
        return 1
        
    # RhinoCommonで扱うRhino.Geometryに変換。これしないとRhinoCommonのメソッド使用できない。
    base_crv = rs.coercecurve(base_crv)
    fit_crv = rs.coercecurve(ori_fit_crv)
    
    # 両端点と中点（パラメータ上の）を取得
    b_start_p = base_crv.PointAtStart
    b_end_p = base_crv.PointAtEnd
    
    bool, curve_parameter = base_crv.NormalizedLengthParameter(0.5)
    b_mid_p = base_crv.PointAt(curve_parameter)
    
    f_start_p = fit_crv.PointAtStart
    f_end_p = fit_crv.PointAtEnd
    
    # Moveで端点をあわせる。
    vec_move = b_start_p - f_start_p
    xf = Rhino.Geometry.Transform.Translation(vec_move)
    fit_crv.Transform(xf)
    
    # ベクトルを用いてMoveとRotateを行っている。
    f_end_p = fit_crv.PointAtEnd
    f_start_p = fit_crv.PointAtStart
    
    r_vec1 = f_end_p - f_start_p
    r_vec2 = b_end_p - f_start_p
    
    cross_vec = Rhino.Geometry.Vector3d.CrossProduct(r_vec1, r_vec2)
    degree = Rhino.Geometry.Vector3d.VectorAngle(r_vec1, r_vec2, cross_vec)
    
    xf = Rhino.Geometry.Transform.Rotation(degree, cross_vec, b_start_p)
    fit_crv.Transform(xf)
    
    bool, f_crv_para = fit_crv.NormalizedLengthParameter(0.5)
    bool, b_crv_para = base_crv.NormalizedLengthParameter(0.5)
    f_mid_p = fit_crv.PointAt(f_crv_para)
    b_mid_p = base_crv.PointAt(b_crv_para)
    
    
    axis_crv = Rhino.Geometry.Line(b_start_p, b_end_p)
    a_crv_para = axis_crv.ClosestParameter(b_mid_p)
    a_mid_p = axis_crv.PointAt(a_crv_para)
    a_start_p = axis_crv.PointAt(0)
    a_end_p = axis_crv.PointAt(1)
    
    r_vec1 = f_mid_p - a_mid_p
    r_vec2 = b_mid_p - a_mid_p
    cross_vec = Rhino.Geometry.Vector3d.CrossProduct(r_vec1, r_vec2)
    degree = Rhino.Geometry.Vector3d.VectorAngle(r_vec1, r_vec2, cross_vec)
    
    xf = Rhino.Geometry.Transform.Rotation(degree, cross_vec, a_mid_p)
    fit_crv.Transform(xf)
    
    # 移動させたCurveをデリート。Rhino.GeometryをRhinoに描画（型変換している）
    rs.DeleteObject(ori_fit_crv)
    scriptcontext.doc.Objects.AddCurve(fit_crv)
    
    return 0
```

結局のところRhinoCommonを使用して記述してしまいましたが、結果的にこちらのほうが良いと思います。rhinoscriptsyntaxはメソッドを使用するたびにRhinoに描画されてしまいますのでどうしても実行速度が遅くなってしまいます。一方でRhinoCommonは明示的に描画しないとRhinoに描画されない性質を持っています。

最後に作成したファイルをCtrl+Cかなにかで上書き保存しRhinoのコマンドラインにCrvFitMoveと入力すると作成したコマンドが実行されると思います。
![スクリーンショット (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400162/3592ec8a-10d9-ab56-7fc9-6119762ae098.png)

実行し、選択すると。。。
![スクリーンショット (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400162/dd695017-5f82-754e-601f-f6c303e4dad3.png)

こう、重なります。。わかりにくいですが。。。笑



#終わりに
後半は駆け足になってしまいましたが、いかがでしたでしょうか。
Pythonを使用しRhinoをカスタマイズすることで可能なことは多いと思います。
僕自身も色々と試しながら記事を書いていこうと思っていますので皆さんよろしくお願いいたします。
最後までご拝読いただきありがとうございました。