OpenCASCADE BRep ファイルの入出力

OpenCASCADE では、標準のファイル形式として OpenCASCADE BRep ファイルというものを用いています。一般的に BREP というと、Boundary REPresentation (境界表現) 自体のことを差しますが、ここでは、「OpenCASCADE で表現できるトポロジー形状を保存するためのファイルフォーマット」とし区別して、BRepファイルと呼ぶことにします。

TopoDS_Shape 型になっているトポロジー形状のファイルへの保存、読み込みは、すごく簡単です。実際にコードを見てみましょう。

[cpp]

include <iostream>

include <TopoDS.hxx>

include <gp_Pnt.hxx>

include <TopoDS_Solid.hxx>

include <BRepPrimAPI_MakeBox.hxx>

include <BRepTools.hxx>

int main(int c, char** v)
{
// ソリッド作成
gp_Pnt opos(0.0, 0.0, 0.0);
BRepPrimAPI_MakeBox box(opos, 10.0, 10.0, 10.0);
TopoDS_Solid solid = box.Solid();

// ファイルへ出力
BRepTools::Write(solid, &quot;test.brep&quot;);

// ファイルから入力
TopoDS_Shape shape;
BRep_Builder bb;
BRepTools::Read(shape, &quot;test.brep&quot;, bb);

std::cout &lt;&lt; &quot;Readed shape type no is &quot; &lt;&lt; shape.ShapeType() &lt;&lt; std::endl;
// TopAbs_SOLID を表す、「2」が表示される

return 0;

}
[/cpp]

今回のキモは、BRepTools.hxxを読み込み、BRepTools::Write() または Read() でファイルパスや TopoDS_Shape オブジェクトを指定している部分です。まあ、ほとんど説明する必要がないと思います。
Write()/Read() は、ファイルパス文字列の他、ストリームを渡すことも可能です。

[cpp]
// 文字列ストリームへ出力
std::ostringstream ost;
BRepTools::Write(solid, ost);
std::cout << ost.str() << std::endl;

// 文字列ストリームから入力
std::istringstream ist;
ist.str(ost.str());
BRepTools::Read(shape, ist, bb);
std::cout << "Readed shape type no is " << shape.ShapeType() << std::endl;
[/cpp]

BRep ファイルの中身は、プレーンテキストです。上の例で出力した内容は次のようになりました。

[cpp]
DBRep_DrawableShape

CASCADE Topology V1, (c) Matra-Datavision
Locations 0
Curve2ds 24
1 0 0 1 0
1 0 0 1 0
1 10 0 0 -1
1 0 0 0 1
1 0 -10 1 0
1 0 0 1 0
1 0 0 0 -1
1 0 0 0 1
1 0 0 1 0
1 0 10 1 0
1 10 0 0 -1
1 10 0 0 1
1 0 -10 1 0
1 0 10 1 0
1 0 0 0 -1
1 10 0 0 1
1 0 0 0 1
1 0 0 1 0
1 10 0 0 1
1 0 0 1 0
1 0 0 0 1
1 0 10 1 0
1 10 0 0 1
1 0 10 1 0
Curves 12
1 0 0 0 0 0 1
1 0 0 10 -0 1 0
1 0 10 0 0 0 1
1 0 0 0 -0 1 0
1 10 0 0 0 0 1
1 10 0 10 0 1 0
1 10 10 0 0 0 1
1 10 0 0 -0 1 0
1 0 0 0 1 0 -0
1 0 0 10 1 0 -0
1 0 10 0 1 0 -0
1 0 10 10 1 0 -0
Polygon3D 0
PolygonOnTriangulations 0
Surfaces 6
1 0 0 0 1 0 -0 0 0 1 0 -1 0
1 0 0 0 -0 1 0 0 0 1 1 0 -0
1 0 0 10 0 0 1 1 0 -0 -0 1 0
1 0 10 0 -0 1 0 0 0 1 1 0 -0
1 0 0 0 0 0 1 1 0 -0 -0 1 0
1 10 0 0 1 0 -0 0 0 1 0 -1 0
Triangulations 0

TShapes 34
Ve
1e-07
0 0 10
0 0

0101101
*
Ve
1e-07
0 0 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 1 0 0 10
2 1 1 0 0 10
2 2 2 0 0 10
0

0101000
-34 0 +33 0 *
Ve
1e-07
0 10 10
0 0

0101101
*
Ed
1e-07 1 1 0
1 2 0 0 10
2 3 1 0 0 10
2 4 3 0 0 10
0

0101000
-31 0 +34 0 *
Ve
1e-07
0 10 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 3 0 0 10
2 5 1 0 0 10
2 6 4 0 0 10
0

0101000
-31 0 +29 0 *
Ed
1e-07 1 1 0
1 4 0 0 10
2 7 1 0 0 10
2 8 5 0 0 10
0

0101000
-29 0 +33 0 *
Wi

0101000
-32 0 -30 0 +28 0 +27 0 *
Fa
0 1e-07 1 0

0111000
+26 0 *
Ve
1e-07
10 0 10
0 0

0101101
*
Ve
1e-07
10 0 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 5 0 0 10
2 9 6 0 0 10
2 10 2 0 0 10
0

0101000
-24 0 +23 0 *
Ve
1e-07
10 10 10
0 0

0101101
*
Ed
1e-07 1 1 0
1 6 0 0 10
2 11 6 0 0 10
2 12 3 0 0 10
0

0101000
-21 0 +24 0 *
Ve
1e-07
10 10 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 7 0 0 10
2 13 6 0 0 10
2 14 4 0 0 10
0

0101000
-21 0 +19 0 *
Ed
1e-07 1 1 0
1 8 0 0 10
2 15 6 0 0 10
2 16 5 0 0 10
0

0101000
-19 0 +23 0 *
Wi

0101000
-22 0 -20 0 +18 0 +17 0 *
Fa
0 1e-07 6 0

0111000
+16 0 *
Ed
1e-07 1 1 0
1 9 0 0 10
2 17 2 0 0 10
2 18 5 0 0 10
0

0101000
-23 0 +33 0 *
Ed
1e-07 1 1 0
1 10 0 0 10
2 19 2 0 0 10
2 20 3 0 0 10
0

0101000
-24 0 +34 0 *
Wi

0101000
-14 0 -22 0 +13 0 +32 0 *
Fa
0 1e-07 2 0

0111000
+12 0 *
Ed
1e-07 1 1 0
1 11 0 0 10
2 21 4 0 0 10
2 22 5 0 0 10
0

0101000
-19 0 +29 0 *
Ed
1e-07 1 1 0
1 12 0 0 10
2 23 4 0 0 10
2 24 3 0 0 10
0

0101000
-21 0 +31 0 *
Wi

0101000
-10 0 -18 0 +9 0 +28 0 *
Fa
0 1e-07 4 0

0111000
+8 0 *
Wi

0101000
-27 0 -10 0 +17 0 +14 0 *
Fa
0 1e-07 5 0

0111000
+6 0 *
Wi

0101000
-30 0 -9 0 +20 0 +13 0 *
Fa
0 1e-07 3 0

0111000
+4 0 *
Sh

0101100
-25 0 +15 0 -11 0 +7 0 -5 0 +3 0 *
So

1100000
+2 0 *

+1 0
[/cpp]

onnanoko本日のまとめ

TopoDS_Shape 型のオブジェクトを保存、読み込むには BRepTools::Write() または Read() を用いる。

OpenCASCADE のトポロジーオブジェクト

トポロジーオブジェクトの種類

OpenCASCADE には、次のようなトポロジカルな形状があります。※トポロジーとジオメトリーに関しては、別記事をご覧ください。

occtopodsshapes

  • Vertex バーテックス
    ジオメトリの Point に相当する0次元の形状。
  • Edge エッジ
    始終点に Vertex を持つ曲線や直線。
  • Wire ワイヤ
    連続した Edge の集合。曲線を含む複数のEdgeからなるポリラインや、閉じた輪形状などを表わす。単にEdge が複数連なっているだけではなく、どのEdgeがどのEdgeに連結しているか、という結びつけの情報や、「自身が Closed であるか」(輪形状か)という属性値を持つ。
     
  • Face フェイス
    閉じたWireによって囲まれた曲面。平面も含む。
  • Shell シェル
    Edgeにより連結されたFaceの集合。複数の面から構成されたものや、閉じた空間などを表す。Faceを並べただけではなく、あるFaceは別のFaceと、あるEdgeを共有して連結している、という結びつけの情報を持つ。
     
  • Solid ソリッド
    閉じたシェルによって構成される空間を内と外に分けたもの。空間が分けられることにより、ボリューム(容積)を計算することが可能。シェルを空の箱に例えると、ソリッドは中身が積まった固体
  • CompSolid コンプ・ソリッド
    Faceにより連結された Solid の集合。
  • Compound コンパウンド
    上記、7種類を全て含むことができる「複合体」。種類の違う一つ以上のオブジェクトをまとめる為に利用される形状なので、「形状のグループ」として考えても相違ない。Compound自身もCompoundの中に詰め込むことができる。

それぞれ、プログラムコード中では、TopoDS_Edge や TopoDS_Face というように、TopoDS_ 接頭辞が付きます。また、これらの形状をまとめて Shape (形状)と言い、TopoDS_Shape 型で表わされます。TopoDS_Shape 型は、これらの形状の型の親クラスとなっているので、ワイルドカード的な利用ができます。

occtopodsshape-inheritedmap

TopoDS_Shape 型

OpenCASCADE では、幾何演算の結果をよく TopoDS_Shape 型でユーザに返却します。これは、前述のとおり、点だろうが面だろうが関係なく、漠然と「形状(Shape)」を表している型なので、ユーザが正しく形状を扱うには、Shape の実体として何が詰まっているかを知らなければなりません。そこで、TopoDS_Shape には、ShapeType() というメンバ関数が準備されており、このタイプを見ることによって、TopoDS_Edge や TopoDS_Face に安全にダウンキャストすることができます。

[cpp]
// 何らかの処理で Shape 型の S を受け取る
TopoDS_Shape S = …
if (S.ShapeType() == TopAbs_FACE) { // Face かな?
TopoDS_Face F = TopoDS::Face(S); // Shape を Face にダウンキャスト

}
else …
[/cpp]

ShapeType() の戻り値はTopAbs_ShapeEnum 列挙型で、この列挙型の内容は上で紹介した型と対応しています。また、TopoDS_Shape のそれぞれの派生クラスへのダウンキャストは、TopoDS::Face(S) のように行ないます。

トポロジー・エクスプローラ

上で紹介したとおり、ある形状は子要素となる形状によって構成されています。例えばFaceの場合、Faceを構成しているWireやEdge、Vertexといった子要素が含まれています。これらの親要素と子要素は、ともにトポロジカルな関係を持っていますので、例えば、Edgeの集合(つまりWire)からFaceを作成した後に、Edgeの一部を変更した場合、それに追従してFaceの形状も変化することになります。

ある形状を構成している子要素を取得するには、トポロジー・エクスプローラ TopExp_Explorer を用います。

[cpp]
// ソリッド作成
gp_Pnt opos(0.0, 0.0, 0.0);
BRepPrimAPI_MakeBox box(opos, 10.0, 10.0, 10.0);
TopoDS_Solid solid = box.Solid();

// ソリッドに対し、構成要素のFaceを取得するエクスプローラを作成
TopExp_Explorer ex(solid, TopAbs_FACE);

// Face を取得
for (; ex.More(); ex.Next()) {
TopoDS_Face face = TopoDS::Face(ex.Current());
std::cout << "Face: " << face.HashCode(0xFF) << std::endl;
}
[/cpp]

上記のように、対象物を solid とし、取得したい型の TopAbs_FACE を指定して TopExp_Explorer のインスタンスを作成してあげます。TopExp_Explorer はイテレータ型ではありませんが、イテレータのように振る舞うオブジェクトですので、::More() や ::Next()、::Current() メソッドを使って、構成している子要素を取得していきます。列挙される順番は保証されていないようです。

上記の例では、6 枚の Face を取得することができます。また、取得した子要素 Face に対して、さらに別のエクスプローラ(TopAbs_EDGE を指定)を作成して、Edge を取ってくることもできますし、直に TopAbs_VERTEX を指定すると子要素の Vertex を取得することも可能です。

[cpp]
TopExp_Explorer ex(solid, TopAbs_FACE);
for (; ex.More(); ex.Next()) {
TopoDS_Face face = TopoDS::Face(ex.Current());
// エクスプローラで取得したFaceに対して、今度はFaceを構成するEdgeを取得しに行く
TopExp_Explorer ex_edge(face, TopAbs_EDGE);
for (; ex_edge.More(); ex_edge.Next()) {
TopoDS_Edge edge = TopoDS::Edge(ex_edge.Current());

}

}
[/cpp]

onnanoko本日のまとめ

  • OpenCASCADE のトポロジー形状には、Vertex、Edge、Wire、Face、Shell、Solid、CompSolid、Compaundがある。
  • これらの型は、総じてShape(TopoDS_Shape)として扱われる。
  • Shape オブジェクトの内容物は ShapeType() で判別できる。また、TopoDS::トポロジー形状の名前(TopoDS_Shapeオブジェクト)でダウンキャストができる。
  • トポロジー形状を構成する子要素の取得は、トポロジーエクスプローラ(TopExp_Explorer)を用いる。

自転車プチ探訪: 相浦一周withうどんげルート(約24km)

よくお邪魔する定食屋さんに、うちの使ってないパソコンを譲ることになり、昼過ぎに自宅まで取りに来てもらいました。引き取ってもらった後、電源ケーブルの3ツ穴 to 2ツ穴コネクタを渡し忘れたことに気付き、自転車でお店まで届けることにしました。お店には、ちょうど旦那さんとおかみさんが居て、「良い天気ですね、これからどこに行くの?」と言われたので、何となく「ホームセンターまで買いものに」と答えてしまいました。ホームセンターは大好きなので、行ったら行ったで何か用事が見つかります。ちょうど、自転車で行ける範囲にあるホームセンターのキャンプ用品の品揃えを確認したかったので、そのまま出掛けることにしました。

電動アシスト自転車を買ってから、干尽のナフコには行ったし、大塔のコーナンは、先日の早岐と同じルートになりそうだったので、今度は日野のOKホーム&ガーデンに行くことにしました。(ちなみに私は、佐世保市役所周辺に住んでいます) OKホーム&ガーデンは、車でも10年以上行っていなかったので、キャンプ用品どころかどんなものが売ってあるかすら知りません。また、前から計画している黒島旅行の下見も兼ねて、SSKバイパス→鹿子前→日野→相浦港の道も見ておきたかったので、向かうべき方向はすぐに決まりました。

DSC01669.JPGDSC01672.JPGDSC01673.JPG

前回の早岐に行った時は忘れてましたが、今回はちゃんとカバンの中にサイバーショットを入れてました。すごく天気が良くて日焼けしそうです。上は、総合病院の前にある旧海軍佐世保鎮守府凱旋記念館とハンバーガーショップの老舗、ヒカリの前です。日曜日の昼下がりなので、いつものように混雑してました。

DSC01677.JPGDSC01678.JPG DSC01679.JPG DSC01680.JPG

米軍佐世保基地周辺です。オートフォーカスだと、フェンス越しの風景が上手く撮れませんなぁ…。もう少し頑張れば遠景に焦点を合わせたまま、引けたのかもしれませんが、2枚で諦めました。SSKバイパスを走っていると、どんどん歩道が狭くなってかなり走るのが大変でした。風景を見ながらゆったり走る暇もありません。
バイパスの終わりのところにあるセブンイレブンで、給水と簡単な昼食(鶏のからあげ1個)を済ませて、鹿子前トンネルに入っていきました。普段、生身の状態でトンネルを抜けることがめったにないので、久々に驚きましたが、トンネル内ってかなりうるさいですね。ちなみに、自転車に標準装備されているオートライトも正しく動いてくれていました。地味に便利です。

DSC01681.JPG DSC01682.JPG DSC01683.JPG DSC01688.JPG DSC01689.JPG

トンネルを抜けると、そこはヨットハーバーでした!良いお天気だったので、広場で親子連れが遊んでいたり、シーカヤックに乗った人がいたりと気持ちいい感じでした。同僚の方がこの近くにマンションを買ったらしく、市街地から自転車で行ける距離&素敵なリゾート地でいいなあ、と改めて実感。

ここで、西海パールシーリゾートの対岸にある半島の公園で、トビカズラの花が咲いているニュースを思い出しました。実は、連休中にも母と車で公園の前まで行ったのですが、駐車が出来なかったので結局見れず終いだったので、ついでに見に行くことにしました。

DSC01690.JPG DSC01692.JPG

公園は「長尾半島公園」という名前で、1枚目の写真の向かって右側の半島です。木がわさわさ茂っているだけの部分。左側のコの字のゲートが並んでいるところがパールシーですね。

小さな港町を抜け、住宅の路地を進んだ突き当たりに長尾半島公園はありました。

DSC01693.JPG DSC01694.JPG

駐車場らしきスペースはあるものの、車止めでしっかりガードされています。公園に至る道にも、何箇所かに「車では行けないので、パールシーから徒歩で!」という看板が目立ちました。公園の前の道が狭い住宅に面した路地なので、近隣住民に配慮してのことでしょうね。バイクや自転車の乗り入れもしっかりと禁止されていましたので、自転車はここでお留守番です。

DSC01695.JPG DSC01696.JPG DSC01700.JPG DSC01702.JPG

公園内は、意外と綺麗に遊歩道が整備してありました。入口のベンチでおじさんが寝転がっていたのを除けば、自分一人だけでした。ところどころ、育成を観察している旨の札が立てられていました。散歩して歩くのは気持ちいいですが、まあ、何もないと言えば何もない。

DSC01709.2

目的のトビカズラが見当たらないまま、突き当たりの展望台まで来てしまいました。ついでなので、パノラマ撮影でパシャリ。どこにあるのかなー、と注意深く来た道を戻ってみると、東屋になっているところにありました。

DSC01717.JPG DSC01715.JPG DSC01710.JPG

「あったー!…けど思ってたより地味ー!」と思いました。ついでに言うと、花が少ないです。足もとにちょっとしかありません。最初はなんかの雑草が茂っているだけかと思いました。ちなみにこの花は、日本では九十九島のトコイ島と熊本の2ヶ所にしか自生していない珍しい花で、「数百年に一度しか花を咲かせない『うどんげ』の花」とも言われている、ありがたい花だそうです。うどんげにしては地味です。

目的のモノも拝めたので、来た道を戻ってホームセンターに行くことにしました。日野のOKホーム&ガーデンでシュラフが4種類ほどあったので、最低使用温度15度のものを購入。オールシーズン使える厚いシュラフは、既に自宅にあるんですが(しかも3つも)、自転車を積むことを考えて、コンパクトで軽いものを購入しました。ここで、自宅を出発してから8kmほど。サイクリングコンピュータは1500円でもすごく役に立ってます。

さて、次の目的地、相浦港に行くことにします。日野のララプレイスを通り、自動車学校の前を北上していると、この辺りから土地勘がなくなってきました。とりあえず、看板に出ていた陸上自衛隊佐世保駐屯地の方を目指すことに。

DSC01719.JPG

陸自脇の道から撮影した写真です。手前の自動車学校みたいな土地も多分自衛隊施設です。自衛隊に入ると普通免許に加え、いろんな免許が取れるらしいですが、ここで訓練しているんでしょうかね。脇の道を北上していくと、どんどん道が狭くなり、何故か山を登り始めました。

DSC01721.JPG

ちょ、港は海に面しているんじゃ…と思いつつ、ひたすら坂を登ります。あの曲り角を越えれば下り坂だろう、と、3回ほど思って、3回ほど裏切られました。結構登ってるよコレ!目の前には海どころか、登り坂と空と住宅地しか見えません。…自転車を押さないと登らないくらいの斜面を登りきったところに、垣根の剪定をしているおじさんが居たので、港までの道を聞きました。すごく丁寧に親切に教えていただきました。どうやら今来た道を戻らなくて済みそう。ありがたやありがたや!

ちなみに、上の写真のリアキャリアにくくりつけてあるのがホームセンターで購入したシュラフ。さっそく自転車にくくりつけてみたかったので、箱はお店で捨ててもらいました。

DSC01723.JPG DSC01724.JPG DSC01725.JPG

おじさんの言うとおりに道を進むと、無事、相浦港に到着することができました。こう言うとアレかもしれませんが、田舎の小ぢんまりした漁港っていいですよね。子供たち7、8人が自転車に乗って港をうろうろしていました。すごく平和だ。3枚目の写真は、黒島行きのフェリーが出ている待合所です。このターミナルの向かいに小さな商店と宿がありました。

さて、次の目的地は…と考えていると、結構疲れがきていました。走った距離は10kmちょっとでしたが、公園や店内をウロウロしたり、走行中も強い日射しの下だったりと体力の消耗が思ったよりあったようです。しかし、今来た道を戻るのもなんだかアレだし、時間もまだ午後3時だったので、勇気を出して(?)大野経由で市役所前まで帰ることにしました。

DSC01726.JPG DSC01727.JPG DSC01728.JPG

相浦大橋の上を走っていると、左手には愛宕山、右手には先ほどの港が見えました。長崎県立大学の脇を抜け、途中にあったダイソーに寄って、ひたすら大野方面を目指します。本山のモスバーガーの隣にあるガーデニングショップのグリーンズに立ち寄り、赤ベタ(熱帯魚)を1匹購入。ベタを傷つけないようにゆっくり走りました。その後、すき家で昼食(2回目)を取ってから、道なりに南下しました。相浦から大野までって、結構距離があるはずなのに何もない感がすごいです。そんな事言うと怒られてしまうか…。

DSC01730.JPGDSC01732.JPG DSC01733.JPG

途中、今宮神社というのがあったのでちょっとだけ自転車をとめて見てました。参拝はしていません。
大野から市役所の方へ帰って行く途中、物凄い勢いで追い抜いていったロードバイクの人がいました。ウェアから何からガチの方です。50km/sくらい出てそうだったので、さすがに車道を走っていました。ちなみに自分の自転車は、アシストナシの状態で、下り坂で40.5km/sが出たのが最高です。こんだけ重かったら、さもありなんといった感じ。

DSC01738.JPG

そんなこんなで、懐かしの市役所周辺まで戻ってきました。もう日が傾いてますね。サイクリングコンピュータによると、全行程で24.62km。早岐往復が24.97kmだったので350mほど今回は短い旅だったようです。学生時代、新聞配達の奨学金制度を利用していた為、配達用や通勤用自転車(シティサイクル)で毎日50km以上走ってたので、自分も当時の体型、かつロードバイクなんて持ってたら、5、60kmくらい平気で走れたんだろうなぁ、と思いました。まあ、今後の目標にしてもいいか。以前持っていた(盗難されてしまった)クロスバイクでは考えもしなかった行動範囲の移動ができるので(しかも膝を痛めず!)、電動アシストと言えど動かないよりマシなはず。

DSC01739.JPG

自分の電動アシスト自転車「ビビ・ストロング」です。パナソニック製で、設計荷重120kgというかなり強靭な仕様。クロスバイク風のハリヤーなんかと迷いましたが、普段使いでも長く使いたかったので、頑丈かつ重たい車体を選んでいます。シティサイクルにパニアバッグをつけてるので、見る人が見たら結構滑稽に見えるのかもしれませんが、この無骨なフレームに秘められた輸送能力(だけ)は、ロードバイクなんか目じゃないぜ!と思いたいです。(なので、すごく理にかなった形なのです。きっと)

ちなみに、ロードバイクなら車体重量は7、8kg、クロスバイクでも10kg前後なご時世ですが、このビビストロングの重量は33.9kgもあります(何も積んでない状態で)。丈夫にする→重くなる→パワーアップ→重くなる…の負の連鎖が見えますね(苦笑)。
まあ、アシストを完全にオフにしても走れなくなるほど重くはないので、車体を軽くするより自分の体重を数kgでも落とした方が経済的だと思いました。

 DSC01743.JPG DSC01745.JPG DSC01747.JPG  DSC01753.JPG DSC01754.JPG

バッテリーは250ワットの12アンペア。ギアの奥にモーターを搭載しています。フロントライトはLEDの自動点灯機能付き、リアライトはソーラー発電で、これも自動点灯点滅仕様です。頑丈に作ってあるせいか、標準装備の前後キャリアがすごく重そうです。今の時点では、サイクリングコンピュータをつけたくらいでほぼ無改造のまま走っていますが、いかにもママチャリっぽいフロントキャリアは外して、リアはパニアバッグ用のキャリアに変更しようと思っています。

さあ、次は本当の旅が出来ればいいなぁ。

DSC01759.JPG

あ、忘れてましたが、前回の早岐神社に行った時に買ったお守りは自転車のキーにつけてます。

自炊ZIPをスマホや電子ブックリーダー向けに最適化する

去る2月、楽天の Kobo を中古購入しました。投げ売り価格の2500円でした。私は風呂でよく本を読むので、浴槽に落としてもそんなに凹まない安価な端末を探していました。期待をまるでしていない、という意味では Kobo は最高の端末です。

2500円という価格にしては、Linuxが中で動いているし、電子ブックリーダーでしか触ることのできない電子ペーパーを扱えたり、WiFiに対応していたり、本体を開ければ簡単にカスタマイズできたり、と Raspberry Pi よりも「香ばしい」端末なんですけどね。あんなに薄いのにバッテリーまで搭載しているし。

ソフトウェア面は最悪です。電子ブックリーダーなのに本が読みにくい。本棚の表示が遅かったり、改ページが遅かったり。電子ペーパーの画素書き換えの遅さではなく、それ以上にソフトウェアが下手に組んである遅さが気になりました。20世紀末の Windows 98 全盛期くらいの最新 PC くらいのスペックは持ってるんだし、ブックリーダーに用途は特化されているんだから、「もうちょっとどうにかしてよ」という感じ。サービス面に至っては、今更論評するまでもなしですね。

Kobo は至るところで散々叩かれまくっているので、用途を割り切るつもりで購入しました。割り切って使うと決めて購入したら、現物を触ってみてもそんなに落差がないです。
「ああ、やっぱ遅いな」とかそのくらいの印象でした。同時に、「割り切って使う」という目的はなんとかクリアできそうという実感も。

hangousuihan

hangousuihan は、画像ファイルを含むZIP圧縮ファイルに対し、解凍、画像処理、再圧縮の一連の処理を行うツールです。以前、Windows 向けに書きました。このツールの目的は、スマートフォンやタブレットPC、電子ブックリーダーなどの端末の画面にジャストフィットするサイズに画像を縮小して、圧縮ファイル自体をコンパクトにすることです。超高解像度のマンガファイル、1冊300MBなんていうのは家に保存しておいて、実際に見るのは30MBくらいのファイルで充分ですよね、というアプローチです。私が持っている Kobo も、micro SD カードを差すことができますが、水没を前提と考えているので、あまりお金をかけて拡張する気がありません。内蔵メモリだけで済ませたいところです。

Windows の場合、カチッとしたアプリケーションを拵えない限り使いものにはなりませんが、UNIX 系 OS の場合はスクリプトを書けば事足ります。私は普段、Linux しか使ってませんので、次のシェルスクリプトを用いています。

[bash]

!/bin/bash

wd="/tmp/$$"

for target in "$@"
do
[ ! -d "$wd" ] && mkdir "$wd"
unzip -j "$target" -d "$wd"
[ ! -d "$wd/result" ] && mkdir "$wd/result"
tname=basename &quot;$target&quot;
for item in find &quot;$wd&quot; -type f
do
name=basename &quot;$item&quot;
convert -type grayscale -quality 40 -resize 600×800 "$item" "$wd/result/$name"
zip -9 "/tmp/$tname".cbz "$wd/result/$name"
done
mv "/tmp/$tname".cbz /media/$USER/KOBOeReader/
rm -rf "$wd"
done
[/bash]

Kobo の画面解像度にフィットする 600×800 に画像を圧縮し、グレイスケール化しています。Ubuntu では、Kobo のファイルシステムのマウント場所は /media/$USER/KOBOeReader となるので、そこにダイレクトに CBZ で出力するようにしています。

使い方は、

[bash]$ ./hangousuihan.sh comic01.zip comic02.zip comic03.zip[/bash]

のように、変換したいファイルパスを引数に与えてやればOKです。

nfsをやめてsshfsに一本化

うちの自宅LANには、普段からLinuxサーバとLinuxのラップトップだけしか利用していないので、いわゆる「Windowsネットワーク」というものが存在しません。ファイルの共有はNFSというSunが大昔に開発したUNIXファイル共有ネットワーク環境を、ネットワーク・ログインはNIS認証サーバ(yellowpage)を利用していました。

で、ここにきて以下の理由からNFSをやめてSSHFSと思いました。

NFSから切り替える理由

  1. 古い。1984年生まれなので、私より年上です。UNIXの世界では、「古い」というのは良い意味でも悪い意味でも使われますが、後者の方です。規格がRFCに取りまとめられ、かなり前から標準化が行なわれていますが、最新の NFS version 4 (RFC3530)でも制定れたのは今から10年前の2003年。セキュリティの向上や性能の向上も計られつつも、移り変りの早いコンピュータ業界の流れに着いていけてない感が否めないです。
  2. 1に関係してくる事ですが、最近では
    • 自宅でも無線LAN環境が普通
    • 出先でも3GやLTE回線を用いた通信環境が普通
    • 職場の環境がWindows。SFU?何それ?
    • AndroidやiOSなど、色々なモバイル端末も一般化

    てなご時世ですので、通信の品質があまり良くない環境からも使うことが多くなりました。NAT越えのマウントとかも当たり前ですしね。

  3. それで、例えば、マウントしているクライアントが音信不通になったり、ちょっと不安定になるとnfs-kernel-serverを再起動してやらないといけないとか、パーミッションのちぐはぐ感であったりとか、ユーザランドでの使い勝手が悪い点、NAT越えをさせてもセキュリティ的にどうなの、などなど…そういうところが不便です。「磐石なシステム」を念頭に設計されているシステムは、NFSに限らず古くからあるUNIX系のソフトには多いことです。
  4. あ、あとサーバ側もexportsを管理してやったりと、設定がちょっと面倒ですね。AndroidなんかのLinuxカーネルは、nfs-clientモジュールが入ってなかったりと、クライアント側の負担もちょっとあります。
  5. その点、SSHFSは活発にメンテナンスが行なわれていますし、セキュリティ面はSSH本体に丸投げ、SSHd自体は常に稼動させているくらいスタンダードなデーモンなのでよし、fuseと絡めてユーザランドでの使い勝手も良いし、気になっていた負荷や速度面でも、試してみると問題がないようです。

挙げ出すとキリがないかもしれませんが、「これまでお世話になりました。」という気持ちでそっと remove しました。

自転車プチ探訪: 早岐神社周辺

今は空家となっている母方の実家には、小学生の頃までしょっちゅう泊まりに行っていました。母方の祖父母は私が小学生の頃、亡くなってしまいましたが、早岐の田子ノ浦界隈は子供の頃に過ごした思い出でいっぱいです。スタジオ撮影の合間、人手が足りてそうだったので、四ヶ町アーケードから自転車で早岐方面に向かいました。(こういう時に限って、カメラを持参してないんですよね。以下、ミニスマホのカメラで撮影した写真です。)

■正一位稲荷大明神

自動車学校のすぐ側、車道から見上げる形で、コンクリート壁の崖の斜面にある小さな社です。近付いて見てはいませんが、朱い綺麗な鳥居があり、比較的整備されている感じでした。

■正位稲荷大明神

2013-05-05 16.21.49
上の社と名前が似ていますが、小高い丘の中腹にある社です。全体的に古い感じがしましたが、よく草が刈り込まれていて手入れが行き届いている様子。

2013-05-05 16.23.15

石燈籠の文字を見ると、明治34年の文字が見受けられたので、120年近い歴史があるようです。小さな鳥居と、小さな社が二つ。名前のとおり、お稲荷さまが祀ってあるようです。

■乙姫神社(下陣ノ内町公民館)

正位稲荷大明神の近くをうろうろしていると、ふと子供の頃、もっと別の場所に神社があったような気がして、周辺をふらふらしていました。平戸街道跡に差しかかる直前に、路地の先にある鳥居を発見。木が茂っているため、鳥居の奥は薄暗く、鳥居が目に入った瞬間、ドキッとしてしまいました。

2013-05-05 16.30.38
鳥居には「乙姫神社」の文字。

2013-05-05 16.36.12
鳥居の裏側には、竜宮伝説でお馴染の亀が掘ってありました。

2013-05-05 16.31.06
本来、本殿があるであろう位置には「下陣ノ内町公民館」という建物が建っています。神社らしからぬ建物なので、社殿ではなく名前のとおり公民館なのでしょう。

2013-05-05 16.31.28
敷地内の一角に、小さな社もありました。近くに石碑があったので読んでみると、賤津(しつ)の地主らが山林を売り公民館設立に貢献した事を讃える碑文がありました。賤津の名は、今でこそ残っていませんが、江戸時代の古地図を見ると、陣ノ内周辺にある地名だと言うことが分かります。

2013-05-05 16.32.41
結局、この神社は何が祀ってあるのか、そもそも本当に神社なのか、良く分かりませんでした。

■早岐神社

2013-05-05 16.42.02

平戸街道跡の石畳の上を走り、車道から長い坂道を上り、さらに長い階段を登ったところに早岐神社があります。早岐一帯では一番大きな神社です。
子供の頃の記憶はあまりなかったのですが、一通りお参りさせていただきました。

2013-05-05 16.47.09
早岐神社は別名「速来宮」とも言われており、奈良時代に編纂された「肥前国風土記(ヒゼンノクニフドキ)」に登場する速来津姫伝説に何か関係があるのかな、と思っていました。

プチ探訪の思い出ということで、交通安全のお守りを買う際、宮司さんとお話をさせていただきました。早岐神社は熊野の神様をお祀りしているらしく、速来津姫とは関係がないらしいです。明治期の一村一社政策の折に、早岐村にあった神社は早岐神社に習合されたらしいのですが、その中にも速来津姫を祀っていた神社はなかったとのこと。また、周辺にある小さな社でも速来津姫を祀ったものは知らないとおっしゃっていました。
早岐神社は、「速来」という名だけが地名として残り、そこに建てられた神社なので速来宮の名を冠しているようです。(ちなみに、速来宮のパンフレットもいただきました。)

直接お話を伺うと、由緒書きでは見えてこない事も分かるので面白いですね。

より大きな地図で 130505 を表示

その後、折り返して、パソコン工房、釣具のポイント、エレナに立ち寄って帰りました。

2013-05-05 18.57.18
自転車に取りつけたサイクルコンピュータで計測したところ、全行程で24km弱でした。出発時に満タンにしていたバッテリーも到着時には40%になっていました。始終ハイパワーモードで走っていましたが、計算だと40km近く走れそうですね。標準体重でメーカー公表値が37kmだったのでなかなか良い数字。さすがパナソニック。

実際に走っている時間も、ぐるりと巡って1時間45分だったので、早岐も十分に活動範囲内のようです。

プチ島探訪

長崎県は日本有数の島だらけ地帯であり、フェリーも出ていて手軽に島に行けるので、ゴールデンウィークを利用して島に行ってみたいなあ、と思っています。私が住んでいる佐世保市には、島への定期航路がある港は佐世保港と相浦(あいのうら)港の二つがあって、五島列島や平戸島へ行くことができます。同じ長崎県である壱岐や対馬のフェリーは、佐賀や福岡との航路しかないみたいです。(あまり詳しく調べたわけではないので、ひょっとしたらあるのかもしれませんが)

佐世保港、相浦港からは、五島、大島、宇久、黒島方面にフェリーがでているようです。私の場合、釣りはせず、カメラを持って自転車で観光するのが目的なので、手近な黒島を調べてみることにしました。

黒島は、隠れキリシタンで有名な島で、黒島天主堂がある島です。先日、たまたま読んでいた「聖おにいさん」にも聖地巡礼ネタとして、黒島の名前が出てきていました。宿は次の3つがあるようです。

民宿つるさき
喜久屋旅館
山した旅館

どこも、一泊二食付きで5500円から6000円より。素泊まりできるつるさきは3000円より、のようです。宿泊だけではなく、食事利用だけでもOKのところもあるので、私のような貧乏人旅行者でも助かりますね!

さて。同じ佐世保市の九十九島にあるとは言え、意図的に行こうとしない限り行かないもんですねぇ、島は。陸続きだったら、どこかの通り道に「通った事はある」程度の経験はあると思いますが、海の向こうだとなかなか目的がないと行きません。そういう意味では、近場にも関わらず、ちょっとした遠出気分を味わえる、絶好の場所なんだと思います。

Googleマップで黒島の地図を見ていても、思った以上に大きいようです。相浦から出ている黒島行きのフェリーは自転車も手荷物として300円で載せてもらえるらしいので、サイクリングにはぴったりかもしれません。フェリーは一日3往復あり、高島を経由しています。ゴールデンウィークなどの季節は、一日4往復に増便されているようです。佐世保市内から、十分に日帰り旅行もできるようなので、週末のプチ旅行でもいいのかもしれません。

ちなみに、自分が勤めている市内の職場に以前、黒島出身の方がいらっしゃって、ご不幸があって亡くなられたようですが、その方もクリスチャンで、黒島天主堂でご葬儀があったようです。自分が入社する直前だったようで、直接は面識がなかったのですが、私と同世代だったし、同じ職場の仲間になったあろう方なので残念でなりません。

松浦鉄道の自転車持ち込み(輪行)について

全国の自転車普及率を見てみると、長崎県はワースト2位らしいです。そういえば、大阪の平地に比べたら、自転車に乗っている人をほとんど見かけません。街角に駐輪しているのも自転車ではなくバイクが中心です。ほとんど平地がない上に小さな島が散らかっているので、あまり普及していないのかもしれません。

さて、長崎県の北松周辺に走る唯一の鉄道、松浦鉄道に自転車の持ち込み乗車(輪行)について問い合わせてみました。2013年4月時点では、持ち込み方法には二つのパターンがあるらしく、次のようになっています。

1) 土日祝日に運行している特定列車を利用する

… 乗車代金のみで自転車ごと乗車できる。自転車を折り畳まなくて可。収納袋ナシで可。一日に上り、下りともに4本運行している(下記参照)。

2) 平日や特定列車以外を利用する

… 全列車に持ち込みは可だが、乗車代金のほか260円の手回品代金が必要。自転車は折り畳み、収納袋へ入れて持ち込む。

どちらのパターンであっても、乗車前に松浦鉄道佐世保駅まで電話連絡(番号は下記)が必要らしいです。また、団体乗車やイベントなどの都合で、持ち込めない事もあるらしいです。
私の場合は、週末にたびら方面でサイクリングをしたいだけなので、特定列車が利用できるようです。

■ 土日祝日に運行している特定列車の時刻表

上り

  • 佐世保発 08:08 → たびら平戸口発 09:48 → 伊万里着 10:57
  • 佐世保発 09:27 → たびら平戸口発 10:48 → 伊万里着 11:55
  • 佐世保発 12:25 → たびら平戸口発 13:48 → 伊万里着 14:55
  • 佐世保発 14:25 → たびら平戸口発 15:50 → 伊万里着 16:55

下り

  • 伊万里発 08:43 → たびら平戸口発 09:48 → 佐世保着 11:09
  • 伊万里発 10:08 → たびら平戸口発 11:18 → 佐世保着 12:39
  • 伊万里発 12:40 → たびら平戸口発 13:48 → 佐世保着 15:09
  • 伊万里発 14:36 → たびら平戸口発 15:47 → 佐世保着 17:07

詳しくは、こちらの時刻表をご覧ください。

※ このページの情報は、あくまでも参考までにお使いください。
※ 最新の情報は、必ず松浦鉄道佐世保駅(TEL:0956-25-3900)までお問い合わせください。

世代や職業によって変化する”Hello World”プログラム

GNUのジョーク集「Hello World!」という面白い記事があったので、少しだけ邦訳して、コメントを添えて掲載します。

中高生

[vb]
10 PRINT "HELLO WORLD"
20 END
[/vb]

まずは基本のBASICです。

大学生(初年度)

[perl]
program Hello(input, output)
begin
writeln(‘Hello World’)
end.
[/perl]

Pascalを覚えたようです。

大学生(卒業年)

[perl]
(defun hello
(print
(cons ‘Hello (list ‘World))))
[/perl]

Lispを使えるようになりました。

新人プログラマー

[cpp]
#include <stdio.h>

void main(void)
{
char *message[] = {"Hello ", "World"};
int i;
for(i = 0; i < 2; ++i)
printf("%s", message[i]);
printf("\n");
}
[/cpp]

Cを覚えました。ポインタも配列も使えます。

ベテランプログラマー

[cpp]
#include <iostream.h>
#include <string.h>
class string
{
private:
int size;
char *ptr;
public:
string() : size(0), ptr(new char(‘\0’)) {}
string(const string &s) : size(s.size)
{
ptr = new char[size + 1];
strcpy(ptr, s.ptr);
}
~string()
{
delete [] ptr;
}
friend ostream &operator <<(ostream &, const string &);
string &operator=(const char *);
};

ostream &operator<<(ostream &stream, const string &s)
{
return(stream << s.ptr);
}
string &string::operator=(const char *chrs)
{
if (this != &chrs)
{
delete [] ptr;
size = strlen(chrs);
ptr = new char[size + 1];
strcpy(ptr, chrs);
}
return(*this);
}
int main()
{
string str;
str = "Hello World";
cout << str << endl;
return(0);
}
[/cpp]

クラスを用いて、C++を使いこなせていますね。

システム管理者

[cpp]
#include <stdio.h>
#include <stdlib.h>
main()
{
char *tmp;
int i=0;
/* on y va bourin */
tmp=(char *)malloc(1024*sizeof(char));
while (tmp[i]="Hello Wolrd"[i++]);
/* Ooopps y’a une infusion ! */
i=(int)tmp[8];
tmp[8]=tmp[9];
tmp[9]=(char)i;
printf("%s\n",tmp);
}
[/cpp]

Cですが、コメントを見るとフランス人のようです。
まわりくどいことをやっている例でしょうか。

見習いハッカー

[perl]
#!/usr/local/bin/perl
$msg="Hello, world.\n";
if ($#ARGV >= 0) {
while(defined($arg=shift(@ARGV))) {
$outfilename = $arg;
open(FILE, ">" . $outfilename) || die "Can’t write $arg: $!\n";
print (FILE $msg);
close(FILE) || die "Can’t close $arg: $!\n";
}
} else {
print ($msg);
}
1;
[/perl]

書き易くて読みにくいPerlは、ハッカーの基本言語ですね。

上級ハッカー

[cpp]
#include <stdio.h>
#include <string.h>
#define S "Hello, World\n"
main(){exit(printf(S) == strlen(S) ? 0 : 1);}
[/cpp]

CでPerlのような読みにくいコードをガリガリ書けるようになり、
書いた本人も空気のように読めるようになってきます。

熟練ハッカー

[shell]
% cc -o a.out ~/src/misc/hw/hw.c
% a.out
Hello, world.
[/shell]

熟練になると、よく使うコードはコンパクトに持ち歩けるようにしておき、
必要な時にコンパイルして動かします。コーディングはしません。

グル・ハッカー

[shell]
% cat
Hello, world.
[/shell]

プログラミングの神様は、コーディングどころかコンパイルすらしません。
(catコマンドは引数を与えないと、標準入力に入ったものを標準出力に吐きます)

新人マネージャ

[vb]
10 PRINT "HELLO WORLD"
20 END
[/vb]

上の覚えてます? ;-)

中間管理職マネージャ

[shell]
% mail -s "Hello, world." bob@b12
Bob, could you please write me a program that prints "Hello, world."?
I need it by tomorrow.
^D
[/shell]

「明日までに必要なんだ!」

上級マネージャ

[shell]
% zmail jim
I need a "Hello, world." program by this afternoon.
[/shell]

「今日の午後、必要なんだ!」

経営責任者

[shell]
% letter
letter: Command not found.
% mail
To: ^X ^F ^C
% help mail
help: Command not found.
% damn!
!: Event unrecognized
% logout
[/shell]

「使い方が分からん、畜生!」

科学研究者

[vb]
PROGRAM HELLO
PRINT *, ‘Hello World’
END
[/vb]

科学分野で人気のFortran90です。

熟練科学研究者

[vb]
WRITE (6, 100)
100 FORMAT (1H ,11HHELLO WORLD)
CALL EXIT
END
[/vb]

さらに古いFORTRAN77です。

これらはジョークなので、本当にこれらの人が上のようなコーディングをしているわけではありませんが、スキルや考え方、他業種から見たイメージなどを上手く皮肉っていますね。

tumblrの画像を一括ダウンロードするスクリプト

tumblrの画像を一括ダウンロードするbash向けシェルスクリプトを書いてみました。

あまり効率的ではないですが、投稿を1ページずつ取得して、含まれる画像を抜き出しています。また、ダウンロードが完了したらZIPファイルに固めてくれます。

[bash]

!/usr/bin/env bash

#

simple picture downloader for tumblr

#

written by dyama (https://dyama.org/), Feb 1 2013

#

usage:

gettumblr http://example.tumblr.com/

function gettumblr()
{
url=$1

# fix the url
url=${url#http://} # this pattern is not regex!
url=${url%%/*}

# check the url and get tumblr user acount
if [[ $url =~ ^([a-z0-9]+)\.tumblr\.com$ ]]; then
    USER=${BASH_REMATCH[1]}
    echo OK, Target user name is $USER. &gt;&amp;2
else
    echo Error: $url is not tumblr URL. &gt;&amp;2
    return 1
fi

# working directory
wdir=&quot;/tmp/gettumblr$$&quot;
if [ ! -d $wdir ]; then
    mkdir $wdir
    if [ $? -ne 0 ]; then
        echo Error: Cannot make working directory at $wdir. &gt;&amp;2
        return 1
    fi
fi

pushd $wdir

for i in {1..999};
do
    echo Getting posted page $i ... &gt;&amp;2
    wget -O - -q &quot;http://$url/page/$i&quot; | grep -oE 'http[^&quot;]+post[^&quot;]+' \
        | grep $USER &gt; list
    [ $? -ne 0 ] &amp;&amp; break;
    echo -e '\tGetting included image ...' &gt;&amp;2
    grep -v frame list | wget -q -i - -O - | grep 'og:image' \
        | grep -v facebook | grep -oE 'http[^&quot;]+' | wget -i - -nv
done

file=$wdir/tumblr_$USER.zip
zip $file *

popd

mv $file .
rm -rf $wdir

echo Done. &gt;&amp;2

}

gettumblr "$*"
exit $?
[/bash]

関数 gettumblr() の冒頭部分のパターンによる変数保持値の編集や正規表現パターンマッチ構文は、bash依存です。また、grep の -o オプション(パターンに一致した行の、パターンに一致した部分だけを標準出力に印字する)も多用しています。このオプションは古めの grep にはなかったはずなので、注意が必要です。

ターゲットのtumblrユーザが存在しない場合、ファイルが削除できなかった場合など、例外処理はまったく書いていなかったり、一回で済むような処理を分けて書いていたりと、トライアンドエラーの跡が残ったままですので、もし流用する場合には適宜書き換えてください。