How to get correct hexagon shape?

How do I get the shape tool to create an accurately proportioned hexagon (i.e.where all internal angles are 60°)?

If I hold down the shift key (the intuitive action), I get a hexagon that is cube-like (i.e. as tall as it is wide). This is not a proper hexagon.

Help, please! Thanks.

Might be worth looking at:

http://www.petermcm.dircon.co.uk/software/og_reg_polys/index.html

Or, here calling omniJS code from the Script Editor to create six hexagons:

09

(() => {

    // MAIN -------------------------------------------------------------------
    const ogJSContext = () => {

        // main :: IO ()
        const main = () => {
            const
                radius = 50,
                XCenter = 300,
                YCenter = 300,
                cnv = document.portfolio.canvases[0];

            return (
                polygon(6, XCenter, YCenter, radius * 2)
                .forEach(
                    tplXY => ogHexagon(
                        cnv, tplXY[0], tplXY[1], radius
                    )
                ),
                'Six hexagons created;'
            );
        };

        // ogHexagon :: Real -> Real -> Real -> OGShape
        const ogHexagon = (cnv, x, y, radius) =>
            Object.assign(cnv.newShape(), {
                shapeControlPoints: concatMap(
                    tpl => replicate(3, new Point(tpl[0], tpl[1])),
                    polygon(6, x, y, radius)
                )
            });

        // pointFromRadialVector :: Real -> Real -> Real -> Real -> Point
        const pointFromRadialVector = (xOrigin, yOrigin, distance, angle) => {
            const delta = (f, from) => f(angle) * distance + from;
            return Tuple(
                delta(Math.cos, xOrigin),
                delta(Math.sin, yOrigin)
            );
        };

        // polygon :: Int -> Float -> Float -> Float -> (Float, Float)
        const polygon = (n, x, y, radius) => {
            const d = (Math.PI * 2) / n;
            return map(
                i => pointFromRadialVector(
                    x, y, radius, d * i
                ),
                enumFromToInt(0, n - 1)
            );
        };

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

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

        // concat :: [[a]] -> [a]
        // concat :: [String] -> String
        const concat = xs =>
            xs.length > 0 ? (() => {
                const unit = typeof xs[0] === 'string' ? '' : [];
                return unit.concat.apply(unit, xs);
            })() : [];

        // enumFromToInt :: Int -> Int -> [Int]
        const enumFromToInt = (m, n) =>
            n >= m ? Array.from({
                length: Math.floor(n - m) + 1
            }, (_, i) => m + i) : [];

        // map :: (a -> b) -> [a] -> [b]
        const map = (f, xs) => xs.map(f);

        // concatMap :: (a -> [b]) -> [a] -> [b]
        const concatMap = (f, xs) => [].concat.apply([], xs.map(f));

        // replicate :: Int -> a -> [a]
        const replicate = (n, x) =>
            Array.from({
                length: n
            }, () => x);

        // showJSON :: a -> String
        const showJSON = x => JSON.stringify(x, null, 2);

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

    return Application('OmniGraffle')
        .evaluateJavascript(
            '(' + ogJSContext + ')()'
        );
})();

Or, for packed hexagons:

01

(() => {

    // Packed polygons

    // MAIN -----------------------------------------------------------------
    const ogJSContext = () => {

        // main :: IO ()
        const main = () => {
            const
                radius = 50,
                XCenter = 300,
                YCenter = 300,
                cnv = document.portfolio.canvases[0];

            return (
                // Middle hexagon,
                ogHexagon(
                    cnv, 0, XCenter, YCenter, radius
                ),

                // list of centers for surrounding hexagons, and
                rotatedPolygon(
                    Math.PI / 2, 6, XCenter, YCenter, radius * Math.sqrt(3)
                )

                // surrounding hexagons themselves.
                .forEach(
                    tplXY => ogHexagon(
                        cnv, 0, tplXY[0], tplXY[1], radius
                    )
                ),
                'Seven packed hexagons created;'
            );
        };

        // ogHexagon :: OGCanvas -> Real -> Real -> Real -> Real -> OGShape
        const ogHexagon = (cnv, rotation, cx, cy, radius) =>
            Object.assign(cnv.newShape(), {
                shapeControlPoints: concatMap(
                    tpl => replicate(3, new Point(tpl[0], tpl[1])),
                    rotatedPolygon(rotation, 6, cx, cy, radius)
                )
            });

        // pointFromRadialVector :: Real -> Real -> Real -> Real -> Point
        const pointFromRadialVector = (ox, oy, angle, distance) => {
            const delta = (f, from) => f(angle) * distance + from;
            return Tuple(
                delta(Math.cos, ox),
                delta(Math.sin, oy)
            );
        };

        // rotatedPolygon :: Real -> Int
        //          -> Real -> Real -> Real -> (Real, Real)
        const rotatedPolygon = (rotation, n, cx, cy, radius) => {
            const d = (Math.PI * 2) / n;
            return map(
                i => pointFromRadialVector(
                    cx, cy, (d * i) + rotation, radius
                ),
                enumFromToInt(0, n - 1)
            );
        };

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

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

        // concat :: [[a]] -> [a]
        // concat :: [String] -> String
        const concat = xs =>
            xs.length > 0 ? (() => {
                const unit = typeof xs[0] === 'string' ? '' : [];
                return unit.concat.apply(unit, xs);
            })() : [];

        // enumFromToInt :: Int -> Int -> [Int]
        const enumFromToInt = (m, n) =>
            n >= m ? Array.from({
                length: Math.floor(n - m) + 1
            }, (_, i) => m + i) : [];

        // map :: (a -> b) -> [a] -> [b]
        const map = (f, xs) => xs.map(f);

        // concatMap :: (a -> [b]) -> [a] -> [b]
        const concatMap = (f, xs) =>
            [].concat.apply([], xs.map(f));

        // replicate :: Int -> a -> [a]
        const replicate = (n, x) =>
            Array.from({
                length: n
            }, () => x);

        // showJSON :: a -> String
        const showJSON = x => JSON.stringify(x, null, 2);

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

    return Application('OmniGraffle')
        .evaluateJavascript(
            '(' + ogJSContext + ')()'
        );
})();

Thanks for the advice.

But really, resorting to scripts / code just to produce a shape…!!

Any app that calls itself a drawing program, but can’t even produce true geometric shapes… Well, that’s a hobby for coders, not a tool for designers.

My mistake, I thought Omni was better than that. Disappointed.

Once you have a single geometrically correct hexagon, you can put it in a template like any other shape.

No need for script thereafter, and no need for you to write the script :-)

Or so I thought :-)

It turns out that:

  • while we can indeed create geometrically correct hexagons in OG 7.8,
  • they are automagically distorted to fit into a perfect square, after the first selection-release after a drag to move.

Looks like a bug …

Before in the foreground below (geometrically correct)
After selection and release in the background below – now too thin – distorted to fit, against Nature, Reason, Euclid and the properties of 2D space*, into a perfect square …

(The height of an equilateral triangle is not equal to the length of its base, but OG is squishing triangles to make them fit into perfect squares here …)

And the same fate is suffered (in 7.8 test (v188.2 r310917)), by any regular N* polygons which we drag into a canvas from Peter McMaster’s regular polygons template at:

http://www.petermcm.dircon.co.uk/software/og_reg_polys/index.html

(* Where N is not a multiple of four)

( Note also that ⌘Z Edit > Undo doesn’t restore the lost aspect ratio after this happens. )

Update for 7.8 test (v188.2 r311098)

It could be that there has now been some progress on aspect ratio, but hard to be sure, because the shapes now become invisible on drag+release - they are converted from Bezier to Rectangle and lose their visible line etc properties

( Note ⌘Z Edit > Undo doesn’t restore their visibility - the GIF below is just being looped by the browser )

If we change their stroke setting in the Inspector from No Stroke -> Single Stroke then we are left with a visible rectangle, but no sign of the lost hexagon.

ScreenFlow3

And this also happens in the GUI to any shape manually created with Edit > Objects > Convert Line to Shape

A workaround which may shed some light is that if we:

  1. Create a shaped by Edit > Objects > Convert Line to Shape,
  2. save and close the file before drag-moving the Bezier shape(s), and
  3. re-open the file

Then the loss of size, stroke and visibility from the new shapes when they are drag-moved in the UI does not occur …

UPDATE

Fixed here, with the help of Omni Support, by removing an old template and restarting with the default one.

The aspect ratio is now stable, and the shapes no longer shed their properties and turn into rectangles when selection is released at the end of a drag-to-move.