HN 日本語サマリー

← 一覧へ戻る
プログラミング

ブラウザでFreeCADを実行

FreeCAD in the Browser (magik.net)

28 pointsby cui16 コメント

要約

この記事は、複雑な3D CADソフトウェアであるFreeCADをWebAssembly(Wasm)を使用してブラウザ上で実行可能にした試みについて詳述しています。開発者は、Qt for WebAssembly、JSPI、および静的依存関係スタックを活用し、約4日間でFreeCADのブラウザ版を構築しました。初回ロードサイズは約96MBですが、ブラウザキャッシュ後は高速に動作します。このポートは、特にPySide6やOpenCASCADEなどのコンポーネントをブラウザ環境で動作させるという、これまで困難とされてきた技術的課題を克服した点が注目されます。

全文翻訳

magik.net ブラウザでのFreeCAD 2026年7月7日掲載 きっかけはHacker Newsのスレッドでした。LibreCAD、Qtデスクトップアプリケーション全体をWebAssemblyにコンパイルし、タブで実行するものです。magikはそれをリリースし、次にOpenSCADをリリースしました。そのパターンはレシピのように見えました。Qt for WebAssembly、JSPI、静的依存関係スタック、そしてアプリケーションがスレッド、ファイルシステム、OpenGLについて仮定していることの正直な監査です。次に当然浮かんだ疑問は、どこまでスケールするかということでした。そこで、FreeCADです。パラメトリック3D CAD、スケッチ制約付きソリッドモデリング、組み込みPython、1ダース以上のワークベンチ。他の2つと比較して、その規模は約10倍です。C++で約150万行、Pythonで70万行あり、現在はブラウザタブで動作します。ポート作業は、HackerNewsのスレッドから現在の状態まで約4日間かかりました。実装はほぼすべてFableによって行われ、私が大規模で複雑なプロジェクトでFableの能力をストレステストするための方法でした。もしそれが大きな主張のように聞こえるなら、検証可能です。4日間のすべてのプロンプトは、この記事の最後にそのまま再現されています。それら48個すべてです。そして、完全なセッションのトランスクリプト、すべてのサブエージェントとすべてのワークフローは、閲覧可能なアーカイブとして公開されています。 FreeCADを起動 → 初回ロードは約96MB(Brotli圧縮)です。これは大きなプログラムであり、それを隠すつもりはありません。ブラウザは後でキャッシュします。最近のChromiumベースのブラウザ(Chrome/Edge 137+)が必要です。このポートはWebAssembly JSPIに依存しており、FirefoxとSafariはまだ提供していません。起動すると、デモドキュメント(円筒形のカットが入ったボックス)がライブ3Dビューポートに開きます。この記事の残りの部分は、最も切実な質問に答えるためにFableによって完全に書かれた技術ノートですが、ここからはAIによる投稿です。 FreeCADは1つのプログラムではありません。プラットフォームです。Qt GUIの下には、産業用B-repジオメトリカーネルであるOpenCASCADE(OCCT)、3Dビューを駆動するOpen InventorシーングラフであるCoin3DとQuarter、完全な組み込みCPython 3.14、PySide6/shiboken6 Qtバインディング(PythonコードでQt UIを構築可能)、そして今回はFEMワークベンチのために特別にクロスコンパイルされたVTK 9.3のサブセットとSalome SMESHメッシャーがあります。その上に、Part、Sketcher、PartDesign、Draft、Spreadsheet、Measure、Surface、Import、Mesh/MeshPart/Points/Inspection/Robot、TechDraw、Assembly、CAM、BIM、FEM、Material、Startといった17以上のワークベンチがあります。すべてがQt 6.11とともに、単一の196MBのWebAssemblyモジュールに静的にコンパイルされます。そのうち2つの部分は、私の知る限り、ブラウザで初めて行われたことです。PySide6とpivy(Coinのバインディング)がwasmで動作すること、そしてOCCT+Coin+CPython+PySide+VTK+SMESHが単一モジュールにリンクされたことです。この記事の残りは、それがどのようにしてそこに至ったか、発生順に説明します。 ビルド、シーケンス このセクションは意図的に長いです。リクエストは、すべての修正とすべての方向性を順に、そして具体性が重要であるということでした。アプリを少し触ってみたいだけであれば、起動ボタンは上にあります。 0 · ツールチェーン:自分でビルドする必要のあるQt LibreCADはプリビルドのQt-for-wasmを使用できましたが、FreeCADはできません。その理由はJSPIです。FreeCADはモーダルダイアログで飽和しています。C++で185、Pythonでさらに156のexec()サイトがあり、ブロッキングPythonコードはコールバックに機械的に書き換えることができません。QDialog::exec()セマンティクスを維持する唯一の方法は、ダイアログが開いている間wasmスタックを一時停止することです。これはまさにWebAssembly JSPI(JavaScript Promise Integration)が行うことです。これには-feature-wasm-jspiが必要であり、ソースからビルドされたQtが必要です。したがって、/opt/toolchains下のツールチェーンは、Emscripten 4.0.12上でJSPIとネイティブwasm例外でビルドされたQt 6.11.1であり、OCCT、CPython、ICU、Boost、Xerces、{fmt}、yaml-cppのハンドビルド静的プレフィックスの隣にあります。 1 · カーネル、ヘッドレスから この規模のプロジェクトを管理可能にした規律は、カーネルファーストでした。GUIのピクセルに触れる前に、OCCT + CPython + FreeCAD Appレイヤーがwasmで実行されることを証明しました。FreeCADのC++モジュールはPython拡張モジュール(PyMOD_INIT_FUNC)であり、通常は実行時にdlopenされます。wasmモジュールにはdlopenはありません。そのため、すべてのモジュールは静的にリンクされ、Py_Initializeの前にPyImport_AppendInittabでインタープリタのinittabに登録されます。ホームパス、Python環境、node-sideテスト用のNODERAWFSビルドが整理された後、FreeCADCmdはヘッドレスで実行されました。つまり、.FCStdファイルを開き、再計算し、STEPをラウンドトリップしました。最初はnodeで、次にブラウザで。それが最初にリリースされたもので、それ自体が製品です。タブで実行可能なCADカーネルです。 2 · GUIのリンク、そしてOpenGLへの嘘 フルGUIターゲットをリンクすることは、ほとんど事務処理でした。共有inittab、QProcessのスタンドイン(タブ内では子プロセスなし)、Startモジュールのタイムゾーン/プロセスに関する仮定の保護です。興味深い部分はOpenGLでした。FreeCADはCoin3Dを通じてレンダリングします。これは正直な1990年代の固定機能OpenGLです。マトリックススタック、glBegin/glVertex、glLightfv、クライアント頂点配列です。WebGL2にはそれらのどれもありません。そのため、リンクは手書きの固定機能GL on GLES2シムをプルします。これはOpenSCADが直面したのと同じ壁であり、同じ方法で解決されましたが、Coinのより大きなGLサーフェスに対してです。 3 · メインウィンドウの起動 リンクされたモジュールは実行中のモジュールではありません。GUIの起動は2つのことと戦いました。まず、C++の静的初期化順序です。すべてが1つのモジュールにあるため、OCCT、Coin、Python、Qtからのグローバルコンストラクタが誰も設計しなかった順序で実行され、それらのうちのいくつかは後続のものが設定する状態を読み取ります。次に、Qt-wasmプラットフォームプラグインは、特定のエクスポートされた関数と、実際に一時停止できるイベントループを必要とします。初期のQtメッセージハンドラが、ブロッカーを判読可能にした最後の要因でした。それは、イベントループがapp.exec()を生き残るためにasyncifyを必要とすることを示しました。それにより、メインウィンドウが表示されました。ドキュメントツリー、プロパティエディタ、ツールバー、メニュー、モーダルダイアログが機能しました。ステージ3ゲートを通過しました。 4 · WebGL2ビューポート、そしてその中に隠れていたバグ これが困難なフロンティアでした。Coinのレンダラーは約20ファイルにわたる約70のglBeginサイトとマトリックススタック演算です。私は約900行の固定機能エミュレータ(WasmGLFixedFunc)を構築しました。これは、GLES 3.0上でCoinが使用するサブセットを正確に実装しています。CPU側マトリックススタック、2ライトのピクセルごとのシェーディングを持つ1つのGLSL ESプログラム、イミディエイトモードバッチ処理です。しかし、OpenSCADも発見した最も厄介な壁は、コンポジットでした。QtはGLウィジェットをトップレベルウィンドウのコンテキストでフレームバッファテクスチャをラップすることによってコンポジットしますが、WebGLコンテキストは何も共有しません。修正は、シーンがレンダリングされるオフスクリーンフレームバッファ(WasmGLWidget)であり、読み取られてQtの通常のラスタコンポジットに渡されます。その後、それぞれが小さな物語であった一連のバグが発生しました。ソリッドが不可視でレンダリングされました。GLエミュレータはglGetDoublev(GL_DEPTH_CLEAR_VALUE)を0にスタブしたため、デプスバッファはニアプレーンにクリアされ、すべてがデプス テストに失敗しました。1行のデフォルトで宇宙が修正されました。INVALID_ENUMエラーの洪水:CoinはWebGLが拒否するレガシー固定機能列挙型をglGetFloatv/glGetBooleanv経由でクエリします。それらをローカルでインターセプトして応答します。選択ドラッグ中にパーツが白くなったのは、ラバーバンドオーバーレイが空白のフレームバッファを掴んだためです。オーバーレイの下にライブシーンを維持しました。そして長いもの:頂点配列の高速パス。CoinのisSupportedゲートは、10個すべてのレガシークライアント配列エントリポイントが解決されない限り頂点配列を拒否するため、no-opのglIndexPointer/glArrayElementを追加しました。その後SoVertexArrayIndexerはWebGL2では利用できないglDrawRangeElementsとglMultiDrawElementsを要求したため、それらをエミュレータにルーティングしました。決定的な問題はより微妙でした。エミュレータ自身のスクラッチVBOが描画後にバインドされたままだったため、次の収集はそれらをCoinのバッファとしてキャプチャし、間違ったメモリから頂点を読み取りました。シーンは三角形ではなく点のフィールドとしてレンダリングされました。エミュレータのバッファを_ownBufsセットで追跡し、バインディングをスナップショットするときにそれらを無視することで修正され、頂点配列パスは〜1.3 fpsのイミディエイトモードから快適にインタラクティブになりました。 5 · 例外をエンコードする2つの方法 しばらくの間、ビルドはEmscriptenのASYNCIFYとJavaScriptベースの例外を使用しました。JSPIとネイティブwasm例外への移行は、実行されました。