MoNo.RAIL Quick Template β
- Visual Studio Project Template for F#

株式会社モノコミュニティ
http://www.monocommunity.com/

2015-05-20

Overview

What is MoNo.RAIL ?

What is MoNo.Studio 3D ?

Screenshot of MoNo.Studio 3D
Screenshot of MoNo.Studio 3D

What is MoNo.RAIL Quick Template ?

Overview of MoNo.RAIL Quick Template
Overview of MoNo.RAIL Quick Template

License

MoNo.RAIL, MoNo.Studio 3D, Quick Template はすべて下記の条件で配布いたします。

Getting Started

  1. Visual Studio Gallery から MoNo.RAIL Quick Template β をダウンロード して Visual Studio 2013 にインストールします。
  2. プロジェクトの新規作成でテンプレートに MoNo.RAIL Quick Template を指定します。
  3. ビルドしてMoNo.Studio 3D を起動し、使い方を覚えます。(下記 "How to Use" 参照)
  4. テンプレートに含まれているサンプルコードを参考にしてメニューを追加します。(下記 "サンプルコード解説" 参照)

How to Use

プロジェクトをビルドして実行すれば、MoNo.Studio 3D が起動します。 MoNo.Studio 3D の使い方について概略のみをごく簡単に説明します。

ビュー操作

Operation Name Description
右ボタンドラッグ Rotation 3次元的なビューの回転
Shift + 右ボタンドラッグ Pan カメラのパン(平行移動)
Ctrl + 右ボタンドラッグ Zoom In/Out 拡大縮小
Ctrl + Alt + 右ボタンドラッグ Spin 2次元的なビューの回転
ホイール Zoom In/Out 拡大縮小
中ボタンクリック Focus クリック位置をビューの中心(焦点)に移動
F5 Fit ビューの範囲を対象物にフィット
F6 Align ビューの向きを軸平行に合わせる
F7 Clip 立方体状のクリップ平面
F8 Edge ポリゴンのエッジを表示

Import コマンド

ツールバーの Import ボタンを押すと下記のダイアログが表示されます。

画像右下にインポート可能なファイル形式が表示されています。 下記にこれらのファイル形式についてまとめた表を示します。

Format Extension Description
Foo File *.foo FileImporterSample.fs で定義されたもの(実装は空)
MoNo.RAIL Binary Format *.mono MoNo.RAIL のシリアライザによるファイル形式
STL - Standard Triangulated Language *.stl 3Dプリンタや光造形などで用いられる三角ポリゴン形式
Wavefront OBJ *.obj ポリゴン形式(完全対応ではない)

特に "Foo File (*.foo)" は FileImporterSample.fs によってプラグインされたファイル形式です。 (ただし実装は空なので実際には何もインポートできません。)

コンテキストメニュー

STLファイルをインポートしてオブジェクト選択し、右クリックすると下記のコンテキストメニューが出ます。

Samples メニュー

メインメニューには次のようなサンプルメニューが付いています。

Sample Menu
Sample Menu

これらのメニューは付属しているサンプルコードによって生成されています。 ソースファイルとサンプルメニューの対応関係は下記の通り。

source file sample menu
MessageMenu.fs MessageBox Samples
OpenGLMenu.fs OpenGL Samples
OperationMenu.fs Operation Samples
EntityMenu.fs Entity Samples
AsyncMenu.fs Async/Progress Samples

サンプルコード解説

Samples メニュー

メインメニューのプラグインは MEF; Managed Extensibility Framework の仕組みを利用して行います。 その基本形(ひながた)を下記のコードに示しました。 MoNo.Studio.MenuFactory<'con> を継承したクラスを定義し、CreateMenuItems 関数をオーバーライドしてプラグインするメニューを定義します。

open System.ComponentModel.Composition
open MoNo
open MoNo.Studio

type [<Export (typeof<IMenuFactory>)>] private MyMenuFactory() =
  inherit MenuFactory<QuickAppContext>()
  override __.CreateMenuItems con =
    []

Samples / MessageBox Samples (MessageMenu.fs)

メッセージボックスを表示するだけの最もシンプルなサンプルです。次のような形をしています。

type [<Export (typeof<Studio.IMenuFactory>)>] private MessageMenuFactory() =
  inherit Studio.MenuFactory<QuickAppContext>()

  override __.CreateMenuItems con =
    let items =
      [
        MenuItem.New (...)
        MenuItem.New (...)
        ...
      ]
    [ MenuItem.Folder ("Samples", [ MenuItem.Folder ("MessageBox Samples", items) ]) ]

最も単純なメッセージボックスの表示コマンドは次のように定義されます。

MenuItem.New ("InformationMessage",
  cont {
    do! con.Messenger.Send (Wpf.InformationMessage ("Hello World", "InformationMessage", MessageBoxImage.Information))
  } |> Wpf.Cont.toWpfCommand con None)

Samples / OpenGL Samples (OpenGLMenu.fs)

ひな形

OpenGL のサンプルコードは概ね次のひな形に従っています。

MenuItem.New ("...",
  cont {
    // MoNo.Graphics.IScene のインスタンスを生成
    let scene =
      { new Graphics.IScene with
          member __.Draw sc =
            // ここに何らかの描画コードを書く
            ... }
    con.Document.Val.Entries.Add (Wpf.Entry scene) // シーンオブジェクトを登録
  } |> Wpf.Cont.toWpfCommand con None)

OpenGL Direct Call

member __.Draw sc =
  // MoNo.OpenGL.GL クラスを利用すると P/Invoke により直接 OpenGL の API を呼び出すことができます。
  GL.glDisable( GL.GL_LIGHTING )  // ライティングを無効化
  GL.glColor3f( 1.f, 1.f, 0.f )   // 色を設定
  GL.glBegin( GL.GL_LINES ) 
  GL.glVertex3f( 0.f, 0.f, 0.f )
  GL.glVertex3f( 1.f, 0.f, 0.f )
  GL.glEnd()

By MoNo.OpenGL.MGL

member __.Draw sc =
  MGL.LightingEnabled <- false
  MGL.Color <- System.Drawing.Color.Pink.ToColor4f()
  use gl = MGL.Begin( GLPrimType.Lines )
  gl.Vertex( Point3d.Zero ) 
  gl.Vertex( Point3d( 1.0, 1.0, 1.0 ) )

By MoNo.Graphics.ISceneContext

member __.Draw sc =
  use scope = sc.Push() // scope を利用すると Lighting 等の状態変更が using スコープ内に限定されます
  scope.Lighting <- false
  scope.Color <- System.Drawing.Color.Plum
  sc.DrawLines (fun gl -> gl.Vertices( Point3d.Zero, Point3d( 1.0, 2.0, 3.0 ) ) )

VertexArray (MoNo.OpenGL.MGL)

// 頂点配列を作成
let array =
  [|
    Point3d( 0.0, 0.0, 0.0 )
    Point3d( 0.2, 0.0, 0.0 )
    Point3d( 0.2, 0.2, 0.0 )
    Point3d( 0.0, 0.2, 0.0 )
    Point3d( 0.0, 0.4, 0.0 )
    Point3d( 0.2, 0.4, 0.0 )
    Point3d( 0.2, 0.6, 0.0 )
    Point3d( 0.0, 0.6, 0.0 )
  |]
...
member __.Draw sc =
  use scope = sc.Push()
  scope.Lighting <- false
  scope.Color <- System.Drawing.Color.Salmon
  MGL.DrawArrays (GLPrimType.LineStrip, array)

VertexArray (MoNo.GraphicsUT)

// 頂点配列を作成
let array =
  GraphicsUT.CreateScene (GLPrimType.LineStrip,
    [|
      Point3d( 0.0, 0.0, 0.0 )
      Point3d( 0.2, 0.0, 0.0 )
      Point3d( 0.2, 0.2, 0.0 )
      Point3d( 0.0, 0.2, 0.0 )
      Point3d( 0.0, 0.4, 0.0 )
      Point3d( 0.2, 0.4, 0.0 )
      Point3d( 0.2, 0.6, 0.0 )
      Point3d( 0.0, 0.6, 0.0 )
    |])

Texture Mapping

let texture = MGL.CreateTextureObject( bitmap );
...
member __.Draw sc =
  use scope = sc.Push()
  scope.Lighting <- false
  scope.Color <- System.Drawing.Color.White
  use binding = texture.Bind() // テクスチャをバインドします。
  sc.DrawQuads (fun gl ->
    gl.TexCoord( 0.0, 0.0 ); gl.Vertex( 0.0, 0.0, 0.0 )
    gl.TexCoord( 1.0, 0.0 ); gl.Vertex( 1.0, 0.0, 0.0 )
    gl.TexCoord( 1.0, 1.0 ); gl.Vertex( 1.0, 1.0, 0.0 )
    gl.TexCoord( 0.0, 1.0 ); gl.Vertex( 0.0, 1.0, 0.0 ))

Samples / Operation Samples (OperationMenu.fs)

Click

cont {
  // クリックを1回検出します。クリック待ち状態のマウスカーソルを Corsors.Hand に設定しています。
  let! sender, e = con.GetLButtonClick (fun op -> op.Cursor <- Forms.Cursors.Hand)
  do! con.Messenger.Send (Wpf.InformationMessage (sprintf "Clicked: Location = %A" e.Location, "Click Sample"))
}

Click Twice

cont {
  // クリックを2回検出します。
  let! _, e1 = con.GetLButtonClick ()
  let! _, e2 = con.GetLButtonClick ()
  do! con.Messenger.Send (Wpf.InformationMessage (sprintf "1st click = %A\n2nd click = %A" e1.Location e2.Location, "Click Twice Sample"))
}

Draw Line

cont {
  // 一点目のクリックを検出します。
  let! view, e1 = con.GetLButtonClick ()
  
  // 直線の両端点の座標を作成します。
  let p1 = view.Camera.ScreenToWorld e1.Location
  let p2 = ref p1 // p2 はこの時点では p1 と同じ座標を設定しておきます

  // 二点目のクリックを検出します。
  let! _, e2 = con.GetLButtonClick (fun op ->
    op.MouseMove.Add (fun e -> p2 := view.Camera.ScreenToWorld e.Location; view.Invalidate())
    op.WorldScenes.Add (fun sc ->
      use scope = sc.Push()
      scope.Color <- Drawing.Color.Turquoise
      sc.DrawLines (fun gl -> gl.Vertices (p1, !p2))) |> ignore)

  // 2点 p1, p2 から Polyline3d オブジェクトを生成します。
  let pol = Polyline (false, [| p1; !p2 |])

  // エンティティを登録します。
  con.Document.Val.Entries.Add (Wpf.Entry pol)
}

Object Picking

cont {
  let! view, e = con.GetLButtonClick (fun op -> op.Cursor <- Forms.Cursors.Hand)
  let pick = view.PickAt<Wpf.Entry> (e.Location.ToPoint2i())
  if pick <> null then
    do! con.Messenger.Send (Wpf.InformationMessage (sprintf "HitObject = %A" pick.HitObject.Object, "Object Picking"))
}

Samples / Entity Samples (EntityMenu.fs)

Polyline3d (= Polyline<Point3d>)

cont {
  // MoNo.Geometries.Polyline<Point3d> エンティティを生成
  let polyline =
    [| Point3d( 0.0, 0.0, 0.0 )
       Point3d( 1.0, 0.0, 0.0 )
       Point3d( 1.0, 1.0, 0.0 )
       Point3d( 0.0, 1.0, 1.0 ) |] |> Polyline.ofArray false

  // シーングラフにエントリを登録
  con.Document.Val.Entries.Add (Wpf.Entry polyline)
}

Tris3d (= Soup<Triangle3d>)

cont {
  let tris = Tris3d ([| Triangle3d( Point3d( 0.0, 0.1, 0.3 ), Point3d( 0.2, 0.0, 0.5 ), Point3d( 0.4, 0.3, 0.0 ) )
                        Triangle3d( Point3d( 0.8, 0.5, 0.4 ), Point3d( 0.0, 0.2, 0.4 ), Point3d( 0.2, 0.1, 0.6 ) )
                        Triangle3d( Point3d( 0.8, 0.6, 0.3 ), Point3d( 0.3, 0.5, 0.4 ), Point3d( 0.2, 0.2, 0.0 ) )
                        Triangle3d( Point3d( 0.5, 0.2, 0.5 ), Point3d( 0.3, 0.6, 0.1 ), Point3d( 0.0, 0.3, 0.7 ) ) |])
  con.Document.Val.Entries.Add (Wpf.Entry tris)
}

Mesh3d (= Mesh<Point3d>)

cont {
  let mesh =
    let vertices = // 頂点配列
      [| Point3d( 0.0, 0.0, 1.0 )
         Point3d( 1.0, 0.0, 1.0 )
         Point3d( 1.0, 1.0, 1.0 )
         Point3d( 0.0, 1.0, 1.0 ) |]
    Mesh3d (vertices, [| Facet (0, 1, 2); Facet (0, 2, 3) |])
  con.Document.Val.Entries.Add (Wpf.Entry mesh)
}

Samples / Async/Progress Samples (AsyncMenu.fs)

Async

let traceLine s = Diagnostics.Trace.WriteLine s
...
cont {
  let! result = con.ByAsync (async {
      for i = 1 to 10 do
        traceLine "calculating ..."
        Threading.Thread.Sleep 300
      traceLine "done."
      return 1234
    })
  do! con.Messenger.Send (Wpf.InformationMessage (sprintf "result = %d" result, "Async Calculation"))
}

Progress

let calcProgressive1 () =
  Progress.Make (fun token ->
    trace "calcProgressive1: "
    for i = 1 to 10 do
      Threading.Thread.Sleep 200
      trace "."
      token.Notify 0.1
    traceLine "done.")

let calcProgressive2 () =
  Progress.Make (fun token ->
    token.Run (0.6, calcProgressive1 ())
    Threading.Thread.Sleep 500
    traceLine "calcProgressive2 - 1"
    token.Notify 0.2
    Threading.Thread.Sleep 500
    traceLine "calcProgressive2 - 2"
    token.Notify 0.2
    Math.PI )
...
cont {
  let! result = con.ByProgress (calcProgressive2 ())
  do! con.Messenger.Send (Wpf.InformationMessage (sprintf "result = %f" result, "Async Calculation"))
}

コンテキストメニュー (ContextMenu.fs)

type [<Export (typeof<IContextMenuFactory>)>] Tris3dMenu () =
  inherit ContextMenuFactory<Tris3d> ()
  override __.CreateMenuItems (target, entry, con) =
    [
      MenuItem.New ("facet count",
        con.CreateCommand ( cont {
          do! con.Messenger.Send (Wpf.InformationMessage (sprintf "count = %d" target.Items.Length, "facet count"))
        }))
    ]

インポートファイル形式 (FileImporterSample.fs)

type [<Export (typeof<Studio.IFileImporter>)>] private FileImporterSample() =
  interface Studio.IFileImporter with
    member val Caption = "Foo File"
    member val FileFilters = ["*.foo"]
    member __.TryImport path =
      Progress.Make (fun token -> None)