milvinae

web & product design.

  • FreeCADでGPUレンダリングする方法

    FreeCADでGPUレンダリングする方法

    ※下記内容はすべてWindows11向けです。
    ~~~~~~~~~~~~~~~~~~~~~~~


    CADソフトは世に数あれど、オープンソースで開発が進んでいるCADソフトはFreeCADだけでしょう。
    昨年2024年に満を持してVersion1.0正式リリースとなり、界隈の一層の発展が予想されるところであります。

    そんなFreeCADですが、数多くのワークベンチ(機能)が存在します。
    その一部にRenderというワークベンチがあります。

    これは作成したモデルにマテリアル情報を付与、カメラ・ライティングを設定して、”見え方”をシミュレートする機能です。
    3Dデータを実物に近づけることで、プロダクトアウトしたときとのギャップを減らすことが出来ます。

    そんな便利なレンダー機能ですが、機能実体はFreeCADの中にはなく、外部レンダーを呼び出しているのが実情です。

    レンダーソフトはAppleSeed、LuxCore、PovRayなどがありますが、GPUを使ってレンダリングするソフトは限られています。
    AppleseedはGUIを使った操作で多機能ですが、CPUレンダリングなので描画が遅いです。
    困りました。速いソフトが使いたい。

    そこで登場するのがCycles。
    Cyclesは、かのマルチメディアソフトBlenderに搭載されているパストレースレンダーであり、GPUを使った高速トレースが魅力のレンダーです。
    ありがたいことにFreeCADはCyclesレンダーをサポートしているのです。

    ですが、現時点(25年5月)ではCycles単体の配布は行われていないため、自前でコンパイルする必要があります。

    コンパイル方法は公式に従って進めていきます。
    https://projects.blender.org/blender/cycles/src/branch/main/BUILDING.md

    では下準備から。
    必要なのはGit、Cmake、Python3です。
    git   : https://gitforwindows.org/ or https://github.com/apps/desktop
    cmake : https://cmake.org/download/
    python3 : https://www.python.org/downloads/

    gitはGithubDesktopから導入するのが楽ですね。
    先だってアカウントを取得しておきましょう。

    cmakeは環境に合ったものをDL&インストールすればOKです。
    インストール時に ”Add Cmake to the System Path ~~” にチェックを入れることを忘れないようにしましょう。
    Python3もサイトで最新をDL&インストールすればOKです。
    もちろんインストール時に ”Add Python 3.xx to PATH” にチェックを入れましょう。

    全部インストールしたら、コマンドプロンプトでパスが通っているか確認します。
    git、cmake、python3をそれぞれ実行して、”操作可能なプログラムまたはバッチ ファイルとして認識されていません”とでないことを確認すればOKです。
    ※Python3のデフォルトエイリアスはpython3ですが、以前別のバージョンのPythonがインストールされてた場合望ましく呼び出されない場合があります。
    ”Python エイリアス”で調べれば解法が出てくるので自分でどうにかしてください。


    さて、下準備が終わったのでCyclesをコンパイルしていきます。

    1.Gitファイルを置くフォルダを任意の場所に作成(フォルダ名も何でもいい)
    2.作成したファイルでプロンプトを起動し、下記コマンドを実行
      git clone https://projects.blender.org/blender/cycles.git
    3.cyclesフォルダができるのでチェンジディレクトリしてmakeする
      cd cycles
      make update
      make
    4.makeを実行するとinstallフォルダができるので、その中にある”cycles.exe”のフルパスをコピーしておく


    これでCyclesがコンパイルできました。
    次はFreeCADに適用していきましょう。


    FreeCADのRenderワークベンチを選んで、Render Settingを開きます。
    Cyclesの設定があるので、Executableに先ほどコピーしたcycles.exeのパスを入れます。
    Testボタンを押してエラーが出なければOKです。

    ここで重要なのがRender parametersです。
    ここに --device CUDA を指定します。
    このパラメータを指定することでGPUレンダリングが有効になります。


    左はGPUあり、右はGPUなしのRender実行時のパフォーマンスです。
    いい感じですね!
    ちゃんとCPU使用率が下がってGPU使用率が上がっています。

    Renderは頻繁に使う機能ではありませんが、使うとなった時に遅いとイライラしてしまいます。
    なるべく高速化できるところは高速化しておきましょう。


    閲覧ありがとうございました。



  • ウェブサイトにあげる3Dデータをスクレイピングから守るには?

    ウェブサイトにあげる3Dデータをスクレイピングから守るには?

    やった!3Dデータを作ったぞ!
    3Dプリントもできるし、モデルデータ販売サイトにも登録した!
    よーし、サイトに公開してみんなに周知しよう!

    ………これ、配置しただけだとスクレイピングでデータ窃取できちゃうな…。



    サイバー空間にアップしたいけど上げれない、そんなジレンマがあるかと思います。
    今回は本サイトでも採用している回避方法を紹介します。


    まずやりたいこととその問題を洗い出していきましょう。


    <やりたいこと>
    ・3Dモデルをサイト上に公開したい。
    ・回転させたり拡大縮小させたりしてぐりぐり操作したい。
    ・パスは隠してDLできないようにしたい。
    ・サイトへの導入はシンプルに、使いまわせるようにしたい。

    <問題>
    ・3Dビューアを作るのはコストがかさむ。
     → model-viewerを使う。
    ・Wordpressのメディアライブラリでは3Dモデルを扱えない。
    ・クライアント側(model-viewer)にモデルのパスを渡す形では、DLできてしまうしディレクトリトラバーサルされる危険性もある。


    クライアント側(JavascriptとCSS)だけではちょっと解決が難しいかもしれません。
    サーバ側のことはあんまりよくわからないので、こういう時はGPTちゃんに聞くことにします。

    GPTちゃん、頼りになるわぁ。

    ユーザ側に表示されている以上完全に防御することは不可能。
    まぁそうだよね。
    とはいえ、し難くすることはできそうな感じです。

    アクセス制限やログイン制にするのは本末転倒なので、glTFストリーミングする方向で考えていきましょう。
    ただし、分割処理や暗号複合、アクセス制限などはコスト高めなので、とりあえず”サーバから生データでモデルをもらう”という形にしておきましょう。


    さて、全体の構成を考えていきましょう。



    構成はいたってシンプル。
    model-viewerはWordpressのカスタムURLで記述、データ問い合わせとViewerへのセットを担当するJS、サーバ側のデータ処理を担当するPHPで構成されています。

    1.model-viewerが表示されるのをトリガーに、PHPにモデル名をFetch。
    2.PHPでモデル名を検索して、見つかればモデルの生データをSend。
    3.データが返ってきたら生データにURLを付与してmodel-viewerのsrcに流し込む。

    というシーケンスです。

    では カスタムHTML ⇒ JS ⇒ PHP の流れで見ていきましょう。
    <script  type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
    <model-viewer class="mviewer" name="modelname" loading="lazy" type="model/gltf-binary" exposure="0.6"></model-viewer>
    document.addEventListener("DOMContentLoaded", () => {
        var mvlist = document.querySelectorAll(".mviewer");
        if(mvlist){
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if(entry.isIntersecting){
                        loadmodel(entry.target);
                        observer.unobserve(entry.target);
                    }
                })
            })
            mvlist.forEach(mv => {observer.observe(mv);});
        }
    });
    
    function loadmodel(target){
        const fileName = target.getAttribute("name") + ".glb"; 
        const url = `/wp/response.php`;
    
        fetch(url, {
            method: "POST",
            headers: { 'Content-Type': 'model/gltf-binary' }, 
            body : fileName
        })
        .then(res => {
            if (!res.ok) throw new Error("取得失敗" + url);
            return res.blob();
        })
        .then(blob => {
            const objectURL = URL.createObjectURL(blob);
            target.src = objectURL;
        })
        .catch(err => console.error("モデル読み込みエラー: ", err + url));
    }
    <?php
    if (class_exists( 'loadmodel' ) ) {
        error_log("double exist ");
    	return;
    }
    
    $filename = file_get_contents('php://input');
    if (!preg_match('/^[a-zA-Z0-9_\-]+\.glb$/', $filename)) {
        error_log("loadmodel not glb : " . $filename);
        http_response_code(400);
        exit("Invalid file name");
    }
    
    $baseDir = __DIR__ . '/wp-content/uploads/model/';
    $filePath = $baseDir . $filename;
    
    if (!$filePath || strpos($filePath, realpath($baseDir)) !== 0) {
        error_log("loadmodel access denied : " . $filePath);
        http_response_code(403);
        exit("Access denied");
    }
    
    if (!file_exists($filePath)) {
        error_log("loadmodel not found : " . $filePath);
        http_response_code(404);
        exit("File not found");
    }
    
    header('Content-Type: model/gltf-binary');
    header('Content-Disposition: inline; filename="'.$filename.'"');
    header('Content-Length:' . filesize($filePath));
    readfile($filePath);
    exit;
    ?>

    JSでは、intersectingObserverでmodelviewerの表示を検知しFetchを実行します。
    Fetchでは指定したPHP(response.php)にファイル名を投げて処理を依頼。
    レスポンスが返ってきたら中身をチェックして、問題なければ中身にURLを付与してModelViewerのSrcに渡します。

    なお、表示されるたびに実行されても無駄なので一回実行したらObserve対象から外しています。


    PHPはかなりシンプルで、Fetchでもらったファイル拡張子のチェック・パスのチェック・レスポンスヘッダの記述・生データの取り出しという流れです。

    注意点として、PHPはhttpから始まるURLは処理できません。
    パスを渡すときはローカルパスで指定するようにしましょう。
    PHPは全く触ったことがなかったので、ここでかなり躓いてしまいました…。
    なぜかPHPの純正ログも出ないしWordpressのログも使えないしよー……チクショー…。


    重要なのはcontent-typeにmodel/gltf-binaryを指定すること。
    Fetchで指定するかレスポンスで指定するか、どちらかでいいと思います。たぶん。
    今回は念のため両方とも書いてます。


    いい感じですね!
    DevToolのインスペクターにはURLも書かれていませんし、PHPからの応答も生データなのでデータの窃取もしにくくなっています。

    場合によってはサーバがglb,gltfに対応してない場合があります。
    その場合は.htaccessにてMIMEタイプの追加をしてみるといいかもしれません。
    <IfModule mime_module>
    AddType model/gltf-binary .glb
    </IfModule>

    この仕組みは、Content-typeを変えれば3Dデータだけでなく画像や動画にも応用できそうですね。
    良ければご活用ください。