この文書
メジャーなCADソフトである,AutoCADの自動化を考えている人向けです。
AutoCADのヘルプを見ると,右方に リソース > 開発者用ドキュメント というリンクがあります。リンク先にはAutoLISP,Managed .NET,といったものが並んでいますが,JavaScriptもあります。ん,これならVisual Studioを用意して,ソースを書いて,build,なんてことをしなくても,コードを書けば動く?というわけで触ってみました。結論,できることはコマンドの自動化程度で,大したことはできません。手を出さない方がいいと思う,です。
筆者
筆者はJavaScriptもHTMLもほんのちょっとしかやったことがありません。「JavaScript」で検索して出てくるコードを見ると,JavaScriptでは名前のない関数を「その場に」書いてしまうのが流儀のようですが,ここに書いてあるコードはC言語風です。
チュートリアル
まずはチュートリアルをさっと眺めてみます。To Create a Rectangleがありますが,APIで長方形を作成するわけではなく,RECTANGコマンドの発行です。その先を見ても大したものはなさそうです。つまり,できることはせいぜいコマンド発行の自動化程度で,例えば,既存のポリラインの頂点座標を読み込んで何かを作成する,といったことはできなそうです。では,順に。
To Create a Custom Command
JavaScriptをAutoCADにロードするにはwebloadコマンドです。このコマンドは全くのCUIであるらしく,JavaScriptのファイル名をフルパスで手で打ち込まないといけないようです。これは面倒なので,筆者はファイルをD:\Jディレクトリに置いています。以下の文章ではD:\Jを各自適当に読み替えてください。コードをmyerase.jsという名前で保存します。内容は
_erase _all
を実行するMYERASEコマンドを登録するものです。コードは「信頼できる場所」になければならないので,オプションの[ファイル]タブの[信頼できる場所]にD:\Jを追加します。webloadコマンドを発行し,[ロード]オプションで「URLを入力」というプロンプトに対し,d:\j\myerase.js と入力します。URLとプロンプトにはありますが,これでOKです。以降,MYERASEコマンドですべてが消えます。
ここまでやると,わかった,チュートリアルなんかもう要らない,リファレンスを見ながらコードを書いてwebloadで実行すればいいのだな,という筆者のような方がいるかもしれません。困るのは思ったように動作しないときです。エラーメッセージがどこにも出ないように見えます。webloadに対するファイル名の指定を間違ったときにも何もメッセージが出ませんので,ファイル名の指定を間違ったのか,実行したらエラーになったのかすらわからないという困った環境です。チュートリアルには大した時間はかかりませんので,一通り見るべきです。
To Set a System Variable Value
thenという2つのfunctionを引数とするmethodはPromise Patternというものです。これが何だかなあ,感があるものです。AutoCAD API独自のものではなく,JavaScriptの共通なもののようです。
To Create a Rectangle
executeCommandにおいて,firstPointとsecondPointの間に空文字列が入っていますが,これはなくても動作しました。
To Zoom in to an AutoCAD Entity
callback functionであるonCompleteで引数のステータスをresultObj.statusで調べていますが,Reference GuideのAcad.PromptEntityResultを見てもstatusは見当たりません。チュートリアルの方が正しいようです。
function ext の中に arg.minPoint3d とありますが,Reference Guideを見ると,Acad.Bounds3dのPropertiesはminPt3dです。が,minPt3dと書き換えたら動作しなくなりました。チュートリアルの方が正しいようです。
function ext の中でminPoint3dをわざわざ作っていますが,Acad.Bounds3d.minPoint3dそのものですから,
Acad.Editor.CurrentViewport.zoomExtents(arg.minPoint3d, arg.maxPoint3d, true);
でOKです。
To Capture an Image Preview
paletteはAutoCADでエンティティの[オブジェクト プロパティ管理]で出てくるものです。このチュートリアルではpaletteについて何も解説がなく,いきなり使用されていますが,paletteはHTMLとJavaScriptを処理できる環境です。他に,Forms.UserControlも貼り付けられます。
JavaScriptをpaletteで実行する大きなメリットはエラーがわかることです。例えば,capture.jsでHTMLのパスに間違いがあるとpalette内に「ファイルがみつかりません」と出ます。capture.html内のJavaScriptコードに間違いがあるときには,paletteで右ボタンメニューを出し,[開発者ツールで調査する]をクリックすると,エラーメッセージを見ることができます。
結論,HTMLを必要としない,JavaScriptだけのプログラムについては,開発中はpaletteの下で実行し,完成したらwebloadで実行してもよい,ということになります。チュートリアルではaddPaletteの第2引数にHTMLのファイル名を与えていますが,JavaScriptの場合はここにJavaScriptのファイル名を書けばよいです。
To Implement an AutoCAD Palette
このセクション,省略
Hello
まずはHelloということになっています。が,AutoCADプログラミングではテキスト出力は鬼門です。
エディター出力
まずはエディター出力です。Reference GuideのEditorの下でそれらしいものは…あれ,見当たりません。
console
チュートリアルのTo Zoom in to an AutoCAD Entityにはconsole.log()というものがありました。
console.log("\nHello, JavaScript");
チュートリアルのTo Capture an Image PreviewではpaletteでHTMLを表示していましたが,直接JavaScriptを動作させることもできます。
Acad.Application.addPalette("Sample Palette", "c:/AutoCAD/capture.html");
の第2引数を "D:/J/console.js" として,paletteの下で実行し,[開発者ツールで調査する]で見ると,結果は開発者ツールのConsoleに出ていました。
alert
チュートリアルのTo Set a System Variable Valueではalert()が使用されています。
alert("Hello, JavaScript");
これはもちろん動作します。これを一般の情報出力に使っていいものでしょうか。筆者はalertという名前のものを一般の情報出力に使用するのはなんだかな,という気がします。
showTaskDialog
Reference Guideで情報出力らしいものを探すとAcad.TaskDialog.showTaskDialogです。
Acad.TaskDialog.showTaskDialog(
"strWindowTitle", "strMainInstruction", "strContentText",
Acad.TaskDialogButton.kButtonOk);
コマンド実行
円を描いてみます。
Acad.Editor.executeCommand("._CIRCLE 30,20 10");
同様に,線分を描いてみます。
Acad.Editor.executeCommand("._LINE 30,20 60,30");
が,これだと,次の点を求めるプロンプトが出た状態で終わってしまいます。線分を1本描いてそれで終わりにするなら,次の点の入力をキャンセルする空白を入れます。
Acad.Editor.executeCommand("._LINE 30,20 60,30 ");
あるいは,これでもいいです。
Acad.Editor.executeCommand("._LINE 30,20 60,30", "");
エディター入力のstatus
ユーザーはエディター入力のプロンプトに対し,プログラムが期待する入力をするとは限りません。プログラムの実行を打ち切ろうとEscを押すかもしれません。チュートリアルを見ていくと,To Create a Rectangleではこのような正常ではない入力を考慮していません。次のTo Zoom in to an AutoCAD Entityでstatusを調べています。そこで,まず,エディター入力のstatusの種類を知っておく必要があります。そのためのプログラムが
function promptStatus()
{
const optionsE = new Acad.PromptEntityOptions("Entity");
Acad.Editor.getEntity(optionsE).then(success, error);
}
function success(resultE)
{
let msg;
switch(resultE.status) {
case Acad.PromptStatus.OK:
msg = "OK";
break;
case Acad.PromptStatus.None:
msg = "None";
break;
case Acad.PromptStatus.Cancel:
msg = "Cancel";
break;
default:
msg = "Status: " + String(resultE.status);
}
alert(msg);
}
function error()
{
alert("Error");
}
promptStatus();
です。ユーザーがEscを押すとCancel,Enterやスペースを押すとNoneになります。CancelもNoneもPromise Patternのsuccess側です。
Promise Patternは非同期
Promise.jsはユーザーに文字列を入力させ,入力文字列をalertで表示するものです。
function main()
{
const optionsS = new Acad.PromptStringOptions("\nString");
optionsS.allowSpaces = true;
Acad.Editor.getString(optionsS).then(onString, err);
//alert("End of program");
}
function onString(erS)
{
if (erS.status == Acad.PromptStatus.OK) {
alert(erS.stringResult);
}
}
function err()
{
alert("Error");
}
main();
main()内にはコメントアウトされた文があります。Promise Patternの後ろにありますので,この文を生かすと,ユーザーから見ると,入力文字列のalertのOKをクリックした後,「End of program」のalertが出るのでしょうか。それがそうはなりません。「End of program」のalertはエディター入力のプロンプトと同時に出ます。Promise Patternの処理は「非同期」です。関数に関して,同期/非同期とは関数が関数の中身の終了を待つ/待たないです。実行はPromise Patternを一瞬で通り過ぎ,「End of program」のalertまで進みます。
コードの再利用
コードの再利用のためには動的importというものを使用するのが安直のようです。Hello.jsをモジュール化したものです。
export function hello()
{
Acad.TaskDialog.showTaskDialog(
"strWindowTitle", "strMainInstruction", "strContentText",
Acad.TaskDialogButton.kButtonOk);
}
Hello.jsのモジュール使用版です。
// import version
function myHello()
{
import('./HelloM.js').then(onImport, nothing);
}
function onImport(module)
{
module.hello();
}
function nothing()
{
}
myHello();
JavaScriptを実行する3つの環境
サンプルプログラムとして,サイズと中心点をエディター入力して星を描くプログラムを作成します。
StarM.jsは星を描くモジュールです。
export function star(size, center)
{
const dt = (4.0 / 5.0) * Math.PI;
const t0 = -1.5 * Math.PI;
Acad.Editor.executeCommand("._PLINE")
for(let i=0; i<5; i++)
{
const t = t0 + i * dt;
const x = center.x + size * Math.cos(t);
const y = center.y + size * Math.sin(t);
Acad.Editor.executeCommand(x + "," + y);
}
Acad.Editor.executeCommand("C");
}
Star2M.jsは目的の動作をするモジュールです。
let size, center;
export function star2()
{
const optionsD = new Acad.PromptDoubleOptions("\nSize");
Acad.Editor.getDouble(optionsD).then(onSize, nothing);
}
function onSize(resultD)
{
if (resultD.status != Acad.PromptStatus.OK)
return;
size = resultD.value;
const optionsP = new Acad.PromptPointOptions("\nCenter");
Acad.Editor.getPoint(optionsP).then(onCenter, nothing);
}
function onCenter(resultP)
{
if (resultP.status != Acad.PromptStatus.OK)
return;
center = resultP.value;
import('./StarM.js').then(onImport, nothing);
}
function onImport(module)
{
module.star(size, center);
}
function nothing()
{
}
Star2.jsはStar2M.jsを使用して目的の動作をするプログラムです。
function myStar2()
{
import('./Star2M.js').then(onImport, nothing);
}
function onImport(module)
{
module.star2();
}
function nothing()
{
}
myStar2();
これをwebloadコマンドで動かす,paletteの下で実行する,の2通りの実行はもちろん動作します。ただし,メッセージを日本語にする方法はわかりません。
JavaScriptを動作させる第3の環境として,paletteの下で動作するHTMLから呼び出される,というものがあります。ボタンをクリックしたらJavaScriptの関数を実行するStartX.htmlを用意しました。
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="Star2H.js"></script>
</head>
<body>
<button type="button" onclick="myStar2()">星を描く</button>
</body>
</html>
Star2H.jsはStar2.jsから最終行の myStar2(); を削除したものです。が,動作しません。開発者ツールで調査すると,
Uncaught ReferenceError: Acad is not defined
と出ます。え,paletteにJavaScriptを与えると動作するのにHTML経由だと動作しない?!
ここで,動作しているチュートリアルのTo Capture an Image Previewを見ると,
<script type="text/javascript" src="https://df-prod.autocad360.com/jsapi/v4/Autodesk.AutoCAD.js"></script>
という行があります。名前がAutodesk.AutoCAD.jsですので,ここにAcadのコードが入っているようです。というわけで,動作するHTMLはこうです。
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://df-prod.autocad360.com/jsapi/v4/Autodesk.AutoCAD.js"></script>
<script type="text/javascript" src="Star2H.js"></script>
</head>
<body>
<button type="button" onclick="myStar2()">星を描く</button>
</body>
</html>
パラメーターがあるHTML
HTMLの下で実行できるようになったので,チュートリアルのTo Implement an AutoCAD Paletteを見ながら,HTMLからパラメーターを渡してみます。プログラムはまた星を描くもので,サイズをHTMLから取得します。ユーザーがサイズに与えた文字列が数値として妥当かのチェックはしておりません。
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://df-prod.autocad360.com/jsapi/v4/Autodesk.AutoCAD.js"></script>
<script type="text/javascript" src="Star1H.js"></script>
</head>
<body>
<font color="white">
<h3>星を描く</h3>
<table border="0">
<tr>
<td>
<label for="radius_id">サイズ:</label>
<input type="text" name="size" id="size_id"/>
</td>
</tr>
<tr>
<td>
<button type="button" onclick="myStar1()">星を描く</button>
</td>
</tr>
</table>
</font>
</body>
</html>
let size, center;
function myStar1()
{
size = document.getElementById('size_id').value;
if (size == "")
alert("Empty input");
else if (size <= 0)
alert("size <= 0");
else {
const optionsP = new Acad.PromptPointOptions("\nCenter");
Acad.Editor.getPoint(optionsP).then(onCenter, nothing);
}
}
function onCenter(resultP)
{
if (resultP.status != Acad.PromptStatus.OK)
return;
center = resultP.value;
import('./StarM.js').then(onImport, nothing);
}
function onImport(module)
{
module.star(size, center);
}
function nothing()
{
}
書いてみる
だいたい様子がわかったので,いくつかプログラムを書いてみます。
アイテムのリストを取得する
Acad.DataItemCollectionManager.getKnownCollection()で画層のリストを取得してみます。
function dataItem()
{
Acad.DataItemCollectionManager.getKnownCollection("LAYER").then(
successKC, error);
}
function successKC(collec)
{
alert(collec.getName());
alert(collec.colName);
alert(String(collec.dataItems.length));
const ni = collec.dataItems.length;
const n = ni < 30 ? ni : 30;
let s = "";
for (let i=0; i<n; i++) {
s += collec.dataItems[i].name + "\n";
}
Acad.TaskDialog.showTaskDialog(
"Layer", "Layer", s, Acad.TaskDialogButton.kButtonOk);
}
function error()
{
alert("Error");
}
dataItem();
エンティティのプロパティを取得する
Acad.DataItem.getProperties()でエンティティのプロパティを取得してみます。
function properties()
{
const optionsE = new Acad.PromptEntityOptions("Entity");
Acad.Editor.getEntity(optionsE).then(successGE, error);
}
function successGE(resultE)
{
if (resultE.status != Acad.PromptStatus.OK)
return;
const e = new Acad.DBEntity(resultE.objectId);
e.getProperties().then(successGP, error);
}
function successGP(map)
{
let s = "";
for (const pair of Object.entries(map)) {
s += pair[0] + " | " + pair[1] + "\n";
}
Acad.TaskDialog.showTaskDialog(
"Properties", "Properties", s,
Acad.TaskDialogButton.kButtonOk);
}
function error()
{
alert("Error");
}
properties();
書けない
ユーザーにサイズと座標を入力させ,星を描くというプログラムは書けました。サイズを入力させた後,「座標を入力させ,星を描く」を繰り返す,というプログラムが書けません。筆者の手には余ります。