Compile Time Changes

Removed: public void mhFastImportAdjacentAisles(Snapper{} rowsToImport) {
Removed: public symbol getRailLayer(LayerSet classification) {

Removed: public class MhBayEditorPreviewSpace extends MhBayEditorSpace {

public class MhStorageEditorDialog extends DialogWindow : abstract {
- Old: extend public bool shouldbuildSaveLoad() {
- New: extend public bool shouldBuildSaveLoad() {

public class MhStorageGfxSingleton extends MhStorageSingleton : abstract {
- Old: extend public str cache3DKey(MhSnapper owner, FetchEnv3D env) {
- New: extend public str cache3DKey(MhSnapper owner, FetchEnv3D env, str geomKey=null) {
- Old: extend public void appendCache3DKeyArgs(MhSnapper owner, FetchEnv3D env, Object[] args) {
- New: extend public void appendCache3DKeyArgs(MhSnapper owner, FetchEnv3D env, Object[] args, str geomKey=null) {
- Old: extend public Primitive3D get3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D get3D(MhSnapper owner, FetchEnv3D env, str geomKey=null) {
- Old: extend public Primitive3D cachable3D(MhSnapper owner, FetchEnv3D env) : abstract {
- New: extend public Primitive3D cachable3D(MhSnapper owner, FetchEnv3D env, str geomKey=null) : abstract {

public class MhFrameGfxBehavior extends MhGenericGfxBehavior {
- Old: extend public Primitive3D uprightFoot3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D uprightFoot3D(MhSnapper owner, FetchEnv3D env, str geomKey) {
- Old: extend public Primitive3D uprightCap3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D uprightCap3D(MhSnapper owner, FetchEnv3D env, str geomKey) {
- Old: extend public Primitive3D vertical3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D vertical3D(MhSnapper owner, FetchEnv3D env, str geomKey) {

public class MhStorageEditorConfiguration extends MhSystemConfiguration {
- New: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- New: extend public Animation spawnConfigSpreadAnimation(symbol s) {
- New: extend public void removeUnqualifiedRowChildren(MhSnapper row, MhSnapper{} newSnappers) {
- New: extend public MhSnapper{} qualifiedRowChildren(MhSnapper row, MhSnapper{} newSnappers) {
- New: extend public void resetTransform(MhSnapper[] rows) {

public class MhBayEditConfiguration extends MhStorageEditorConfiguration {
- Removed: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- Removed: extend public Animation spawnConfigSpreadAnimation(symbol s) {
- Removed: extend public void removeUnqualifiedRowChildren(MhSnapper row, MhSnapper{} newBays) {
- Removed: extend public MhSnapper{} qualifiedRowChildren(MhSnapper row, MhSnapper{} newBays) {
- Removed: extend public void resetTransform(MhSnapper[] rows) {

public class MhFrameEditConfiguration extends MhStorageEditorConfiguration {
- Removed: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- Removed: extend public Animation spawnConfigSpreadAnimation(symbol s) {
- Old: extend public void pickupFrames(Snapper{} snappers) {
- New: extend public void appendRefSnappers(Snapper{} snappers) {
- New: extend public void pickupFrames(MhSnapper{} snappers) {
- Old: extend public void removeUnusedFrames(Snapper{} snappers) {
- New: extend public void removeUnusedFrames(Snapper{} snappers, Snapper{} accepted=null) {

public class MhStorageEditorDialog extends DialogWindow : abstract {
- New: extend public function(Control) spreadButtonCallback() {

public class MhDrawRowREDBehavior extends MhBehavior {
- Removed: extend public void polyColorToRED(MhPolylineColor pc, Graph g, REDEnv2D lbEnv, REDDynamicLineEnv lineEnv) : deprecated {

public class MhLevelArrangeFunction extends MhSystemEngineFunction {
- Removed: extend public MhEngineEntry[] getPickAndDropEntries(MhEngine engine) {
- Removed: extend public void movePickAndDropEntry(MhEngineEntry main, double oldZ, MhEngineEntry[] pds) {

Change argument type for isBeam classification checking method.

- Old: public bool isBeam(Snapper snapper) {
- New: public bool isBeam(Object o) { 

Added filter when calculating overhang.

- Old: public <double, double> mhRowUnitLoadOverhang(MhSnapper row) {
- New: public <double, double> mhRowUnitLoadOverhang(MhSnapper row, SnapperFilter filter=null) {

MhUnitLoadSpawner changes

Add unitLoadKey as an argument when creating entry collision primitive. This is so that we have more control of the unit load shape, not just getting the unit load shape from current configuration.

public class MhUnitLoadSpawner extends MhStorageSpawner {
- Old: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, LayerSet rootParentClassification) {
- New: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, str unitLoadKey, LayerSet rootParentClassification) {
- New: extend public MhEngineConstructionEntry engineConstructionEntry(str unitLoadKey) {

Added MhEntryLayoutEnv class

Introduce MhEntryLayoutEnv to wrap all necessary info for creating entry layout. So now we can just pass MhEntryLayoutEnv on entryLayout creation instead of having different methods with different arguments.

- New: public class MhEntryLayoutEnv {

public class MhSnapperSpawner extends SnapperSpawner {
- Removed: extend public MhEntryLayout entryLayout(MhSystemSpawnerSelector ss, LayerSet rootParentClassification) {
- Removed: extend public MhEntryLayout entryLayout(MhSystemSpawnerSelector ss) {

MhStorageGfxSingleton additional argument for geometry key

Methods for 3D graphics in MhStorageGfxSingleton have been updated to take in an optional str geomKey argument in an effort to help support building different graphics for specific geometries.

MhFrameGfxBehavior better support for different graphics based on geometries

MhFrameGfxBehavior has been made easier to build separate graphics for the front upright and the back upright.

We have updated existing 3D graphics methods to take in an optional str geomKey argument.

- Old: extend public Primitive3D uprightFoot3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D uprightFoot3D(MhSnapper owner, FetchEnv3D env, str geomKey) {
- Old: extend public Primitive3D uprightCap3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D uprightCap3D(MhSnapper owner, FetchEnv3D env, str geomKey) {
- Old: extend public Primitive3D vertical3D(MhSnapper owner, FetchEnv3D env) {
- New: extend public Primitive3D vertical3D(MhSnapper owner, FetchEnv3D env, str geomKey) {

The logic for building the frame graphics has also been modified. Previously it would build graphics for the front upright, and copy and transform it to represent the back upright. It will now separately build the graphics for the back upright.

Old:
    extend public Primitive3D frame3D(MhSnapper owner, FetchEnv3D env) {
        return cache3D(cacheKey3D(owner, env)) {
            const box b = owner.shapeBound([sUpright]);
            Primitive3D[] uprightPrims();
            Primitive3D vert = vertical3D(owner, env);
            uprightPrims << vert;
            uprightPrims << Instance3D(vert, 180deg, (b.w, b.d, 0));
            ...
            Primitive3D[] footPrims();
            Primitive3D foot = uprightFoot3D(owner, env);
            footPrims << mhInstance3D(foot, symTransform(owner, cUprightFootFront));
            footPrims << mhInstance3D(foot, symTransform(owner, cUprightFootBack));
            ...
            Primitive3D[] capPrims();
            Primitive3D cap = uprightCap3D(owner, env);
            capPrims << mhInstance3D(cap, symTransform(owner, cUprightFootFront));
            capPrims << mhInstance3D(cap, symTransform(owner, cUprightFootBack));
            ...
        };
    }

New:
    extend public Primitive3D frame3D(MhSnapper owner, FetchEnv3D env) {
        return cache3D(cacheKey3D(owner, env)) {
            const box b = owner.shapeBound([sUpright]);
            Primitive3D[] uprightPrims();
            Primitive3D frontVert = vertical3D(owner, env, cUprightFront);
            uprightPrims << frontVert;
            Primitive3D backVert = vertical3D(owner, env, cUprightBack);
            uprightPrims << Instance3D(backVert, 180deg, (b.w, b.d, 0));
            ...
            Primitive3D[] footPrims();
            Primitive3D frontFoot = uprightFoot3D(owner, env, cUprightFootFront);
            footPrims << mhInstance3D(frontFoot, symTransform(owner, cUprightFootFront));
            Primitive3D backFoot = uprightFoot3D(owner, env, cUprightFootBack);
            footPrims << mhInstance3D(backFoot, symTransform(owner, cUprightFootBack));
            ...
            Primitive3D[] capPrims();
            Primitive3D frontCap = uprightCap3D(owner, env, cUprightCapFront);
            Primitive3D backCap = uprightCap3D(owner, env, cUprightCapBack);
            capPrims << mhInstance3D(frontCap, symTransform(owner, cUprightFootFront));
            capPrims << mhInstance3D(backCap, symTransform(owner, cUprightFootBack));
            ...
        };
    }

It is also easier to override the singletons used for building upright graphics.

public class MhFrameGfxBehavior extends MhGenericGfxBehavior {

    /***********************************************************************
     * Singletons
     ***********************************************************************/

    /**
     * Get upright type.
     */
    extend public MhStorageUprightType getUprightType(MhFrameShape shape, str geomKey=null) {
        return shape.getUprightType();
    }


    /**
     * Get upright foot.
     */
    extend public MhStorageUprightFoot getUprightFoot(MhFrameShape shape, str geomKey=null) {
        return shape.getUprightFoot();
    }


    /**
     * Get upright cap.
     */
    extend public MhStorageUprightCap getUprightCap(MhFrameShape shape, str geomKey=null) {
        return shape.getUprightCap();
    }
}

Removed MhBayEditorPreviewSpace class

MhBayEditorPreviewSpace has been removed and its functionality of preventing snapper selections has been moved to the parent class MhBayEditorSpace as an optional feature. Set MhBayEditorSpace.blockEdit = true to block space selections.

public class MhBayEditorSpace extends MhStorageEditorSpace {

    /**
     * Select.
     */
    public void select(SpaceSelection s,
                       bool includeSnapped=false,
                       bool updatePropDlg=true,
                       bool undoable=false,
                       bool updateCameras=false) {
        if (blockEdit) s = null;
        ...
    }

MhStorageEditorConfiguration changes

Some methods have been moved down from MhBayEditConfiguration and MhFrameEditConfiguration.

public class MhStorageEditorConfiguration extends MhSystemConfiguration {
- New: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- New: extend public Animation spawnConfigSpreadAnimation(symbol s) {
- New: extend public void removeUnqualifiedRowChildren(MhSnapper row, MhSnapper{} newSnappers) {
- New: extend public MhSnapper{} qualifiedRowChildren(MhSnapper row, MhSnapper{} newSnappers) {
- New: extend public void resetTransform(MhSnapper[] rows) {
public class MhBayEditConfiguration extends MhStorageEditorConfiguration {
- Removed: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- Removed: extend public Animation spawnConfigSpreadAnimation(symbol s) {
- Removed: extend public void removeUnqualifiedRowChildren(MhSnapper row, MhSnapper{} newBays) {
- Removed: extend public MhSnapper{} qualifiedRowChildren(MhSnapper row, MhSnapper{} newBays) {
- Removed: extend public void resetTransform(MhSnapper[] rows) {
public class MhFrameEditConfiguration extends MhStorageEditorConfiguration {
- Removed: extend public Window buildSpreadButtons(Window w, function(Control) callback) {
- Removed: extend public Animation spawnConfigSpreadAnimation(symbol s) {

MhFrameEditConfiguration changes

The existing method pickupFrames(Snapper{} snappers) has been renamed to appendRefSnappers(Snapper{} snappers) which better fits the logic of this method. Replace all overrides and calls to the new method name.

public class MhFrameEditConfiguration extends MhStorageEditorConfiguration {
- Old: extend public void pickupFrames(Snapper{} snappers) {
- New: extend public void appendRefSnappers(Snapper{} snappers) {

A new method pickupFrames(MhSnapper{} snappers) has been introduced to generate copies of a set of snappers and put them into editor space, similarly to the existing MhBayEditConfiguration.pickupBays(MhSnapper{} snappers).

public class MhFrameEditConfiguration extends MhStorageEditorConfiguration {
- New: extend public void pickupFrames(MhSnapper{} snappers) {

Replace all overrides and calls to removeUnusedFrames(Snapper{} snappers) with removeUnusedFrames(Snapper{} snappers, Snapper{} accepted=null).

public class MhFrameEditConfiguration extends MhStorageEditorConfiguration {
- Old: extend public void removeUnusedFrames(Snapper{} snappers) {
- New: extend public void removeUnusedFrames(Snapper{} snappers, Snapper{} accepted=null) {

MhStorageEditorDialog changes

The method spreadButtonCallback() has been moved down from MhBayEditor and MhFrameEditor.

public class MhStorageEditorDialog extends DialogWindow : abstract {
- New: extend public function(Control) spreadButtonCallback() {

MhStorageEditorConfiguration and MhStorageEditorItem changes

When pushing properties to preview snappers, we now add the property that trigger this event as part of the argument.

public class MhStorageEditorConfiguration extends MhSystemConfiguration {
- Old: extend public void pushPropToPreview(Space space, MhStorageEditorItem item) {
- New: extend public void pushPropToPreview(Space space, MhStorageEditorItem item, CoreProperty property=null) {

public class MhStorageEditorItem extends MhSystemConfigurationItem : abstract {
- Old: extend public MhSnapper{} applySnappers(Space space) {
- New: extend public MhSnapper{} applySnappers(Space space, CoreProperty property=null) {

MhRowChildStealConfigFunction changes

Instead of assuming that there will only be 1 child at 1 z position, now this children map stores an array of children per 1 z positon.

public class MhRowChildStealConfigFunction extends MhSystemEngineFunction {
- Old: private double->MhEngineEntry children;
- New: private double->MhEngineEntry[] children;

MhAisleUpdateWidthFunction changes

As we change some calculations for overhang, we rename some functions to better suit their purposes.

public class MhAisleUpdateWidthFunction extends MhSystemEngineFunction {
- Old: extend public box unmodifedBoundIncludingChildren(MhEngineBoxEntry entry, Transform t, MhEngine engine, LayerSet ls=null) {
- New: extend public box unmodifiedOverhangLocalBound(MhEngineBoxEntry entry, Transform t, MhEngine engine, LayerSet ls=null) {
- Old: extend public box localBoundIncludingChildren(MhEngineEntry entry, Transform t, MhEngine engine, LayerSet ls=null) {
- New: extend public bool shouldIncludeChildrenBound(MhEngineEntry entry) {

MhStorageConfigurator changes.

Instead of only having one dialog at a time, now we can have multiple dialogs with package as the key.

- Old: private DialogWindow _dialog : keep;
- New: private symbol->DialogWindow _dialogs : keep;

All buttons (add, duplicate, delete, rename, import, export) are moved to a menubar.

public class MhStorageConfiguratorDialog extends DialogWindow {
- Removed: public Button addBtn;
- Removed: public Button duplicateBtn;
- Removed: public Button deleteBtn;
- Removed: public Button renameBtn;
- Removed: public Button importBtn;
- Removed: public Button exportBtn;
- Removed: extend public void buildButtons(Window w) {
- Added: extend public void buildMenuBar(MenuBar menuBar) {

- New: public class MhConfiguratorCoolMenuBar extends FaceliftMenuBar {

MhPrimPopulatorStepper and MhEngineEntryPopulatorStepper

Introduce new field alwaysResolve to control when the populator should be resolving or skip the resolver if it's not colliding.

- Old: public constructor(CollisionPrimitive startPrim, CollisionPrimitive endPrim=null, CollisionPrimitive additionalPrim=null, bool useFixedStep=false) {
- New: public constructor(CollisionPrimitive startPrim, CollisionPrimitive endPrim=null, CollisionPrimitive additionalPrim=null, bool useFixedStep=false, bool alwaysResolve=true) {

Removed public functions

The function mhFastImportAdjacentAisles(Snapper{} rowsToImport) has been removed. Its functionality has been moved into MhAisleFastImportBehavior.fastImportAdjacentAisles(Snapper{} rowsToImport).

Removed: public void mhFastImportAdjacentAisles(Snapper{} rowsToImport) {

public class MhAisleFastImportBehavior extends MhBehavior {

    /**
     * Run fast import on adjacent aisle to get the loadToLoad gap prop.
     */
    extend public void fastImportAdjacentAisles(Snapper{} rowsToImport) {
        if (rowsToImport.empty) return;
        
        MhSnapper{} visited(rowsToImport.count);
        for (MhSnapper row in rowsToImport) {
            if (row in visited or !isRow(row) or isAisle(row)) continue;
            for (r in getConnectedRows(row)) {
                if (r in visited) continue;
                mhFastImportToEngine(r);
                visited << r;
            }
        }
    }
}

The function getRailLayer(LayerSet classification) has been removed as it was not used.

Removed: public symbol getRailLayer(LayerSet classification) {

MhDrawRowREDBehavior changes

The deprecated method polyColorToRED(MhPolylineColor pc, Graph g, REDEnv2D lbEnv, REDDynamicLineEnv lineEnv) has been removed. Use polyColorToRED(MhPolylineColor pc, Graph g, REDLayerBuffer2D lb, REDEnv2D lbEnv, REDDynamicLineEnv lineEnv) instead.

- Removed: extend public void polyColorToRED(MhPolylineColor pc, Graph g, REDEnv2D lbEnv, REDDynamicLineEnv lineEnv) : deprecated {

MhLevelArrangeFunction changes

The following methods getPickAndDropEntries(MhEngine engine) and movePickAndDropEntry(MhEngineEntry main, double oldZ, MhEngineEntry[] pds) have been removed as they are no longer needed.

- Removed: extend public MhEngineEntry[] getPickAndDropEntries(MhEngine engine) {
- Removed: extend public void movePickAndDropEntry(MhEngineEntry main, double oldZ, MhEngineEntry[] pds) {

Runtime/Behavior Changes

Drawing configurations changes for bay and frame editors

Drawing configurations are now directly editable in the bay and frame editors. We have made several updates to the editors and configurations to support this.

You can control which configurations are editable by overriding isConfigEditable(MhStorageEditorConfiguration config). This will disable the various editor tabs, quick properties as well as preview snappers for the given configuration.

public class MhStorageEditorDialog extends DialogWindow : abstract {

    /**
     * Is config editable?
     */
    extend public bool isConfigEditable(MhStorageEditorConfiguration config) {
        if (config.?drawingConfig) {
            for (snapper in mainSpace.snappers) {
                forChildrenRecursive(MhSnapper child in snapper) {
                    if (child.configEq(config)) {
                        if (child.readOnly()) {
                            return false;
                        }
                    }
                }
            }

            return drawingConfigsEditable();
        }
        return true;
    }
}

Additionally if you do not want drawing configurations to be editable like before, then you can override drawingConfigsEditable().

public class MhStorageEditorDialog extends DialogWindow : abstract {

    /**
     * Drawing configs editable?
     */
    extend public bool drawingConfigsEditable() {
        return true;
    }
}

MhStorageEditorDialog now has a default implementation for buildApplyButtons(Window w). It is broken up into 2 sub-windows, spreadSub and saveAndApplyButtonsSub. spreadSub is built by the configuration and should already be present in existing implementations. We have added saveAndApplyButtonsSub as a new sub-window which by default is positioned to the left of spreadSub. saveAndApplyButtonsSub is used by drawing configurations to apply the modifications directly to snappers in the drawing.

If you have overridden how your dialog builds its windows, you may have to look into adding the saveAndApplyButtonsSub to your overridden code to ensure users can apply drawing configuration changes. The methods below were added to support controlling these 2 sub-windows.

public class MhStorageEditorDialog extends DialogWindow : abstract {

    /**
     * Build apply buttons.
     */
    extend public Window buildApplyButtons(Window w) {
        MhStorageEditorConfiguration config = currentConfig();
        if (!config) return null;

        SubWindow sub(w, frameStyle=noFrame);
        spreadSub = config.?buildSpreadButtons(sub, spreadButtonCallback);
        saveAndApplyButtonsSub = buildSaveAndApplyButtons(sub);

        if (saveAndApplyButtonsSub and spreadSub) {
            spreadSub.rightOf(saveAndApplyButtonsSub);
            saveAndApplyButtonsSub.alignCenterY(spreadSub);
        }
        sub.autoSize();
        return sub;
    }


    /**
     * Spread buttons subwindow enabled?
     */
    extend public bool spreadSubEnabled() {
        MhStorageEditorConfiguration config = currentConfig();
        return config and (!config.drawingConfig or !isConfigModified(config));
    }


    /**
     * Show save and apply buttons.
     */
    extend public bool showSaveAndApplyButtons() {
        return currentConfig().?drawingConfig;
    }


    /**
     * Save and apply button enabled?
     */
    extend public bool saveAndApplyButtonEnabled() {
        MhStorageEditorConfiguration config = currentConfig();
        return config and config.drawingConfig and isConfigModified(config);
    }


    /**
     * Save and apply all button enabled?
     */
    extend public bool saveAndApplyAllButtonEnabled() {
        return anyModifiedConfigs();
    }


    /**
     * Build save and apply buttons.
     */
    extend public Window buildSaveAndApplyButtons(Window w) {
        SubWindow sub(w, frameStyle=noFrame);
        saveAndApplyBtn = Button(sub, key="saveAndApply",
                                 label=$saveAndApplyExisting, textSide=alignment.down,
                                 callback=buttonCallback());
        saveAndApplyAllBtn = Button(sub, key="saveAndApplyAll",
                                    label=$saveAndApplyAll,
                                    textSide=alignment.down,
                                    callback=buttonCallback());
        saveAndApplyAllBtn.rightOf(saveAndApplyBtn);
        sub.autoSize();
        return sub;
    }


    /**
     * Toggle spread buttons.
     */
    extend public void toggleSpreadButtons() {
        spreadSub.?enable(spreadSubEnabled(), update=true);
    }


    /**
     * Toggle save and apply buttons.
     */
    extend public void toggleSaveAndApplyButtons() {
        if (showSaveAndApplyButtons()) {
            saveAndApplyButtonsSub.show();
            saveAndApplyBtn.?enable(saveAndApplyButtonEnabled(), update=true);
            saveAndApplyAllBtn.?enable(saveAndApplyAllButtonEnabled(), update=true);
        } else {
            saveAndApplyButtonsSub.hide();
        }
    }
}

As for applying configurations to drawing snappers, a new method in MhStorageEditorConfiguration was added to support this which uses the existing spread animation defined by this class.

public class MhStorageEditorConfiguration extends MhSystemConfiguration {

    /**
     * Apply to space.
     */
    extend public bool applyToSpace(Space space, Snapper{} affected=null, bool validate=false) {
        if (MhSnapperApplyAnimation anim = getApplyToSpaceAnim(space)) {
            animate(anim);
            anim.applyEvent();
            if (affected) affected += anim.affectedSnappers;
            if (validate) space.validateSnapperInvalidations();
            abortAnimation();
            return true;
        }

        return false;
    }
}

When a user attempts to close the bay/frame editor, if there are any unapplied drawing configuration changes, they will by default receive a prompt notifying them. You can override this feature with promptUnappliedChanges().

public class MhStorageEditorDialog extends DialogWindow : abstract {

    /**
     * Close event.
     */
    public void close() {
        if (!promptUnappliedChanges()) return;
        ...
    }


    /**
     * Prompt unapplied changes dialog.
     * Return false when user clicks cancel.
     */
    extend public bool promptUnappliedChanges() {
        if (anyModifiedConfigs()) {
            str label = unappliedChangesLabel();
            if (!label) {
                if (developMode) ptrace("Missing label");
                return true;
            }

            FlexDialog mgBox(label);
            mgBox.setCustomCaption(getLabel());
            mgBox.addButton($saveAndApplyAll, dialogResult.ok, image=icon("apply"), closeOnClick=true);
            mgBox.addButton($close, dialogResult.no, image=icon("no"), closeOnClick=true);
            mgBox.addButton($cancelButtonLabel, dialogResult.cancel, image=icon("cancel"), closeOnClick=true);

            int res = mgBox.showModal();
            if (res.dialogResult.cancel) {
                return false;
            } else if (res.dialogResult.ok) {
                event("saveAndApplyAll");
            }
        }

        return true;
    }
}

Lastly, we now emit different events for selecting a snapper selectionChanged as well as renaming configs configRenamed. Previously both these actions would emit the afterPropertyChanged event.

package class MhStorageEditorWorldEvents extends EventViewer {

Old:
    /**
     * Event.
     */
    public bool event(symbol event, Object z, str->Object args) {
        if (event == #endUndoStep) {
            if (z as UndoStep) {
                mhStorageEditorNotifyEvent("afterPropertyChanged");
            }
        }
        return false;
    }


New:
    /**
     * Event.
     */
    public bool event(symbol event, Object z, str->Object args) {
        if (event == #endUndoStep) {
            if (z as UndoStep) {
                if (z.operations.count == 1) {
                    UndoOp op = z.operations.first;
                    if (op in UndoSelectOp) {
                        mhStorageEditorNotifyEvent("selectionChanged");
                    } else if (op in MhStorageEditorConfigRenameUndoOp) {
                        mhStorageEditorNotifyEvent("configRenamed");
                    }
                } else {
                    mhStorageEditorNotifyEvent("afterPropertyChanged");
                }
            }
        }
        return false;
    }
}

Rows break all soft connections

MhRowShape has extended the breakAllConnections feature to also break all soft connections.

public class MhRowShape extends MhSnapperShape {

    /**
     * Disconnect all connections in this snapper, except those
     * who are attached to a snapper in 'exceptTo'.
     */
    public void breakAllConnections(Snapper{} exceptTo=null) {
        if (owner.isActive and owner.useSoftConnect()) {
            mhBreakAllSoftConnections(owner, exceptTo=exceptTo);
        }
    }
}

MhRowStretchAnimation has also been updated to maintain soft connections so that all soft connections in a system will not be broken whenever the stretch animation is run.

public class MhRowStretchAnimation extends MhPropertyStretchAnimation {

    /**
     * Keep connected.
     */
    public Snapper{} keepConnected() {
        Snapper{} res = super(..);

        Snapper{} snappers();
        snappers += ogSelection.snappers;

        Snapper{} visited();
        for (z in snappers) {
            for (MhRowConnectLine c in z.connectors()) {
                if (c.isSoftConnected()) {
                    bool cr = c.keepConnectedDuringAnimation(ogSelection);
                    Snapper{} connSnappers = mhGetSoftConnectedSnappers(c);
                    for (z2 in connSnappers) {
                        if (z2 in visited) continue;
                        visited << z2;
                        if (z2.keepConnectedDuringAnimation(ogSelection) or cr) {
                            snappers += connSnappers;
                        }
                    }
                }
            }
        }

        return res + snappers;
    }

MhStorageConfiguration changes

When loading configuration, instead of checking the configuration package against class.pkg, check with the package. As well as when getting addtional suffixes in additionalSuffixes, embeding security info in embedPackageInfo, and validating package info in validPackageInfo. This is due to when we save the configuration, we put the package into the formatter.

    /**
     * Pre loading check for configuration file.
     */
    public bool isAllowedLoadConfigurationFile(Url url) {
	bool canLoad = super(..);
	using (File f = url.openForRead()) {
	    RobustFormatter formatter(f, Version(fastRobustVersion));
	    if (formatter.stream.remaining > 0) {
		// label object.
		formatter.getObject();
	    }
	    if (formatter.stream.remaining > 0) {
		// package object.
		?symbol savedPkg = formatter.getObject();
		if (savedPkg != package) canLoad = false;
	    }
	}
	return canLoad;
    }

MhLevelArrangeFunction2, a better and simpler MhLevelArrangeFunction

MhLevelArrangeFunction2 is replacing the old MhLevelArrangeFunction, providing simpler logic and clearer structure. It also separate out levelChildArrange into another engine function.

- New: public class MhUnitLoadStandOnUpdateFunction extends MhSystemEngineFunction {
- New: public class MhLevelArrangeFunction2 extends MhSystemEngineFunction {

MhUnitLoadArrangement, to handle unitload layout on unit load.

New unit load arrangement class to help populating unit loads on level. MhUnitLoadConstructionalPopulateFunction will now be using the arrangement class to handle unit load creation.

- New: public class MhUnitLoadArrangement
- New: public class MhConstructionUnitLoadArrangement extends MhUnitLoadArrangement
- New: public class MhPopulateUnitLoadArrangement extends MhUnitLoadArrangement
- New: public class MhClearanceUnitLoadArrangement extends MhUnitLoadArrangement
- New: public class MhFillSpacesUnitLoadArrangement extends MhUnitLoadArrangement

Introduce MhUnitLoadEnsureClearanceFunction2 to replace MhUnitLoadEnsureClearanceFunction to use the new unit load arrangement. The MhUnitLoadEnsureClearanceFunction will still be available for extensions to opt-out from this changes by overriding the engine function registered in assortment.

MhPopulator

Added new method reset in MhPopulator class to reset the state of populator. Engine now is a copy by reference field to allow for populator copy.

- New: public void reset() {

MhCompartmentConfigurationItem

Added support noOfUnitLoadsY to handle number of unitload in perpendecular direction.