The abstract class MhSystemConfiguration, and the child classes MhStorageConfiguration and MhStorageEditorConfiguration have had similar methods declared in them.
MhStorageConfiguration.configUserPropertyChanged(str key, Object value, Object oldValue, Object propOwner, MhStorageConfigurationItem item) and MhStorageEditorConfiguration.userPropertyChange(MhStorageEditorItem item, CoreProperty property, Object oldValue) were both doing similar logic and so we have now created a new method in the parent class MhSystemConfiguration that calls this similar functionality. If you had previously overridden either of these two methods, you should now override configUserPropertyChanged(MhSystemConfigurationItem item, CoreProperty property, Object oldValue) instead.
public class MhSystemConfiguration extends CoreObject { Added: /** * Config user property changed. */ extend public void configUserPropertyChanged(MhSystemConfigurationItem item, CoreProperty property, Object oldValue) { if (property.owner != item) { item.?additionalPropChanged(property.key, property.value, oldValue, property.owner); } configUserPropertyChanged(property.key, property.value, oldValue, property.owner, item); } } public class MhStorageConfiguration extends MhSystemConfiguration { Removed: /** * configUserPropertyChanged - user changed property. Adjust configuration accordingly. */ extend public void configUserPropertyChanged(str key, Object value, Object oldValue, Object propOwner, MhStorageConfigurationItem item) { for (it in items) { it.configUserPropertyChanged(..); } } } public class MhStorageEditorConfiguration extends MhSystemConfiguration { Removed: /** * User property changed. */ extend public void userPropertyChange(MhStorageEditorItem item, CoreProperty property, Object oldValue) { if (property.owner != item) { item.additionalPropChanged(property.key, property.value, oldValue, property.owner); } if (space and !skipPushToPreview(..-)) { beforePushPropToPreview(..); pushPropToPreview(space, ..-); afterPushPropToPreview(..); } } }
The function Snapper getNextSingleAisle(MhSnapper snapper, bool top=true) has been removed and a function Snapper getNextAisle(MhSnapper snapper, bool top=true) has been added.
Removed: public Snapper getNextSingleAisle(MhSnapper snapper, bool top=true) { for (r in getAllRows(snapper, top=top, reverse=false)) { if (r == snapper) continue; if (r.isSingle and r.isAisle) return r; } return null; } Added: public Snapper getNextAisle(MhSnapper snapper, bool top=true) { for (r in getAllRows(snapper, top=top, reverse=false)) { if (r == snapper) continue; if ((r.isDouble and r.isAisle and !r.isFlueGap) or (r.isSingle and r.isAisle)) return r; } return null; }
Previously MhEngineEntry has a map field str->Object additionalShapeArgs that allows storing key-value data. This has now been changed to a private field str->Object _additionalShapeArgs and new public methods have been introduced with the same name. This is so that the source of this map can be overridden.
For example, MhEngineConstructionEntry overrides the additionalShapeArgs methods to put/retrieve data from the owned constructionInfo() object instead. The class MhEngineConstructionEntryInfo now has a new field str->Object _additionalShapeArgs and method str->Object additionalShapeArgs(). This allows the map of the info class to be set independently and for the data to later be assigned to a MhEngineConstructionEntry at a later time.
// cm/abstract/materialHandling/engine/mhEngineEntry.cm public class MhEngineEntry extends MhEngineEntryBase { Old: public str->Object additionalShapeArgs; New: private str->Object _additionalShapeArgs; New: extend public str->Object additionalShapeArgs() { } New: extend public str->Object additionalShapeArgs=(str->Object args) { } New: extend public Object getAdditionalShapeArg(str key) { } New: extend public void putAdditionalShapeArg(str key, Object val) { } } // cm/abstract/materialHandling/engine/mhEngineConstructionEntry.cm public class MhEngineConstructionEntryInfo { New: private str->Object _additionalShapeArgs; New: extend public str->Object additionalShapeArgs() { } } // cm/abstract/materialHandling/mhSnapperSpawner.cm public class MhSnapperSpawner extends SnapperSpawner { Old: extend public MhEngineConstructionEntry engineConstructionEntry() { } New: extend public MhEngineConstructionEntry engineConstructionEntry(MhEngineConstructionEntryInfo info=null) { } }
The additionalShapeArgs are primarily used to push property values to spawned snappers on constructions. They can also be used to hold distinct entry values to be used during engine functions.
These additionalShapeArgs are applied in three areas during construction, one during instantiation of an MhEngineConstructionEntry, one during spawning of a snapper, and one more time after export of entry.
1. Instantiation of `MhEngineConstructionEntry` public class MhSnapperSpawner extends SnapperSpawner { /** * Engine construction entry (used by entry layout only). */ extend public MhEngineConstructionEntry engineConstructionEntry(MhEngineConstructionEntryInfo info=null) { MhSnapperShape shape = createShape(); for (key, val in info.?additionalShapeArgs) shape.?changeShape(key, val); ... } } 2. Spawning of a snapper public class MhEngineConstructionEntry extends MhEngineSnapperEntry { /** * Spawn snapper. */ extend public MhSnapper spawnSnapper(MhEngine engine, MhSystemSpawnerSelector spawnerSelector=null) { ... if (MhSnapper s = spawnerSelector.?spawnSnapper(classification, shapeArgs=shapeArgs+additionalShapeArgs, env=this)) { return s; } ... } } 3. After export of entry. public class MhSnapperShape extends CorePropObj { /** * Entry exported. */ extend public void entryExported(MhSnapper snapper, MhEngineEntry entry) { ... // Apply additional shape args on exported as they are not included in entry cache key so export function might have spawned snapper without using correct shape args from mhSystemEngineCache. if (entry as MhEngineConstructionEntry) { for (key, val in entry.additionalShapeArgs()) { changeShape(key, val); } } } }
Several changes have been made for handling level clearances in the abstract. First of all, the class MhClearanceSpecLevelClearanceBehavior and the field levelClearanceZ field in MhClearanceSpec have been removed.
How this would be used is the MhClearanceSpecLevelClearanceBehavior class would call spec.?collisionPrimitives(shape, env) which by default would call MhClearanceSpec.levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) that returns a CollisionPrimitive built based on spec.levelClearanceZ. This would result in a CollisionPrimitive always being built for the level.
Removed: public mhLazyGlobal MhBehavior mhClearanceSpecLevelClearanceBehavior = MhClearanceSpecLevelClearanceBehavior(); Removed: public class MhClearanceSpecLevelClearanceBehavior extends MhClearanceSpecBehavior public class MhClearanceSpec extends CorePropObj { Removed: public Double levelClearanceZ; Removed: Prop cMhLevelClearanceZPK : attributes={#allowPush, #allowPushNull}; }
Instead of always building a CollisionPrimitive for the level to represent the level clearance, we now only append this primitive during the construction of entries.
public class MhLevelShape extends MhBoxSnapperShape { Old: extend public void appendLevelClearancePrimitives(CollisionPrimitive{} prims, MhCollisionFetchEnv env) { if (?MhClearanceBehavior b = owner.?behavior("levelClearance")) { if (!env) env = MhCollisionFetchEnv(); Transform t = env.transform; double elevation; if (t) elevation = t.pos.z; else elevation = owner.toSpaceTransform().pos.z; MhClearanceCollisionFetchEnv clearanceEnv(env, elevation, owner.rootParent.MhSnapper.?classification); b.appendCollisionPrimitives(this, clearanceEnv, prims); } } New: extend public void appendLevelClearancePrimitives(CollisionPrimitive{} prims, MhCollisionFetchEnv env) { } } public class MhLevelSpawner extends MhStorageSpawner { New: /** * Return construction entry collision primitive. */ public CollisionPrimitive engineConstructionCollision(MhSnapperShape shape, MhEngineConstructionEntry entry) { CollisionPrimitive res = super(..); appendLevelClearancePrimitive(res, shape, entry); return res; } New: /** * Append level clearance primitive. * Simple implementation of level clearance when using MhCompartmentType with no unit loads. For custom logic, override either this or MhClearanceSpec.levelClearanceCollision(). */ extend public void appendLevelClearancePrimitive(CollisionPrimitive prim, MhSnapperShape shape, MhEngineConstructionEntry entry) { if (prim as CollisionPrimitiveSet) { if (?MhClearanceBehavior b = statelessBehavior("Clearance")) { MhCollisionFetchEnv env(reason=#construction, entry=entry); b.appendCollisionPrimitives(shape, env, prim.subPrims); } } } }
Due to the above change in behavior, the ownership of a level clearance value has been changed from the clearance spec to the engine entry, more specifically it is stored in the additionalShapeArgs() of MhEngineEntry.
public class MhClearanceSpec extends CorePropObj { Old: /** * Collision primitives based on `levelClearanceZ`. */ extend public CollisionPrimitive[] levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) { CollisionPrimitive[] res(4); if (levelClearanceZ and shape) { box b = shape.localBound(); b.h = levelClearanceZ.v; static Layer layer = Layer(layerSet(sClearance, sLevelClearanceZ), layerSet(sLevel, sLevelFrame)); res << mhGetBoxCollisionPrimitive(b, layer, env.transform); } return res.any ? res : null; } New: /** * Collision primitives for level clearance. */ extend public CollisionPrimitive[] levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) { CollisionPrimitive[] res(4); // Append level clearance collision from MhCompartmentType during construction. if (env.?reason == #construction) { MhEngineEntry entry = env.entry; if (?Double levelClearance = entry.?getAdditionalShapeArg("levelClearance")) { box b = shape.?localBound([sLevelFrame]); b.h += levelClearance.v; static Layer layer = Layer(layerSet(sClearance, sLevelClearanceZ), layerSet(sLevel, sLevelFrame)); res << mhGetBoxCollisionPrimitive(b, layer); } } return res.any ? res : null; } } public class MhCollisionFetchEnv { New: public MhEngineEntry entry; Old: public constructor(symbol reason=#none, Transform transform=null, bool includeChildren=false, Object arg=null) New: public constructor(symbol reason=#none, Transform transform=null, bool includeChildren=false, MhEngineEntry entry=null, Object arg=null) }
Changes have been made to support different level entries having different unit load clearances. MhUnitLoadSpawner can pass in different clearance specs for each level entry instead of always using the same clearance spec from the configuration.
public class MhEntryLayoutEnv { New: public MhEngineEntry entry; } public class MhUnitLoadSpawner extends MhStorageSpawner { Old: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, str unitLoadKey, LayerSet rootParentClassification, MhEntryLayoutEnv env) { ... for (MhClearanceSpecBehavior behavior in collection.behaviors) { MhStorageClearanceCollisionFetchEnv clearanceEnv(..., clearanceSpec=config.?clearanceSpec); behavior.appendCollisionPrimitives(loadShape, clearanceEnv, set.subPrims); } } New: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, str unitLoadKey, LayerSet rootParentClassification, MhEntryLayoutEnv env) { ... MhClearanceSpec spec; if (?MhEngineConstructionEntry parent = env.entry) { if (?MhClearanceSpec cSpec = parent.getAdditionalShapeArg(cMhClearanceSpecPK)) { spec = cSpec; } } if (!spec) spec = config.?clearanceSpec; for (MhClearanceSpecBehavior behavior in collection.behaviors) { MhStorageClearanceCollisionFetchEnv clearanceEnv(..., clearanceSpec=spec); behavior.appendCollisionPrimitives(loadShape, clearanceEnv, set.subPrims); } } }
We have added further control over the number of unit loads populated during construction.
MhUnitLoadConstructionalPopulateFunction class (unitLoadConstructionalPopulate) had the argument loadCount, which was passed to MhConstructionUnitLoadArrangement to populate unit loads perpendicular to the populate vector. It has been renamed to repeatCount. Additionally, a new argument ulCount has been added to represent the number of unit loads to populate in the direction of population. If left as null, the previous behavior will be used which is to populate as many loads as possible. Similarly the MhConstructionUnitLoadArrangement class now holds fields repeatCount and ulCount in place of the previous field count.
Effectively, previous specification of loadCount will now need to specify repeatCount instead. Make sure to search for loadCount in your extension as engine function execute arguments do not throw compile errors for incorrect arguments.
// cm/abstract/materialHandling/storage/engine/mhUnitLoadConstructionalPopulateFunction.cm public class MhUnitLoadConstructionalPopulateFunction extends MhSystemEngineFunction { Old: public Int loadCount; New: public Int repeatCount; //replaces loadCount New: public int ulCount; } // cm/abstract/materialHandling/storage/mhUnitLoadArrangement.cm public class MhConstructionUnitLoadArrangement extends MhUnitLoadArrangement { Old: public int count; New: public int repeatCount; //replaces count New: public int ulCount; Old: public constructor(MhPopulator populator, MhEngineEntry defaultLoadEntry, int count=1, vector direction=(0, 1, 0), bool spreadEvenly=true) { } New: public constructor(MhPopulator populator, MhEngineEntry defaultLoadEntry, int ulCount=0, int repeatCount=1, vector direction=(0, 1, 0), bool spreadEvenly=true) { } }
The repeatCount and ulCount fields for MhUnitLoadConstructionalPopulateFunction are typically passed in from MhLevelConstructionalPopulateFunction.levelChildConstruction(MhEngineConstructionEntry entry) where it passes the values from entry.constructionInfo. Additionally in MhEngineLevelConstructionEntryInfo, the field noOfUnitLoads was renamed to noOfUnitLoadsX and both noOfUnitLoadsX and noOfUnitLoadsY fields are now boxed Int types instead of primitive int.
// cm/abstract/materialHandling/storage/engine/mhLevelConstructionalPopulateFunction.cm public class MhLevelConstructionalPopulateFunction extends MhSystemEngineFunction { /** * levelChildContruction */ extend public void levelChildConstruction(MhEngineConstructionEntry entry) { ... ?MhEngineLevelConstructionEntryInfo info = entry.constructionInfo; Int ulXCount = info.?noOfUnitLoadsX; Int ulYCount = info.?noOfUnitLoadsY; if (!ulYCount) ulYCount = configuration.unitLoadCountY(); engine.exec("unitLoadConstruction", ..., ulCount=ulXCount, repeatCount=ulYCount, ...); ... } } public class MhEngineLevelConstructionEntryInfo extends MhEngineConstructionEntryInfo { Old: public int noOfUnitLoads; New: public Int noOfUnitLoadsX; Old: public int noOfUnitLoadsY; New: public Int noOfUnitLoadsY; }
Some method and argument names in MhUnitLoadEnsureClearanceFunction2 were updated to more accurately represent that the function operates on populated child entries rather than the imported entries.
// cm/abstract/materialHandling/storage/engine/mhUnitLoadEnsureClearanceFunction.cm public class MhUnitLoadEnsureClearanceFunction2 extends MhSystemEngineFunction { Old: extend public MhUnitLoadArrangement unitLoadArrangement(MhEngineEntry[] imported, MhPopulator populator) New: extend public MhUnitLoadArrangement unitLoadArrangement(MhEngineEntry[] children, MhPopulator populator) Old: extend public MhEngineEntry loadEntryToKeep(MhEngineEntry[] imported) New: extend public MhEngineEntry loadEntryToKeep(MhEngineEntry[] children) Old: extend public bool spreadUnitLoad(MhEngineEntry[] imported) New: extend public bool spreadUnitLoad(MhEngineEntry[] children) Old: extend public MhEngineEntry[] importedChildren(MhEngineEntry level) New: extend public MhEngineEntry[] childEntries(MhEngineEntry level) }
From the MhLevelArrangeFunction2, the field arrangedByOtherBay was removed as it was not used.
// cm/abstract/materialHandling/storage/engine/mhLevelArrangeFunction.cm public class MhLevelArrangeFunction2 extends MhSystemEngineFunction { Removed: public Bool arrangedByOtherBay; }
For doing config comparisons between two snappers, a new strict argument was introduced where it allows a new comparison logic when strict=false. When strict=false, classifications are compared by doing a LayerSet.eval() where one symbol match is required instead of a total match between layers.
// cm/abstract/materialHandling/storage/behavior/mhConfigBehaviors.cm Old: public bool basicConfigEq(MhSnapper a, MhSnapper b, SnapperFilter childFilter=null) New: public bool basicConfigEq(MhSnapper a, MhSnapper b, bool strict=true, SnapperFilter childFilter=null) Old: public bool basicConfigNoChildrenEq(MhSnapper a, MhSnapper b) New: public bool basicConfigNoChildrenEq(MhSnapper a, MhSnapper b, bool strict) Old: public bool basicConfigNoChildrenNoShapeEq(MhSnapper a, MhSnapper b) New: public bool basicConfigNoChildrenNoShapeEq(MhSnapper a, MhSnapper b, bool strict=true)
Undo operation class MhStorageEditorConfigPropModifyUndoOp now stores the Guid to the config instead of the config object itself.
public class MhStorageEditorConfigPropModifyUndoOp extends MhStorageEditorUndoOp { Removed: public MhStorageEditorConfiguration config : copy=reference; New: public Guid configGid; New: public constructor(MhStorageEditorDialog dialog, Guid configGid, str propKey, Object propVal) { }
Removed classes MhStorageEditorDimensionToolbarModelItem and MhBayEditorElevArrowToolbarModelItem. Both functionalities are now implemented in MhStorageEditorConfiguration. Instead of separate toolbar items, they have been combined into one visibility toolbar item.
Removed: public class MhStorageEditorDimensionToolbarModelItem extends ToolbarModelItem { Removed: public class MhBayEditorElevArrowToolbarModelItem extends ToolbarModelItem { Removed: public const str cMhArrowVisibilityStateKey = "arrowVisibilityState"; public class MhStorageEditorConfiguration extends MhSystemConfiguration { New: /** * Allow dimension visibility toggle? */ extend public bool allowDimensionVisibilityToggle(View view) { return false; } New: /** * Allow arrow visibility toggle? */ extend public bool allowArrowVisibilityToggle(View view) { return false; } New: /** * Dimension visibility toggle changed. */ extend public void dimensionVisibilityToggleChanged(bool state, View view) { if (Space space = view.?space) { mhStorageEditorDimensionTogglePutCached(space, value=state); for (s in space.snappers) mhUpdateEditorDimensionVisibility(s); } } New: /** * Arrow visibility toggle changed. */ extend public void arrowVisibilityToggleChanged(bool state, View view) { if (Space space = view.?space) { mhStorageEditorArrowTogglePutCached(space, value=state); insertArrowVessel(space); } } New: /** * Update snappers visibility. */ extend public void updateSnappersVisibility() { for (s in space.snappers) mhUpdateEditorDimensionVisibility(s); insertArrowVessel(space); } }
The dimensions toggle is visible by default for bay editor. The arrow toggle is visible by default for frame editor, and bay editor's 3D view.

MhConfigNameDialog was a dialog specifically used to rename configurations. It was used together with the function str mhConfigMakeNameDialog(Window parent, str label, sizeI size=sizeI(400, 150), str initName=null, str headerName=null, pointI pos=(-1, -1)). Both the class and function have been removed.
Removed: public class MhConfigNameDialog extends NameDialog { Removed: public str mhConfigMakeNameDialog(Window parent, str label, sizeI size=sizeI(400, 150), str initName=null, str headerName=null, pointI pos=(-1, -1)) {
This functionality has been replaced with the class MhValidNameDialog which shares most of the functionality, but instead takes in a sequence of str that is considered invalid. Replace calls of the function with the following code instead:
Old: str name = mhConfigMakeNameDialog(this, "Name", initName=config.label); New: var cont = mhSystemConfigurationManager.getContainer(config); str[] invalidNames(); for (otherConfig in cont.configs) { if (otherConfig == config) continue; invalidNames << otherConfig.label; } str name = mhMakeValidNameDialog(this, $name, initName=config.label, invalidNames=invalidNames);
This class had two methods void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch) and void switchChildren(Snapper oldSnapper, Snapper newSnapper) which have been removed and replaced with void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch=false).
This change is to combine the two methods into one. You can view the 16.5 Major migration guide section "Changes to spread tools switchChildren logic" for more comprehensive information regarding the usage of switchChildren.
public class MhSnapperApplyAnimation extends MhSnapperSpreadToolAnimation { Removed: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch) { Removed: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper) { Added: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch=false) {
We have undertaken a series of enhancements to the Preconfigurator UI builder and preview system to improve overall performance, increase consistency during navigation, and introduce new features that support additional preview snappers as well as orthographic camera.
// cm/abstract/materialHandling/storage/mhStorageConfiguratorUIBuilder.cm Old: extend public Window propertiesWindow(Window w, MhSystemConfiguration config) New: extend public Window buildPropertiesWindow(Window w, MhSystemConfiguration config)
Additional methods has been introduce for each configurator item to allow preview to be display for each MhCompartmentType.
// Snappers preview for each item. public class MhStorageConfigurationItem extends MhSystemConfigurationItem /** * Snappers preview. */ extend public Snapper{} previewSnappers() { return null; } }
The MhConfigSnapperPreviewWindow class has been replaced with MhConfigViewContainer to better support model‑contextual elements and streamline the overall design.
Additionally, with the introduction of the new orthographic camera in the Preconfigurator, some methods have been deprecated as they are no longer relevant.
A feature from pre-configura has been removed from 17.0. Previously, the previewWindow functionality was intended to support custom 2D graphics—such as supplementary diagrams or detailed visual overlays—but this approach is now superseded by the enhanced capabilities of the orthographic camera system
// cm/abstract/materialHandling/storage/mhConfiguratorPreview3D.cm Old: public class MhConfigSnapperPreviewWindow extends SubWindow New: public class MhConfigViewContainer extends MultiViewContainer // cm/abstract/materialHandling/storage/mhStorageConfigurator.cm Removed: public CardWindow cardWindow; Removed: public MhConfigSnapperPreviewWindow snapperPreviewContainer; Removed: extend public bool changeSnapperPreviewParent(CardWindow cw, SubWindow sub, bool requiresRebuild=false) Removed: extend public bool changeSnapperPreviewParent(MhStorageConfigurationItemUIBuilder itemBuilder, SubWindow sub) // cm/abstract/materialHandling/storage/mhStorageConfiguratorUIBuilder.cm Removed: public MhConfigurationContentSubWindow main; Removed: extend public void zoomToMainComponent(MhStorageConfiguratorDialog dialog) Removed: extend public MhConfigurationContentSubWindow createContentSubWindow(Window w) Removed: extend public Window previewWindow(Window w, MhSystemConfiguration config) Removed: extend public Image previewButtonIcon(str key)

This class MhRowAnimationEngineGfxBehavior is now made abstract and can no longer be instantiated. The logics in this class has been moved out to MhRowAnimationEngineGfxBehaviorG1 which is the replacement of the original class. This was introduce as part of the new MhRowAnimationEngineGfxBehaviorG2 which mean to unified 2D and 3D generation.
Old: public class MhRowAnimationEngineGfxBehavior extends MhGenericEngineGfxBehavior New: public class MhRowAnimationEngineGfxBehavior extends MhGenericEngineGfxBehavior : abstract /** * Row Animation Graphics. * * It ultimately draws what is in the 'groupedEntries', little to no interpretation. * * G1: * creates the rects/boxes for graphics individually for 2D/3D methods. * supports cmSym * collision forces red coloring */ public class MhRowAnimationEngineGfxBehaviorG1 extends MhRowAnimationEngineGfxBehavior /** * Row Animation Graphics. * * It ultimately draws what is in the 'groupedEntries', little to no interpretation. * * Key differences from G1: * unified box creation for 2D and 3D * check for collision passed into the color method for more flexibility * * NOTE: no CmSym Support. */ public class MhRowAnimationEngineGfxBehaviorG2 extends MhGenericEngineGfxBehavior
Additional implementation has been added into the MhTunnelShape class that support fill cross and material for the tunnel graphic.
// cm/abstract/materialHandling/storage/racking/behavior/mhTunnelGfxBehavior.cm Old: extend public APath2D crossShape(box b) New: extend public AShape2D crossShape(MhSnapper owner, box b) Old: extend public APath2D tunnelSymPath(box b) New: extend public APath2D tunnelPath(MhSnapper owner, box b) Old: extend public GMaterial3D material3D() New: extend public Material3D material3D(MhSnapper owner)
The bool shouldReplace now is passed into the class contructor.
public class MhUnderUnitLoadCollisionAlternative extends MhFixedPointCollisionAlternative { Old: /** * Constructor */ public constructor(Snapper snapper, box unitLoadbound, point ip, orientation ir=(0deg, 0deg, 0deg), bool middle=true, double depth=0d, double dualPlacementOffset=0d, Double distanceToSnapper=null) { super(snapper, ip, ir, shouldReplace=shouldReplace, distanceToSnapper=distanceToSnapper); set*(this: middle, depth, dualPlacementOffset); this.unitLoadbound = unitLoadbound; } New: /** * Constructor */ public constructor(Snapper snapper, box unitLoadbound, point ip, orientation ir=(0deg, 0deg, 0deg), bool shouldReplace=false, bool middle=true, double depth=0d, double dualPlacementOffset=0d, Double distanceToSnapper=null) { super(snapper, ip, ir, shouldReplace=shouldReplace, distanceToSnapper=distanceToSnapper); set*(this: middle, depth, dualPlacementOffset); this.unitLoadbound = unitLoadbound; } }
We have removed fields and methods related to the collection of left and right strechers. These stretchers were only used in multibay/multiframe setups. They have been moved into MhMultiRowShape instead in the cm.abstract.materialHandling.storage.multi abstract extension dedicated to multibay/multiframe setups.
public class MhRowShape extends MhSnapperShape { Removed: public Connector[] leftStretchers : stream=null, copy=null; Removed: public Connector[] rightStretchers : stream=null, copy=null; Removed: /** * Init stretchers. */ extend public void initStretcherConnectors() { Removed: /** * Init left stretcher connectors. */ extend public void initLeftStretcherConnectors() { Removed: /** * Init right stretcher connectors. */ extend public void initRightStretcherConnectors() { Removed: /** * Clear stretcher connectors. */ extend public void clearStretcherConnectors() { Removed: /** * Update stretcher connectors. */ extend public void updateStretcherConnectors() { }
The following classes MhAisleRowEngineBehavior, MhFlueGapRowEngineBehavior, and MhBayRowEngineBehavior have had the same interfaces changed for the method shouldRunMultiChildrenLink().
public class MhAisleRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { } public class MhFlueGapRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { } public class MhBayRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { }
The global constant sFrameAccessory has been moved from package cm.abstract.materialHandling.storage.racking to cm.abstract.materialHandling.storage.
In 17.0 Major we have fixed an issue involving level snappers inserted without any unit loads. They do not have a MhSnapperBehavior in its stateBehaviorCollection field after insertion. The problem is that after inserting a UnitLoad onto a level, it does not get absorbed as a MhSnapperInfo due to the level missing the MhSnapperBehavior, and this behavior does not get initialized for existing levels.
We have since fixed it to start initializing MhSnapperBehavior when needed, and to ensure old drawings are handled we added this load code. If you have a custom level shape class that does not extend from MhLevelShape, you will need to copy the below code into your class.
public class MhLevelShape extends MhBoxSnapperShape { public void loaded1(ObjectFormatter formatter, LoadFailInfo failInfo) { super(..); mhUnstreamStoredSpec(formatter); // CETC-134365. if (formatter.version(#:package) < version(17, 0, 0)) { if (owner and !owner.snapperInfosBehavior()) { if (MhSystemConfiguration config = owner.configuration()) { forChildren(MhSnapper child in owner) { if (config.exportAsBehavior(child.classification)) { owner.initSnapperInfosBehavior(); break; } } } } } } }
The following spawners have had the mhRowChildSelectionBehavior added to them. This changes their group selection behavior such that the row is no longer included in the selection when the accessory is selected. You may need to exclude this behavior if your custom class already appends a selection behavior.
The bay editor has a material override applied to the frames. The key used for this override has changed.
Old: private void overrideFrameMaterial(Snapper snapper, REDOverrideMaterial mat) { ... const str key2D = "overrideMaterialVessel2D"; ... const str key3D = "overrideMaterialVessel3D"; ... } New: private void overrideFrameMaterial(Snapper snapper, REDOverrideMaterial mat) { ... const str key2D = "mhElevSpaceOverrideMaterialVessel2D"; ... const str key3D = "mhElevSpaceOverrideMaterialVessel3D"; ... }