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; }
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.

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.