0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【VC++&STEP CADデータ】STEPファイル(AP203)をyamlファイルに変換する

Last updated at Posted at 2022-10-24

過去記事

今までの記事は紹介みたいなのばかりで、
あまり内容としては重要なものがなかったので、
今回はもう少しSTEPデータの中身に近づけたものを試してみたいと思う。

概要

以前やったSTEPファイルをもう少し使える形に変換してみたいということで、
最終的に3Dレンダリングに持っていける形式にする。

STEPファイルはすごくわかりづらい形をしている。
以前まで使っていたスプロケットの例で言うと、

KANA_NK35B10D9_K.stp
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データ

image.png

以前ダウンロードしたものを利用。
規格はAP203固定

実行結果

result.yamlでこんな感じで出力されるようになった

image.png

だいぶ見やすい。

今までのSTEPファイルは次のように、
上の3行のデータを次の形式でまとめていたが、
#56, #57, #58のデータ内移動が発生しており、
それが何なのかが不明であるため、
一目見て何をしようとしているかだいぶ分かりづらい感じであった。

image.png

注意点

再帰的なノードの実装処理

今回は、とりあえず簡略的に再帰を実装することで、上記のデータを取り出した。

STEPのデータ構造はいわゆるノードリンクモデルであり、階層化されている。
例えば、次のような感じ。

image.png

ただ、一般的なツリー構造とは違い、親と子に対しての数の制約がない。
現に今操作しているファイルも、親が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で表した結果は次の通り。

result.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のデータはここで#~と書かれているようなので、「~」の部分の値を数値化して読むことで工夫した

main.cpp
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ソフトウエアの問題点などを評価するのにも使えるはずなので、いろいろ考えていきたい。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?