Design Automation API:WorkItem からの JSON 反映

Design Automation API を利用するアプリでは、多くの場合、Web ブラウザをフロントエンド インタフェースに使用して、エンドユーザが Design Automation API の AppBundle(アドイン)が処理すべきパラメータを指定します。

パラメータは、 JSON データとて WorkItem の実行領域にファイル(.json)に保存されるので、アドインが JSON ファイルを読み取り、成果ファイルの生成や編集などに反映することが出来ます。

場合によっては、逆に WorkItem(アドイン)が処理した JSON データの内容を、クライアントの Web ブラウザに渡したい場合があります。

例えば、過去にご紹介した次のような処理を挙げることが出来ます。

クライアント コンピュータからアップロードした図面ファイル内をパース、不要になったブロック定義を削除しつつ、削除したブロック定義に含まれていた図形数をグラフ化してレポートするような処理です。

この処理では、グラフ表示にオープン ソースの Chart.js(https://www.chartjs.org/)を使用しています。Chart.js では、グラフ化する情報を JSON で定義することになります。

例えば、次の円グラフは、後述の JavaScript コードで描画することが可能です。(‘graph’ は、HTML に定義したグラフ領域を示す <canvas> タグに付けた ID です。)

_chart_json = {
    type: $('#charttype').text() /*'pie'*/,
    data: {
        labels: ['Block A', 'Block B', 'Block C', 'Block D', 'Block E', 'Block F'],
        datasets: [
            {
            label: '# of inner element',
            data: [
                    40, 30, 20, 10, 5, 3
                ],
        
            }
        ]
    
    },
    options: {
        plugins: {
            colorschemes: {
                scheme: 'brewer.Greys7'
            
            }
        
        },
        legend: {
            position: 'right'
        
        }
    
    }
};
var canvas = document.getElementById('graph');
_chart = new Chart(canvas, _chart_json);

つまり、Design Automation API の AppBundle、ここでは C# を使った .NET API アドインは、図面をパースして、パージしたブロック定義内の要素数をカウント、動的に JSON データを生成する処理の実装が必要になります。

次の C# コードは、アドインが上記処理をする箇所の抜粋です。JSON 生成には、Newtonsoft 社がオープンソースとして公開している Json.NET パッケージ(ライブラリ)を使用しています。

   Database db = Application.DocumentManager.MdiActiveDocument.Database;
   Log("\nGot database ...");
   InputParams inputParams = JsonConvert.DeserializeObject(File.ReadAllText(".\\params.json"));
   bool boolPurge = inputParams.purge;
   bool boolPreview = inputParams.preview;
   Log("\nAddin retrieves Purge:{0}, Preview:{1}", boolPurge, boolPreview);
   try
   {
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
     ObjectIdCollection objIds = new ObjectIdCollection();
     BlockTable tbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead, false) as BlockTable;
     IEnumerator enu1 = tbl.GetEnumerator();
     while (enu1.MoveNext())
     {
      objIds.Add((ObjectId)enu1.Current);
     }
     db.Purge(objIds);
     Log("\nBlockTableRecords are listed by Purge ...");
     List strLabels = new List();
     List lValues = new List();
     List datasets = new List();

     ObjectId objId;
     BlockTableRecord rec;
     long length;
     IEnumerator enu2 = objIds.GetEnumerator();
     while (enu2.MoveNext())
     {
      objId = (ObjectId)enu2.Current;
      rec = tr.GetObject(objId, OpenMode.ForWrite, false) as BlockTableRecord;
      System.Collections.Generic.IEnumerable idCollection = rec.Cast();
      length = idCollection.Count();
      if (boolPurge)
      {
       rec.Erase();
       Log("\n {0} BlockTableRecord\t - contains {1} entities -\t was purged\n", rec.Name, length);
      }
      else
      {
       Log("\n {0} BlockTableRecord\t - contains {1} entities -\t can be purged\n", rec.Name, length);
      }
      strLabels.Add(rec.Name);
      lValues.Add(length);
     }
     tr.Commit();

     datasets.Add(new datasets());
     datasets[0].label = "# of inner element";
     datasets[0].data = lValues.ToArray();
     data data = new data();
     data.labels = strLabels.ToArray();
     data.datasets = datasets.ToArray();
     OutputParams outputParams = new OutputParams();
     outputParams.type = "pie";
     outputParams.data = data;
     outputParams.options = new JRaw("{ \"plugins\": { \"colorschemes\": { \"scheme\": \"brewer.Paired12\" } }, \"legend\": { \"position\": \"right\" } }");
     string output = JsonConvert.SerializeObject(outputParams);
     using (var file = new StreamWriter(@".\\chart.json", false, System.Text.Encoding.UTF8))
     {
      file.Write(output);
     }
     if (boolPurge)
     {
      string strName = ".\\" + Application.GetSystemVariable("DWGNAME") as string;
      Application.DocumentManager.MdiActiveDocument.Editor.Command("QSAVE");
      Log("\n{0} was saved ...", strName);
     }
    }
   }
   catch (Autodesk.AutoCAD.Runtime.Exception ex)
   {
    Log(ex.Message);
   }
   finally
   {
   }

ログ出力やヘルパークラスの定義は次の通りです。

private static void Log(string format, params object[] args) {
   Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(format, args); 
  }
  public class InputParams
  {
   public bool purge { get; set; }
   public bool preview { get; set; }
  }
  public class OutputParams
  {
   public string type { get; set; }
   public data data { get; set; }
   public JRaw options { get; set; }
  }
  public class data
  {
   public string[] labels { get; set; }
   public datasets[] datasets { get; set; }
  }
  public class datasets
  {
   public string label { get; set; }
   public long[] data { get; set; }
  }

このアドイン処理によって、WorkItem 実行時の作業領域に、グラフ化する情報が chart.json ファイルとして保存されることが分かります。

もちろん、chart.json ファイルを正しくクライアントに渡せるよう、Design Automation API 側の Activity で chart.json 用の動作を定義、WorkItem で処理するように指定することも必須です。

次の JavaScript コード抜粋は、Activity 定義時に POST activities endpoint へ渡すの JSON Body 記述です。

  // Create Activity
  var payload =
  {
   "id": DA4A_UQ_ID,
   "commandLine": ['$(engine.path)\\accoreconsole.exe /i "$(args[DWGInput].path)" /al "$(appbundles[PurgeBlock].path)" /s $(settings[script].path)'],
   "parameters": {
    "DWGInput": {
     "zip": false,
     "ondemand": false,
     "verb": "get",
     "description": "Source drawing",
     "required": true
    },
    "Params": {
     "zip": false,
     "ondemand": false,
     "verb": "get",
     "description": "Input parameters to specify behavior",
     "required": true,
     "localName": "params.json"
    },
    "DWGOutput": {
     "zip": false,
     "ondemand": false,
     "verb": "put",
     "description": "Output DWG drawing",
     "required": false,
     "localName": "purged.dwg"
    },
    "ChartOutput": {
     "zip": false,
     "ondemand": false,
     "verb": "put",
     "description": "Output Chart JSON",
     "required": true,
     "localName": "chart.json"
    }
   },
   "settings": {
    "script": {
     "value": "PurgeBlock\n"
    }
   },
   "engine": "Autodesk.AutoCAD+24_1",
   "appbundles": [DA4A_FQ_ID],
   "description": "Purge Block"
  };

同様に POST workitems endpoint に渡す WorkItem の JSON Body は次のようになります。変数 CHART_JSON には、予め、WorkItem 実行時に生成しておいた chart.json 入出力用の Signed URL が格納されている点にご注意ください。

      // Create WorkItem
      var payload =
      {
       "activityId": DA4A_FQ_ID,
       "arguments": {
        "DWGInput": {
         "url": signedURLforInput,
         "headers": {
          "Authorization": "Bearer " + credentials.access_token,
          "Content-type": "application/octet-stream"
         },
         "verb": "get"
        },
        "Params": {
         "url": "data:application/json," + paramsJSON
        },
        "DWGOutput": {
         "url": signedURLforOutput,
         "headers": {
          "Authorization": "Bearer " + credentials.access_token,
          "Content-Type": "application/octet-stream"
         },
         "verb": 'put',
         "localname": SOURCE_DWG
        },
        "ChartOutput": {
         "url": CHART_JSON,
         "headers": {
          "Authorization": "Bearer " + credentials.access_token,
          "Content-Type": "application/json"
         },
         "verb": 'put'
        },
        "onComplete": {
         "verb": "post",
         "url": "-deployed root URL-/api/oncomplete"
        }
       }
      };

今回の例では、確認の目的でクライアント側に Forge Viewer を配置しています。Model Derivative API で変換した SVF/SVF2 を Viewer 上に表示するには、viewables:read の Scope(スコープ)を持つ Access Token(アクセス トークン)を利用するのが一般的です。一方、Design Automation API の各種処理(endpoint 呼び出し)には、code:all の Scope を持つ Access Tokenが必要です。

クライアント側に code:all の Scope を持つ Access Token が渡ってしまうのは、セキュリティ上、好ましい状態ではありませんので、Forge を利用する Web サーバー(Forge アプリ)で独自にルーティングした endpoint を用意して、 code:all の Scope を持つ Access Token をクライアントから隠蔽している点にご注意ください。Forge Viewer を持つ Forge アプリでは、通常、このような実装がおこなわれています。

この例では、GET workitems/:id endpoint を使ったポーリング処理で WorkItem の完了を検出し次第、クライアントから Web サーバー上にルーティングした endpoint を呼び出し、前述の Signed URL からグラフ用に生成した JSON データを得るようになっています。

Node.js で実装した Chart.js 用の JSON 取得用 endpoint 実装は次のとおりです。

// Get Chart.json on bucket
router.get("/get-chart", function (req, res) {
    https.get(CHART_JSON, function (chartres) {
        var body = '';
        chartres.setEncoding('utf8');
        chartres.on('data', function (chunk) {
            body += chunk;
            console.log(" Chart JSON = " + body);
            res.end(body);
        });
    }).on('error', function (e) {
        console.log(e.message);
    });
});

クライアント(Web ブラウザ)からの上記 endpoint 呼び出し(AJAX)とグラフ更新は、次の JavaScript コードが担います。

// Get Chart JSON
uri = '/api/get-chart';
$.ajax({
url: uri,
type: 'GET',
contentType: 'application/json'
}).done(function (res) {
     _chart.data = JSON.parse(_chart_json).data;
_chart.options = JSON.parse(_chart_json).options;
_chart.update();
if (JSON.stringify(JSON.parse(JSON.stringify(JSON.parse(res).data)).labels) === '[]') {
console.log("!!! No blocks to be purgeable");
alert("No blocks to be purgeable");
}
}).fail(function (jqXHR, textStatus, errorThrown) {
console.log('Failed to get Chart JSON: ', jqXHR, textStatus, errorThrown);
});

※ 本記事は 2022年8月 に「Design Automation API:AppBundle からの JSON 取得」から「Design Automation API:WorkItem からの JSON 反映」に改題しました。

By Toshiaki Isezaki

Discover more from Autodesk Developer Blog

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

Continue reading