Adobe XD の ドキュメント構造は scenegraph.root をルートノードとした木構造として表現されています。
これらのノードをスクリプトから操作するには、この木構造をたどる必要があります。
いちいちたどるのは面倒なので、木構造からリストに変換することを考えることにします。
たとえば、以下のように list 変数を traverse 再帰関数に含める形で実装することもできるのですが:
const traverse = (node, list)=>{
if( node.children.length==0 ){
return;
}
list.push(node);
node.children.forEach( (childNode)=> {
traverse(childNode, list);
});
};
const scenegraph = require("scenegraph");
const rootNode = scenegraph.root;
traverse( rootNode, list );
list 変数を使わないで、 const nodeList = toNodeList(scenegraph.root);
とするだけでリストに変換できるような toNodeList 関数を書いてみようと思います。
最初に Adobe XD なしでコードがテストできるように、自前の SceneNode クラスをつくります。
class SceneNode {
constructor(name, depth){
this.name = name;
this.depth = depth;
this.children = [];
}
add(childNode){
this.children.push(childNode);
}
toString(){
const indent=' ';
return [...Array(this.depth).keys()].reduce( (a,b)=> a + indent, '' ) + this.name
}
}
次にテスト用の木構造をつくります:
const rootNode = new SceneNode('root', 0);
const childNode1 = new SceneNode('child1', 1);
const childNode11 = new SceneNode('child11', 2);
const childNode12 = new SceneNode('child12', 2);
const childNode2 = new SceneNode('child2', 1);
const childNode21 = new SceneNode('child21', 2);
const childNode22 = new SceneNode('child22', 2);
const childNode3 = new SceneNode('child3', 1);
const childNode31 = new SceneNode('child31', 2);
const childNode32 = new SceneNode('child32', 2);
rootNode.add(childNode1);
childNode1.add(childNode11);
childNode1.add(childNode12);
rootNode.add(childNode2);
childNode2.add(childNode21);
childNode2.add(childNode22);
rootNode.add(childNode3);
childNode3.add(childNode31);
childNode3.add(childNode32);
これをまずは、traverse 関数でテストしてみます。
const traverse = (node, list)=>{
if( node.children.length==0 ){
return;
}
list.push(node);
node.children.forEach( (childNode)=> {
traverse(childNode, list);
});
};
const list = [];
traverse( rootNode, list );
list.forEach( (node)=> {
console.log(node.toString());
});
root
child1
child11
child12
child2
child21
child22
child3
child31
child32
うまくいきました。
今度は list 変数を使わない toNodeList 関数を実装します。
const toNodeList = (node)=> {
if( node.children.length==0 ){
return [node];
}
return node.children.reduce( (a,b)=> a.concat( toNodeList(b) ), [node]);
};
reduce の部分で初期値
[node]
を与える場所が最後になっているので、とても読みづらいですね。致し方ない。
これで以下のように使う側はスッキリ書くことができます。
toNodeList(rootNode).forEach( (note)=> { console.log(node.toString()); } );
以上から、たとえば Adobe XD で scenegraph.root (シーングラフ) から 画像の入った SceneNode だけを抽出するには:
const scenegraph = require("scenegraph");
const { ImageFill } = require("scenegraph");
const imageNodeFilter = (node)=> (node.fill && node.fill instanceof ImageFill);
const rootNode = scenegraph.root;
toNodeList( rootNode ).filter( imageNodeFilter ).forEach( (node)=>{
console.log(node);
});
これでうまくいくはずです。
Adobe XD は InDesign や Photoshop の ExtendScript と違い 最新の JavaScript仕様で plugin を書くことができるのでよい。 ExtendScript が ES6 で記述できる日は来るのであろうか? それとも、ある日突然使えなくなるとか... ESTK はすでに見捨てられたし。