Design Automation API for 3ds Max:Forge Viewer ビューの反映

Design Automation API for 3ds Max:MAXScript レンダリング フロー で生成されるレンダリング画像は、シーンファイル Table Fan.max の第 4 ビューポートになっていました。

今回は、Forge Viewer 上で表示しているビュー(カメラ)を 3ds Max シーンに反映して、レンダリング画像を得る方法を考察してみます。

画像左側にはForge Viewerに表示されている扇風機の3Dモデルがあり、右側にはそのモデルを基にレンダリングされた画像が示されています。

Forge Viewer:シードファイルのビュー復元 で触れたとおり、Forge では,、シードファイル(元のデザインファイルが持つビューを Forge Viewer で表示するための情報を得る方法が用意されています。

逆に Forge Viewer からシードファイルへのビューの反映については、シードファイルへの書き込みをしない「Viewer」としての位置づけから、特に一貫した方法が用意されていない状況です。ただ、Forge ポータルへの過去の問い合わせ実績から、一定の需要があることも知られています。

英語のブログ記事になってしまいますが、Map Forge Viewer Camera back to Revit や Map Forge Viewer Camera back to Navisworks では、Forge Viewer:State API でビューを更新 でもご紹介した State API からのビュー情報を利用する方法が説明されています。

3ds Max では、少し前の Autodesk University クラス、3ds Max Design Automation: Add Beautiful Renders to Your Web Site で、少し違った方法が紹介されています。いずれも、あらゆるケースで試行したものではありませんので、ここでご紹介する内容も、調整が必要になる可能性があります。その点は事前にご了承ください。

前述の Autodesk University クラスの手法では、State API ではなく、次のようにビュー情報を得ています。

    getCameraCoordinates() {
        let cam = this.viewer.getCamera()
        let matrix = cam.matrixWorld
        let position = new THREE.Vector3();
        let rotation = new THREE.Quaternion();
        let scale = new THREE.Vector3();
        matrix.decompose(position, rotation, scale)

        //TODO: replace magic numbers with derived ones
        let offset = new THREE.Vector3(60.5, -24.5, 60.5)

        position.addVectors(position, offset);
        let aspect = cam.aspect;

        //TODO: derive the 1.67 multiplicator
        let new_fov = cam.fov*1.67;

        return {
            position: position.toArray(),
            rotation: rotation.toArray(),
            fov: new_fov,
            renderingSize: [this.viewer.canvas.width, this.viewer.canvas.height]
        }
    }

青字で記した部分は、シーン毎に調整が必要な値であることを意味しています。

Forge Viewer は、シードファイルから得た 3D モデルを、カンバス領域の中心に表示するようになっています。この際にシードファイル時の位置との差を補正しているのが、Global Offset(グローバル オフセット)と呼ばれる位置合わせ用の(X、Y、Z)値です。上記コードの最初の // TO DO には、この Global Offset を当てはめることが出来ます。Global Offset については、過去に Forge Viewer シーンへの複数モデルの表示(一部改定・追記) のブログ記事でも触れたことがあります。

2 つめの // TO DO には、Web ブラウザで表示している Forge Viewer 上の 3D モデルと、レンダリングした画像を、オーバーラップした領域に同じ大きさで表示させる、FOV(Field Of View、視野角)の値に乗算させる画像表示用の任意の係数が含まれます。

下記は、前述の getCameraCoordinates() に Global Offset と表示係数を設定し、JSON パラメータしてWeb サーバーへ投げかける箇所の抜粋です。

    function getCameraCoordinates() {
        let cam = _viewer.getCamera();
        let matrix = cam.matrixWorld
        let position = new THREE.Vector3();
        let rotation = new THREE.Quaternion();
        let scale = new THREE.Vector3();
        matrix.decompose(position, rotation, scale);

        //TODO: replace magic numbers with derived ones
        let element = JSON.stringify(_viewer.model.getData().globalOffset);
        let offset = new THREE.Vector3(JSON.parse(element).x, JSON.parse(element).y, JSON.parse(element).z);

        position.addVectors(position, offset);
        let aspect = cam.aspect;

        //TODO: derive the 1.67 multiplicator
        let new_fov = cam.fov * 2.4; // Table Fan.max

        return {
            position: position.toArray(),
            rotation: rotation.toArray(),
            fov: new_fov,
            renderingSize: [_viewer.canvas.width, _viewer.canvas.height]
        }
    }

    // Render button
    $(document).on("click", "[id^='start']", function () {
       
        var vp = getCameraCoordinates();
        var params = '&color=' + _colorIndex +
            '&quantity=' + $("#quantity").val() +
            '&leaf=' + _leafFlag +
            '&width=' + JSON.parse(JSON.stringify(vp)).renderingSize[0] +
            '&height=' + JSON.parse(JSON.stringify(vp)).renderingSize[1] +
            '&fov=' + JSON.parse(JSON.stringify(vp)).fov +
            '&position=' + JSON.parse(JSON.stringify(vp)).position +
            '&rotation=' + JSON.parse(JSON.stringify(vp)).rotation;
       
        var uri = '/api/process/' + params;
        $.ajax({
            url: uri,
            type: 'POST',
            contentType: 'text/plain'
        }).done(function (res) {
            …

このコードから渡される JSON パラメータは次のようになります。太字部分が前回の JSON パラメータから拡張された部分です。

    {
        "color": "3",
        "quantity": "1",
        "leaf": "true",
        "width": "1920",
        "height": "942",
        "fov": "54.28767677618733",
        "position": "-241.98164558410645,-447.09261417388916,179.48493644408882",
        "rotation": "0.6990782139317431,-0.16547652955068382,-0.1602335643066114,0.6769286269720552"
    }

カンバスの幅と高さをレンダリング画像のサイズとして渡すのは、Forge Viewer のカンバスに重ね合わせて表示するこをを意図しているためです。

次のコードは、Web サーバー実装でルーティングに用意した /process endpoint から呼び出した WorkItem が処理する MAXScript です。太字部分が前回の MAXScript から拡張された部分です。レンダリングするビューを、JSON パラメータを元に作成したカメラに設定していることがわかります。

    /* JSON 読み込み関数
     参照:https://forums.cgsociety.org/t/json-and-maxscript/1552038/11
    */
    fn getJsonFileAsString filePath=(
        local  jsonString = ""
        fs=openFile filePath
        while not eof fs do(
            jsonString += readchar fs
        )
        close fs
        return jsonString
    )

    /* JSON 読み込み */
    paramsFilePath = "params.json"
    jsonString = getJsonFileAsString paramsFilePath
    myJObject = dotNetObject "Newtonsoft.Json.Linq.JObject"
    myJObject = myJObject.parse jsonString

    /* パラメータ取得 */
    global col = myJObject.item["color"].value as integer
    global leaf = myJObject.item["leaf"].value as booleanClass
    global width = myJObject.item["width"].value as integer
    global height = myJObject.item["height"].value as integer
    global fov = myJObject.item["fov"].value as double
    global temp = myJObject.item["position"].value
    global pos = FilterString temp ","
    temp = myJObject.item["rotation"].value
    global rot = FilterString temp ","

    /* ColorX 画層オフ */
    global lay
    for i = 1 to 6 do
    (
        if col != i then (
            lay = LayerManager.getLayerFromName ( "Color" + i as string )
            lay.on = false
        )
    )

    /* Leaf 画層オン/オフ */
    lay = LayerManager.getLayerFromName ( "Leaf" )
    lay.on = leaf

    /* ART レンダラー設定 */
    global art = ART_Renderer()
    art.quality_db = 20
    art.render_method = 1
    art.anti_aliasing_filter_diameter = 2.0
    art.enable_noise_filter = true
    art.noise_filter_strength = 1
    renderers.current = art

    /* カメラ設定 */
    x = rot[1] as float
    y = rot[2] as float
    z = rot[3] as float
    w = rot[4] as float
    global cam = freecamera rotation:(quat x y z w) position:[pos[1] as float, pos[2] as float, pos[3] as float] fov:fov

    /* レンダリング & 生成画像保存 */
    fname = sysInfo.currentdir + "/result.jpg"
    undisplay ( render camera:cam outputwidth:width outputheight:height outputfile:fname )
An animated GIF of a 3D model of a table fan in a web browser interface, showcasing color options and rendering features.

Web ブラウザのズーム設定(Web ページの表示スケール)やウィンドウ サイズ(カンバスのサイズ)だけでなく、Windows 側のデスクトップ表示スケールの要素もあり、条件に合わせて表示状態を動的に変化させてしまう Forge Viewer との表示の一致はチャレンジな部分があります。

ただし、前提条件を固定化出来るのであれば、このシナリオを別のシーンで利用することも可能かと思います。

ウェブアプリケーションでテーブルファンを表示するインターフェース。左側にはカラーパレットがあり、右側には価格情報とレンダーボタンが配置されている。

By Toshiaki Isezaki

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading