Design Automation API for 3ds Max:MAXScript レンダリング フロー で生成されるレンダリング画像は、シーンファイル Table Fan.max の第 4 ビューポートになっていました。
今回は、Forge Viewer 上で表示しているビュー(カメラ)を 3ds Max シーンに反映して、レンダリング画像を得る方法を考察してみます。

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 )

Web ブラウザのズーム設定(Web ページの表示スケール)やウィンドウ サイズ(カンバスのサイズ)だけでなく、Windows 側のデスクトップ表示スケールの要素もあり、条件に合わせて表示状態を動的に変化させてしまう Forge Viewer との表示の一致はチャレンジな部分があります。
ただし、前提条件を固定化出来るのであれば、このシナリオを別のシーンで利用することも可能かと思います。

By Toshiaki Isezaki

You must be logged in to post a comment.