Switching between grids easily

I usually use 100 px/10 steps grid, but occasionally I want to fine-tune things and increase grid to 10px/10 steps. Does anyone have an example of a macro or a shortcut I can use to do this quickly?

TL;DR There seem to be two bugs with scripted grid adjustments in OG 7.5 at the moment.

  1. With omniJS through Automation > Show Console, scripted grid adjuments crash the application.
  2. With JavaScript for Automation (JXA) through Script Editor etc, no crash, but also no change on the screen, then local repaints of the screen in the wake of moving objects, with a final repaint only when an object is released.

When I tried this with omniJS through Automation > Show Console

(() => {
   'use strict';

   // SETTINGS --------------------------------------------------------------
   const
       gridGrouping = 10,
       gridBroad = 100,
       gridFine = 10;

   // TOGGLE BETWEEN BROAD AND FINE GRID LINE SETTINGS ---------------------

   const grid = document.portfolio.canvases[0].grid;
   return (
       // Effect
       grid.spacing = (grid.spacing !== gridFine ? gridFine : gridBroad),
       grid.majorSpacing = gridGrouping, 
       // Return value
       {
           grouping: grid.majorSpacing,
           spacing: grid.spacing
       }
   );
})();

it consistently crashed OG 7.5 (v181.4 r297378).

Switching to JavaScript for Automation, (through Script Editor etc) the good news was no crash with the following JXA code:

(() => {
    'use strict';

    // SETTINGS --------------------------------------------------------------
    const
        gridGrouping = 10,
        gridBroad = 100,
        gridFine = 10;

    // TOGGLE BETWEEN BROAD AND FINE GRID LINE SETTINGS ---------------------

    const grid = Application('OmniGraffle').documents.at(0).canvases.at(0).grid;
    return (
        grid.spacing = (grid.spacing() > gridFine ? gridFine : gridBroad),
        grid.majorSpacing = gridGrouping, {
            grouping: grid.majorSpacing(),
            spacing: grid.spacing()
        }
    );
})();

The bad news was that it appears to have no effect.

(Then, if you select and move something around, the affected subsections of the screen do get repainted with an updated grid as the object travels, and a final repaint may follow when you release the object. Distracting to work with, and probably a good thing to report to Omni Support)

Some kind of gap between model and display is always a difficulty in scripting OmniGraffle – a canvas.repaint() method in the scripting interfaces would be very helpful.

ScreenFlow

Thanks, I got it working by replacing grid.spacing() with grid.spacing()[0] .
However it doesn’t seem very practical to open Script Editor each time, I’ll have to look for a solution that lets me assign shortcut to a script

1 Like

a solution that lets me assign shortcut to a script

Apart from the good 3rd party solutions, (Keyboard Maestro, FastScripts)

There is also a Script Menu mechanism

replacing grid.spacing() with grid.spacing()[0]

Thanks - corrected above.

Sorry about this! The OmniJS crash is fixed in OG 7.6.

1 Like

So if you want to upgrade to the new 7.6 Beta release, then you can now use the better solution below, which:

  1. Can still be launched as a JXA script (Script Editor, Script Menu, Keyboard Maestro, FastScripts etc), but
  2. passes a function through to the omniJS evaluation context, and
  3. toggles the grid with full and immediate screen repaint.

Much less distracting - many thanks to Greg and colleagues for the very quick fix and new release.

Code for Script Editor etc:

(() => {
    'use strict';

    ObjC.import('AppKit');

    // FUNCTION TO EVALUATE IN THE OMNIJS CONTEXT -----------------

    // gridToggle :: () -> IO { grouping :: Int, spacing :: Real }
    const gridToggle = () => {
        // SETTINGS -----------------------------------------------
        const
            gridGrouping = 10,
            gridBroad = 100,
            gridFine = 10;

        // TOGGLE BETWEEN BROAD AND FINE GRID LINE SETTINGS -------
        const grid = document.portfolio.canvases[0].grid;
        return (
            // Effect
            grid.spacing = (grid.spacing !== gridFine ? (
                gridFine
            ) : gridBroad),
            grid.majorSpacing = gridGrouping,
            grid.visible = true,
            // Return value
            {
                grouping: grid.majorSpacing,
                spacing: grid.spacing
            }
        );
    };

    // FUNCTION TO EVALUATE IN THE JXA CONTEXT -------------------
    // (omniJS function stringified and invoked as URL)

    // omniJS Function -> Arguments -> Bool
    // runOmniJSinOmniGraffle :: omniJS Function -> a -> Bool
    const runOmniJSinOmniGraffle = (f, args) =>
        $.NSWorkspace.sharedWorkspace.openURL(
            $.NSURL.URLWithString(
                'omnigraffle:///omnijs-run?script=' +
                encodeURIComponent(
                    '(' + f + ')(' + JSON.stringify(args) + ')'
                )
            )
        );

    // MAIN -----------------------------------------------------
    runOmniJSinOmniGraffle(gridToggle);

})();

Or if you want to assign a key to cycle between 3+ states ((fine -> coarse -> invisible ->) for example) you could fine-tune something like this, editing the details of the cycle list at the top of the script:

(() => {
    'use strict';

    ObjC.import('AppKit');

    // gridCycle :: () -> IO { grouping :: Int, spacing :: Real }
    const gridCycle = () => {

        // SETTINGS -----------------------------------------------
        // cycle :: [Grid Dict]
        const cycle = [{
                visible: true,
                spacing: 10,
                majorSpacing: 10
            },
            {
                visible: true,
                spacing: 100,
                majorSpacing: 10
            },
            {
                visible: false,
                spacing: 10,
                majorSpacing: 10
            }
        ];

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

        // and :: [Bool] -> Bool
        const and = xs => {
            let i = xs.length;
            while (i--)
                if (!xs[i]) return false;
            return true;
        };

        // Handles two or more arguments
        // curry :: ((a, b) -> c) -> a -> b -> c
        const curry = (f, ...args) => {
            const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
                function () {
                    return go(xs.concat(Array.from(arguments)));
                };
            return go([].slice.call(args));
        };

        // eqDict :: Dict -> Dict -> Bool
        const eqDict = (a, b) => {
            const ks = Object.keys(a);
            return (ks.length === Object.keys(b)
                .length) && and((ks
                .map(k => a[k] === b[k])
            ));
        };

        // findIndex :: (a -> Bool) -> [a] -> Maybe Int
        const findIndex = (p, xs) =>
            xs.reduce((a, x, i) =>
                a.nothing ? (
                    p(x) ? just(i) : a
                ) : a, nothing('No match found for: ' + p.toString()));

        // just :: a -> Just a
        const just = x => ({
            nothing: false,
            just: x
        });

        // nothing :: () -> Nothing
        const nothing = (optionalMsg) => ({
            nothing: true,
            msg: optionalMsg
        });

        // rem :: Int -> Int -> Int
        const rem = (n, m) => n % m;

        // GRID CYCLING ------------------------------------------------------

        // readSetting :: [Grid Dict] -> Grid -> Grid Dict
        const readSetting = (cycle, grid) =>
            cycle.length > 0 ? (
                Object.keys(cycle[0])
                .reduce((a, k) => (a[k] = grid[k], a), {})
            ) : {};

        // nextSetting :: [Grid Dict] -> Grid Dict -> Grid Dict
        const nextSetting = (cycle, dctSetting) => {
            const mbIndex = findIndex(
                curry(eqDict)(dctSetting),
                cycle
            );
            return mbIndex.nothing ? (
                cycle[0]
            ) : cycle[rem(mbIndex.just + 1, cycle.length)];
        };

        // GRID CYCLE MAIN ---------------------------------------------------
        const
            grid = document.portfolio.canvases[0].grid,
            dctNext = nextSetting(
                cycle, readSetting(
                    cycle, grid
                )
            );

        return Object.keys(dctNext)
            .map(k => grid[k] = dctNext[k]);
    };
    // END OF OMNIJS FUNCTION ------------------------------------------------

    // JXA EVALUATION CONTEXT ------------------------------------------------
    // (omniJS function stringified and invoked as URL from JXA)

    // omniJS Function -> Arguments -> Bool
    // runOmniJSinOmniGraffle :: omniJS Function -> a -> Bool
    const runOmniJSinOmniGraffle = (f, args) =>
        $.NSWorkspace.sharedWorkspace.openURL(
            $.NSURL.URLWithString(
                'omnigraffle:///omnijs-run?script=' +
                encodeURIComponent(
                    '(' + f.toString() + ')(' + JSON.stringify(args) + ')'
                )
            )
        );

    return (
        // Effect
        runOmniJSinOmniGraffle(gridCycle),
        // Value
        true
    );
})();