Counting and colors in OmniGraffle and AppleScript
May 8, 2019 at 10:56 PM by Dr. Drang
This morning I had a fight with AppleScript and thought you might be interested in the blow-by-blow.
I had imported a floor plan into OmniGraffle and marked it up with translucent circles, using different colors for different features of interest. When I was done, it looked sort of like this with red, green, and blue circles scattered over the drawing:1
I wanted the counts of the different features, which is the sort of thing that a child can do, but which an adult is likely to screw up (this adult, anyway). And because drawings often don’t reflect what was actually built, I had reason to believe I’d be modifying the annotations after visiting the building, which means I’d have to redo the counts. So I figured the best way to do the counting and recounting was to write a script to do it.
OmniGraffle for the Mac has an AppleScript dictionary, so I girded my loins, opened up Script Debugger, and started bashing away. AppleScript bashed back, but eventually I won. Here’s what I ended up with:
applescript:
1: set redFill to {64614, 0, 1766}
2: set greenFill to {5246, 39932, 635}
3: set blueFill to {0, 0, 65416}
4: set redCount to 0
5: set greenCount to 0
6: set blueCount to 0
7:
8: tell application "OmniGraffle"
9: tell layer "Annotations" of canvas 1 of document 1
10: set fColors to fill color of every shape whose name is "Circle"
11: end tell
12: end tell
13:
14: repeat with c in fColors
15: if c as list is redFill then
16: set redCount to redCount + 1
17: end if
18: if c as list is greenFill then
19: set greenCount to greenCount + 1
20: end if
21: if c as list is blueFill then
22: set blueCount to blueCount + 1
23: end if
24: end repeat
Lines 1–3 define the RGB triplets for the three different colors I used for the circles. You’ll note that the individual components aren’t limited to the usual 8-bit 0–255 range, they seem to use a 16-bit range of 0–65,535. I figured out the values by running AppleScript lines like
applescript:
get fill color of shape 1 of layer "Annotations" of canvas 1 of document 1
(I had made the OmniGraffle document with two layers, one for the floor plan and one for all my circle annotations.)
Lines 4–6 then initialize the counts for the different colors.
Lines 8–12 get a list of all the colors of all the circles in the “Annotations” layer. The result, stored in the variable fColors
, is a list of lists that looks like this:
{{64614, 0, 1766}, {64614, 0, 1766}, {0, 0, 65416}, …}
Lines 14–24 loop through fColors
, comparing each triplet with the colors defined in Lines 1–3 and incrementing the count for the matching color. When the script is done, redCount
, greenCount
, and blueCount
have the values I want. There’s no output statement at the end of the script because Script Debugger can show you the values of all the variables in a pane on the right side of the script window.
If you’re thinking the script could have been shorter, you’re thinking just like I was. When I first tried writing this, I thought I could get what I wanted without the repeat
loop. It seemed to me that I should be able to do this:
applescript:
tell application "OmniGraffle"
tell layer "Annotations" of canvas 1 of document 1
set reds to every shape whose name is "Circle" and fill color is {64614, 0, 1766}
set greens to every shape whose name is "Circle" and fill color is {5246, 39932, 635}
set blues to every shape whose name is "Circle" and fill color is {0, 0, 65416}
end tell
end tell
And then I’d just see how long the reds
, greens
, and blues
lists were. But that didn’t work. The script ran, but the lists came out with lengths of zero. I tried several variations, but came up empty (literally) every time. Because the point of this exercise was to get counts, not investigate every nook and cranny of AppleScript and the OmniGraffle dictionary, I added the inelegant repeat
loop and got on with life.
But even the repeat
loop didn’t work out the way I first thought it would. Lines 15, 18, and 21 all have the same form:
applescript:
if c as list is redFill then
When I first wrote it, these lines looked like
applescript:
if c is redFill then
After all, fColors
is a list of lists, so each item of fColors
is a list. I shouldn’t have to tell AppleScript to treat c
as a list—it is a list. In fact, when I added a debugging line to the top of repeat
loop,
applescript:
repeat with c in fColors
set t to class of c
Script Debugger told me the value of t
was list
. So why the need for as list
? I don’t know, but the counts came out zero without it.
If you know what I could have done to simplify this script, I’m all ears. I feel certain there’s a magical incantation that will get the set reds to every shape whose…
command to work.
But please don’t tell me to use Omni’s cross-platform JavaScript system. I’m not much more fond of JavaScript than I am of AppleScript, and I especially dislike writing AppleScript-flavored JavaScript.
Update May 10, 2019 7:53 PM
More discussion and a better script here.
-
In the real drawing, I have other annotations other than circles, but for simplicity I’m showing just circles here. ↩