omniJS: Setting .shape to 'Bezier' deletes .shapeVertices point list

Perhaps this is by design, but it took a bit of debugging to understand why Bezier shapes were vanishing from the canvas, though still displayed in the outline sidebar, and it seems like a possible analogue of the property setting order bugs that have now been fixed with text and magnet properties.

i.e.

.shape = 'Bezier' 

if fine if set before

.shapeVertices = ...

but turns out to be (perhaps unexpectedly ?) destructive if it follows the supply of the vertices.

If we start out with this:

.shape = 'Bezier' 

will leave us with this:

(If the .shape property had not been specified before the .shapeVertices were)

You can experiment with the setting sequence permutations of .shape and .shapeVertices in the code below.

The pattern seems to be:

.shapeVertices on its own is OK ( the .shape getter will then respond with ‘Bezier’)

.shape then .shapeVertices is OK,

.shape then .shapeVertices then .shape again is also OK,

BUT

.shapeVertices then .shape (not previously specified) turns out to be destructive …

(() => {
   'use strict';

    // log :: a -> IO ()
        const log = (...args) =>
            console.log(
                args
                .map(JSON.stringify)
                .join(' -> ')
            );

        // show :: Int -> a -> Indented String
        // show :: a -> String
        const show = (...x) =>
            JSON.stringify.apply(
                null, x.length > 1 ? [x[1], null, x[0]] : x
            );

        var g1 = Object.assign(
            document.windows[0].selection.canvas.newShape(),
            {
                'geometry' : new Rect(290.57, 235.28, 83.60, 132.03),
                //'shape':'Bezier',
                'shapeVertices': [new Point(385.33, 384.31), new Point(382.58, 379.13), new Point(380.20, 373.78), new Point(378.20, 368.27), new Point(376.59, 362.63), new Point(375.38, 356.89), new Point(374.57, 351.09), new Point(374.17, 345.24), new Point(374.19, 339.37), new Point(374.61, 333.53), new Point(375.44, 327.72), new Point(376.68, 321.99), new Point(378.31, 316.36), new Point(380.34, 310.86), new Point(382.74, 305.52), new Point(385.52, 300.35), new Point(388.65, 295.39), new Point(392.12, 290.67), new Point(395.91, 286.20), new Point(400.00, 282.00), new Point(404.38, 278.10), new Point(409.03, 274.52), new Point(413.91, 271.27), new Point(419.00, 268.38), new Point(424.29, 265.84), new Point(429.74, 263.69), new Point(435.33, 261.92), new Point(432.65, 252.28), new Point(457.77, 268.85), new Point(443.38, 290.82), new Point(440.70, 281.18), new Point(436.44, 282.53), new Point(432.29, 284.17), new Point(428.27, 286.10), new Point(424.39, 288.30), new Point(420.68, 290.77), new Point(417.15, 293.50), new Point(413.82, 296.47), new Point(410.70, 299.66), new Point(407.82, 303.06), new Point(405.18, 306.66), new Point(402.80, 310.43), new Point(400.68, 314.36), new Point(398.85, 318.43), new Point(397.31, 322.61), new Point(396.07, 326.89), new Point(395.13, 331.26), new Point(394.49, 335.67), new Point(394.17, 340.12), new Point(394.16, 344.58), new Point(394.47, 349.03), new Point(395.08, 353.45), new Point(396.00, 357.81), new Point(397.23, 362.10), new Point(398.75, 366.30), new Point(400.56, 370.37), new Point(402.65, 374.31)],
                'shape':'Bezier'
            }
        );
        log('g1 ', g1.constructor.name );
        log('g1.shapeVertices.length', g1.shapeVertices.length);
        return show(
            ['x','y','width','height'].map(k => g1.geometry[k])
        );
    })();

Or, a simpler test example – with a rectangle:

(() => {
   'use strict';

    // log :: a -> IO ()
        const log = (...args) =>
            console.log(
                args
                .map(JSON.stringify)
                .join(' -> ')
            );

        // CREATE A SIMPLE RECTANGLE ---------------------------------------------
        var
            canvas = document.windows[0].selection.canvas,
            g = canvas.newShape();

        g.geometry = new Rect(203.21, 155.91, 64.77, 58.66);

        // CACHE THE RECTANGLE'S SHAPE VERTICES
        // ( in case they are deleted unintentionally )

        var points = g.shapeVertices;

        log('BEFORE', 'g.shapeVertices', g.shapeVertices);

        // REDEFINE THE RECTANGLE AS A BEZIER SHAPE ------------------------------
        g.shape = 'Bezier'

        // Its .shapeVertices have now been unexpectedly DELETED,
        // and the rectangle is left invisible on the canvas

        log('AFTER', 'g.shapeVertices', g.shapeVertices);

        // Unless we restore the points which we may or may not have cached ...


        // UNCOMMENT THE FOLLOWING
        // FOR A VERSION WHICH RESTORES THE RECTANGLE TO VISIBILITY:

        // g.shapeVertices = points

    })();

As a footnote, it is very helpful to make properties insensitive to the order in which they are set – if one passes a whole dictionary of properties to Object.assign, the JavaScript standard doesn’t define the sequence in which the keys of an object/dictionary will be read, and it’s a boon to the scripter not to have to think about it :-)

Thanks, this is fixed in r294090! The old behavior when setting g.shape to ‘Bezier’ was to create a new bezier with no control points, but it is much more reasonable to do as you suggest and convert the control points of the existing shape (whatever it may be) into a new bezier shape with those points.

1 Like