過去記事
今までの記事は紹介みたいなのばかりで、
あまり内容としては重要なものがなかったので、
今回はもう少しSTEPデータの中身に近づけたものを試してみたいと思う。
概要
以前やったSTEPファイルをもう少し使える形に変換してみたいということで、
最終的に3Dレンダリングに持っていける形式にする。
STEPファイルはすごくわかりづらい形をしている。
以前まで使っていたスプロケットの例で言うと、
ISO-10303-21;
HEADER;
FILE_DESCRIPTION( ( 'STEP AP203' ), '1' );
FILE_NAME( 'p:/temp/worker_7/temp/exporttempdir_4092_1410_eV1Z3/format_0/KANA_NK35B10D9_K-_0-N_A_(H 720 N_A 0 0 N_A N_A).stp', '2020-10-28T10:44:47', ( 'License CC BY-ND 4.0' ), ( 'CADENAS' ), ' ', 'PARTsolutions', ' ' );
ENDSEC;
DATA;
#1 = DESIGN_CONTEXT( '', #27, 'design' );
#2 = APPLICATION_PROTOCOL_DEFINITION( 'international standard', 'config_control_design', 1994, #27 );
#3 = PRODUCT_CATEGORY_RELATIONSHIP( 'NONE', 'NONE', #28, #29 );
#4 = DATE_AND_TIME( #30, #31 );
...
要は、DATAって書いてあるところに対して、
所定のスキーマAPIに従った関数があてはめられていて、
それを1行ずつパースするようだ。
APIは次のサイトにある:
だが、CADデータというのは
本来ジオメトリや軸、マテリアルの情報などが構造化されて出ているはずだ。
例えば、
点→線→面→要素
と言った具合に定義が大きくなっていく。
当然関数のレベルは、点と要素ではその大きさが全く違っているであろう。
構造化されたデータを記述するのに便利な記法は?
と考えたときに、パッと思いつくのはXMLファイルなのだろうが、それは冗長すぎる。
そこで、Yamlファイルにすれば多少は構造化されたファイルにしやすいのではないかと考え、サンプルとして作ってみた。
プロジェクト
動作環境
項目 | 値 |
---|---|
IDE(開発環境) | Visual Studio 2019 |
言語 | C++14 |
cmake | 3.19.0 rc1 |
yaml-cpp | v0.6.0 |
STEPCode | v0.8.1 |
submoduleを使っているので注意
ライブラリ
使用するSTEPデータ
以前ダウンロードしたものを利用。
規格はAP203固定
実行結果
result.yamlでこんな感じで出力されるようになった
だいぶ見やすい。
今までのSTEPファイルは次のように、
上の3行のデータを次の形式でまとめていたが、
#56, #57, #58のデータ内移動が発生しており、
それが何なのかが不明であるため、
一目見て何をしようとしているかだいぶ分かりづらい感じであった。
注意点
再帰的なノードの実装処理
今回は、とりあえず簡略的に再帰を実装することで、上記のデータを取り出した。
STEPのデータ構造はいわゆるノードリンクモデルであり、階層化されている。
例えば、次のような感じ。
ただ、一般的なツリー構造とは違い、親と子に対しての数の制約がない。
現に今操作しているファイルも、親が2つの子が1つ、みたいなパターンがあり得るので、
一般的なデータ構造を使用することを考慮できない部分がある。
yamlで表現するには割と悪くないモデルなのではあるが、
親が多重化していると、微妙に扱いづらいので、
この辺りは要改良であろう。
補足
このデータ構造は、アプリケーション毎で別の制約を持たせることで、変更することは可能。
しかし、そんな規格が標準規格であると呼んでいいかは疑問の余地が残る。
例えばFusion 360で作成したSTEPデータは、他のアプリケーションで読めないみたいなことも起きており、ソフトによって生成されるノードの数も全然違う。
(Fusion 360で同データをエクスポートすると60000ノードくらいになって大変だなあ、と思ったり)
型表現
STEPデータには、主要な型表現がいくつか存在し、
その中でいくつかオブジェクトが他のノードの参照を行なう。
型 | 値 | 定義 |
---|---|---|
Entity型 | (参照)またはDerive | スキーマのオブジェクト |
Select型 | 型 | 型のクラスを選択し、それが同等の意味を持つように変換する。OOPでいうスーパークラスに近い |
Aggregate型 | 配列 | 値またはInstance型の集合 |
Complex型 | ([スキーマ1][スキーマ2]...[スキーマN]) | N個のスキーマの複合体 |
ノードにはattributeが存在し、
これは、他のノードを参照することを表す。
attributeを以下の形式で変換する。
/* operator []が定義されているのでこの形式で参照する */
auto attribute = &instance->attributes[j];
auto attr_select = attribute->Select(); // Select型
auto attr_instance = attribute->Entity(); // Entity型
auto attr_aggr = attribute->Aggregate(); // Aggregate型
これ以外にも値型(数値や文字列など)も存在する。
Entity型
1個のオブジェクトを表す。
例として、
#1 = DESIGN_CONTEXT( '', #56, 'design' );
とあるが、#56を見ると、
#56 = APPLICATION_CONTEXT( 'configuration controlled 3D designs of mechanical parts and assemblies' );
と書いてあり、これはAPPLICATION_CONTEXTというスキーマ関数を#1から単に実行しているのと同じ。
Aggregate型
複数のオブジェクトまたは値型を表す。
例として、複数の値型になっている例では、
3次元座標系CARTESIAN_POINT
#4946 = CARTESIAN_POINT( '', ( 32.9810691886818, -1.11762031629104, 3.02385093961795 ) );
があり、これは分かりやすい。
オブジェクトになっている例として、
スプライン補間をしている線B_SPLINE_CURVE_WITH_KNOTSなどがある。
#2734 = B_SPLINE_CURVE_WITH_KNOTS( '', 3, ( #3447, #3448, #3449, #3450, #3451, #3452, #3453, #3454 ), .UNSPECIFIED., .F., .F., ( 4, 2, 2, 4 ), ( 0.00401969515132148, 0.00439619365685814, 0.00477269216239481, 0.00552568917346814 ), .UNSPECIFIED. );
と長い表現があるが、#3447~#3454は、それぞれCARTESIAN_POINTが振られており、その各点においてスプライン補間を行なっている線であることを表す。
ちなみに、以上のSPLINEのデータの一部をyamlで表した結果は次の通り。
edge_geometry:
sc_fileid: 2734
sc_function: B_Spline_Curve_With_Knots
name: "''"
degree: 3
control_points_list:
- sc_fileid: 3447
sc_function: Cartesian_Point
name: "''"
coordinates:
- -27.6749055384899
- -5.60781048700473
- 4.3
- sc_fileid: 3448
sc_function: Cartesian_Point
name: "''"
coordinates:
- -27.7168136134358
- -5.72658161523084
- 4.3
- sc_fileid: 3449
sc_function: Cartesian_Point
name: "''"
coordinates:
- -27.768318640701
- -5.84384281529098
- 4.2993313269661
- sc_fileid: 3450
sc_function: Cartesian_Point
name: "''"
coordinates:
- -27.8867645478438
- -6.06658150505576
- 4.29557390864968
- sc_fileid: 3451
sc_function: Cartesian_Point
name: "''"
coordinates:
- -27.9537675118713
- -6.17267764724977
- 4.29249895314713
- sc_fileid: 3452
sc_function: Cartesian_Point
name: "''"
coordinates:
- -28.1777375748232
- -6.47533524682626
- 4.27799771103716
- sc_fileid: 3453
sc_function: Cartesian_Point
name: "''"
coordinates:
- -28.3592036816
- -6.65692035269854
- 4.26099657575213
- sc_fileid: 3454
sc_function: Cartesian_Point
name: "''"
coordinates:
- -28.5629624822334
- -6.80713277063426
- 4.23326633448113
curve_form: UNSPECIFIED
closed_curve: F
self_intersect: F
knot_multiplicities:
- 4
- 2
- 2
- 4
knots:
- 0.00401969515132148
- 0.00439619365685814
- 0.00477269216239481
- 0.00552568917346814
knot_spec: UNSPECIFIED
same_sense: T
orientation: F
yamlで表すだけで、どこにSPLINE補間があるのか、
B-SPLINE補間の特性がどのような描画をすべきかなどを
明確にできる。
Select型
複数の型を同一視したいときに使う。
例として、axis2_placementと呼ばれるスキーマがあり、
これは原点と1方向の単位ベクトルでなるAXIS2_PLACEMENT_2Dと
原点と2つの方向の単位ベクトルでなるAXIS2_PLACEMENT_3D
2つが存在している。これらが何を利用されているというと、3次元空間の下で、決められた単位ベクトルの空間で図形を描画するのに使用されている。
(APIの詳細については分析が必要なので、ここでは触れない)
例:
#2744 = CIRCLE( '', #3477, 2.54000000000000 );
#3477 = AXIS2_PLACEMENT_3D( '', #5346, #5347, #5348 );
#5346 = CARTESIAN_POINT( '', ( -30.0701715294190, -4.76264728814982, 0.000000000000000 ) );
#5347 = DIRECTION( '', ( 0.000000000000000, 0.000000000000000, 1.00000000000000 ) );
#5348 = DIRECTION( '', ( 1.00000000000000, 0.000000000000000, 0.000000000000000 ) );
このSelect型をAXIS2_PLACEMENT_2Dにしても、データ構造としては問題ありませんよ、という意味である。
よって、このAXIS2_PLACEMENTは2つのクラスのスーパークラスの意味合いに近いとされる。
しかし、STEPCodeでは現状このSelectorがしっかり機能していない(変換した型をポインタで変換する方法がない)ようなので、ちょっと工夫が必要で、
STEPのデータはここで#~と書かれているようなので、「~」の部分の値を数値化して読むことで工夫した
std::string out_text;
attr_select->STEPwrite(out_text);
// ap203 only?
std::regex id_detect_regex("\\#(\\d+)");
std::smatch match;
if(!std::regex_search(out_text, match, id_detect_regex) ||
match.length() < 2)
{
std::cout << "Regex Pattern Match failed" << std::endl;
continue;
}
int id = 0;
try
{
id = std::stol(match[1].str());
}
catch(exception)
{
std::cout << "Regex ID error" << std::endl;
continue;
}
auto select_entity = inst_mgr->FindFileId(id);
if(select_entity == nullptr)
{
std::cout << "Regex Conversion error" << std::endl;
continue;
}
auto select_instance = select_entity->GetApplication_instance();
ここで、inst_mgrというのはInstanceの管理をしているクラスであり、
そこに定義されているFileIdを直接読み取ることでSelectorを取り出している。
この部分はIssueでも議論されていたようなので、
上手い方法があれば解決に向かうかもしれないが、現状は難しい。
(C++では動的なクラス型があるわけではないので・・・)
Complex型
STEPでは、次のような表記がある
#99 = ( NAMED_UNIT( * )PLANE_ANGLE_UNIT( )SI_UNIT( $, .RADIAN. ) );
これはスキーマを複数個連ねていることから、Complex型として定義され、別の表現法を用いなくてはいけない。
次のようにすると、Complex型を取り出せる。
for (auto iter = instance_complex->head; iter != nullptr; iter = iter->sc)
{
auto inst = dynamic_cast<SDAI_Application_instance*>(iter);
// instをcomplex型のSDAI_Application_instance*型として
// 取得しなおすことが出来る。
}
効果
このようにすると、規定したyamlファイルにおいて、どこを変えたらそのパラメータを反映されるかを可視化出来るようになる。
例えば、今回のSTEPデータの変換結果で、
- sc_fileid: 49
sc_function: Shape_Definition_Representation
definition:
sc_fileid: 89
sc_function: Product_Definition_Shape
name: "'NONE'"
description: "'NONE'"
definition:
sc_fileid: 85
sc_function: Product_Definition
id: "'_SET-SCREW5-5'"
description: "'_SET-SCREW5-5'"
formation:
sc_fileid: 84
sc_function: Product_Definition_Formation_With_Specified_Source
id: "' '"
description: "'NONE'"
of_product:
sc_fileid: 86
sc_function: Product
id: "'_SET-SCREW5-5'"
name: "'_SET-SCREW5-5'"
description: "'PART-_SET-SCREW5-5-DESC'"
frame_of_reference:
- sc_fileid: 107
sc_function: Mechanical_Context
name: "''"
frame_of_reference:
sc_fileid: 56
sc_function: Application_Context
application: "'configuration controlled 3D designs of mechanical parts and assemblies'"
discipline_type: "'mechanical'"
make_or_buy: NOT_KNOWN
frame_of_reference:
sc_fileid: 1
sc_function: Design_Context
name: "''"
frame_of_reference:
sc_fileid: 56
sc_function: Application_Context
application: "'configuration controlled 3D designs of mechanical parts and assemblies'"
life_cycle_stage: "'design'"
...(省略)...
という感じだったが、実は「_SET-SCREW5-5」は随所にあり、ここをFreeCADで変えたりすると、反映されている部分が見づらかったのだ。
それを、86:nameの値を変えるだけで分かるというのが一目で分かったというだけでも結構大きな効果である。
ただし、この変更がどのように名前として割り当てられるのかについては、CADソフトによって挙動が違うはずなので、完全にワンルールではなかった(Free CADでは、_SET-SCREW5-5が2つ読み込まれていたので、もう1つの方のモデルの名前が_SET-SCREW5-006に変換されてしまっていた)
このあたり、フリーで環境を作るときにいろいろ吟味できると面白いのかなと思う。
CADソフトウエアの問題点などを評価するのにも使えるはずなので、いろいろ考えていきたい。