Compile Time Changes

Engine entry changes

In class MhEngineEntryBase, removed argument MhEngine engine from method parent as it is no longer required (explained below).

public class MhEngineEntryBase {
    Old: extend public MhEngineEntry parent(MhEngine engine) { return null; }
    New: extend public MhEngineEntry parent() { return null; }
}

Moved a field MhEngineEntry parent from child class MhEngineConstructionEntry up to the parent class MhEngineSnapperEntry.

public class MhEngineSnapperEntry extends MhEngineBoxEntry {
    New: public MhEngineEntry parent : copy=reference;
}


public class MhEngineConstructionEntry extends MhEngineSnapperEntry {
    Removed: public MhEngineEntry parent : copy=reference;
}

With this, MhEngineSnapperEntry now has a direct reference to its parent entry. The parent() method (previously parent(MhEngine engine)) no longer needs to refer to its snapper's parent.

public class MhEngineSnapperEntry extends MhEngineBoxEntry {
Old:
    /**
     * Parent.
     */
    public MhEngineEntry parent(MhEngine engine) {
        ?MhSnapper parent = z.parent;
        return engine.?entry(parent);
    }


New:
    /**
     * Parent.
     */
    public MhEngineEntry parent() {
        return parent;
    }
}

For your info, there is now a proper interface to change the parent of an engine entry.

public class MhEngineEntryBase {
    New: extend public void changeParent(MhEngine engine, MhEngineEntry newParent) { }

In class MhEngineBoxEntry renamed methods box unmodifedBound() to box unmodifiedBound() and Transform unmodifedTransform() to Transform unmodifiedTransform().

public class MhEngineBoxEntry extends MhEngineCollisionEntry {
    Old: extend public box unmodifedBound() {
    New: extend public box unmodifiedBound() {

    Old: extend public Transform unmodifedTransform() {
    New: extend public Transform unmodifiedTransform() {
}

The public function box unmodifedBoundIncludingChildren(MhEngineBoxEntry entry, MhEngine engine) has been renamed to box unmodifiedBoundIncludingChildren(MhEngineBoxEntry entry, MhEngine engine).

Old: public box unmodifedBoundIncludingChildren(MhEngineBoxEntry entry, MhEngine engine) {
New: public box unmodifiedBoundIncludingChildren(MhEngineBoxEntry entry, MhEngine engine) {

MhAlignLoadsToAisleFunction changes

The method box unmodifedLocalBoundIncludingChildren(MhEngineEntry entry, Transform t, MhEngine engine) has been renamed to box unmodifiedLocalBoundIncludingChildren(MhEngineEntry entry, Transform t, MhEngine engine).

public class MhAlignLoadsToAisleFunction extends MhSystemEngineFunction {
    Old: extend public box unmodifedLocalBoundIncludingChildren(MhEngineEntry entry, Transform t, MhEngine engine) {
    New: extend public box unmodifiedLocalBoundIncludingChildren(MhEngineEntry entry, Transform t, MhEngine engine) {
}

MhMasterLinkFinalizeFunction changes

Added new argument runMultiChildrenLink to this engine function. The purpose of this argument is to link nested bays/frames together (bays within bay, frames within frame). See "Multi-bay and multi-frame support" in New Features for more info.

public class MhMasterLinkFinalizeFunction extends MhSystemEngineFunction {
    /**
     * Run multi children link.
     */
    public Bool runMultiChildrenLink;

MhRowFillVoidEntriesFunction changes

Added new argument flattenedFill to this engine function. The purpose of this argument is for racking systems with multi-bay/multi-frame structure (see "Multi-bay and multi-frame support" in New Features for more info). Set this argument to true if you want to fill empty bay space depth-wise when you have a multi-bay. Control this argument with MhBayRowEngineBehavior.shouldFlattenRowForFill(symbol event="", Object env=null).

public class MhBayRowEngineBehavior extends MhEngineBehavior {
    /**
     * Should flatten row for fill voids?
     */
    extend public bool shouldFlattenRowForFill(symbol event="", Object env=null) {
        return true;
    }
}

We've also broken up the existing logic in fillVoidEntries(MhEngineEntryBlock block) into several new methods for easier override.

public class MhRowFillVoidEntriesFunction extends MhSystemEngineFunction {

    /**
     * Fill removed snappers void entries.
     */
    extend public void fillRemovedSnappersVoidEntries(MhEngineEntryBlock block) {
        ...
    }


    /**
     * Fill gaps between entries with void entries.
     */
    extend public void fillGapsVoidEntries(MhEngineEntryBlock block,
                                           sorted int->MhEngineVoidEntry[] voidEntries) {
        ...
    }

MhRowRemoveLoneFramesFunction changes

Added argument MhEngineEntry{} bayEntries to the method shouldRemoveEntry.

public class MhRowRemoveLoneFramesFunction extends MhSystemEngineFunction {
    Old: extend public bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry) {
    New: extend public bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry, MhEngineEntry{} bayEntries) {
}

MhBayConfigurationItem changes

Added argument SnapperFilter filter to the method getBay.

public class MhBayConfigurationItem extends MhStorageConfigurationItem {
    Old: extend public MhSnapper getBay() {
    New: extend public MhSnapper getBay(SnapperFilter filter) {
}

MhRowApplyAnimation changes

Added argument MhSnapperGroup group to the method applyPickedUpSnappers.

public class MhRowApplyAnimation extends MhSnapperApplyAnimation {
    Old: extend public void applyPickedUpSnappers(Snapper[] groupSnappers, Snapper firstS, Snapper first, box oldBound, bool needToRotateChildren) {
    New: extend public void applyPickedUpSnappers(MhSnapperGroup group, Snapper[] groupSnappers, Snapper firstS, Snapper first, box oldBound, bool needToRotateChildren) {
}

Config function changes

Added argument SnapperFilter childFilter to the public function basicConfigEq.

Old: public bool basicConfigEq(MhSnapper a, MhSnapper b) : inline {
New: public bool basicConfigEq(MhSnapper a, MhSnapper b, SnapperFilter childFilter=null) : inline {

Added argument SnapperFilter childFilter to the public function childrenConfigEq.

Old: public bool childrenConfigEq(MhSnapper a, MhSnapper b, function(mhConfigSortEntry, mhConfigSortEntry, Object):int sortFunc, Object env=null) {
New: public bool childrenConfigEq(MhSnapper a, MhSnapper b, function(mhConfigSortEntry, mhConfigSortEntry, Object):int sortFunc, Object env=null, SnapperFilter childFilter=null) {

These work together with MhStorageConfigBehavior now having a new method Snapper childrenFilter() that is used in the existing method configEq(MhSnapper a, MhSnapper b, Object env=null) to now be able to filter out specific children from the equality comparison.

Removed deprecated class MhUnitLoadOffsetBehavior

The class MhUnitLoadOffsetBehavior has been deleted as well as the global singleton mhUnitLoadOffsetBehavior.

As such, we have also updated MhRowAnimationInfo and removed the method Point unitLoadPopulateOffset(MhSnapper snapper) which used this removed class.

public class MhRowAnimationInfo extends CoreObject {

Removed:
    /**
     * Unit load populate offset.
     * Override here if you have different overhang values.
     */
    extend public Point unitLoadPopulateOffset(MhSnapper snapper) {
        for (MhUnitLoadOffsetBehavior b in snapper.behaviors) {
            if (Point p = b.unitLoadPopulateOffset(snapper, configuration())) {
                return p;
            }
        }
        return null;
    }
}

MhBayEditorMeasurementsBehavior changes

Removed method void removeOldMeasurements(Space space) as it is not used.

public class MhBayEditorMeasurementsBehavior extends MhBehavior {

    /**
     * Remove old measurement.
     */
    extend public void removeOldMeasurements(Space space) {
        for (s in space.?snappers)  if (s in MhBayEditorMeasureDimension) space.remove(s);
    }
}

This logic already exists in MhBayEditConfiguration and this class handles the removal and insertion of dimensions (e.g. re-inserting dimensions when pulling props from snappers).

public class MhBayEditConfiguration extends MhStorageEditorConfiguration {

    /**
     * Remove old measurement.
     * Will be added on validate.
     */
    extend public void removeOldMeasurements(Space space) {
        SpaceSelection sel = space.?selection;
        for (MhBayEditorMeasureDimension s in space.?snappers) {
            if (!s.isActive) continue;
            if (s in sel) sel.remove(s);
            space.undoableRemove(s);
        }
    }


    /**
     * Pull props from snappers.
     */
    public void pullPropsFromSnappers() {
        removeOldMeasurements(space);

        for (s in snappers) {
            forChildrenRecursive(MhSnapper c in s) {
                if (c.isBay and hasRealConfig(c)) {
                    pullPropsFromSnapper(c);
                    insertMeasurements(c, space);
                }
            }
        }
    }
}

MhStorageEditorConfiguration changes

Updated interface of method Object{} pullPropsFromSnapper. The argument MhSnapper snapper has been widened to Snapper snapper.

public class MhStorageEditorConfiguration extends MhSystemConfiguration {
    Old: extend public Object{} pullPropsFromSnapper(MhSnapper snapper, Object env=null) {
    New: extend public Object{} pullPropsFromSnapper(Snapper snapper, Object env=null) {
}

MhStorageEditorItem changes

Updated interface of method MhSnapper{} applyToSnappers. The return value MhSnapper{} has been widened to Snapper{}.

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

MhSystemConfiguration changes

The interface of various methods in this class have been updated such that their arguments MhSnapper or MhSnapper{} have been widened to Snapper or Snapper{}. See below for the full list.

public class MhSystemConfiguration extends CoreObject {
    Old: extend public PushPropsVisitor pushPropsVisitor(MhSnapper snapper) {
    New: extend public PushPropsVisitor pushPropsVisitor(Snapper snapper) {

    Old: extend public PushPropsVisitor pullPropsVisitor(MhSnapper snapper) {
    New: extend public PushPropsVisitor pullPropsVisitor(Snapper snapper) {

    Old: extend public void applyTo(MhSnapper{} snappers) {
    New: extend public void applyTo(Snapper{} snappers) {

    Old: extend public void beforeApply(MhSnapper{} snappers, World w=null) {
    New: extend public void beforeApply(Snapper{} snappers, World w=null) {

    Old: extend public void afterApply(MhSnapper{} snappers, MhSnapperChangedEnv[] changedEnvs, World w=null) {
    New: extend public void afterApply(Snapper{} snappers, MhSnapperChangedEnv[] changedEnvs, World w=null) {

    Old: extend public Object{} executeApply(MhSnapper{} snappers) {
    New: extend public Object{} executeApply(Snapper{} snappers) {

    Old: extend public bool allowPush(MhSnapper snapper) {
    New: extend public bool allowPush(Snapper snapper) {
}

MhSystemConfigurationItem changes

Updated interface of method bool pulledFromSnapper. The argument MhSnapper snapper has been widened to Snapper snapper.

public class MhSystemConfigurationItem extends CoreObject {
    Old: extend public bool pulledFromSnapper(MhSnapper snapper) { return false; }
    New: extend public bool pulledFromSnapper(Snapper snapper) { return false; }
}

MhBayRowStretchEngineBehavior changes

The method void putAisleUpdateFunctions(MhEngine engine, MhSnapper snapper, MhSnapper aisleRow, MhSnapper topRow, MhSnapper downRow) has been removed as it is no longer used.

public class MhBayRowStretchEngineBehavior extends MhEngineBehavior {

Removed:
    /**
     * Put connected aisle update function.
     */
    extend public void putAisleUpdateFunctions(MhEngine engine, MhSnapper snapper, MhSnapper aisleRow,
                                                   MhSnapper topRow, MhSnapper downRow) {
        SnapperSet set();
        set <<? topRow;
        set <<? downRow;
        mhPutEngineRunFunction(engine, "aisleUpdateShape", snapper=aisleRow, connectedRows=set);
    }
}

MhFrameConfigBehavior changes

The method bool isFirstFrame(MhSnapper z) has been removed as it is no longer used and moved to custom package.

public class MhFrameConfigBehavior extends MhStorageConfigBehavior {

Removed:
    /**
     * Is first frame in selection?
     */
    extend public bool isFirstFrame(MhSnapper z) {
        Snapper root = z.rootParent;

        SnapperSelection sel = z.singleSelection(null);
        if (sel.count > 1) {
            bool rotated = false;
            forChildren(MhSnapper c in root) if (c.isBay) { rotated  = c.rot != 0deg; break; }
            Snapper[] snappers = sel.snappers.seq;
            snappers.sort(function snapperYPosSort, null);
            if (rotated) snappers.reverse();
            return z == snappers.first;
        }
        return true;
    }
}

Following the removal of the method in abstract, please proceed to implement this within the custom logic for the case of this mething being overriden.

public class DrFrameConfigBehavior extends MhDeepStorageFrameConfigBehavior {
    /**
     * Should put the config to the config manager?
     */
    public bool shouldPutConfigToManager(MhSnapper z) {
            if (!isFirstFrame(z)) return false;
            return true;
    }


    /**
     * Skip config ID?
     */
    public bool skipConfigId(MhSnapper z) {
            if (!isFirstFrame(z)) return true;
            return super(..);
    }


    /**
     * Is first frame in selection?
     */
    extend public bool isFirstFrame(MhSnapper z) {
        ...
    }

MhBayStretchFunction changes

The method void levelPopulate() has been removed as it is no longer used. Replace calls with the following method void levelPopulate(MhSnapper s) instead.

public class MhBayStretchFunction extends MhSystemEngineFunction {
    Removed: extend public void levelPopulate() {
    Replace: extend public void levelPopulate(MhSnapper s) {
}

MhRowFillVoidEntriesFunction changes

The field Rect systemRect has been removed as it is no longer used.

public class MhRowFillVoidEntriesFunction extends MhSystemEngineFunction {
    Removed: public Rect systemRect;
}

MhRowRemoveLoneFramesFunction changes

The method bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry) has been removed as it is no longer used. A new method bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry, MhEngineEntry{} bayEntries) has been added so replace calls to this method instead.

public class MhRowRemoveLoneFramesFunction extends MhSystemEngineFunction {
    Removed: extend public bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry) {
    New: extend public bool shouldRemoveEntry(MhEngine engine, MhEngineEntry frameEntry, MhEngineEntry{} bayEntries) {
}

MhFlueGapAnimation changes

The method void initFrameVessel has been removed as it is no longer used. Replace calls with the following method void initApplyColorFrameVessel() from parent class MhSnapperToolAnimationG2 instead.

public class MhFlueGapAnimation extends MhSnapperSpreadToolAnimation {
    Removed: extend public void initFrameVessel() {
    New: public void initApplyColorFrameVessel() {
}

MhRowApplyAnimation changes

The method void initFrameVessel has been removed as it is no longer used. Replace calls with the following method void initApplyColorFrameVessel() from parent class MhSnapperToolAnimationG2 instead.

public class MhRowApplyAnimation extends MhSnapperApplyAnimation {
    Removed: extend public void initFrameVessel() {
    New: public void initApplyColorFrameVessel() {
}

MhConfigManager changes

The method void validateWaitingList() has been removed as it is no longer used. A new method MhConfigRef[] validateSortedWaitingList() has been added so replace calls to this method instead.

public class MhConfigManager {
    Removed: extend public void validateWaitingList() {
    New: extend public MhConfigRef[] validateSortedWaitingList() {
}

MhSnapperInsertToolAnimation changes

The method MhSnapperInsertedEnv inserted(MhSnapper z, MhSnapperInsertedEnv insertedEnv) has been removed as it is no longer used. Replace calls with the following method MhSnapperInsertedEnv inserted(MhSnapper z, MhSnapperInsertedEnv insertedEnv, SpaceSelection sel) instead.

public class MhSnapperInsertToolAnimation extends MhSnapperSpreadToolAnimation {
    Removed: extend public MhSnapperInsertedEnv inserted(MhSnapper z, MhSnapperInsertedEnv insertedEnv) {
    Replace: extend public MhSnapperInsertedEnv inserted(MhSnapper z, MhSnapperInsertedEnv insertedEnv, SpaceSelection sel) {
}

Example usage:

    SpaceSelection sel = initSelectionForApply();
    for (entry in insertEntries) {
        ... // insert snapppers code
        insertedEnv = inserted(insertSnapper, insertedEnv, sel);
    }

        if (insertedEnv) insertedEnv.owner.invalidateBehaviorsIfRequired(sSnapperInserted, insertedEnv);
        if (sel) space.select(sel);

MyRowChildWidthChangePropagateFunction changes

Removed field propagateFrames. It was used to block propagating width changes to frame entries. This ability has now been moved out to a new method bool shouldPropagateToEntry(MhEngineEntry entry, MhEngineEntry refEntry) to better control which entry we propagate to.

public class MyRowChildWidthChangePropagateFunction extends MyRowChildChangePropagateFunction {
    Removed: public Bool propagateFrames;

    /**
     * Props.
     */
    public props : cached=true {
        Removed: "propagateFrames";
    }


    Added:
    /**
     * Should propagate to entry?
     */
    extend public bool shouldPropagateToEntry(MhEngineEntry entry, MhEngineEntry refEntry) {
        return refEntry.isBay and entry.isBay;
    }

Propagation changes and alignment of row's children

The propagation mechanism within the racking system has undergone minor revisions. Previously, propagation functions would attempt to reposition the row based on the directional vector of the noticer or refEntry, aiming to maintain alignment across operations. However, this approach proved limiting in multi-bay or multi-frame configurations, where entries within the same row may have differing orientations. Moreover, the previous behavior introduced unnecessary overhead by performing additional row movements solely for alignment purposes.

The rationale behind these changes is to ensure that updates and propagation to affected snappers occur prior to any row adaptation or alignment. This sequencing improves clarity and control over the propagation process, allowing adjustments to be applied more predictably.

MhBayRowEngineBehavior changes

During depth propagation changes, when the system collecting snappers needed to be propagated, the method now will only try to populate affected snappers that will get the changes instead of having additional filter during function execution to filter out non-propagatable snappers.

    /**
     * Append propagation for row children?
     */
    extend public void appendPropagationRowChildren(MhSnapper row, MhSnapperChangedEnv env, str key, MhSnapper{} res) {
	    if (key == "d") {
	        MhSnapper owner = env.owner;
	        if (!owner.isBay and !owner.isFrame) return;

    	    ?MhSnapper parent = owner.parent;
	        Transform t = parent.?isMulti ? parent.?toSpaceTransform() : null;
	    
    	    forChildrenRecursive(MhSnapper c in row) {
		        if (c.isFrame or c.isBay) {
    		        if (!t) {
			            res << c;
		            } else if (c.shapeBound.d == env.oldValue.safeDouble) {
    			        point p = point0.transformed(c.toSpaceTransform() - t);
			            if (p.y == env.lastPos.y) res << c;
		            }
		        }
	        }
	    }
    }

As for the function arguments, the flag moveRow is set to false by default. We're expecting only to update row position and shape during MhRowAlignToChildrenFunction and MhRowUpdateShapeFunction.

	  case "rowChildWidthPropagate", "rowChildDepthPropagate", "rowChildHeightPropagate" : {
	      if (env as MhSnapperChangedEnv) {
		  SnapperSet rows();
		  for (s in propagatingRows(snapper, env)) {
		      // should probably handle aisles in the aisle/flue gap engine behavior.
		      //if (s.rootParent.isAisle) continue;
		      rows << s;
		  }
		  
		  return props { snapper=snapper, noticer=env.owner,
				 rows=rows,
				 moveRow=false };
				 //moveRow=animation.?isStretchAnimation() };
	      }
	  }

MyRowChildWidthChangePropagateFunction changes

Row child propagation will no longer implicitly trigger row movement. Instead, during width propagation, the function now attempts to reposition the propagated entry based on the directional vector of the refEntry. This adjustment clarifies the function’s intent by explicitly aligning movement behavior with reference-based directionality.

public class MyRowChildWidthChangePropagateFunction extends MyRowChildChangePropagateFunction {
    public Object exec() {
        ...

	    double diff = refEntry.w - e.w;
	    e.setW(refEntry.w);

	    angle eRot = e.toSpaceTransform(engine).rot.yaw;
	    if (eRot != rot) {
	        // Get the sign direction to move this entry.
	        double v = eRot.v - rot.v; v /= |v|;
	        if (refEntry.pDiff == point0) {
		        e.move((v*diff, 0, 0));
	        }
	    } else {
	        e.move(refEntry.pDiff);
	    }   
        ...
    }

MyRowChildDepthChangePropagateFunction changes

Similarly, the depth propagation function will no longer initiate row movement by default during propagation. To streamline its execution and clarify its intended behavior, a new method has been introduced and to opt out from this new approach, please set the return type for this useNewPropagate to false.

public class MyRowChildDepthChangePropagateFunction extends MyRowChildChangePropagateFunction {
    /**
     * Return true if use new depth propagate.
     */
    extend public bool useNewPropagate() {
	    return true;
    }


    /**
     * Propagate depth.
     */
    extend public void propagateDepth2(MhEngine engine, Snapper{} set, MhEngineRowChildEntry refEntry) {
	    MhEngineEntry rootParent = refEntry.rootParent(engine);
	    Transform parentT = refEntry.parent().toSpaceTransform(engine);
	    box refB = refEntry.bound();
	
	    for (MhSnapper s in set) {
	        ?MhEngineBoxEntry entry = engine.entry(s);
	        if (!mhFrameOrBayEntryFilter.accepts(entry)) continue;
	    
	        entry.setD(refEntry.d);

	        if (entry.rootParent(engine) == rootParent) {
		        box b = entry.localBound();
		        b.transform(entry.toSpaceTransform(engine) - parentT);
		        double diff = b.p0.y - refB.p0.y;
		        entry.move((0, -diff, 0));
	        }
	    }
    }

With these changes, the depth propagation function will no longer attempt to rearrange deep racking frame configurations. Its execution is now limited strictly to applying depth changes, minimizing unnecessary operations. Consequently, extensions utilizing deep racking frame setups must explicitly exclude these frames from propagation by applying the following filter when collecting affected objects.

public class DrBayRowEngineBehavior extends MhBayRowEngineBehavior {
    /**
     * Propagating filter.
     */
    public SnapperFilter propagatingFilter(MhSnapper noticer, str key) {
	    SnapperFilter res = super(..);
	    if (key == "d") {
	        if (noticer.isFrame) { // Exclude selection snappers from propagate.
		        return makeCombinedFilter(res, NotSnapperFilter(CurrentSelectionFilter()));
	        } else {
                // Added this filter so depth change will not be applied to the frame.
		        return makeCombinedFilter(res, NotSnapperFilter(frameFilter)); 
	        }
	    }
	    return res;
    }

MhRowChildrenAlignFunction changes

The current alignment implementation is no longer compatible with the newly introduced multi-bay and multi-frame configurations together with non-movable row during propagation. With that, we introduced new concept in the MhRowChildrenAlignFunction class to anchor the affected object and only push next or previous objects while maintaining the position of changed object.

X-axis alignment:

    /**
     * Align X.
     */
    extend public void alignX(sorted double->(MhEngineEntry[]) groupedEntries) {
	    // This is anchor index.
	    int index = {
	        if (ignoreAnchor.?v) result 0;
	        for (k, entries in groupedEntries, index=i) {
		        for (MhEngineBoxEntry e in entries) {
    		        if (e in tempList) ?e = tempList.get(e);
    		        if (e.wDiff != 0) result i;
	    	    }
	        }
	        result 0;
	    };

        ...

	    for (x in xs, start=index + 1) {
            // Push objects to the right of index
	    }
	
	    for (x in xs, reverse, start=index - 1) {
            // Push object to the left of index.
	    }
    }

Y-axis alignment:

    /**
     * Align Y.
     */
    extend public void alignY(sorted double->(MhEngineEntry[]) groupedEntries) {
	    // This is anchor index.
	    int index = {
	        if (ignoreAnchor.?v) result 0;
	        for (k, entries in groupedEntries, index=i) {
		        for (MhEngineBoxEntry e in entries) {
		            if (e in tempList) ?e = tempList.get(e);
		            if (e.dDiff != 0) result i;
		        }
	        }
	        result 0;
	    };
	
	    double[] xs = groupedEntries.keys;
	    box bound;
	    for (e in groupedEntries.get(xs[index])) if (first) bound = e.bound; else bound += e.bound;

        // Will prioritize to align on the object that changed.
    }

These alignments now can also be performed on different groups (blocks). All the populated entries of the row can be grouped into distinct blocks based on their y-position. This grouping by default will be conditional, only done if a difference in the y-bound of entries at the same x-position is found between entries of the same classification. However the grouping can be always done or always skipped, controlled with the groupByY argument and Bool shouldGroupByY() method. There may be cases where you would want to turn off this grouping, such as deep racking implemented with a non-multiframe structure (one row owns multiple child frames).

    /**
     * Align rows. 
     */
    extend public void alignRows(MhEngineRowEntry row) {
        ...
        if (MhSystemEngineEnvironment env = systemEnvironment()) {
            MhEngineEntryBlock[] blocks = env.getPopulatedBlocks(snapper);
            MhEngineEntryBlock[] blocksToAlign = blocksToAlign(blocks);
            populateDistancesBetweenBlocks(blocksToAlign);
            
            for (block in blocksToAlign, index=i) {
                sorted double->(MhEngineEntry[]) groupedEntries();

                MhEngineEntry[] entries = mhYSortedEntries(block.entries, unmodify=true);
                // Group entries based on x-location.
                for (e in entries) {
                    ...
                    groupedEntries.put(x, es);
                    es << e;
                }

                // Align along x-axis.
                alignX(groupedEntries);

                // Align along y-axis.
                alignY(groupedEntries);
            }

            // Align filtered imported block.
            alignBlocks(blocksToAlign);
            
            // Move back to original parent.
            resetToParentTransform(engine, row, blocksToAlign);
        }
    }


    /**
     * Blocks to be align.
     */
    extend public MhEngineEntryBlock[] blocksToAlign(MhEngineEntryBlock[] populated) {
        ...        
        MhEngineEntry[] entries();
        bool grpByY = false;
        Bool forceGroupByY = shouldGroupByY();
        if (forceGroupByY) grpByY = forceGroupByY.v;

        // Collecting all populated entries into a seq.
        for (block in populated) {
            for (e in block.entries) {
                ...
            }
        }

        sorted double->(MhEngineEntry[]) groupedEntries();
        // First pass to create all groups.
        for (MhEngineBoxEntry e in entries) {
            ...
        }

        // Second pass to add other entries that overlap with y to group.
        if (grpByY) {
            ...
        }

        // Create groups sorted by x.
        for (y, es in groupedEntries, index=i) {
            ...
        }
        ...
    }


    /**
     * Should group by y-pos?
     */
    extend public Bool shouldGroupByY() {
        return groupByY;
    }

MhRowAlignToChildrenFunction and MhRowUpdateShapeFunction changes

All logic in this class has been moved up to more generic class to be used by multi-bay and multi-frame as well.

    New: public class MhAlignToChildrenFunction extends MhSystemEngineFunction {
    New: public class MhUpdateShapeFunction extends MhSystemEngineFunction {

MhSystemPopulateFunction changes

Removed argument int adders from method buildRows.

public class MhSystemPopulateFunction extends MhSystemEngineFunction {

    Old: extend public void buildRows(MhSystemEngineEnvironment env, rect r, int adders) {
    New: extend public void buildRows(MhSystemEngineEnvironment env, rect r) {
}

This argument can be replaced with the following code in this method.

    /**
     * BuildOfRows
     */
    extend public void buildRows(MhSystemEngineEnvironment env, rect r) {
        ...
        int adders = info.aisleCount(config);
        if (adders < 0) return;
        ...
    }

Runtime/Behavior Changes

MhFrameSpreadPatternBehavior changes

MhFrameSpreadPatternBehavior now returns false for strictClassification(MhSnapper snapper, str event), which means by default it no longer requires an exact classification LayerSet match for the spread to take place.

MhRowPopulateFunction changes

The existing engine function MhRowPopulateFunction has been replaced with MhRowPopulateFunction2 in the MH abstracts engine function library registered to the key rowPopulate. If you still want to use the old MhRowPopulateFunction, you should override the rowPopulate key in your extension's engine function library to do so.

MhBaySpawner and MhFrameSpawner changes

MhBaySpawner and MhFrameSpawner both now have implementations for configKey() and init(Snapper snapper). These methods were often implemented with the same code in many different extensions so we have now moved the code into these abstract classes. Check your spawner classes to see if these method implementations can be removed from them.

public class MhBaySpawner extends MhStorageSpawner {

    /**
     * Config key.
     */
    public symbol configKey() {
        str s = spnn(super().str, ".", "bay");
        return s.symbol;
    }


    /**
     * Init.
     */
    public void init(Snapper snapper) {
        super(..);
        if (snapper as MhSnapper) {
            snapper.config = createConfig();
        }
    }
}


public class MhSnapperSpawner extends SnapperSpawner {

    /**
     * Create new config.
     */
    extend public MhConfigRef createConfig() {
        return MhConfigRef(guid(), configKey);
    }
}

getAllSnappersInARow function changes

The function getAllSnappersInARow has been updated so that the default value for argument allowBackToBack is now false. allowBackToBack=true will result in including child snappers that do not intersect with the snapper in terms of y-bound. This change was made as that was the more common case in other extensions.

Old: public Snapper{} getAllSnappersInARow(MhSnapper snapper, SnapperFilter filter, bool allowBackToBack=true, Box ownerBound=null, MhSnapper parent=null) {
New: public Snapper{} getAllSnappersInARow(MhSnapper snapper, SnapperFilter filter, bool allowBackToBack=false, Box ownerBound=null, MhSnapper parent=null) {

For cases where you do want to include child snappers despite their y-bound, you will now have to call the function with allowBackToBack=true. This will include products like deep racking which has multiple frames, especially with a "Row" spread pattern.

We have added several methods to make it easier to pass in allowBackToBack=true for MhStorageRowSpreadPattern. You can either subclass MhStorageRowSpreadPattern and override its bool allowBackToBack(MhSnapper snapper) method, or override the snapper shape method bool allowBackToBackSpreadCandidates(MhSnapper snapper, MhSnapperSpreadPattern pattern) in your shape class.

public class MhStorageRowSpreadPattern extends MhSnapperSpreadPattern {

    New:
    /**
     * Allow back-to-back?
     */
    extend public bool allowBackToBack(MhSnapper snapper) {
        return snapper.?allowBackToBackSpreadCandidates(this);
    }
}


public class MhSnapper extends Snapper {

    New:
    /**
     * Allow back-to-back for spread pattern candidates?
     */
    extend public bool allowBackToBackSpreadCandidates(MhSnapperSpreadPattern pattern) {
        if (shape) return shape.allowBackToBackSpreadCandidates(this, pattern);
        return false;
    }
}


public class MhSnapperShape extends CorePropObj {

    New:
    /**
     * Allow back-to-back for spread pattern candidates?
     */
    extend public bool allowBackToBackSpreadCandidates(MhSnapper snapper, MhSnapperSpreadPattern pattern) {
        return false;
    }
}

Examples:

1. Essential Deep Racking
public class DrFrameShape extends GenRackFrameShape {

    /**
     * Allow back-to-back for spread pattern candidates?
     */
    public bool allowBackToBackSpreadCandidates(MhSnapper snapper, MhSnapperSpreadPattern pattern) {
        if (pattern in MhStorageRowSpreadPattern) return true;
        return super(..);
    }
}


2. Essential Cantilever Racking
public class CrStorageRowSpreadPattern extends MhStorageRowSpreadPattern {

    /**
     * Allow back-to-back?
     */
    public bool allowBackToBack(MhSnapper snapper) {
        if (animation as MhSnapperInsertToolAnimation) {
            ?MhSnapper inserter = animation.inserter;
            if (?CrSpreadSidesPatternBehavior b = inserter.?behavior("spreadPattern")) {
                return b.spreadSide.ieq("double");
            }
        }

        return super(..);
    }
}

Changes to selected snappers

As part of the changes to support multi-bays and multi-frames within a single row, we have made some modifications to MhStorageSelectionBehavior. The method void additionalFilteredSnappers(MhSnapper snapper, SnapperSelection sel, Line mouseLine) would append snappers of the same classification and "column" (same x-position) to the selection. This was mainly used for deep racking which would have multiple frames of the same x-position in a single row and usually we would want all of them to be included when one is selected. But this behavior is not wanted for multi-bays/multi-frames where they should be able to be individually selected even though they share the same x-position. We now feel this behavior should not be the default and have added an additional check bool selectAllOfSameColumn(MhSnapper snapper) to block or allow this behavior.

public class MhRowChildSelectionBehavior extends MhStorageSelectionBehavior {

    /**
     * AdditionalFilteredSnappers
     */
    extend public void additionalFilteredSnappers(MhSnapper snapper, SnapperSelection sel, Line mouseLine) {
        if (!selectAllOfSameColumn(snapper)) return;

        Snapper{} visited();
        SnapperFilter f = additionalFilter(snapper);

        ?MhSnapper row = snapper.rootParent;
        MhSnapper[] group = myRowGroup(row, visited, mhRowNotAisleFilter);

        for (r in group) {
            forChildren(c in r) {
                if (f.accepts(c)) {
                    sel << c;
                    forChildrenRecursive(cChild in c) {
                        sel << cChild;
                    }
                }
            }
        }
    }


    /**
     * AdditionalFilter
     */
    extend public SnapperFilter additionalFilter(MhSnapper snapper) {
        return MhCombinedFilter(MhClassificationSnapperFilter(snapper.classification),
                                MhSameColumnSnapperFilter(snapper));
    }


    New:
    /**
     * Return true if should select all child of the same column in a row.
     */
    extend public bool selectAllOfSameColumn(MhSnapper snapper) {
        if (!snapper.rootParent.?isDoubleDeep) return false;
        return true;
    }
}

We have also added a new behavior MhDeepstorageFrameSelectionBehavior in cm.abstract.materialHandling.storage.racking.deepstorage that overrides bool selectAllOfSameColumn(MhSnapper snapper) so that deep racking frames will still all be selected together. If you have a deep racking product but your frame spawner does not extend from MhDeepstorageFrameSpawner, make sure to append this new behavior to your frame spawner class. You should also do the same for other non-deep racking products that do require this behavior.

public class MhDeepstorageFrameSelectionBehavior extends MhRowChildSelectionBehavior {

    /**
     * Return true if should select all child of the same column in a row.
     */
    public bool selectAllOfSameColumn(MhSnapper snapper) {
        return true;
    }
}

Example usage:

public class MhDeepstorageFrameSpawner extends MhRackFrameSpawner {

    /**
     * CustomOtherBehaviors.
     */
    public MhBehavior[] customOtherBehaviors() {
        MhBehavior[] res = super(..);
        res.exclude(mhRowChildSelectionBehavior);
        res << mhDeepstorageFrameSelectionBehavior;
        return res;
    }
}

Changes to spread tools and dimension propagation

As part of the changes to support multi-bays and multi-frames within a single row, one aspect to consider is how to handle configurations (MhConfigRef). Typically a bay/frame that owns a non-temporary config will be visible in the bay/frame editor dialogs, and bays often display their config names in 2D.

One thing we have added to help support allowing either the child bays or the multi bay to own the non-temporary config is by adding this new method bool hasRealConfig(). Such snappers are considered to have a "real config" and is treated as a "config owner".

public class MhSnapper extends Snapper {

    /**
     * Check whether this snapper has config that is not temp.
     */
    extend public bool hasRealConfig() {
        if (config) return !config.temp;
        return false;
    }
}

We have also added a new SnapperFilter class MhConfigOwnerFilter that uses this new check.

public class MhConfigOwnerFilter extends SnapperFilter {

    /**
     * Accept
     */
    public bool accepts(Snapper s) {
        if (s as MhSnapper) {
            if (!s.hasRealConfig) return false;
        }
        return super(..);
    }
}

This new filter is now used in bay and frame spread tools to improve the experience of picking bays/frames as candidates. Now only "config owners" will be picked by these animations and snappers with temporary configs will be ignored. Note that this means if your snappers simply do not use "real configs" but you want the spread tools to work with them, you will have to override these filter methods to exclude mhConfigOwnerFilter.

public class MhBayPickupAnimation extends MhSnapperPickupAnimation {

    /**
     * Candidate filter.
     */
    public SnapperFilter candidateFilter() {
        static CombinedFilter cb(bayFilter, mhConfigOwnerFilter);
        return cb;
    }
}


public class MhBayApplyAnimation extends MhSnapperApplyAnimation {

    /**
     * CandidateFilter
     */
    public SnapperFilter candidateFilter() {
        SnapperFilter sf = super();
        return CombinedFilter(mhNotDoubleDeepRowFilter, mhConfigOwnerFilter, sf);
    }
}


public class MhFramePickupAnimation extends MhSnapperPickupAnimation {

    /**
     * Candidate filter.
     */
    public SnapperFilter candidateFilter() {
        return CombinedFilter(frameFilter, mhConfigOwnerFilter);
    }
}


public class MhFrameApplyAnimation extends MhSnapperApplyAnimation {

    /**
     * CandidateFilter
     */
    public SnapperFilter candidateFilter() {
        SnapperFilter sf = super();
        return CombinedFilter(frameFilter, sf, mhConfigOwnerFilter);
    }
}

MhBayRowEngineBehavior has also received a change related to "config owners". Now when propagating depth or height changes, we will only propagate to bays/frames that are "config owners". This is to prevent propagating a depth change of a child bay to its parent multi bay which should have a larger depth encompassing multiple child bays. Similar to the spread tools, this should be overridden if your product line does not use "real configs" so that dimension propagation continues to work for them.

public class MhBayRowEngineBehavior extends MhEngineBehavior {

    /**
     * Propagating filter.
     */
    extend public SnapperFilter propagatingFilter(MhSnapper noticer, str key) {
        // Make use of spread filter as well.
        SnapperFilter f = classificationFilter(noticer, key);
        MhAssortmentRefSnapperFilter assortmentFilter(noticer.assortmentRef);
        return MhCombinedFilter(assortmentFilter, MhCombinedOrFilter(rowFilter, f));
    }


    /**
     * Classification filter.
     */
    extend public SnapperFilter classificationFilter(MhSnapper noticer, str key) {
        if (key == "w") return noticer.isBay or noticer.parent.?isBay ? bayFilter : frameFilter;
        return MhCombinedFilter(mhConfigOwnerFilter, mhBayOrFrameFilter);
    }
}

Changes to spread tools switchChildren logic

In MhSnapperApplyAnimation, the method void switchChildren(Snapper oldSnapper, Snapper newSnapper) was typically used to retain the current child snappers of the snapper that is currently being applied to (retain oldSnapper.children). This is used for cases where we do not want to replace the child snappers with what was picked up (newSnapper.children).

Changes have been made to support being able to filter out specific child snappers so that they are always switched (so certain oldSnapper.children are always retained upon apply). A new method MhSnapperApplyAnimation.childrenFilter() has been introduced. Previously void switchChildren(Snapper oldSnapper, Snapper newSnapper) would always run its intended logic when executed and the conditional logic is usually checked before calling switchChildren(). We have now moved that logic down into switchChildren() itself so that every child can be specifically checked. If you have overridden switchChildren(Snapper oldSnapper, Snapper newSnapper) or switchSnapper(MhSnapper parent, Snapper oldSnapper, MhSnapper newSnapper), consider moving down any additional logic into switchSnapper().

As for how the childrenFilter() method works, any child snapper that is accepted by the filter will be affected by switchChildren() so those snappers in the pickedUp snapper will not be applied to the applied snapper, or those snappers in the applied snapper will be retained and not removed after the apply. This is useful for cases like certain child snappers should not affect a bay's configuration so the pickUpAndApply should ignore them.

public class MhSnapperApplyAnimation extends MhSnapperSpreadToolAnimation {

    New:
    /**
     * Alternative candidates filter.
     */
    extend public SnapperFilter childrenFilter() {
        return null;
    }


    /**
     * SwitchSnapper
     */
    extend public void switchSnapper(MhSnapper parent, Snapper oldSnapper, MhSnapper newSnapper) {
        ...
        Old: if (!includeChildren) switchChildren(oldSnapper, newSnapper);
        New: switchChildren(oldSnapper, newSnapper);
    }


    Old:
    /**
     * SwitchChildren
     */
    extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper) {
        Snapper{} children = oldSnapper.children;
        
        //remove children from old
        for (child in children) {
            oldSnapper.removeChild(child);
            child.setParent(null);
        }

        //remove newSnappers children
        forChildren(child in newSnapper) {
            newSnapper.removeChild(child);
            child.setParent(null);
        }
        
        //add the old children to new snapper
        for (child in children) {
            child.setParent(newSnapper);
            newSnapper.addChild(child);
        }
    }


    New:
    /**
     * SwitchChildren
     */
    extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper) {
        Snapper{} children = oldSnapper.children;

        SnapperFilter cFilter = childrenFilter;
        //remove children from old
        for (child in children) {
            if (!includeChildren or (cFilter and !cFilter.accepts(child))) {
                oldSnapper.removeChild(child);
                child.setParent(null);
            }
        }

        //remove newSnappers children
        forChildren(child in newSnapper) {
            if (!includeChildren or (cFilter and !cFilter.accepts(child))) {
                newSnapper.removeChild(child);
                child.setParent(null);
            }
        }
        
        //add the old children to new snapper
        for (child in children) {
            if (!includeChildren or (cFilter and !cFilter.accepts(child))) {
                child.setParent(newSnapper);
                newSnapper.addChild(child);
            }
        }
    }
}

There may still be a need to unconditionally execute switchChildren() which is no longer posssible due to the changes to the existing method. As such, we have temporarily introduced a new method void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch) where if forceSwitch=true, will just execute the switchChildren() logic without conditional checks for each child. One such example is in MhLevelApplyAnimation.adjustLevelClassification(MhSnapper oldSnapper, MhSnapper newSnapper), when we are picking-up or applying a top beam that should not have child snappers, so this prevents applying unit load snappers to the top beam.

public class MhSnapperApplyAnimation extends MhSnapperSpreadToolAnimation {

    New:
    /**
     * Switch children.
     */
    extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper,
                                      bool forceSwitch) {
        if (forceSwitch) {
            Snapper{} children = oldSnapper.children;

            //remove children from old
            for (child in children) {
                oldSnapper.removeChild(child);
                child.setParent(null);
            }

            //remove newSnappers children
            forChildren(child in newSnapper) {
                newSnapper.removeChild(child);
                child.setParent(null);
            }
        
            //add the old children to new snapper
            for (child in children) {
                child.setParent(newSnapper);
                newSnapper.addChild(child);
            }
        } else {
            switchChildren(oldSnapper, newSnapper);
        }
    }
}


public class MhLevelApplyAnimation extends MhSnapperApplyAnimation {

    /**
     * Adjust level classification.
     */
    extend public void adjustLevelClassification(MhSnapper oldSnapper, MhSnapper newSnapper) {
        ...
        if (oldSnapper.isTop) {
            newSnapper.classification += {sTop};
            switchChildren(oldSnapper, newSnapper, forceSwitch=true);
        } else if (newSnapper.isTop) {
            newSnapper.classification -= {sTop};
            switchChildren(oldSnapper, newSnapper, forceSwitch=true);
        }
    }
}

Improved support for connector snapping

MhSnapBehavior now also appends alternatives MhAttachSnapAlternative for each connector in the attach target.

public class MhSnapBehavior extends MhBehavior {

    /**
     * Append snap alternatives.
     */
    extend public void appendSnapAlternatives(Snapper snapper, SnapAlternative[] alternatives, Animation a, AnimationMouseInfo mi, SnapperFilter filter=null) {
        ...
        if (!spread) appendAttachSnapAlternatives(snapper, s, r.v1, alternatives, r.v2);
    }


    /**
     * Append attach snap alternative.
     */
    extend public void appendAttachSnapAlternatives(Snapper main, Snapper closest, point ip,
                                                    SnapAlternative[] alternatives, Double distanceToSnapper=null) {
        for (c in closest.?connectors()) alternatives << MhAttachSnapAlternative(c);
    }
}

For MhSnapperInsertToolAnimation, inserting using MhAttachSnapAlternative will now also try to snap the inserting snapper together with the alternative's snapper.

public class MhSnapperInsertToolAnimation extends MhSnapperSpreadToolAnimation {

    /**
     * StdInsert
     */
    public void stdInsert() {
        ...
                    if (alt as AttachSnapAlternative) {
                        beforeSnap(cMain, alt);
                        snapAllAligned({Snapper: cMain, alt.c.?snapper});
                        afterSnap(cMain, alt);
                    }
        ...
    }


    /**
     * Before snap.
     */
    extend public void beforeSnap(MhSnapper snapper, AttachSnapAlternative alt) { }


    /**
     * After snap.
     */
    extend public void afterSnap(MhSnapper snapper, AttachSnapAlternative alt) { }
}

MH animations now allows idle validation

The following classes now return true for the method bool allowIdleValidation(). This change was made so that graphics validation could still occur during animation for large drawings.

  • MhRowInsertToolAnimationG2
  • MhSnapperToolAnimationG2
  • MhDragAnimation
  • MhPropertyStretchAnimation