ただし空気抵抗は無視しないものとする

流体解析の勉強の題材として日頃気になったことをまとめたり確かめたりしていく予定です。OSSしか使わないつもりです。

ParaViewのプラグインでフィルターを作ってみた

はじめに

 かゆい所にも手が届く3D描画ソフトParaViewですがそれでも更に機能が欲しい時があるかと思います。 基本的にはProgrammable Filterを用いることが多いかと思いますが、今回は一歩踏み込んで公式ドキュメント(ParaView/Plugin_HowTo)を見ながらプラグインを作ってみました。  

作ってみたもの

 こんな感じで回転方向に複数個コピーできるようなフィルターを作ってみました。 ただ作っている途中にこの機能を持ったvtkのライブラリを発見してしまいましたのでほぼその真似となっています。

f:id:inabower:20190316122245g:plain

f:id:inabower:20190316121038p:plain

環境

  • Ubuntu 18.04
  • ParaView 5.6.0 (OpenFOAM v1812同梱)
  • cmake 3.10.2
  • gcc 7.3.0

プラグインのExampleをみてみる(下準備を兼ねて)

 ParaViewのソースの中にはExampleが用意されています。このコピーを元にプラグインを作成していきます。

構成

Examples/Filterというプラグインの構成を見てみましょう。

$ tree $WM_THIRD_PARTY_DIR/ParaView-v5.6.0/Examples/Plugins/Filter
.
├── CMakeLists.txt
├── MyElevationFilter.xml
├── vtkMyElevationFilter.cxx
└── vtkMyElevationFilter.h

 メインの挙動を示すのは*.cxx*.hになります。 これをCMakeLists.txtの設定に従いcmakeでコンパイルすることになります。 *.xmlにはParaViewの中で表示される項目などの設定が入っています。

 なにはともあれ試しにこれをコンパイルして動かしてみましょう。

コンパイル

 適当な場所に作業ディレクトリを用意してこのExample/Filterをコンパイルします。

$ buildDir=myFilters/exampleFilter/build
$ mkdir -p $buildDir && cd $buildDir
$ cp -r $WM_THIRD_PARTY_DIR/ParaView-v5.6.0/Examples/Plugins/Filter ../
$ cmake ..
-- The C compiler identification is GNU 7.3.0
-- The CXX compiler identification is GNU 7.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: ???/myFilters/exampleFilter/build
$ make
Scanning dependencies of target SMMyElevationHierarchy
[  9%] For SMMyElevation - updating SMMyElevationHierarchy.txt
[  9%] Built target SMMyElevationHierarchy
[ 18%] Generating Documentation HTMLs from xmls
Processing wiki sources
Processing wiki filters
Processing wiki writers
Processing wiki readers
[ 27%] Compiling Qt help project SMMyElevation.qhp
ファイル構造の構築中...
カスタムフィルターの登録中...
フィルターにヘルプデータを登録中 (1 / 1)...
ファイルの挿入...
コンテンツの挿入...
索引の挿入...
ドキュメントの作成に成功しました。
[ 36%] Generating SMMyElevation_doc.h
-- Generate module: SMMyElevation
[ 45%] CS Wrapping - generating vtkMyElevationFilterClientServer.cxx
[ 54%] Generating vtkSMXML_SMMyElevation.h
-- Generate module: MyElevationFilter
Scanning dependencies of target SMMyElevation
[ 63%] Building CXX object CMakeFiles/SMMyElevation.dir/vtkMyElevationFilter.cxx.o
[ 72%] Building CXX object CMakeFiles/SMMyElevation.dir/vtkMyElevationFilterClientServer.cxx.o
[ 81%] Building CXX object CMakeFiles/SMMyElevation.dir/SMMyElevationInit.cxx.o
[ 90%] Building CXX object CMakeFiles/SMMyElevation.dir/SMMyElevation_Plugin.cxx.o
[100%] Linking CXX shared library libSMMyElevation.so
[100%] Built target SMMyElevation



 こんな感じでコンパイルされました。これによりshared objectファイル○○.soが生成されますのでこれをParaViewで読み込むこととなります。

ParaViewで読み込む

 ParaViewで適当なケースを読み込んだ後に今コンパイルしたプラグインを読み込んでみましょう。 プラグインは以下の手順で読み込みます。

f:id:inabower:20190315220250p:plain

  • ツールバー → Tools → Manage Plugin
  • Load New... → 〇〇.soを選択 → close
  • ツールバー → Filters → Search → 「myで検索」

 これによりMyElevationというフィルターが読み込まれていることが確認できます。

f:id:inabower:20190315220829p:plain

作っていく

 それでは作っていきましょう。以下のフィルターを参考にしたりコピペしたりしながら作成しました。

  • $WM_THIRD_PARTY_DIR/ParaView-v5.6.0/VTK/Filters/General/vtkRotationFilter.h
  • $WM_THIRD_PARTY_DIR/ParaView-v5.6.0/VTK/Filters/General/vtkTransformFilter.h
  • $WM_THIRD_PARTY_DIR/ParaView-v5.6.0/VTK/Filters/Modeling/vtkRotationalExtrusionFilter.h

名前を変更

 今回作るフィルターの名前をMultiRotationFilterとして先程コピーしたファイルを以下のように名前を変更していきます。

tree
.
├── CMakeLists.txt
├── MultiRotationFilter.xml
├── vtkMultiRotationFilter.cxx
└── vtkMultiRotationFilter.h

CMakeLists.txt

 コンパイルの設定ファイルです。まずはこの中身を以下のように変更します。

cmake_minimum_required(VERSION 3.3)

if (NOT ParaView_BINARY_DIR)
  find_package(ParaView REQUIRED)
  include(${PARAVIEW_USE_FILE})
endif()

if(NOT DEFINED CMAKE_MACOSX_RPATH)
  set(CMAKE_MACOSX_RPATH 0)
endif()

include(ParaViewPlugins)

ADD_PARAVIEW_PLUGIN(MultiRotationFilter "1.0"
  SERVER_MANAGER_XML MultiRotationFilter.xml
  SERVER_MANAGER_SOURCES vtkMultiRotationFilter.cxx)

 オリジナルから変更を加えたのは下の三行です。 ADD_PARAVIEW_PLUGINという項目に名前などを追加することでプラグインとして使用可能にします。 ADD_PARAVIEW_PLUGIN([.soの名前] [バージョン] [入力設定] [自作プラグイン])という構成になります。

xmlの構成

 まず変更前のXMLの内容を見てみましょう。構成としては以下のようになっていると思われます。

<ServerManagerConfiguration>
  <ProxyGroup name="filters">
   <SourceProxy name="生成されるオブジェクト名" class="cxxに書かれるクラス名" label="検索などで表示される名前">
     <InputProperty name="Input" command="SetInputConnection"> 
        <!-- フィルター前の特徴等 -->
      </InputProperty>

      <DoubleVectorProperty name="cxxや.hでの変数名" label="ParaViewでの表示名" command="関数名" number_of_elements="3">
        <!-- 最小値など。この部分が入力画面となる -->
      </DoubleVectorProperty>

   <!-- End MyElevationFilter -->
   </SourceProxy>
 </ProxyGroup>
</ServerManagerConfiguration>

 SourceProxyの宣言の際にParaView内でのこのフィルターの表示名などを指定します。 またそのあとのInputPropertyではこのフォルターがどのようなデータについて扱うのか(例:CellDataだけ や 面だけ など)について指定します。 その後には〇〇Propertyが複数続きますが、主に下図のようなParaViewの左枠の項目を設定することとなります。

f:id:inabower:20190316064214p:plain

変数をParaViewで入力できるようにする

 今回のフィルターにおいて入力時に設定したい変数としては以下の3つです。

  • 回転軸の方向(以降Axis
  • 回転軸の中心(以降Center
  • 複製数(以降NumberOfCopies

 まずはxmlを変更してこれらをParaViewの左画面で入力できるようにしていきます。

MultiRotationFilter.xml

<ServerManagerConfiguration>
  <ProxyGroup name="filters">
   <SourceProxy name="MultiRotationFilter" class="vtkMultiRotationFilter" label="Multi Rotation">
     <InputProperty
        name="Input"
        command="SetInputConnection">
           <ProxyGroupDomain name="groups">
             <Group name="sources"/>
             <Group name="filters"/>
           </ProxyGroupDomain>
           <DataTypeDomain name="input_type">
             <DataType value="vtkDataSet"/>
           </DataTypeDomain>
      </InputProperty>

      <IntVectorProperty command="SetAxis" default_values="0" name="Axis" number_of_elements="1">
        <EnumerationDomain name="enum">
          <Entry text="Axis X" value="0" />
          <Entry text="Axis Y" value="1" />
          <Entry text="Axis Z" value="2" />
        </EnumerationDomain>
      </IntVectorProperty>
      
      <DoubleVectorProperty command="SetCenter" default_values="0 0 0" name="Center" label="Center" number_of_elements="3">
      </DoubleVectorProperty>
      
      <IntVectorProperty command="SetNumberOfCopies" default_values="4" name="NumberOfCopies" label="NumberOfCopies" number_of_elements="1">
        <IntRangeDomain min="1" name="range" />
      </IntVectorProperty>
      
   </SourceProxy>
 </ProxyGroup>
</ServerManagerConfiguration>

 上のようにInput Propertyの後に"Axis""Center""NumberOfCopies"をそれぞれ追加しました。 ただこのままコンパイルしてもParaViewはエラーで止まってしまいますので続けて.cppと.hも変更していきます。

vtkMultiRotationFilter.h

 以下のような内容のものを作成します。

#ifndef vtkMultiRotationFilter_h
#define vtkMultiRotationFilter_h

#include "vtkFiltersGeneralModule.h" // For export macro
#include "vtkUnstructuredGridAlgorithm.h"

class VTKFILTERSGENERAL_EXPORT vtkMultiRotationFilter : public vtkUnstructuredGridAlgorithm
{
public:
  static vtkMultiRotationFilter *New();
  vtkTypeMacro(vtkMultiRotationFilter, vtkUnstructuredGridAlgorithm);
  void PrintSelf(ostream &os, vtkIndent indent) override;

  enum RotationAxis
  {
    USE_X = 0,
    USE_Y = 1,
    USE_Z = 2
  };

  //@{
  vtkSetClampMacro(Axis, int, 0, 2);
  vtkGetMacro(Axis, int);
  void SetAxisToX() { this->SetAxis(USE_X); };
  void SetAxisToY() { this->SetAxis(USE_Y); };
  void SetAxisToZ() { this->SetAxis(USE_Z); };
  //@}

  //@{
  vtkSetVector3Macro(Center,double);
  vtkGetVector3Macro(Center,double);
  //@}

  //@{
  vtkSetMacro(NumberOfCopies, int);
  vtkGetMacro(NumberOfCopies, int);
  //@}

protected:
  vtkMultiRotationFilter();
  ~vtkMultiRotationFilter() override;

  int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override;
  int FillInputPortInformation(int port, vtkInformation *info) override;
  int CopyInput;
  
  int Axis;
  double Center[3];
  int NumberOfCopies;

private:
  vtkMultiRotationFilter(const vtkMultiRotationFilter&) = delete;
  void operator=(const vtkMultiRotationFilter&) = delete;
};

#endif

VTKマクロ

 ここでは基本的な宣言を行うのですが注意すべきはマクロです。 今回作るフィルターの中ではvtkのマクロ機能を用いて関数が作成されます。

  //@{
  vtkSetMacro(NumberOfCopies, int);
  vtkGetMacro(NumberOfCopies, int);
  //@}

のような宣言が〇〇.hで行われている場合には〇〇.cxxでは

int nb = this -> GetNumberOfCopies();

のようにして"Get"がついた関数が使用できるようになります。

vtkMultiRotationFilter.cxx

 同様にcxxについても以下のように変更します。

#include "vtkMultiRotationFilter.h"

#include "vtkCellData.h"
#include "vtkGenericCell.h"
#include "vtkIdList.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkUnstructuredGrid.h"
#include "vtkMath.h"
#include "vtkTransform.h"

vtkStandardNewMacro(vtkMultiRotationFilter);

//---------------------------------------------------------------------------
vtkMultiRotationFilter::vtkMultiRotationFilter()
{
  this->Axis = 2;
  this->CopyInput = 1;
  this->Center[0] = this->Center[1] = this->Center[2] = 0;
  this->NumberOfCopies = 4;
}

//---------------------------------------------------------------------------
vtkMultiRotationFilter::~vtkMultiRotationFilter() = default;

//---------------------------------------------------------------------------
void vtkMultiRotationFilter::PrintSelf(ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Axis: " << this->Axis << endl;
  os << indent << "Center: (" << this->Center[0] << "," << this->Center[1]
               << "," << this->Center[2] << ")" << endl;
  os << indent << "NumberOfCopies: " << this->NumberOfCopies << endl;
}

//---------------------------------------------------------------------------
int vtkMultiRotationFilter::RequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{}

int vtkMultiRotationFilter::FillInputPortInformation(int, vtkInformation *info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
  return 1;
}

 この状態で再度コンパイルして読み込むと以下のように入力画面が現れているかと思います。

f:id:inabower:20190316120835p:plain

 ただしこれだけではデータを読み込むことをしないためこのフィルターを通すと何も表示しなくなります。 そのため次はとりあえずデータは読み込むけど何もしないフィルターを作ってみます。

動作を組み込む。

 ここからが本番です。目的となる動作を行うためにvtkMultiRotationFilter.cxxRequestData関数を作っていきます。

 基本的にVTKの内容になってきますのでVTK ExampleVTK Doxygen を参考にゴリゴリ作っていきます。

int vtkMultiRotationFilter::RequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  // get the info objects
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the input and output
  vtkDataSet *input = vtkDataSet::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkUnstructuredGrid *output = vtkUnstructuredGrid::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkIdType i;
  vtkPointData *inPD = input->GetPointData();
  vtkPointData *outPD = output->GetPointData();
  vtkCellData *inCD = input->GetCellData();
  vtkCellData *outCD = output->GetCellData();

  if (!this->GetNumberOfCopies())
  {
    vtkErrorMacro("No number of copy set!");
    return 1;
  }

  double tuple[3];
  vtkPoints *outPoints;
  double point[3], center[3], negativCenter[3];
  int ptId, cellId, j, k;
  vtkGenericCell *cell = vtkGenericCell::New();
  vtkIdList *ptIds = vtkIdList::New();

  outPoints = vtkPoints::New();

  vtkIdType numPts = input->GetNumberOfPoints();
  vtkIdType numCells = input->GetNumberOfCells();

  if (this->CopyInput)
  {
    outPoints->Allocate((this->CopyInput + this->GetNumberOfCopies()) * numPts);
    output->Allocate((this->CopyInput + this->GetNumberOfCopies()) * numPts);
  }
  else
  {
    outPoints->Allocate( this->GetNumberOfCopies() * numPts);
    output->Allocate( this->GetNumberOfCopies() * numPts);
  }

  outPD->CopyAllocate(inPD);
  outCD->CopyAllocate(inCD);

  vtkDataArray *inPtVectors, *outPtVectors, *inPtNormals;
  vtkDataArray *inCellVectors, *outCellVectors, *inCellNormals;

  inPtVectors = inPD->GetVectors();
  outPtVectors = outPD->GetVectors();
  inPtNormals = inPD->GetNormals();
  inCellVectors = inCD->GetVectors();
  outCellVectors = outCD->GetVectors();
  inCellNormals = inCD->GetNormals();

  // Copy first points.
  if (this->CopyInput)
  {
    for (i = 0; i < numPts; i++)
    {
      input->GetPoint(i, point);
      ptId = outPoints->InsertNextPoint(point);
      outPD->CopyData(inPD, i, ptId);
    }
  }
  vtkTransform *localTransform = vtkTransform::New();
  // Rotate points.
  // double angle = vtkMath::RadiansFromDegrees( this->GetAngle() );
  this->GetCenter(center);
  negativCenter[0] = -center[0];
  negativCenter[1] = -center[1];
  negativCenter[2] = -center[2];

  double angle = 360/this->NumberOfCopies;
  for (k = 0; k < this->GetNumberOfCopies(); k++)
  {
   localTransform->Identity();
   localTransform->Translate(center);
   
   switch (this->Axis)
   {
     case USE_X:
        localTransform->RotateX((k+1)*angle);
     break;

     case USE_Y:
        localTransform->RotateY((k+1)*angle);
     break;

     case USE_Z:
        localTransform->RotateZ((k+1)*angle);
     break;
   }
   localTransform->Translate(negativCenter);
   for (i = 0; i < numPts; i++)
   {
    input->GetPoint(i, point);
    localTransform->TransformPoint(point, point);
    ptId = outPoints->InsertNextPoint(point);
    outPD->CopyData(inPD, i, ptId);
    if (inPtVectors)
    {
      inPtVectors->GetTuple(i, tuple);
      outPtVectors->SetTuple(ptId, tuple);
    }
    if (inPtNormals)
    {
      //inPtNormals->GetTuple(i, tuple);
      //outPtNormals->SetTuple(ptId, tuple);
    }
   }
  }

  localTransform->Delete();

  int numCellPts,  cellType;
  vtkIdType *newCellPts;
  vtkIdList *cellPts;

  // Copy original cells.
  if (this->CopyInput)
  {
    for (i = 0; i < numCells; i++)
    {
      input->GetCellPoints(i, ptIds);
      output->InsertNextCell(input->GetCellType(i), ptIds);
      outCD->CopyData(inCD, i, i);
    }
  }

  // Generate rotated cells.
  for (k = 0; k < this->GetNumberOfCopies(); k++)
  {
    for (i = 0; i < numCells; i++)
    {
       input->GetCellPoints(i, ptIds);
       input->GetCell(i, cell);
       numCellPts = cell->GetNumberOfPoints();
       cellType = cell->GetCellType();
       cellPts = cell->GetPointIds();
      // Triangle strips with even number of triangles have
      // to be handled specially. A degenerate triangle is
      // introduce to flip all the triangles properly.
      if (cellType == VTK_TRIANGLE_STRIP && numCellPts % 2 == 0)
      {
        vtkErrorMacro(<< "Triangles with bad points");
        return 0;
      }
      else
      {
        vtkDebugMacro(<< "celltype " << cellType << " numCellPts " << numCellPts);
        newCellPts = new vtkIdType[numCellPts];
        //for (j = numCellPts-1; j >= 0; j--)
        for (j = 0; j < numCellPts; j++)
        {
          //newCellPts[numCellPts-1-j] = cellPts->GetId(j) + numPts*k;
          newCellPts[j] = cellPts->GetId(j) + numPts*k;
           if (this->CopyInput)
           {
             //newCellPts[numCellPts-1-j] += numPts;
             newCellPts[j] += numPts;
           }
        }
      }
      cellId = output->InsertNextCell(cellType, numCellPts, newCellPts);
      delete [] newCellPts;
      outCD->CopyData(inCD, i, cellId);
      if (inCellVectors)
      {
        inCellVectors->GetTuple(i, tuple);
        outCellVectors->SetTuple(cellId, tuple);
      }
      if (inCellNormals)
      {
        //inCellNormals->GetTuple(i, tuple);
        //outCellNormals->SetTuple(cellId, tuple);
      }
    }
  }

  cell->Delete();
  ptIds->Delete();
  output->SetPoints(outPoints);
  outPoints->Delete();
  output->CheckAttributes();

  return 1;
}

 基本的にはvtkRotationFilterのパクリです。角度を複製下数から計算するように変更したくらいです。 これで以下のように回転方向への複数コピーができるようになりました。

f:id:inabower:20190316121038p:plain

f:id:inabower:20190316122245g:plain

最後に

 今回はフィルターを作ってみましたが、プラグインですので用途は様々です。 ただ変わったことをやろうとするとVTKの複雑な構造を理解する必要がありそうですのでそっちの勉強も頑張ってみたいと思います。

参考文献

ParaView/Plugin_HowTo

VTK Example

VTK Doxygen