rdソース(memo.txt) with index.rb
ruby -r erb -e 'ERB.new($stdin.read).run' < memo.txt | ruby "C:\Program Files\ruby-1.8\bin\rd2.rb" -r rd/rd2html-ext-lib --native-inline --ref-extension --headline-title --head-element --with-part=head:head --with-css=hs9587.css > memo.html
全然更新していませんでした。その後別に書き始めました。こことは趣が異なり、まだまだ Ruby とプログラミング中心ですが、おいおい Illustrator系の話しにもなるでしょう。
また、Illustrator ではない話。
Windows環境で、ちょっと音を鳴らすにはどうしたら良いだろうか。 Illustrator の自動処理が終わったら、その時にちょっとチャイムを鳴らすなど。 コマンドラインや VBScriptで音を鳴らせるなら、Ruby からもそれを呼べばいいわけだ。
WIN32OLE.new('WScript.Shell').Run('sndrec32 /play /close "C:\WINNT\Media\ding.wav"', 0, true)
この辺りでは、大概 require済みと思うので、require 'win32ole' の行は省略。
スクリプトからの音声起鳴は何処でも気に掛かるようです、マイクロソフトの 「Hey, Scripting Guy!」 にそういう QandA が挙がっていました。
あとは個々の記述について
サウンドレコーダーのウィンドウが出るのを気にしなければ、 sysytem() や `バッククォート` などでの実行も良いです。
system('sndrec32 /play /close "C:\WINNT\Media\ding.wav"') `sndrec32 /play /close "C:\\WINNT\\Media\\ding.wav"`
Scripting Guy の説明にもありましたが、 wavファイルより一般の音声ファイルにサウンドレコーダーは対応していません。 ウィンドウズメディアプレイヤー mplayer, mplayer2, wmplayer 等を探しましょう。
前回 InputBox (VBS) によって入力ダイアローグを出すことを考えたが、その続き。
相変わらず Illustrator ではない話。というか Ruby の話ですらないかもしれない。
一時作成する .vbsファイルの取り扱い、やっぱり Tempfile を使おう。 そうすれば既存チェックとか File.Delete とか余計な心配しなくていいし。
require 'tempfile' default = 'qwertyuiop' vbsbasename = 'vbsinputbox' tmpvbs = Tempfile.new(vbsbasename) tmpvbs.puts %Q[WScript.Echo InputBox("message", "title", "#{default}")] tmpvbs.close(false) input = `cscript #{tmpvbs.path} //E:VBScript //nologo` tmpvbs.close(true) require 'win32ole' WIN32OLE.new('WScript.Shell').Popup(input.to_s)
前段ではバッククォート(``)で実行したが、 WshScriptExecオブジェクト で実行するとその部分はこのようになる。
exec = wsh.Exec("cscript #{tmpvbs.path} //E:VBScript //nologo") sleep 0.1 while exec.Status == 0 input = exec.StdOut.ReadAll()
同期実行のためには、その Statusプロパティ を見ていてやればいい。 結果の標準出力は StdOutプロパティ から。
これを消すのは難しい。
WshShell.Run なら Window最小化の指定は出来るが値を返す手段がない。 その IntWindowStyle にしたところで、 2、6 とか指定したとき、 cscript窓(コマンドプロンプト窓)だけじゃなくて所要の InputBoxダイアローグまで最小化してしまう。
もう諦めるしかないのか。
さて、メッセージボックスを出すことは 出来る ように なった。 Yes/No/Cancel やその他の選択の返り値も得ることが出来る。
ということで、簡単なテキストの入力ダイアローグが欲しい。 勿論、前述の vruby でも、他の GUIツールキットでも、いろいろ出来るわけだが、 そこまで真面目なことをしなくてもなんとかならないだろうか。
VBScript には InputBox というのがあるので、それでなんとか。
default = 'qwertyuiop' vbsfname = 'inputbox.vbs' raise "A file '#{vbsfname}' already exists." if FileTest.exist?(vbsfname) File.open(vbsfname, 'w'){ |f| f.puts %Q[WScript.Echo InputBox("message", "title", "#{default}")] } value = `cscript #{vbsfname}` File.delete(vbsfname) # input = value.split("\n")[3].to_s input = /\n\n/.match(value).post_match require 'win32ole' WIN32OLE.new('WScript.Shell').Popup(input)
RHG読書会::東京 Sound Stage 「鈴木さんを囲む会 2005-05-14 (土)」(二次会) の項にも同内容を書いています、ご参考まで。
前にもメッセージボックスの事は書いた。 vruby (VisualuRuby) を使うのであった。
実は、WSH(WindowsScriptHost) の WScript.Shell オブジェクトの Popup メソッドでも同じことが出来る。
require 'win32ole' wsh = WIN32OLE.new('WScript.Shell') yes_no = wsh.Popup('メッセージ', 0, 'Title', 4 + 32 + 0x40000) case yes_no when 6 wsh.Popup('Yes', 0, 'Title', 0 + 64 + 0x40000) when 7 wsh.Popup('No' , 0, 'Title', 0 + 16 + 0x40000) end # case yes_no
引数と返り値、アイコンなどについては、 既述 WConstの値と返り値、アイコンと規定値 と同じ。
この辺りでは大概において Win32OLEライブラリは require済みと思うので、 メッセージダイアログを出すだけなら、 そして WScript.Shellオブジェクトを使い捨てにするなら、 次行程度いでも良いかもしれない。
WIN32OLE.new('WScript.Shell').Popup('メッセージ', 0, 'Title', 0 + 48 + 0x40000)
更に、DoJavaScript を使って JavaScript の alertメソッドを呼ぶという手もある。 まあ、alert自体の返り値がないのは不便なので、 わざわざこちらを使うケースは少ないでしょう。
# illu は llustrator の Applicationオブジェクト本体 illu.DoJavaScript('alert("メッセージ");')
既報、 「Rubyist Magazine 0006号」 にも同じことをまとめています。ご参考まで。
このあいだ、GroupItem への集積 については書いた、 今度はそのグループアイテムを解除することを考えよう。 ただ残念なことに、 オブジェクト - メニュー の [グループ解除(U) Ctrl + Shift + G] に相当するものはない。 グループの構成要素をチマチマ一つずつ操作していかねばならない。
# grp はなんか GroupItem grp.PageItems.Count.downto(1) do |i| grp.PageItems.item(i).MoveToBeginning(grp.Layer) end
或いは、
grp.PageItems.item(1).MoveToEnd(grp.Layer) while grp.PageItems.Count>0
ただ、グループ解除後の居場所を考えると前者の方が良い様に思う
そのグループ (GroupItem) にどんなものが入っているかは分からないので、 描画オブジェクトを統一的に取り扱う PageItem のコレクションを使う。 PageItemがもっと多態的だと使い出があるのに。 まあ今は MoveTo云々があるので善しとしよう。
.item() でのインデックス指定は重なりの上からで、 変更があった場合はその場で繰り上がったり下がったりする。 WIN32OLEオブジェクトの提供する each での実行中にも番号がつけ変わってくので、 コレクション.each でこの作業を実行すると変なことになる。
奇数番目だけ消えて、偶数番目が残る。 each が別にインデックスエラーにならないのは、ちゃんと動的に Count をかぞえてる訳だが、 エラーが起こらないだけになんとも変な感じに思えたりする。
そういうわけなので、逆順に実行するか、それとも最初の要素をずっと見続けるようにする。
重なり順を保存するため、逆順なら MoveToBeginning を使う。 最初の要素だけ見るというのなら、MoveToEnd を使うことになる。
いずれにせよ、レイヤーの先頭か末尾かに移動してしまう。 他の描画オブジェクトとの関係で言えば、 重なり順のその場でグループ解除できるわけではない。
また、その際の行き先レイヤーだが、 上記では該当グループ自体の所属するレイヤーとした。 これはアクティヴレイヤー (doc.ActiveLayer) 宛てとしても良いかもしれない。
先にも少し言及したが、それぞれのコレクションでの、 添数付けは、描画オブジェクトの重なり順になっている。
一番上にあるのが .item(1)、一番下になるのが .item(…Items.Count)、 例えばレイヤーなら。
Ruby のウェブマガジン「Rubyist Magazine 0006号」 <URL:http://jp.rubyist.net/magazine/?0006> に、記事を書かせてもらいました。
よければ見てやって下さい。
しばらく更新すべき内容が無かったのですが、またいくつかの事例に出会ったので、少し案件を追加します。
その間に、 Adobe Illustrator は CS版にバージョンアップしましたが、僕は 10.0.3 の儘です。 このメモでは Illustrator 10 のまま話を続けます。
一方、Rubyは 1.8 になりました。こちらは ActiveScriptRuby を使っていて、1.8にバージョンアップしています。とはいっても、 Ruby自体に関わるような記述は余り多くないので、さほど気にする事は無いでしょう。
さらに、Illustratorの自動化という話題も世上に散見されるようになりました。 嬉しいことです。
各種グラフィックItemをグループ化して GroupItem を得る事を考えよう。
どうだろうか。これで良いようなものだが、 Illustrator自体の編集メニューの「カット(コピー)」、「ペースト」(Ctrl+X,C,V) と同じで、自分の位置を忘れてしまうのが不便だ。
スクリプトなんだから事前に Positionメソッドで対象の位置を憶えておけば良い訳だが、 それもなんか迂遠だ。
# doc はなんかIllustrator.Documentオブジェクト grp = doc.GroupItems.Add path = doc.PathItems.item(1) pos = path.Position path.Cut grp.Paste pasted_path = grp.PathItems.item(1) pasted_path.Position = pos
Illustrator編集メニューの「前面へペースト」「背面へペースト」(Ctrl+F,B)に 相当するのが両メソッドである。
他にも個々のグラフィックItemにはこのメソッドがある。 場所がその儘なのは非常に良い。 前記のように一度座標にしておいて代入し直すのと違い、原理的に全く同じ場所なのは非常に良い。 ただ、カット&ペーストしか出来ない(もとのを残せない) のがちょっと限界かな。
# doc はなんかIllustrator.Documentオブジェクト grp = doc.GroupItems.Add path = doc.PathItems.item(1) path.MoveToBegining(grp)
ドーナツ型、というか Pierced disk 型に、真中が抜けていて回りに帯状にものがある状態。 或は、額縁上のオブジェクト。
それには、 Illustrator の使い方として、 パスファイダパレットの「前面オブジェクトで型抜き」 を用いる。
それをアクションに登録しておいて DoScriptで呼び出す。 出来あがりは複合パス CompoundPathItem になるので、扱いには注意が必要。
他の多くのオブジェクトの Selectedプロパティとは違って、 PathPointの Selectedプロパティは真偽値ではなく、AiPathPointSelection enumeration という 専用の値になっていて、端点(アンカーポイント と コントロール ポイント)の選択の様子を表す。
aiAnchorPoint : 2 aiLeftDirection : 3 aiLeftRightPoint : 5 aiNoSelection : 1 aiRightDirection : 4
aiNoSelection : 1 aiAnchorPoint : 2 aiLeftDirection : 3 aiRightDirection : 4 aiLeftRightPoint : 5
選択の状況は名前の通りで、 非選択、 アンカーポイントのみ選択、 アンカーポイントに加えて左右のコントロール(片方or双方)も選択、 という状態を示す。 コントロールポイントだけの選択状態がないのは、もともとの Illustrator の動作を反映している。
Illustrator での作業では、下記 矢印を引く、パスファインダで交点を求める の様に application.DoScript でアクション(メニュー)を呼んでやらないといけないことがいろいろ出てきそうだ。 その際には、アクション類は選択されたオブジェクトに作用する事も多いので、 アクティブドキュメントの Selectionプロパティとのやり取りも重要になってくるだろう。
アクションの実行には結構時間がかかる事があるので、ここできちんと待たないと 期待する動作をしないことになる。 アクションの実行が終る前に必要なオブジェクトの選択を解除してしまってはいけない。 それには application.ActionIsRunning でチェックする。
sleep 0.23 while illu.ActionIsRunning
document.Selection と application.Selection に書いた。 要はアクティブドキュメントの Selectionプロパティを空配列にする。
To deselect all objects in the current document, simply set the selection to Empty
( Array.new() を代入する。nilの代入で済む事もある )
application.DoScript でアクションを実行する。
DoScript(action As String, from As String, [dialogs As Boolean]) Plays an action from the Actions palette.
矢印を引く、パスファインダで交点を求める など参照。 そういえば、矢印を引く の時のサンプルの動作が不安定だったのは、 この注意事項を気にしていなかったせいかもしれない。
application.Selection は、アクティブドキュメントの選択オブジェクトだとという事だが、 その document.Selection とは、少し挙動が違う。選択解除するのに nil の代入が効かない。
require 'win32ole' $illu = WIN32OLE.connect('Illustrator.Application') $doc = $illu::Documents.Add(AiEnum::AiDocumentCMYKColor) $path = $doc.PathItems.Add # <!-- snip path-lines --> def testSelection(str) obj = eval(str) if obj puts(str + '.size = ' + obj.size.to_s) else puts(str + ' is false.') end end # def testSelection(str) def run(str) eval(str) ; puts str end # def run(str) puts "\nnil\n" # => nil testSelection('$illu.Selection') # => $illu.Selection is false. testSelection('$doc.Selection') # => $doc.Selection is false. puts "\napplication.Selection" # => application.Selection run('$path.Selected = true') # => $path.Selected = true testSelection('$illu.Selection') # => $illu.Selection.size = 1 run('$illu.Selection = nil') # => $illu.Selection = nil # nil testSelection('$illu.Selection') # => $illu.Selection.size = 1 # but not nil testSelection('$doc.Selection') # => $doc.Selection.size = 1 puts "\ndocument.Selection" # => document.Selection run('$path.Selected = true') # => $path.Selected = true testSelection('$doc.Selection') # => $doc.Selection.size = 1 run('$doc.Selection = nil') # => $doc.Selection = nil testSelection('$doc.Selection') # => $doc.Selection is false. puts "\nArray.new()\n" # => Array.new() testSelection('$illu.Selection') # => $illu.Selection is false. testSelection('$doc.Selection') # => $doc.Selection is false. puts "\napplication.Selection" # => application.Selection run('$path.Selected = true') # => $path.Selected = true testSelection('$illu.Selection') # => $illu.Selection.size = 1 run('$illu.Selection = Array.new()') # => $illu.Selection = Array.new() testSelection('$illu.Selection') # => $illu.Selection is false. puts "\ndocument.Selection" # => document.Selection run('$path.Selected = true') # => $path.Selected = true testSelection('$doc.Selection') # => $doc.Selection.size = 1 run('$doc.Selection = Array.new()') # => $doc.Selection = Array.new() testSelection('$doc.Selection') # => $doc.Selection is false.
カレントのドキュメント ActiveDocument の全ての選択を解除しようとするとき、 application.Selection へ nil を代入しても選択解除なされないが、 document.Selection への nil の代入では選択解除となる。 何れにせよ、空Array を代入すれば選択解除できる。
application.ActiveDocument.Selection を使うなり、 いつでも Array.new() を代入するようにしたりが無難。
Document.Selection は全ての選択されたオブジェクトの配列とされる。
それで、何も選択されていない時は何も返らない (nilが返る) 事に注意。 Document.Selection の呼出し自体には成功するが、size 零の配列では無くて nil が結果となる。
require 'win32ole' require 'vr/vruby' title = 'IllustRuby' illu = WIN32OLE.connect('Illustrator.Application') begin doc = illu.ActiveDocument #rescue WIN32OLERuntimeError => err rescue => err VRLocalScreen.newform.messageBox(err,title, 0 + 48 + 0x40000) # MB_OK = 0, MB_ICONWARNING = 48, MB_TOPMOST = 0x40000 abort end selected = doc.Selection if selected then num = selected.size msghead = (num==1)?'One item is':"#{num} items are" else msghead = "#{selected.type} are" # NilClass end # if selected then msg = "#{msghead} selected \nin the document `#{doc.Name}'." VRLocalScreen.newform.messageBox(msg,title, 0 + 64 + 0x40000) # MB_OK = 0, MB_ICONINFORMATION = 64, MB_TOPMOST = 0x40000
なんかマトリックスの作用が最初思っててた感じと違って、でもやってみれば当たり前の様な感じでもある。 ちゃんとまとめてないし、サンプルソースも用意できないけど様子だけ標語的に述べる。
矢印を引きたい、しかしそんなオブジェクト、メソッドはスクリプトにはない、 パスファインダで交点を求める の時と同じ様に、アクションを定義しておいて Application.DoScript 。
そもそも Illustrator で矢印を引くこと自体 FAQ な らしい。
path = doc.PathItems.Add a = path.PathPoints.Add b = path.PathPoints.Add a.Anchor = [100, 400] a.LeftDirection = a.Anchor a.RightDirection = a.Anchor a.Selected = false b.Anchor = [240, 300] b.LeftDirection = b.Anchor b.RightDirection = b.Anchor b.Selected = true # 各頂点(始終点)も選択しとかないといけない # 終点(アクションではこっちに矢印をつける様に定義した)だけ選択しといても良いみたい # まあ、両端(と途中の点)を選択しておくのが無難 path.Closed = false path.Selected = true illu.DoScript('myArrow', 'myActions') doc.Selection.each do |item| item.Selected = false end # doc.Selection.each do |item|
最後のブロックは、作った矢印グループを選択解除する為のもの。 ここでなにか変数に代入しておけば今作った矢印をあとから参照できる。
ちなみに、端点を Select しておかないと、 “オブジェクト「矢印にする」は現在使用できません” ダイアローグがでて矢印がひけなかったりする事がある。 またそのダイアローグでキャンセルしたりすると、 アクションパレットにへんなアクションの残骸が残ったりする。
直接生成すべきオブジェクトとして挙げたものの中の Color系オブジェクトは、 では実際どのように生成し、代入すべきなのだろうか。
aiColorNone = 0 aiColorCMYK = 1 aiColorGray = 2 aiColorRGB = 3 aiColorSpot = 4 aiColorPattern = 5 aiColorGradient = 6
aiDocumentRGBColor = 1 aiDocumentCMYKColor = 2
require 'win32ole' # color = WIN32OLE.new('Illustrator::Color') # 'new': Unknown OLE server : `Illustrator::Color' (WIN32OLERuntimeError) color = WIN32OLE.new('Illustrator.Color') puts "N: color.Color = #{color.Color}" # 0 # aiColorNone
作成されただけの Colorオブジェクトの色型は無名 aiColorNone である。
そういえば、win32ole的には、'::'は使えないんだね、'.'でないと。( 無効なクラス文字列です。)
begin color.Color = 2 rescue WIN32OLERuntimeError =begin in 'method_missing': (WIN32OLERuntimeError) OLE error code:0 in <Unknown> <No Description> HRESULT error code:0x8002000e パラメータの数が無効です。 =end ensure puts "N|2: color.Color = #{color.Color}" # 0 # aiColorNone end
Colorオブジェクトの Colorプロパティは R/O ( Read Only ) なので、直接代入する事は出来ない。 WIN32OLERuntimeError になってしまう。
.Color ではないプロパティへのアクセスも難しい。 相当する色空間プロパティに、別途作った CMYKColor や GrayColor を代入する事で item のカラーを設定する。 代入、設定されていない色系プロパティへのアクセスは失敗する。(初めは不定だが、Documentの色空間や Grayを期待しても良かろう)
illu = WIN32OLE.connect('Illustrator.Application') module AiEnum; end WIN32OLE::const_load(illu, AiEnum) doc = illu::Documents.Add(AiEnum::AiDocumentCMYKColor) path = doc.PathItems.Add
gray = WIN32OLE.new('Illustrator.GrayColor') gray.Gray = 40 # 40% color.Gray = gray path.StrokeColor = color
成功
パスはグレーになる、
cmyk = WIN32OLE.new('Illustrator.CMYKColor') cmyk.Cyan, cmyk.Magenta, cmyk.Yellow, cmyk.Black = 0,30,0,0 # 薄桃色 color.CMYK = cmyk path.FillColor = color
成功
Fill は CMYK(薄桃色)に満たされる。
rgb = WIN32OLE.new('Illustrator.CMYKColor') rgb.Red, rgb.Green, rgb.Blue = 0,0,40 # 淡青色 color.RGB = rgb path.StrokeColor = color
失敗 -> なんかCMYK(不定色)になってる
もともとの Dobumentの色空間に反するものは出来ないようである。 しかし、Grayだったものは自分の色空間の天然色にはなるようだ、そして、色値は不定値になる。
余所の画像ファイルを持ってくるにはどうしたら良いのだろうか。
と言う事だ、各 Fileプロパティに所要の画像ファイル名を代入してしまえば良い。
require 'win32ole' illu = WIN32OLE.connect('Illustrator.Application') doc = illu.Documents.Add raster = doc.RasterItems.Add # raster.File = "C:\ruby\illustrator\fromfile\door.jpg" # \のエスケープが難しいので raster.File = 'C:\ruby\illustrator\fromfile\door.jpg' # "文字列ではなく、'文字列が楽 raster.Resize(13,13) # 100% # RasterItem.Resize は 100%が基準の百分率値 raster.Position = [200,500] # Resize、Rotate、Transform(transformationMatrix) は兎も角、 # 移動は Translate するより Positionを直接弄る方が見易い (他のオプションが必要無い時は)。
ベクトル系の画像は PlacedItemとして、そうでない画像は RasterItemとして取り込めば良さそうだ。 ファイル名の指定には、\のエスケープが大変なので、"文字列" ではなくて '文字列' の方が分かり易い。
このようにして呼んで来た画像をファイルに埋込むのかどうかは、この呼出の時に設定する物ではない。 saveas なり何なりで全体を保存するときに指定するのである。
各種 Colorオブジェクトやオプション類などで、Illustrator.Applicationから生成するのではなく、 直接生成すべきオブジェクト。 マニュアルの中ほど、オブジェクトの生成に関する注意書きの所にその様な記述があった。
例えば
require 'win32ole' color = WIN32OLE.new('Illustrator.RGBColor') color.Red, color.Green, color.Blue = 255,0,0 # 真っ赤
Illustrator起動中とはいえ 其々のオブジェクトは個別には稼動しているわけではないので、 connect ではなくて new で生成する。 Illustrator起動中で無いとIllustreatorの起動をはじめて
(WIN32OLERuntimeError) HRESULT error code:0x80080005 サーバーの実行に失敗しました。
とエラーになるのは、Application本体の時と同様。
相変わらず Illustrator ではない話。
前述の様にすればメッセージボックスを表示出来た。 その際、メッセージ本文の左に、 丸く赤い所に白抜き×、黄色い三角にビックリ、青いはてな、青いiの字 といったアイコンを出したい。 また、 「YES」「NO」「CANCEL」とか釦が複数出るときは、デフォルトで選択状態になってる釦を指定してやりたい。
MB_ICONERROR = 16 # 丸く赤い所に白抜き× MB_ICONSTOP = 16 # 同上 MB_ICONHAND = 16 # 同上 MB_ICONQUESTION = 32 # 白い吹出しの中に青い ? MB_ICONEXCLAMATION = 48 # 黄色い三角の中に ! MB_ICONWARNING = 0x30 # 同上 MB_ICONINFORMATION = 64 # 白い吹出しの中に青く i の字 MB_ICONASTERISK = 64 # 同上 MB_USERICON = 128 # (ユーザアイコンの指定法不明)
MB_DEFBUTTON1 = 0 MB_DEFBUTTON2 = 256 MB_DEFBUTTON3 = 512 MB_DEFBUTTON4 = 0x300 # 768
MB_TOPMOST = 0x40000 # 262144
例えば
VRLocalScreen.newform.messageBox('message', 'title', 3 + 32 + 512) # 3 = WConst::MB_YESNOCANCEL # 32 = WConst::MB_ICONQUESTION # 512 = WConst::MB_WConst::MB_
こうすると、はてなアイコンが付いて、3番目の釦(キャンセル)が最初にフォーカスしている。 更に、MB_TOPMOST を加算しておくと、ボックスは常に最前面に表示されるようになる。
他にも contrib/msgboxconst.rb な値はあるので、いろいろ機能があるのでしょう。 その辺は Windows のダイアローグの説明をみれば色々わかるのだと思います。
スクリプトメニューから_2 [DOS窓のない呼出] とすると、 今度はメッセージダイアローグを出したくなる。 それには vruby ( ActiveScriptRuby には最初から入っている) を使う事が出来る。
この節は Illustrator ではなくて vruby (VisualuRuby計画(仮称)) のことになります。 しかも、余り(到底)正当ではない使い方です。
require 'vr/vruby' VRLocalScreen.newform.messageBox('message', 'title', 0) # 0 = WConst::MB_OK
第1項 'message' を表示するダイアローグである、第2項 'title' は Windowタイトルになる。
本来 VRLocalScreen.newform
は新たにフォームオブジェクトを作成するので、
それに各種 Widget を配置して行くのが普通の流れだろう。
それらの Widget の機能と操作によっては Windows のメッセージボックスを呼び出す事もあるわけだ、
その メッセージボックス だけを用いる。
require 'win32ole' require 'vr/vruby' frm = VRLocalScreen.newform msg = "Hello, rubyillustrator is here.\nDo you create a hello document ?" res = frm.messageBox(msg, 'ruby illustrator', 4) # 4 = WConst::MB_YESNO case res when 6 # YES->6, NO->7 illu = WIN32OLE.connect('Illustrator.Application') doc = illu.Documents.Add hello = doc.TextArtItems.Add hello.Contents = 'Hello rubyillustrator world!' hello.Position = [230, 700] hello.Resize(300, 300) when 7 # YES->6, NO->7 frm.messageBox('adieu', 'ruby illustrator', 0) # 0 = WConst::MB_OK else raise end # case res
messageBox引数の第3項には、require 'vr/contrib/msgboxconst' に一覧のある WConst定数の値を指定するとメッセージボックスの表象が変る。 そして、そのダイアローグの釦押下の結果が返ってくる。
MB_OK = 0 # OK->1 MB_OKCANCEL = 1 # OK->1, CANCEL->2 MB_ABORTRETRYIGNORE = 2 # ABORT->3, RETRY->4, IGNORE->5 MB_YESNOCANCEL = 3 # YES->6, NO->7, CANCEL->2 MB_YESNO = 4 # YES->6, NO->7 MB_RETRYCANCEL = 5 # RETRY->4, CANCEL->2
そうは言っても、.vbs
からの呼出の度に DOS窓が現れるのを煩わしく感じる場合も在ろう。
その時は
WshShell.Run( "WScript.exe ""<rubyファイルのフルパス名>"" ")
とすれば良いのだろう。'""' な '"' のエスケープや、フルパス名が安心である。
今まで書いてきた rubyスクリプトは Illustratorの外から実行していた。
Illustratorのスクリプトメニューからの呼出しも出来るようにしたい。
しかし、Illustratorは、.vbs
, .js
, .exe
しか認識しない。
その為、WScript.Shell.Run
, WScript.Shell.Exec
によって
rubyスクリプトを呼び出すだけの VBScriptを書く。
Dim WshShell, oExec Set WshShell = CreateObject("WScript.Shell") Set oExec = WshShell.Exec( <rubyファイルのフルパス名> )
または
Dim WshShell Set WshShell = CreateObject("WScript.Shell") WshShell.Run( <rubyファイルのフルパス名> )
標準入出力などと相互作用出来る Execメソッドは、新しい(XP以降?) WSH にしか無い様なので、 その時は Runメソッドを使用する。
或は exerb によって .exe
実行ファイルにしてやってもよい。
[ファイル]-[スクリプト]-[参照] メニューでの初めに参照するフォルダは (標準的なインストールでは)
C:\Program Files\Adobe\Illustrator 10.0.3\Support Files\Contents\Windows\
になるので、その辺に入れれば良いだろう。
このフォルダには、.vbs
, .js
, .exe
へのショートカット を入れても参照される。
また、[ファイル]-[スクリプト]-メニューから直接呼び出せるようにするには、
C:\Program Files\Adobe\Illustrator 10.0.3\プリセット\スクリプト\
に所要のファイルを置くと良い。ここにはショートカットを置いても参照されない。 実際にこのメニューに登録するには、Illustyratorを再起動する必要があることに注意。
テキスト文字列内で改行するには "\r" を挿む、"\n" ではないのに注意。
一文字毎に "\r" を挿めば縦書きもどきにもなる。 (縦書き文字ツールの名前がわからない)
(V|G|C)Bounds を表す Fixed rectangle の指定するのが、左上座標と右下座標(この順)であるということを 実感していなかった(なんとなく左下点を思い描いてたりした)のが混乱の要因だった様だ。 もう一度状況を整理する。
そして、VisibleBoundsである。これは FixedRectangleとして、左上点と右下点を代表する。
visibles = text.VisibleBounds vw = (visibles[2] - visibles[0]).abs vh = (visibles[3] - visibles[1]).abs
そういう意味では下記にすれば .absは要らない筈なわけだ、
vw = (visibles[2] - visibles[0]) vh = (visibles[1] - visibles[3])
この幅を基準に移動すれば所要位置にテキスト
text.Position = [ center[0] + @left - vw/2, center[1] + @bottom + vh/2 ] # 此れが所要の筈なんだ、pointが左上なんで -vw +vh と符号がずれる(2003/3/16)
centerがテキストの中心になる様に移動したつもり。
To work with rectangular coordinates where there are a pair of x and y values, Illustrator uses the special class called a fixed rectangle. This class is comprised of a list with four items in AppleScript and a variant array with four elements in Visual Basic. The coordinates of a .xed rectangle in order are: left, top, right, bottom.
どうも上下方向で下にずれるような感じ。 TextArea は左上をpointするのでなく、左下をポイントするのか?
というより、pathテキストか、pointテキストか、エリアテキストかをきちんとすべきであった
ってことになるようだ(正対配置の場合)
pathテキストは駄目みたい。pointテキストをちゃんと指定しよう。そしてそれでも、上下的には中央ではない。 ベースラインと上端の中央にくる感じ。(VigibleBoundsってどういう意味?、こんなことなの?)
Application.DoScript で予め作っておいた「パスファインダー アウトライン」アクションを実行する。 得られたアウトライン線のパスStrokeColor.Color は Gray になる傾向がある。 いっぱい出来るうち最後のパスだけは Documentと同じAiColorCMYKになるが、あとはAiColorGrayになってしまう。
Color.Color が該当する Color Enumeration 値でないとき、そもそもそのカラープロパティは無い(nill) 上記のようにGrayになっちゃてるとき、CMYK属性はそもそも存在せず、ましてや CMYK.Cyan なんて nil はそんなメソッド(プロパティ)持って無いといわれる。
保存、読み込み、置き換え
アクション表示ウィンドウのオプションメニューからアクションの保存ファイル操作。
「置き換え」をすると、デフォルト(初期設定)アクションなども含めて置換されてしまい、 ファイルの保存されていたアクションしかなくなってしまうのに注意。
Document.DefaultColor なり、各オブジェクトの StrokeColor とか FillColor とかの Colorオブジェクトについて、 その Colorプロパティの AiColor Enumeration に従ったプロパティ(CMYK,...)の色値を設定する。
Layer.TextArtItems からテキストを書く。
TextArtItem.Kind を設定すれば、point text、path text、area text、を選べる訳だが、取敢えず point系。
title = lay.TextArtItems.Add title.Contents = <文字列> title.ReSize( <width(%)> , <hight(%)> ) title.Position = [ <left> , <top> ]
配置位置の計算には、Heigt, Width (どちらも GemotericBounds を基に計算する)を使って微調整出来るだろう。 より詳しくは、VisibleBounds, (GeometricBounds, ControlBounds) を使う事になる。
TextArtItem の TextRangeメソッドで、適用すべきテキストの範囲を選び(設定し)、 その TextRangeオブジェクトの、Fontプロパティに、所要の TextFace オブジェクト値を設定する。
TextFaceオブジェクトを探すのは大変(TextFace, TextFaces の exampleなど参照)
TextFaces.item(itemKey) itemKey は フォント名称でいいのかな? 未確認
TextFacesコレクションを eachで回して(Count個)、各 TextFace.Name をフォント名にマッチさせる とかすればなんとかなるか?
TextFaces[i] は効くのかな?
線と線(パスとパス)の交点を求めるのに、パスファインダ-アウトライン を使う事が出来る(GUI)。 しかし、スクリプトからは直接これを操作するやり方が解らないので、 あらかじめアクションを定義しておいて、Application.DoScript でアクションを呼ぶ事にする。
DoScript(action As String, from As String, [dialogs As Boolean]) Plays an action from the Actions palette.
全点座標を比較検討して交点座標(と交点)を抜き出したり、 適宜グループの名前や分割された線分の名前を設定するメソッドも必要でしょう
このオプションはスクリプトから操作出来るような感じがしないので、設定値には十分注意する事。 rubyスクリプト実行前に所要の値になっているかどうか、GUIで確認すべき。
十分小さい値にする事。初期設定値 0.028
でも良さそうだけど、0.01
くらいにしようか。
今の場合、塗りの無い線の交点を求めるのが目的なので、これはチェックしない。
こっちのほうは目立った影響はないみたい。
「プリント可能範囲 」(か「用紙の端」?) の左下端。 描画の基準点ではない。
SetEntirePath(pathSpecification As Variant Array of Variant Array of 2 Singles)
を使うと直接直線が引ける筈だがうまく行かない。
pathA = lay.PathItems.Add pathA.setEntirePath( Array[ Array[0,0], Array[100,100] ] )
でエラーになる。
blend.rb:56:in `method_missing': setEntirePath (WIN32OLERuntimeError) OLE error code:80070057 in <Unknown> the argument is not valid HRESULT error code:0x80020009 例外が発生しました。 from blend.rb:56
だからって、引数を Array,Array にしたり、0,0,100,100 にしても
HRESULT error code:0x8002000e パラメータの数が無効です。
が起こる。
PathItems コレクションの要素の item を取り出すメソッド
PathItems.item(itemKey)
itemKey は整数で良かったが、1 から始まる。rubyな 0 始まりで無いのに注意!
恐らく他のコレクションもみんな 1 始まりだろう。要注意。
ずれる。微妙に、1pt程度のズレかも知れないがずれる。 傾いた多角形も自由頂点指定多頂点閉パスとして作成しないと、まさに思った位置には描画できない。
と言う訳でやってみた
doc.DefaultStrokeCap = Illu::AiButtEndCap doc.DefaultStrokeJoin = Illu::AiMiterEndJoin doc.DefaultStrokeMiterLimit = 5
としておいて下から順に、
PointTypeは関係無いみたい。
2本のパスで曳くと谷になっちゃう辺り見難かったかな、ちょっと拡大
何れも丸めるときの半径はStrokeWidthの半分。
AiMiterEndJoin のとき、PathItem.StrokeMiterLimit が十分大きくないと Bevelのように断ち切られる。
StrokeCap: The type of line capping for a path stroke.
StrokeJoin: The type of joints for a path stroke.
<URL:http://partners.adobe.com/asn/developer/scripting/docs/AIJavaScriptReference.pdf> より、 ってわけで
AiStrokeCap
AiButtEndCap : 1 AiProjectingEndCap : 3 AiRoundEndCap : 2
AiStrokeJoin
AiBevelEndJoin : 3 AiMiterEndJoin : 1 AiRoundEndJoin : 2
折線の交点の尖り具合の制御はどうなってるんだろう。 ちょっと尖り過ぎてて、線の向こうに飛び出てしまう。
AiStrokeCap, AiStrokeJoin ってなんだろう。
doc = illu.Documents.Add(Illu::AiDocumentCMYKColor, A5::WidthPt, A5::HeightPt) doc.DefaultStroked = true doc.DefaultStrokeWidth = 0.03
document単位でのパスを引く時の線の幅の指定。
正直、線幅 0.03point というのは細くし過ぎで、0.1より小さい値では、印字線幅に違いは無い様に見える (Canon BJ F200、それほど解像度の高くない非PSプリンタ)。まあ、0.1未満にしとけばいいのだろう。
PathItem.Closed = true
Closed Path を作るとき。 PathItem.PathPoints.Add で頂点を付加え、適宜 Anchor座標(とDirection)を指定した後で、 単に PathItem.Closed を true にすれば良い。 たとえば自由頂点の多角形はそうやって作る。
結構値を返してくれないメソッドやプロパティ代入(ruby的にはこれもメソッド)が多くてストレスが溜まる。 (これはOLEなら仕方ない事だっけ、それとも Illustrator だけの事?)
例1 そういうわけで、直線を引く時 Anchor と Left[Right]Direstion は同座標、
point.LeftDirection = point.ReightDirection = point.Anchor = [<X>, <Y>]
ではエラーになる
point.Anchor = [<X>, <Y>] point.LeftDirection = point.Anchor point.ReightDirection = point.Anchor
としないといけない。
例2
paths.Polygon(云々).Rotate(角度).Name = 云々
は駄目で、
gon = paths.Polygon(云々) gon.Name = 云々 gon.Rotate(角度)
としないといけない。 paths.Polygon(云々).Rotate(角度) は効く。
PathItem.Rotate(角度) は度(degree)
Rotate(Angle As Single, [changePositions As Boolean], [changeFillPatterns As Boolean], [changeFillGradients As Boolean], [changeStrokePattern As Boolean], [rotateAbout As AiTransformation])
PathItems.Polygon で得られた PathItem の PathPoints の各点の Left[Right]Direction は自身だった。
pol = paths.Polygon(A5::WidthPt/2, A5::HeightPt/2, size/2, 5) pol.name = 'Hishi' pps = pol.PathPoints puts "\nPolygon" pps.Count.times do |i| pt = pps.item(i+1) puts "#{pt.PointType}:#{pt.Anchor[0]},#{pt.Anchor[1]}; L:#{pt.LeftDirection[0]},#{pt.LeftDirection[1]}; R:#{pt.RightDirection[0]},#{pt.RightDirection[1]}" end # 是に拠ると Polygon の各頂点の Left[Right]Direction は自身、PointType は勿論 AiCorner
出力
Polygon 2:184.7851563,55.14160156; L:184.7851563,55.14160156; R:184.7851563,55.14160156 2:115.0371094,269.8037109; L:115.0371094,269.8037109; R:115.0371094,269.8037109 2:297.6401367,402.4726563; L:297.6401367,402.4726563; R:297.6401367,402.4726563 2:480.2431641,269.8037109; L:480.2431641,269.8037109; R:480.2431641,269.8037109 2:410.4951172,55.14160156; L:410.4951172,55.14160156; R:410.4951172,55.14160156
PathItems.Polygon の作る正多角形だけど、 作ったあとで回転させるとき、回転中心は、geometric(visible ?) bouds長方形の中心である。 Polygon定義のときに指定した外接円中心ではないのでちょっと混乱した。 また、そういう訳だから、偶数角形のときは大丈夫。
PathItems.Rectangle で得られた PathItem の PathPoints の各点の Left[Right]Direction は自身だった。
rec = paths.Rectangle(left_top[1], left_top[0], size, size) pps = rec.PathPoints pps.Count.times do |i| pt = pps.item(i+1) puts "#{pt.PointType}:#{pt.Anchor[0]},#{pt.Anchor[1]}; L:#{pt.LeftDirection[0]},#{pt.LeftDirection[1]}; R:#{pt.RightDirection[0]},#{pt.RightDirection[1]}" end # 是に拠ると Rectangle の各頂点の Left[Right]Direction は自身、PointType は勿論 AiCorner
出力
2:105.6401367,18.47265625; L:105.6401367,18.47265625; R:105.6401367,18.47265625 2:105.6401367,402.4726563; L:105.6401367,402.4726563; R:105.6401367,402.4726563 2:489.6396484,402.4726563; L:489.6396484,402.4726563; R:489.6396484,402.4726563 2:489.6396484,18.47265625; L:489.6396484,18.47265625; R:489.6396484,18.47265625 AiCorner : 2 AiSmooth : 1
Ellipse([top As Single], [left As Single], [Width As Single], [Height As Single], [reversed As Boolean], [inscribed As Boolean])
Polygon([centerX As Single], [centerY As Single], [radius As Single], [sides As Long], [reversed As Boolean])
Rectangle([top As Single], [left As Single], [Width As Single], [Height As Single], [reversed As Boolean])
X,Y順が直観と反するので凄く混乱した
centimeters 28.346 points = 1 centimeter inches 72 points = 1 inch millimeters 2.834645 points = 1 millimeter picas 12 points = 1 pica Qs 0.709 point = 1 Q (1 Q equals 0.23 millimeter)
Ai constants は <URL:./aiConstants.txt>参照
こうやって
require 'win32ole' #illu = WIN32OLE.new('Illustrator.Application') illu = WIN32OLE.connect('Illustrator.Application') module Illu; end WIN32OLE::const_load(illu, Illu)
作るわけだが、Illustratorは既に起動していないと起動できずに(起動中(終盤)に初期設定の読込みの辺りで)エラーになる。 (.new('Illustrator.Application') の場合)
Illustrator が起動していれば、new で OLEオブジェクトを作る事が出来る。 というわけで、どうせ Illustrator が起動中でないといけないのなら、connect で OLEオブジェクトを作る事にしよう。
(Windows98SE 4.10.2222A, ActiveScriptRuby 1.6.8.0, Ruby1.6.8, Win32OLE 0.5.2) (Adobe Illustrator 10.0.3 Win版)
Illustrator で図を書かねばならない事情が出来た。しかし、マウスで書くのは性に合わず、得意でない。 図は同じ様なものを幾つもいくつも書く必要がある。というわけでスクリプトによる自動処理がしたい。 近くに Mac で AppleScript で自動処理をしている人がいるので可能ではあろう。 是を機会に MacOSX も用意して BSDな人 になるのも一案ではあった。
しかし、調べてみると Visual Basic によるスクリプティングの解説が Adobe自身にあった( <URL:http://partners.adobe.com/asn/developer/scripting/docs/IllustratorScriptingGuide.pdf> AppleScript含む。別に JavaScriptのもある)。 Visual Basic で出来て、VBScript で出来て、JavaScript で出来る: com である。 Windows Active Script なら Illustrator を操作できるわけだ。
そういうわけで「ActiveScriptRuby で Illustrator 10 」。
ここに書いて行くのは、上記に際して出会った事情のメモである。取敢えずの備忘録を兼ねたメモである。 機会があったら将来また見やすくまとめるかもしれないが、現状では単なる事象の順不同な羅列に過ぎない。