Forge Viewerでの断面図表示

Extensionなどのコードを読んでやりたいことの実現方法を調べた結果をまとめています。 どこまでが公式に保証されている動作かは把握できていないので、今後のバージョンで動く保証はできないです。

前提条件

予め断面図を設定しておいたRevitモデルをForge Viewerで表示することとします。

断面図の一覧取得方法

表示できるViewのうち、条件にあうViewを取得するのは以下のコードで実現できます。 ただし、表示できないものもピックアップしてしまう可能性はあります。 なので、predicateにノードの名称(断面図の名前)でフィルタリングできるようにはしています。

function collectCrossSectionViews(model: Autodesk.Viewing.Model, predicate: (name: string) => boolean) {
    const docNode = model.getDocumentNode();
    const rootNode = docNode.getRootNode();

    const nodes: Autodesk.Viewing.BubbleNode[] = [];
    seachNode(nodes, rootNode, predicate);
    return nodes;
}

function seachNode(nodes: Autodesk.Viewing.BubbleNode[], node: Autodesk.Viewing.BubbleNode, predicate: (name: string) => boolean) {
    const name = node.name();
    if (predicate == null || predicate(name)) {
        nodes.push(node);
    }
    if (!node.children) {
        return;
    }
    node.children.forEach(child => seachNode(nodes, child, predicate));
}

collectCrossSectionViewsで断面図のノードのリストを取得できます。 取得したリストから、表示する断面図を選択するUIなどを構築する場合には、各値をviewNodeとすると、viewNode.name()で、断面図の名称が取得できます。

断面図の表示方法

実際にForge Viewerに断面図を表示する場合は、以下のようにloadすればOKです。

function changeView(viewer: Autodesk.Viewing.GuiViewer3D, node: Autodesk.Viewing.BubbleNode) {
    viewer.loadDocumentNode(node.getRootNode().getDocument(), node);
}

使い方の例は以下です。

const nodes = collectCrossSectionViews(viewer.model, name => name.indexOf('断面') >= 0);
if (nodes.length > 0) {
    const node = nodes[0];
    changeView(viewer, node);
}

入力されたモデルのサーフェス上に点があるか判定する

与えられた点がモデルの平面上にあるかどうかを判定するコードをthreejsで書いたのでメモしておきます。 三角形ポリゴンを前提としています。

geomにはTHREE.Geometry、ptにはTHREE.Vector3が渡されるとします。 (THREE.Geometryは公式ではもう廃止されてた気もする。。)

function is_on_surface(geom, pt) {
    // Geometryを構成するする三角形面のうち、条件を満たすものがあるかを判定
    return geom.faces.some(function(f) {
        // 三角形の各頂点を取得する
        const v1 = geom.vertices[f.a];
        const v2 = geom.vertices[f.b];
        const v3 = geom.vertices[f.c];
        
        const v12 = new THREE.Vector3().subVectors(v2, v1);
        const v23 = new THREE.Vector3().subVectors(v3, v2);

        // 三角形の法線ベクトルを算出
        const n = new THREE.Vector3().crossVectors(v12, v23).normalize();

        // 三角形上の頂点から入力点までのベクトル
        const v1p = new THREE.Vector3().subVectors(pt, v1);

        // 三角形の平面と入力点までの距離を算出
        const dist = Math.abs(v1p.dot(n));

        // 平面との距離が0.0001以上なら同一平面にない点と判定
        if (dist >= 0.0001) return false;

        const v31 = new THREE.Vector3().subVectors(v1, v3);
        const v2p = new THREE.Vector3().subVectors(pt, v2);
        const v3p = new THREE.Vector3().subVectors(pt, v3);

        const n1 = new THREE.Vector3().crossVectors(v12, v2p).normalize();
        const n2 = new THREE.Vector3().crossVectors(v23, v3p).normalize();
        const n3 = new THREE.Vector3().crossVectors(v31, v1p).normalize();
        return n1.dot(n2) > 0 && n1.dot(n3) > 0;
    });
}

簡単に解説しておくと2ステップで判定します。

まず最初に、入力された点ptが平面上にあるかどうかを判定します。 平面上にあるというのは平面と点の距離が十分小さいことで判定できますし、 平面と点の距離は、面の法線ベクトルと、面上の任意の点からのベクトルの内積を求めれば、求まります。

今回は誤差もでるので以下のように0.0001以下なら平面上とみなす、としました。 値を見た感じ、もっと厳しく判定しても問題なさそうでしたが、条件によっては調整が必要かもです。

if (dist >= 0.0001) return false;

次に、点が三角形の内部にあるか外部にあるかを判定します。これも外積を使えば簡単に判定できます。 この辺の解説はあちこちにあるので省略します。 www.sousakuba.com

上記の両方を満たせば、三角形ポリゴン内部に点があるといえます。 これをGeometryのポリゴンすべてで実行すれば、モデル表面に点があるかどうかを判定できます。

あと一言添えるとすると、threejsのVector3はmutableなので、crossとか使うと外積元の変数が変わってしまうところに気を付ける必要はあります。

RustでL2からTCPプロトコルを自作する

マスタリングTCP/IPは何度か読んではいるけれど、レイヤーとかいわれても面白くないし、ネットワークあんまりわかってない気がしていたので、もうちょっと理解するために、L2レイヤであるEthernetからL4のTCPまでをRustでだいたい自作してみた。だいたいというのはRustのpnetを使ってさぼった部分があるから。パケットにデータ詰める部分は別に自作する必要もないと思ったし。

やっぱり手を動かすと座学だけではなかなか気づきにくいことに気づけた気がするので、よかったとは思う。ただし、一人でやると時間は結構溶けると思う。

とりあえず、やってみての気づきなど。

各ヘッダのサイズに従った配列を生成しておく必要があること。

これはpnetの問題かもしれないが、イーサネット、IP,TCP,UDP、ICMPなどのプロトコル毎にヘッダがあるが、そのヘッダの長さ分のバッファを用意する必要がある。

パケットのサイズ

ACKなどはpayloadのサイズ分通りseqを進めるのに対して、SYNとFINのみpayloadが0でもseqを1進める必要があること。再送対象のパケットもこのサイズが0じゃないものなので、一緒に考えるとよい。 逆に言えば、サイズ0のACKはロストしても基本的に再送されない。

再送時のACK

再送するパケット、ACK差し替えた方が効率よさそうだけど、最初おくったときのものをそのままでもよさそう。

ACK of FIN

FINというのは、これ以上送信しないことを示すフラグ。FINのACKというのは最後に送ったFINまですべてACKされたことが確認できるということ。ACK | FINフラグではない。

フラグ

SYNフラグは接続要求のときに送信するフラグだけれども、SEQの初期値を同期するためのフラグである。 ACKフラグはACKフィールドが有効であることを示す。そのため、コネクションが確立された後はFINなどもすべてACKも立てる。

ウィンドウプローブ

受信済みのACKを再送してもパケットは破棄される?ので、Windowサイズの変更が通知されることを期待して1オクテット?のパケットを送る。

パケットのロス検出

  1. 同じACKを4回連続で受け取ったとき。ただし最新のSEQまでACKを受け取っているときはその限りではないはず。
  2. 再送時間が経過したとき。これは結構輻輳していると判断される。ただし、最後のパケットがロスした場合も同じ扱いになってしまう。

リロード

通常のクライアント側のポートはランダムに決定するので、リロードするたびに別のポートでコネクションが張られる。そのため、前に接続したコネクションがしばらくTIME-WAITのまま残っていてもほとんど影響は出ない。

3ハンドシェイクしようとすると勝手にKernelがRSTを送ってしまう。

Kernelからすれば、自分が管理してないところでACK受け取ったら拒否するのは当然なのだけれど、なんとかしないと通信できない。 Network Namespaceで新しい仮想のノードを作成し、そのノードではRSTフラグをフィルタリングするようにiptablesを設定することで、KernelからのRSTのみフィルタリングできるようにした。iptablesの設定はL3だが、自作TCPが送信するパケットはL2なので、自作TCPは影響を受けないので都合がよい。

pingのコードを読みたい。

ネットワークの勉強がてらpingソースコードをみたくなってしらべてみた。 環境はWSL2のUbuntuとする。

まずはpingのパッケージを調べる。

$ which ping
/bin/ping
$ dpkg --search /bin/ping
iputils-ping: /bin/ping

iputils-pingだとわかった。 次にaptでiputils-pingソースコードを取得してみる。

$ apt-get source iputils-ping ./
Reading package lists... Done
E: You must put some 'deb-src' URIs in your sources.list

dev-srcの設定が必要とのこと。/etc/apt/sources.listで設定変更する。

具体的にはコメントになっているdev-srcをコメントアウトしておく。どれかわからないのでとりあえず全部の定義をコメントアウトした。

deb http://archive.ubuntu.com/ubuntu/ focal multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ focal multiverse

aptをアップデートしておく。

$ sudo apt update

ソースコードを取得しようとする。

sudo apt-get source iputils-ping ./
Reading package lists... Done
Picking 'iputils' as source package instead of 'iputils-ping'
NOTICE: 'iputils' packaging is maintained in the 'Git' version control system at:
https://salsa.debian.org/debian/iputils.git
Please use:
git clone https://salsa.debian.org/debian/iputils.git
to retrieve the latest (possibly unreleased) updates to the package.
E: Unable to find a source package for .

結局gitからとってこいとのこと。

git clone https://salsa.debian.org/debian/iputils.git

できた。

コードの規模はそんなに大きくなさそうかな。

$ wc -l iputils/ping/*
    28 iputils/ping/meson.build
   446 iputils/ping/node_info.c
  1705 iputils/ping/ping.c
   430 iputils/ping/ping.h
   925 iputils/ping/ping6_common.c
   963 iputils/ping/ping_common.c
  4497 total

Rustの開発環境を整える

Hello, Worldが終わったら最低限の開発環境を整えるのがよいとおもったので、メモ。 いまどき無難なVSCodeで書くとしても最低限の拡張機能のインストールや最低限の設定は必要だった。

Rust Analyzer

公式のLanguage Serverが不安定なようなので。とりあえずこれを入れておくと、補完が効くようになるので捗る。

rustfmtの有効化

AutoFormatterは、好き好きあるかもしれないが。インデントが勝手に崩れたりするよりはましかと思っている。 setting.jsonで以下を追記すれば有効になる。

    "[rust]": {
        "editor.formatOnSave": true
    }

その他

rustfix, clippyなどもあるみたいだが、VSCodeでの活用方法がよくわからず、という感じ。

参考

doc.rust-jp.rs

開発環境が整ったら

一通り公式のチュートリアルあたりから始めるのが結局近道だと感じている。

doc.rust-jp.rs

いきなり、書き始めても結局はまってしまうので。。。

書評:詳解Rustプログラミング

年末年始にざっと読んだので一言残しておく。

www.shoeisha.co.jp

システムプログラミングを題材にRustを説明してくれる本、という感じ。

ただ、システムプログラミングといっても表面的なところで終わっているので、そっちを期待するとちょっと期待外れかもしれない。 じゃあRustの説明はというと体系的に整理されているわけではないので、ちょっとしんどいかもしれない。

個人的な発見は少なめだけど、新しく学べたこともある。

  • DNSRFCを読むきっかけになった(勝手に読んでパケットの読み書きを実装した)
  • NTPのプロトコルに触れるきっかけになった。

Rustちょっと書いてみて。

CとかC++の黒魔術的なバイトコードの操作が認められないので、TypescriptとかJavaとかでシステムプログラミングをやるのに近い感じになる印象。 コンパイル通すのに苦労する当たり、Typescript書くのに結構近い感じがある。

自作キーボード1年目。沼の入口をうろうろする。

Advent Calendarを書くころにはもうちょっと沼にはまっていると思っていたのですが、思ったより沼の浅いところにいます。

自作キーボードに至るまでを振り返りたいと思います。

自作前のキーボード歴

覚えている範囲で。

Microsoft Ergonomic Keyboard

数台使いました。ESCキーが押しづらいみたいな不満がありましたが、まぁまぁだった気がしています。 ただ、1年くらい自分の手垢がやたら気になりました。

RealForceのテンキーレスキーボード

打鍵感は好きだったのですが、どうも窮屈になってきて肩こりがひどくなり、分割キーボードを探すようになりました。

Mistel BAROCCO MD600(日本語配列

日本語配列でFnキーがあるキーボードを探すとほかに選択肢がなかったという感じです。

archisite.co.jp

ただ矢印キーがないのがかなりつらかったので、 このころから、自作キーボードも視野にいれてキーボードを探し始めました。

Mistel BAROCCO MD770

日本語配列だと限界だなぁと思い、英字配列に乗り換えました。

https://archisite.co.jp/products/mistel/barocco-md770/

ちなみに、過去に英字配列を使っていたときは、職場では日本語キーボードを使わないといけなかったりしたので苦労したのですが、今回は職場用にも同じものを調達したり、日本語キーボードには英字用のシールを張ったりして乗り切りました。

www.amazon.co.jp

このころはFnキーのある自作キーボードを探していました。(が見つけられず)

自作キーボード歴

Mint60

ふと、Mint60を見つけて、ESCはともかく、Fnキーくらいならなくてもなんとかなるのでは?と考えるようになり、ついに自作キーボードに手を出してみることにしました。

一般的な英字キーボードに近いのと、矢印キーもちゃんとあるので、結構すんなり使えました。 スターターキットで作ったのですが、Gateronだと打鍵感に引っ掛かりを感じるのがあまりすきではないので、 スイッチを変えたいなーという気分はになっています。

軸は静音でないといろいろ支障があるので、静音軸一択なので、この辺とか。 Kailh BOX Silent スイッチ(10個) – 遊舎工房

Ergo Dash

せっかくなので、もうちょっと沼にはまってみようと思ってErgoDashも組み立てました。

ErgoDashアルミケース – 遊舎工房

ただ、こっちはすんなりとは使えずです。 キーボードに慣れないまま仕事をしようとすると、すぐに精神が崩壊して、集中できなくなってしまったので、なかなか切替できず。。。結局まだ使いこなせていません。ただ、Ergo Dashはキー数多すぎというのはよくわかったので、もうちょっと少ないのがいいかもなーと思っています。

ちなみにこの記事もMint60で書いてます。