Grid properties

Is there a way to get/set a document’s grid properties?

At least for AppleScript, the scripting dictionary shows the following properties for the grid of a canvas - therefore there is a way…
grid n [inh. [item]] : The grid of a canvas
properties
draws in front (boolean) : Does the grid draw in front of all shapes?
major (boolean) : Does the grid have ‘major’ lines?
major spacing (integer) : The number of minor grid lines for each major line
script grid color ([color]) : Color of the grid
script major grid color ([color]) : Color of major lines
snaps (boolean) : Do points snap to the grid?
spacing (real) : Number of pixels between minor grid lines
visible (boolean) : Is the grid visible?

Reading them through the omniJS interface, you could paste and enter something like this in the automation console:

(() => {
    'use strict';

    const
        grid = canvases[0].grid,
        ks = Object.getOwnPropertyNames(grid.constructor.prototype)
        .filter(k => 'constructor' !== k)

    return ks.reduce((a, k) => {
        const v = grid[k];
        return a + k + '\t' + (
            'object' !== typeof v ? (
                v + '\n'
            ) : 'RGB: (' + [v.red, v.green, v.blue]
            .map(x => 255 * x)
            .join(', ') + ')\n'
        );
    }, '');
})();

to obtain a result analogous to:

drawsInFront    true
majorSpacing    5
majorColor    RGB: (47.93519541621208, 47.936570942401886, 47.935833781957626)
minorColor    RGB: (143.72830599546432, 152.28140383958817, 193.3756971359253)
spacing    4
snaps    false
visible    true

And, of course, you can update (and re-check) properties by reversing this process:

(() => {
    'use strict';

    const
        grid = canvases[0].grid,
        ks = ['drawsInFront', 'majorSpacing', 'majorColor', 'minorColor',
            'spacing', 'snaps', 'visible'
        ];

    return (
        // Effects
        grid.drawsInFront = true,
        grid.majorSpacing = 25,
        grid.majorColor = Color.blue,
        grid.minorColor = Color.gray,
        grid.spacing = 5,
        grid.snaps = true,
        grid.visible = true,

        // Values
        ks.reduce((a, k) => {
            const v = grid[k];
            return a + k + '\t' + (
                'object' !== typeof v ? (
                    v + '\n'
                ) : 'RGB: (' + [v.red, v.green, v.blue]
                .map(x => 255 * x)
                .join(', ') + ')\n'
            );
        }, '')
    );
})();

returning the list of updated grid properties:

drawsInFront    true
majorSpacing    25
majorColor    RGB: (0, 0, 255)
minorColor    RGB: (127.5, 127.5, 127.5)
spacing    5
snaps    true
visible    true

Documentation concerning grids are also found at the Omni Automation website:

https://omni-automation.com/omnigraffle/grid-00.html

Cheers!

P.S. Try the toothpick examples!

@Sal thanks! Is this page new? I totally missed it. Anyway, I see the Grid is a read only property, any ETA on when it can be set also?

Thx

BTW, whats in the pipe line for omni-automation? I’d be rather interested to hear what you guys plan to cook up in the near future. Tell us what you can please!

@draft8 Thanks, need to give that a go…

The Grid object is a read-only reference, but all of its properties are RW

(you can test the setting of grid properties with the code above in
https://discourse-test.omnigroup.com/t/grid-properties/44324/4?u=draft8)

Greetings! That page has been up for a while. I updated it last year with the toothpick examples in the hope it would make it a bit more fun and interesting. Making the grid writable is on the wish list for sure!

As to what is coming up for Omni Automation, I”ll leave announcements to Ken ;-)

HINT: very very very cool stuff!

1 Like

We all optimise for different things in the way we write our code.

For my taste, the code at omni-automation.com is a bit heavy-going on the poor reader, especially if they are a beginner – looking for the basic structures and concepts.

It seems, in particular:

  • rather monolithic and unchunked,
  • a bit repetitive and demanding on the poor typist,
  • out of sync with current JS learning materials like the first half of Eloquent Javascript, which (sensibly and helpfully) use ES6 let and const etc rather than the trickier and less predictable ES5 var.

(Remember that omniJS only runs on macOS versions which have ES6 interpreters – any argument for ‘backward-compatibility’ would have to appeal more to personal nostalgia and the joys of retro aesthetics than to practicalities for beginners).

There are lots of ways of re-writing the omni-automation code examples, here is one way of doing it for the second toothpick example of omniJS grid-setting.

Javascript is entirely built around dictionaries of key-value pairs ("Objects" in the documentation), and I think it can be clarifying and simplifying to introduce them straight away, together with the Object.assign() method, which (very helpfully for omniJS scripting) lets us add the key values of one dictionary/‘Object’ to another:

(() => {
    'use strict';

    // A less repetitive, and perhaps more structured, variant
    // of the ToothPick Puzzle #2 code at:
    // https://omni-automation.com/omnigraffle/grid-00.html

    const main = () => {

        // A reference to the front canvas,
        const cnvs = document.windows[0].selection.canvas;

        // key-value pairs for the line style that we want,
        const dictLineStyle = {
            strokeThickness: 10,
            shadowColor: null,
            strokeCap: LineCap.Butt,
            strokeColor: Color.RGB(0.647059, 0.647059, 0.647059)
        };

        // key-value pairs for the the grid style that we want,
        const dictGridStyle = {
            majorSpacing: 10,
            spacing: 10,
            majorColor: Color.blue,
            minorColor: Color.white,
            snaps: true,
            visible: true,
            drawsInFront: false
        };

        // (From, To) pairs for the line coordinates that we want.

        // fromTos :: [((Float, Float), (Float, Float))]
        const fromTos = [
            Tuple([400.00, 210.00], [400.00, 390.00]),
            Tuple([200.00, 210.00], [200.00, 390.00]),
            Tuple([0.00, 210.00], [0.00, 390.00]),
            Tuple([400.00, 10.00], [400.00, 190.00]),
            Tuple([200.00, 10.00], [200.00, 190.00]),
            Tuple([0.00, 10.00], [0.00, 190.00]),
            Tuple([210.00, 400.00], [390.00, 400.00]),
            Tuple([10.00, 400.00], [190.00, 400.00]),
            Tuple([210.00, 200.00], [390.00, 200.00]),
            Tuple([10.00, 200.00], [190.00, 200.00]),
            Tuple([210.00, 0.00], [390.00, 0.00]),
            Tuple([10.00, 0.00], [190.00, 0.00]),
        ]

        // A cleared canvas with the grid features required,

        cnvs.graphics.forEach(x => x.remove());
        cnvs.canvasSizingMode = CanvasSizingMode.Infinite
        Object.assign(cnvs.grid, dictGridStyle)

        // a new line created, positioned, and styled for each of our
        // pairs of (From, To) points.

        fromTos.forEach((pair, index) =>
            Object.assign(

                // A fresh line:
                cnvs.newLine(),

                // with the required start and end points,
                {
                    points: Array.from(pair)
                        .map(x => new Point(...x))
                },

                // the style defined in our dictionary of keys and values,
                dictLineStyle,

                // and a name that varies with the position in the sequence.
                {
                    name: 'Toothpick ' + chr(ord('L') - index)
                }
            )
        )
    };

    // GENERIC FUNCTIONS ----------------------------------

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = (a, b) => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // chr :: Int -> Char
    const chr = x => String.fromCodePoint(x);

    // ord :: Char -> Int
    const ord = c => c.codePointAt(0);

    // ---
    return main();
})();

You make some excellent points about modern JavaScript.

I could imagine that some progress could be made if Omni open sourced all their OmniJS documentation and examples in a Github repo and accepted pull requests.

1 Like

It does all feel just a little silo-ed and stagnant at the moment – I’m not seeing much omniJS in the wild …

Opening it up a bit (on Github, for example) might well infuse fresh energy and exchange.

(The https://github.com/JXA-Cookbook/JXA-Cookbook/wiki model did harvest more attention and generate more resource and editorial range in the early stages of JavaScript for Automation than the https://omni-automation.com model seems able to).

Opening up documentation is just a first step. To have more omniJS in the wild, I believe there is some more things that would need to happen:

  • make automation a standard feature on all platforms so there’s more developers and more demand for automation
  • provide access to NPM to leverage all the javascript code out there
  • support frictionless development (connect a debugger, support updating and reloading plugins without manual interaction etc.)

I hope that is so obvious that at least some of it is amongst the upcoming “very very very cool stuff” Sal hinted at in January.

A footnote – just looked at the 101 example at omni-automation.com

I think I would tend to:

  • skip all the var var var (use of var would puzzle readers of Eloquent JavaScript coming to JS for the first time, and only gets us into trouble anyway. EJ chapter 2: “we’ll rarely use var in this book because it has some confusing properties.”) [^footnote]

  • chunk the structure a bit more to ease the cognitive load of a big rectangular slab of steps.

  • Add use strict to get more informative error messages.

Perhaps sth closer to:

(() => {
    'use strict';

    const dictStyle = {
        strokeThickness: 12,
        strokeColor: Color.red,
        fillColor: Color.RGB(0, 0, 1, 1)
    };

    Object.assign(
        document.windows[0].selection.canvas
        .addShape(
            'Circle',
            new Rect(100, 100, 200, 200)
        ),
        dictStyle
    );
})();

as an alternative to:

var aRect = new Rect(100,100,200,200)
var aFillColor = Color.RGB(0, 0, 1, 1)
var aStrokeColor = Color.red
cnvs = document.windows[0].selection.canvas
var aShape = cnvs.addShape('Circle',aRect)
aShape.strokeThickness = 12
aShape.fillColor = aFillColor
aShape.strokeColor = aStrokeColor

[^footnote]: Confusing properties of var:

  1. Unlike let and const, var leaks scope out of { ... } blocks, up to the whole of the enclosing function, or, in the absence of a function wrapper, out into the global namespace.
  2. The accidents that this can cause are compounded by the fact that var bindings (again unlike let and const bindings) trigger no warning at all when you have accidentally used the same name twice in the same scope, or in what you had quite reasonably imagined to be two different scopes.
  3. Misuse of var as the default pattern for binding names to values also makes mutability a default, and default mutability has an explosive (and quite unnecessary) effect on the program-state complexity and risk of bugs that a beginner has to navigate. const is always better than let, and there is never any need for var. Hard to think of a messier or more expensive candidate for a default.