16
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APEX解体新書

Last updated at Posted at 2023-12-11

はじめに

概要

この記事はHoudiniアドベントカレンダー2023 11日目の記事です。
Houdini20から追加された新機能APEXに関して記載していきたいと思います。
※こちらの記事ではAPEXの概念からグラフの読み書き、リグの組み方までの工程になっており、ANIM以降の工程は記載しておりません。ご了承ください。

公式ドキュメント
https://www.sidefx.com/docs/houdini20.0/character/kinefx/apexgraphs.html

H20の紹介動画。APEXの概念から説明されている。
KineFX - Rigging | Esther Trilsch | H20 HIVE
https://youtu.be/2bjPTvkpNC0

公式サンプルの解説
Luchador & Chicken | Rig & Network Walkthrough
https://youtu.be/wOVoMkjvpEk

公式ではないがこちらもオススメ
APEX Rigging | max rose
https://youtube.com/playlist?list=PLF-ZemGAVNaLOB6IQZg1OBxJ2iq7mMc5-&si=hVoSoP97oMQQwA1V

最近の更新で追加されたAPEX editgraphSOPのヘルプ画面からサンプルを呼び出すことも可能になっているので、是非触ってみてほしい。

使用環境

Houdini 20.0.543
ゴールドビルドは20.0.506ですが、こちらだとAPEXのエラーが多々発生するため、こちらを使用しています。(体感だと530アタリから安定してきた感じがする。)
また、デイリービルドによってはパラメータの名称が異なる場合があるので、その点はご了承ください。

サンプルファイル

こちらからサンプルファイルをDL出来ます。

APEXについて

APEX(All-Purpose EXecution)は、Houdiniの新しいグラフ評価フレームワークで、グラフ構造を構築して実行し、その結果を取得することができます。 (中略) APEXでは、リグロジックをプロシージャルに構築し、ネットワークの後の時点でグラフを評価(実行)することができます。(引用)

ハチャメチャに噛み砕いて説明をすると、
"グラフ"に何かしらの"入力"を行い、そのグラフ構造を"評価(実行)"することで結果が得られるということです。

ステップ1 Hello,APEX!

ここからは実際にグラフを触ってみようと思う。ここではかなりスモールなグラフから深堀を始めてみよう。

ステップ1-1

ステップ1-1
1 FileSOPを呼び出してパスに以下を入力$HFS/packages/apex/geo/hello_apex.bgeo
2 tabキーからAPEX Invoke Graph(invokegraphSOP)を呼び出し、左側の入力1とFileSOPをつなぐ
3 invokeGraphのBindOutputGeometryのチェックを入れてパスにoutput2:geoを入力

"Hello,!"という文字列ジオメトリが表示されていれば成功です。ここで状況を整理しましょう。
fileで読み込んだ点とライン郡がグラフ,invokeのノードが評価(実行) になっています。一旦グラフの部分が何をしているかは置いておいて、次に入力を追加していきます。image.png

ステップ1-2

ステップ1-2
1 tabキーからAttribAdjustDictionarySOP呼び出して、Set Valuesにkey:b,Value:APEXを設定
2 AttribAdjustDictionaryの出力をinvokegraphの右側につなげる
3 invokeGraphのInput Bindingの値を1追加する。

"Hello,APEX!"という文字列ジオメトリが表示されていれば成功です。ステップ1-1の文字列と任意の入力文字列を結合させることが出来ました。さてここでグラフの深堀をしてみましょう。
image.png
新しいパネルを立ち上げます。NewPaneTabType>Animation/APEX Network Viewを表示させます。
fileSOPにDiplayFlagを立ててノードを選択した状態でAPEX Network Viewを見てみましょう。parmsやsop__fontなどが見えるかと思います。
APEX Network ViewでPキーもしくはノードの上で右クリック>Parametersからグラフノードのパラメータの情報が見ることが出来ます。
image.png
・parmsのアウトプットの名前がbになっています。つまりこれが入力部分の辞書型のキーとリンクしています。
・invokegraphのOutputGeometryの部分がoutput2:geoになっていますが、これはグラフ上のoutput2のgeo項目の値が結果として反映されているということですね。
・グラフのノードの位置と接続がpointとprimで表現されているのが分かるかと思います。pointのアトリビュートを見てみると、callbackやname,parmsという文字型アトリビュートがあります。ノード同士の接続はvertexで管理されており、これがAPEXグラフを構成しています。

ポイントアトリビュート 説明
callback 呼び出すグラフノード固有の名前
name 各ノードに設定された名前
parms ノードが持つパラメータの値(無いものはデフォルト値)

ここでoutputのresultがありますね、こちらに触れていきます。invokegraphのoutputDictionaryBindingsの値を1追加してみてください。GeometrySpredsheet等でDetailの結果を確認するとparmsという辞書型のアトリビュートが追加されていて、parms["result"]がジオメトリに表示されている文字列とリンクしていることが分かります。つまり、APEXの実行結果は辞書型のアトリビュート[detail]もしくは何かしらのジオメトリ、その両方だということが分かります。
image.png

parmsの位置に関してはどちらでも実行は可能です。複数の入力や、ジオメトリ入力の時に分かりやすいので、本記事では左を採用しています。
image.png

ちょっと余談ですが、sop__fontというグラフノードが出てきましたが、APEXなのにSOP?となる人もいるかもしれません。SOPのコアシステムのことをverbといい、コアの機能を分離することで別の場所から呼び出せるようになっているらしいです。(ザックリ)ここではAPEXのグラフノードの機能に一部のSOPを組み込めるんだなぁという認識で大丈夫だと思います。
https://www.sidefx.com/docs/houdini/model/verbs.html

さて、次はAPEXグラフの編集をしていきます。

ステップ1-3

ここでは1-2まで使用したグラフを元に末尾に任意のジオメトリを配置するグラフに編集していく。
グラフを編集する方法は基本的にはStashSOPを用いるが、ここではStashSOPをAPEXグラフ用に使いやすくした、editgraphSOPを使用していく。

ステップ1-3
1 fileSOPの下にlayoutgraphSOP,editgraphSOPの順でつなぎ、editgraphSOPを選択した状態でAPEXNetworkViewの方に移動する
2 sop::boundとsop::balst、sop::xformを追加してその順番にgeoinput0とgeoを繋いでいく。
3 sop::blastのパラメータをgroup:2,grouptype:3に変更,sop::xformのパラメータをt:{0.7,0.5,0.0}に変更
4 sop::copytopoint::2.0を追加して、geoinput0にparmsのnextを接続、geoinput1にsop::xformのgeoを接続
5 fontの出力が反転しているのが気になるので、sop::reverseを接続する
6 geo::Mergeを追加してsop::reverseとsop::copoytopoint::2.0のgeoをそれぞれ接続、geo::Mergeの結果をoutput2のgeoに接続
7 parmsに戻って、パラメータbの上で中クリックしてnameをtextに変更、下のgeoinput0もgeoに変更
8 SOPNetworkに戻ってsimple_shapesを追加して、invokegraphの右の接続に追加する。invokegraphのInputBindingを2にして2つ目のBindToGeometryをONにして、入力値をgeoにする。AttribAdjustDictionaryのKeyをtextに変更する。

image.png
image.png
今回は適当なジオメトリを文字列の後ろに表示するためにSimpleShapeSOPを使用していますが、こちらは何でも大丈夫です。
ここで抑える点は2つです。
1:APEXグラフの実行には複数の入力を扱うことが出来る。ただしジオメトリの入力に関してはINPUT情報がそのまま反映される。
2:中クリックでAPEXノードのポート名を変更できる。
ちなみにlayoutgraphSOPに関してはAPEXグラフが綺麗に整頓されるだけなので、機能的には無くても良い。これの中身もAPEXで組まれているのだが、この辺の仕組み(グラフを使用してグラフを編集する)の話はSTEP2-3で解説を行う。

ステップ1-4

さて、ここではAPEXグラフでのsubnetの仕様に関して解説していく。
APEXグラフにもサブネットが存在している。
ステップ1-3で作成したグラフでsop__blast,sop__xform,sop__copytopoints__2ノードなどを選択してShit+c(もしくは右クリックしてCollapseToSubnetを実行)をすると、subnetに格納される。
さてここで1-2を振り返ろう。APEXグラフはpointとprimで表現されると説明した。ではsubnetはどのように情報が格納されているのだろうか。
編集したグラフにDisplayFlagを立てると、もともとのグラフの横に追加でグラフが表示されていることが分かるだろうか。
これがsubnetのノード内が表示されていることが分かる。
また、ノードの情報を確認してみるとPackedGeosが作成されている。つまり、入れ子のようにPack状態にすることで、グラフの情報を保っているということが分かる。
実際に、subnetのAPEXノードに対応しているpoint以外を削除して、unpackをしてあげると先ほどsubnetに格納したノードたちが表示されるだろう。ちなみにここのsubnetの使いまわし(サブグラフ)に関しては2-4で解説を行う。

さてステップ1では既存グラフの簡単な読み方、編集方法に触れてきた。次のステップ2ではグラフを編集するグラフについて理解を深めていこうと思う。

ステップ1-5

ステップ2に行く前に入力AttributeFromParameterSOPについて紹介しておく。AttributeFromParameterSOPは任意のノードのUIパラメータ情報をdetailのparmsアトリビュートにまとめるのが便利なノードになっている。これを使用することによってAPEXの入力部分とパラメータの編集部分を切り分けることが出来るようになった。ちなみにパラメータの編集結果を逐次確認した場合はinvokegraphSOPのForceReloadGraphをONにすると入力情報の更新された結果がすぐに反映されるようになる。
image.png

ステップ2 グラフ生成に関して

前段のステップではAPEXのグラフはpointとprimで表現されているのが分かり(1-2)、かつAPEXの中で一部のSOPノードを使用して、簡単なプロシージャルモデリングのようなことが出来た(1-3)。
このことからグラフ自体をジオメトリとして扱うことで、APEXのグラフを使用して既存のグラフを編集するということが出来るということが分かるだろう。

ステップ2-1

まず準備段階としてベースとなるグラフを作成していこう。

ステップ2-1
1 APEXEditGraphを呼び出してAPEXNetworkViewに移動する
2 sop::box,sop::merge,sop::pack,subnetOutputの順で呼び出して上のgeoのところで接続していく。
3 sop::mergeをmergeに、__output__をoutputに変更して、sop__boxのtを{0,5,0}にセットする。

image.png
このグラフでinvokegraphSOPを使用して、BindOutputGeometry[output:geo]に設定すると、boxが1つpackされた状態で表示されていることが分かるだろう。
さてここに、Sphereを1つ追加するようにグラフを更新していこう。

ステップ2-2

ここからはノードが長くなるので、配布したサンプルを元に確認をしてほしい。
image.png
image.png

要点毎に詳しく解説をしていく。

グラフの読み込み

image.png
・graph::LoadFromGeoは読み込んできたジオメトリをグラフとして読み込む部分
・graph::AddNodeはグラフにAPEXノードを追加する部分になる。nameはノードの名前、callbackは呼び出すノードのcallback名が入力される。今回の場合はnameをsphare,callbackをsop::shpereを呼び出している。(callback名はノードの正式名称[tabを押したときの呼び出し名]、nameは表示するときの名前という認識でOK。)callbackの便利ツールは後半に記載。

グラフの接続

image.png
・ここのアタリはスクリプトでノードを構築したことがある人にとっては馴染みのある構成だと思う。
・graph::FindNodeは接続されているグラフの中にあるノードの名前でノードIDを返す部分。
・graph::FindPortは入力グラフ内のノードIDにあるポート名でポートIDを返す部分
・graph::GetSubPortは入力されたポートIDがサブポート(nextやsrcなど入力時に数が変わるポート)の場合、任意の名前を設定できる部分
・graph::ConnectInputで入力グラフの任意の入出力ポートをつなぐことが出来ます。
image.png
上記の画像では緑の部分が出力ノード→ポートの選択、青の部分が入力ノード→ポートの選択(サブポートリネーム済み)になっている。例えばFindPort1の出力から直接ConnectInputに繋いだ場合、結果として出力されるグラフのmergeの部分はsrcになっているのが分かると思う。注目したい点は、グラフに変更がある部分(ノードの追加、ポート名の変更)のグラフは1本線でつながっている。ここは注意して追うとノード理解が早まるだろう。

グラフを出力

image.png
・graph::Sortはその名の通りグラフを整列させるノードになる。
・graph::WriteToGeoはグラフをジオメトリとして変換するノード。LoadFromGeoの対になるノード。
・sop::packでノードの点群を1つのpackGeoにまとめる。あっても無くてもいいが、ここのロジックはリグでは必ず使用されるので追加している。

ここではグラフをジオメトリ→グラフの追加→ジオメトリの順で更新されているのが分かるだろう。これを基に1-3で使用したlayoutgraphSOPを読み解いていこう。

ステップ2-3

layoutgraphSOPの中身の構成はこうなっている。
image.png
後処理の部分は特筆することがないので、さっそくAPEXグラフを見ていく。ここまでの部分でほとんど読めると言って過言ではないだろう。
image.png
整理の部分はLoadFromGeo→Sort→WriteToGeoというさっき書いた処理と同じです。
下の部分でグループにマッチングするノードの抽出を行っているのだが、この部分はVOPを使用する人にとっては見慣れた組み方になっている。
Andの部分で空白又は「*」かどうかを確認して、それ以外であれば、パターンにマッチするノードをgraph::FindNodesで配列出力、ノードIDでForを回し、graph::NodeDataでノードIDからNameを呼び出してstring::Fromatノードで一行の文字列に変更して、outputで出力している。文字列の扱いがAPEXではかなり便利になっていると言ってもいいだろう。
patternの部分はAttributeAdjustDictionaryでセットしている。
実際のグループの設定はグラフの実行結果でdetailに出力された文字列を参照している。ここのアタリはステップ1-2を見返してもらえると良い。
変形の部分はAPEXノードの位置=それに対応している位置ということが分かるだろう。

さて、これである程度グラフの読み書きが出来るようになったので、もう1段深堀していこう。

ステップ2-4

グラフをHDAのように外部に保存して、graph::AddNodeなどで呼び出すような設定がある。(もちろん頻出処理をTABキーから呼び出すことも出来る。)この仕組みをサブグラフという。
サブグラフを作成していこう。
例として2-3で出てきた文字列の処理を1つのサブグラフにまとめていこう。
image.png
今回作りたい処理は、入力文字列が空白もしくは「*」だった場合Falseを返して、それ以外だった場合Trueを返す関数を作成する。今回はサンプルとして任意の文字列も判定に加えるパターンで作成する。

ステップ2-4
1 APEXEditGraphを呼び出してAPEXNetworkViewに移動する
2 EqualsとNotノードを3つずつ、Andノードを1つ呼び出す。EqualsとNotノードをそれぞれ繋ぎ、notノードのresultはAndノードに全てつなぐ。
3 ノードを全て選択してサブネットを作成、parmsの一番上を全てaに繋ぎ、Equals_String_3のbをその下に繋ぐ。またAndのresultをoutputにつなげておく。
4 Equals_String_1のbのパラメータに「*」を入力する。parmsのaをinput、bをpatternに変更しておく。
5 nameSOPをeditgraphSOPに繋いでprimitiveにAttribute"callback_name"で任意の名前を設定する。
6 FileSOPやFilecacheSOPでbgeo形式で書き出す。

・callback_nameの命名規則だが、「::」 で区切ることでカテゴリ分けをすることが出来る。例えば「AMT::test::hogehoge」のような名前にすると、画像のような表示になる。これは任意のグラフノードを整理するときには便利な機能と言える。

Houdini 20.0.609以降の場合はcallback_nameではなく、nameアトリビュートを参照するようになった。24/02/15追記

image.png
・書き出し場所に関しては$HOUDINI_USER_PREF_DIR/apexgraphに書き出すことで使用可能になる。余談だがpackageディレクトリでも管理できるので、そっちで管理するのもあり。
・複数のサブグラフ(PackedGeos)を1つのbgeoファイルにまとめることが可能である。汎用的なものと、その他などで管理することも可能です。
・書き出したサブグラフはHoudini起動時に自動で読み込まれるが、強制的に読み込み直す場合は以下のpythonコマンドを使用する。

python
import apex;
apex.Registry().reloadSubgraphs()

ステップ2-5

サブグラフの更新に関して。
一番簡単な方法は、さっきのbgeoファイルを読み込んで、任意のポイントを抽出、unpackSOPでunpackして編集、再度サブグラフにして保存しなおすというのが分かりやすい方法だろう。ただ、そうできない場合も多い。
例えば2-4で作成したサブグラフを1つ呼び出してみてNodeInfoを選択すると、PackedGeosではなく、ただの1ポイントとして呼び出されていることが分かる。これではunpack出来ないので上記の方法は使用できない。
解決方法としてはAPEXNetworkView上でunpackしたいサブグラフを選択して、右クリック→ExtractContentsで中身を取り出すことが可能になった。以降の作業は上記と変わらない。これを利用すれば、既存のグラフノードのカスタマイズも可能になる。
image.png

さて、ここまででグラフ生成にまつわる理解が深まったと思う。
いよいよリグの生成をしていこう。

ステップ3 APEXを用いたリグの概要

ここではリグの大まかな仕組みに関して解説していく。実際の組み方に関してはコンテンツライブラリのサンプルを見てみると、この記事の知識でほぼ読めると思う。(すみません、書く時間が無かったです...。)
https://www.sidefx.com/contentlibrary/luchador-chicken/
https://www.sidefx.com/contentlibrary/electra-rig/
https://www.sidefx.com/contentlibrary/apex-pillow/

※このステップで説明するリグとは、スケルトンを動かす仕組みのことを指します。

ステップ3-1

実際のグラフに入る前にpackfolderSOPの方に触れていく。
中身の機能としてはmergepackSOPに近い。nameアトリビュートに(name).(type)を設定することで、pack時の役割分けを行って管理をしている。
同一キャラクターの要素は基本的に同じnameを入れて、役割毎にtypeを割り振ってあげる必要がある。
Houdini内でデフォルトで役割が決まっているものは
・shp [shape/レストポーズジオメトリ(スキニング済み)]
・skel [skeleton/スケルトン(コントローラー用の骨も含む)]
・rig [rig/リグとして実行されるAPEXグラフ]
・ctrl [control/リグの細かい制御設定]
の4種類だが、それ以外の要素などがあれば別のtypeを割り当てても大丈夫。(ブレンドシェイプや、コントローラージオメトリなど)
nameとtypeの組み合わせはユニークである必要がある。
また、既存のpackfolderの構造に足したり置き換えたい場合はinput1の入力に対して情報が更新されるようになる。
ここでは触れないが、複数のキャラクターをpackedFolderでまとめるときはCharactorTypeもしくは任意のタイプ(addFolder)で読み込むことが出来る。
ここで抑えておく点としてはAPEXでリグを組んでいく工程のデータ管理はそれぞれpackされた状態で情報を管理しているという点である。そしてそれぞれにユニークな名前で管理されているという点の2点だけ抑えられてればOK。次に進む。
(ちなみにこのステップの情報はこれ読めば大体書いてある。)
https://www.sidefx.com/docs/houdini20.0/character/kinefx/packedcharacterformat.html

ステップ3-2

APEXでのリグの組み方は大きく2種類ある、その中のAPEX Autorig Componentの方から説明していく。
Autorigの概要としてはキャラクターの.rigデータをAPEXグラフで更新するノードです。
Houdini側で準備されたComponent(グラフ)の他にカスタムしたグラフの適用も可能です。

現状デフォルトのComponentの説明

現状使用できる項目は8つ(2つは使用しない。)
・borndeform
→ジオメトリとスケルトンをpointtransformで接続する
・configurecontrols
→コントローラージオメトリとスケルトンを接続する
・findorcreaterig
→不使用、入力構造に.rig階層が無い時に作成する
・fktransform
→FKを作成する
・simpletest
→不使用、任意の箇所にAddNodeするサンプル。
・smoothik
→IKを作成する。
・spine
→背骨を作成する。
・transformdriver
→腰や原点など全体を動かすルートを設定する。

中身の仕組みはこんな感じです。
image.png
Autorigノードのパラメータはそこで実行されるグラフのparmsに必要な項目が自動で作成される仕組みです。そのパラメータを1-5で使用したAttributeFromParameterSOPで読み込んで使用しています。
また、output2からはinput2のグラフ情報にパラメータの値が割り当てられて出力されるので、デバックや次のノードに繋がります。(例えば右側のリグを作成したあとで差分で左側を設定するときなどに便利)
その他の運用上の小ネタは3-4で解説します。

ステップ3-3

もう1つはAPEX Rigscript Componentです。
Rigscriptの方は中身が複雑そうに見えますが、8割Autorigと一緒です。では何が違うかというと、リグを作成するための更新情報を別で管理する流れがあるということです。
image.png
このinput1をリグスクリプトと言います。このrigscriptを使用するとここまでのリグを一括で構成することもできます。なので、汎用リグの仕組みをリグスクリプトでまとめておいて使いまわす、リグとシェイプを別で管理するなどの運用が考えられます。
ちなみにリグスクリプトは更新内容をpackGeoにして末尾にくっつけるだけなので、末尾のポイントを抽出してunpackするとoutput3と一致するデータが出てくることが分かる。

AutorigとRigscriptの2種類を紹介したが、この2つともリグの仕組みを.rigが更新/保存されている。ここで重要なのは決してリグが組まれているわけではないこと。実際にリグが生成されるのはAPEX Scene Animateが実行されたタイミングで生成される。このような準備と実行のタイミングが違うことで色んなことが出来るらしい。ちなみに拡張を考えるとRigscriptの方が便利だと思う。気軽に使用するならAutorigで充分。

ステップ3-4

運用的な小ネタを少し
・パラメータのレイアウトを組む方法
Autorig等でComponentを読み込んだ時に自動でparmがレイアウトされている。それを作成しようと思う。リグを更新するグラフのdetailのparmoptionsのところでparmsのレイアウトが決定されている。これを簡単に編集する方法としては1度Autorigでグラフを読みこんで、ParametersPanelのEditParameterInterfaceでレイアウトを整えて、パラメータのところに任意の値を入力すると、output2から出るグラフには設定が組まれていることが分かる。これを保存すると良い。
注意点としてはパラメータはなんでも継承される訳ではない。以下は代表的なものになる
toggle/OrderdMenuは可、ColorやSeparator/labelは不可
Folderのtabs/multparmsは可、radiobuttonsは不可
HorizontallyJoinToNextParameterは可、DisableWhenは不可
などなど制約は大きいがある程度整理した状態で用意することは可能になる。

・autorigからの継承
output2から出たグラフをそのまま次のinput2に接続して使用した場合、パラメータのレイアウトは崩れるが全ての項目の前にチェックボックスが追加されるだろう。これは編集箇所を分かりやすくする仕組みになっている。ちなみにOFFだからparmsとして反映されないとかではないので注意。

ステップ4 おまけ

最後にシンプルなリグを用いて軽く要点だけ解説。
image.png
image.png
2点からなるラインにBOXを複製したシンプルな仕組み。
このグラフで注意すべき点はこちら。
image.png
このTransformObjectはAPEX上でインタラクティブに動く点になる。TransformObjectの移動情報とskelを設定した点の移動情報をリンクさせるためにSetPointTransformノードが使用されている。ただ、どのTransformObjectがどの点にリンクしているかは不明なため、SetPointTransformのport名とリンクするnameアトリビュートを持ったポイントと結び付ける仕様になっている。このようにAPEXは持てる情報限度の仕様上port名が重要になることが多々あるので、その点も注意して読み進めると良い。(ちなみに自分はここで1日ハマった。)サンプルとして上記のリグを組むためのコンポーネントグラフも作成したので、参考になればうれしい。
image.png

APEXの便利なツール

最後に便利なツールを紹介する。Houdiniの適当なパネルから、NewPaneTabType>Mics>PythonPanelを選択してその中にAPEXCallbackBrowserというウィンドがある。これは現状使用できるAPEXグラフノードの一覧を表示してくれるウィンドになっていて、ノードの検索も出来るので是非活用してみてほしい。
image.png

おわりに

APEXは奥の深い設計かつ登場したばかりの機能で、調べながらまとめてた部分が大半なので、今回の記事自体がかなり省略した内容となっております。あくまでBETA版のため公式ヘルプやパラメータ名が変わっていきますが、それでもい皆さまの学習の一助になれば幸いです。今回は時間の関係でまとめきれなかったリグの応用的な使い方やアニメーション部分の解説などは時間を見つけてまとめていきます。
それでは、しゃーした~。

16
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?