Counting and colors in OmniGraffle and AppleScript

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

OmniGraffle annotated floor plan

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:

 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
 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
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

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.

Script Debugger with results pane

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:

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:

if c as list is redFill then

When I first wrote it, these lines looked like

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,

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.

  1. In the real drawing, I have other annotations other than circles, but for simplicity I’m showing just circles here.