dwgにおけるハッチングの表現はややこしくて、boundary(境界)オブジェクトが多階層化している。境界オブジェクトには大きく2つあって、1つの閉じたポリラインか複数のその他図形群かとなる。1つのポリラインであれば何てことはなくて、bulge(ふくらみ)によって途中に円弧があるかもしれないねくらいの話で済むが、その他図形群がタイヘンなことであり、線(Line)か円・円弧(Circle or Arc)か楕円・楕円弧(Ellipe or Ellipe arc)か曲線(Nurbs Curve)かという選択になる。線、円、楕円なんてところはわけないので省略して、曲線の場合のラビリンスから抜け出せたので、以下で詳細に解説しよう。なお、以下はTeigha改めODAによってハッチングオブジェクト内容の境界図形を解析するという話であり、一般性が低いことを先に伝えておく。
境界オブジェクトがその他図形群の場合に現れる曲線タイプのクラスがOdGeNurbCurve2d。OdDbSplineだったらexplodeGeometryでポリラインになるから一発解決なんだけど、OdGeNurbCurve2dってのは何だってこと。NURBS曲線であることは字面からわかる。Bスプライン、ベジェ曲線を一般化したものだそうだ。NURBSが非一様有理Bスプラインであるのに対し、ベジェ曲線は一様非有理Bスプライン。。いや、よくわからんよ。興味あるならWikipediaのNURBSでも読んでくれ。
OdGeNurbCurve2dクラスにはexplode系関数がない。継承元のOdGeCurve2dにexplode関数があったから試してみたが例外エラー発生。explode系関数はOdDbEntityクラスにあるものなんで、OdDbEntityクラスを継承しているOdDbSplineにする必要がありそうなことは薄々わかっていた。そこで難関だったのはNURBS系情報の受け渡し。
OdGeCurve2d::getDefinitionDataでGETして、OdDbSpline::setNurbsDataでSETする。この関数の引数が微妙に違っていることに苦労した。コードを以下に貼りける。ちょっとした違いがある。
// OdGeNurbCurve2d* pNurbをOdDbSpline pDstに変換する処理 bool closed = false; DPOINT pnt1 = { pNurb->startPoint().x, pNurb->startPoint().y }; DPOINT pnt2 = { pNurb->endPoint().x, pNurb->endPoint().y }; double dist = GeoGetDis(&pnt1, &pnt2); if (dist <= ZERO * 100) // つながってる closed = true; int degree = 0; bool rational = false, periodic = false; OdGeKnotVector knots; OdGePoint2dArray controlPoints; OdGeDoubleArray weights; pNurb->getDefinitionData(degree, rational, periodic, knots, controlPoints, weights); OdDbSplinePtr pDst = OdDbSpline::createObject(); double controlPtTol = 0, knotTol = 0; OdGePoint3dArray controlPoints3d; for (int j = 0; j < (int)controlPoints.size(); j++) { OdGePoint3d pnt(controlPoints[j].x, controlPoints[j].y, 0); controlPoints3d.append(pnt); } OdGeDoubleArray knots2; for (int j = 0; j < knots.length(); j++) { const double* pPtr = knots.getPtr(); knots2.append(pPtr[j]); } pDst->setNurbsData(degree, rational, closed, periodic, controlPoints3d, knots2, weights, controlPtTol, knotTol);
OdDbSplineに変換できたからexplodeGeometoryすればいいわけだが、その結果現れたのがOdDb2dPolylineというのがミソ。OdDb2dPolylineにはふくらみがあるのは当然として、なんとスプラインが含まれていることもある。元がNURB曲線で、スプラインにして、ポリラインにして、分解したらスプラインって。。いやいや、スプラインを分解したポリラインにスプラインなんて含まれているわけないと断定。よって、以下の通り、頂点(Vertex)情報だけ拾い出して、DPOINTの配列にAddした。
CArray<DPOINT, DPOINT> ary; OdRxObjectPtrArray oary; OdResult err = pDst->explodeGeometry(oary); if (err != eOk) return FALSE; for (UINT k = 0; k < oary.size(); k++) { OdDbEntityPtr pExp = OdDbEntity::cast(oary.getAt(k)); int type = ElmGetType(pExp.get()); if (type == _ENT_PLINE2D) { OdDb2dPolylinePtr pPoly = pExp; OdDbObjectIteratorPtr pIter = pPoly->vertexIterator(); for (; !pIter->done(); pIter->step()) { OdDb2dVertexPtr pVs = pIter->entity(); if (!pVs.get()) continue; OdGePoint3d pos = pVs->position(); DPOINT pnt = { pos.x, pos.y }; ary.Add(pnt); } if (pPoly->isClosed()) ary.Add(ary.GetAt(0)); } }
サクッとかいた閉じたスプラインに対して上記処理を行ったところ、52個の頂点だったのでそこそこの精度でポリライン変換できたことになる。もちろん、ふくらみもスプラインも現れなかった。
ポリラインの点列ができれば、ライトウェイトポリライン(OdDbPolyline)として仕立てて、いろんな編集ができるってもの。
こんなことができて何がうれしいかって?塗りつぶしハッチングをビューポートでぶった切った状態をモデル空間の図形に変換できるようになる。塗りつぶしでないハッチングであれば分解して線の集まりにしてまえば見た目を維持できるが、塗りつぶしとなればハッチング要素として維持しなくてはならず、輪郭オブジェクトとしてのOdGeNurbCurve2dがクリッピングできなかったという話。上記で頂点列にすることができたから、クリッピング処理に流すことができるようになった。そして、塗りつぶしハッチングの境界データのスプライン部分をポリラインに分解してクリッピングができたという次第。
ハッチングの境界データが閉じてないとハッチングとして成り立たないから閉じてやったり、境界データを普通の図形に変換してやったり、境界データをクリッピングするなんてことは、AutoCADや互換CADのアドオンツールとして成り立ちそうな気がしてきた。切り出して、単独ツールとしてみるのもよいだろう。