Canvas and Device

PdfVectorCanvas and PdfVectorDevice are implementations of the VectorCanvas and VectorDevice classes, respectively. These support floating-point based operations rather than integer-based.

Snapper Graph Cache Debug Tool

SnapperGraphCacheDebugDialog

A new SnapperGraphCacheDebugDialog has been created. This tool is designed to detect if a snapper's graphs are not being cached.

It first measures the build and draw time for the snapper's graphs. To do so, it will release the cached graphs on the snapper. z.release2D(); Then, it measures the build and draw time: z.systemDrawGraphs(LayerBuffer(layerSet=normalLayerSet), view=activeView); The graphs are usually rebuilt by this during the drawGraphs call: if (!graph) build2D();

Snappers with small graphs are usually ignored by the tool since the profiler has limited precision. It checks if the build and draw time exceeds some user-defined minimum before proceeding. The default value for the minimum build and draw time is 5 milliseconds.

It then measures the draw time only for 10 times and average the result. Then, it compares the build and draw time, scaled by a user-defined time ratio, against the average draw time. drawTime >= buildAndDrawTime*graphCacheParameters.timeRatio The default time ratio is 0.5.

In other words, when the draw time exceeds half the build and draw time, the tool will flag that snapper as not caching its graphs. This also assumes the build time is around 50%.

Violating snappers will have a bright-red appearance in 2D. The dialog will also display the name and times for each violating snapper.

Caveat

So far, there doesn't seem to be any snappers that have large graphs and did not cache them.

As a sanity-check for the tool, a Simulate No Cache setting is added. During measurements of draw time, it will purposely clear the cached graphs to simulate no graph-caching for the snapper.

cm.abstract.dataSymbol

DsPicklist Item Creation

Added new methods on DsPicklistItemManager to control item creation.

// class DsPicklistItemManager
New: public DsFreeformPData createFreeformData(DsFreeformItem item)
New: public DsFreeformPicklistItem createFreeformPicklistItem(DsFreeformPData data, double quantity)
New: public DsPicklistItem createPicklistItem(DsPData data, double quantity)

Optional No Code

Added a new column for optional features that allows for a code to be exported when this option isn't selected. This is only used for SIF (Order and Catalog) exports.

// class DsPData
New: public void generateOptionalNoOptionRows(SifEnv env, SFeature f, DsiPDataOption selOpt)
New: public str sifOptionalNoCode(SFeature f, Option o, str noCode)
New: public str sifOptionalNoCodeDescription(SFeature f, Option o)

// class DsPDataProxy
New: public str sifOptionalNoCode(DsPDataProxyEnv dsEnv, SFeature f, Option o, str optCode)
New: public str sifOptionalNoCodeDescription(DsPDataProxyEnv dsEnv, SFeature f, Option o)

With these changes, we've added support for more Catalog Creator specific features to be exported when using a SIF Catalog Export.
These include optional no code for optional feature's options and the following feature characteristics : optional select, multiple select, functional, and sku select.

cm.abstract.materialHandling

Engine export

MhSystemExportFunction now has a new bool field removeFailedEntry that when true, will prevent inconsistent entries from being exported.

    /**
     * Remove failed entry.
     */
    public Bool removeFailedEntry;
    extend public void exportSnapper(Space space, MhSnapper s, MhSnapper parent=null, Snapper{} visited=null) {
    ...
        box eb = s.engineEntryBound();
            if (exportFailed(entry, eb)) {
                if (developMode) pln("Export failed for ".eRed; entry; entry.classification);
                if (removeFailedEntry.?v) {
                    entry.state = mhEngineEntryState.markedToRemove;
                    env.removed << s;
                    return;
                }
            }
    ...
    }

Changes to level parent engine behavior

MhLevelParentEngineBehavior can now pass in a limitZ value to the MhLevelPopulateFunction engine function by overriding the levelLimitZ() method.

    /**
     * Max z limit to populate levels.
     */
    extend public Double levelLimitZ(MhSnapper snapper) {
        return null;
    }

Changes to level engine functions

The levelArrange() method in MhLevelArrangeFunction has had some of its code moved out into a separate method levelArrangeMoveEntries().

    /**
     * Level arrange move entries.
     */
    extend public void levelArrangeMoveEntries(MhEngine engine, MhEngineEntry parentEntry,
                                               MhEngineEntry[] childEntries,
                                               MhEngineEntry[] toExport) {
        //TODO remove this when we linking pickAndDrop and its main level in 13.5
        MhEngineEntry[] pdEntries = getPickAndDropEntries(engine);
        bool anyPickAndDrops = pdEntries.any;

        double z;
        MhEngineBoxEntry lastEntry;
        double fixedLimit = fixedLevelLimit();

        bool validStep;
        MhPopulator populator = populator(parentEntry);
        for (MhEngineBoxEntry entry in childEntries, index=i) {
            z = entry.pos.z;
            toExport << entry;

            validStep = populator.step(entry);

            if (validStep) {
                if (!entry.isBottom and !entry.isTunnel) {
                    z = populator.currentPos.z;
                }
            }

            if (z > parentEntry.h and shouldMaintainBayHeight) toExport.exclude(entry);

            double diff = z - entry.pos.z;
            if (diff != 0) {
                if (squeezeLevels.?v or !isFixedLevel(entry, lastEntry, diff, fixedLimit)) {
                    double oldZ = entry.pos.z;
                    entry.move((0, 0, diff));
                    if (anyPickAndDrops) movePickAndDropEntry(entry, oldZ, pdEntries);
                    linkLevels(entry, lastEntry);
                }
            }

            if (validStep) populator.update(entry);
            lastEntry = entry;
        }
    }

The levelPopulate() method in MhLevelPopulateFunction has been broken up into the following methods to make it more extensible.

    /**
     * Remove invalid level.
     */
    extend public void removeInvalidLevel(MhEngineEntry[] children, MhEngineEntry edit) {


    /**
     * Find and return last accepted level and the level before that.
     */
    extend public <MhEngineSnapperEntry, MhEngineSnapperEntry> findLastLevel(MhEngineEntry[] imported, MhEngineEntry edit) {


    /**
     * Try populate last level.
     */
    extend public bool tryPopulateLastLevel(MhEngineSnapperEntry &lastEntry,
                                            MhEngineEntry[] children,
                                            MhEngine mhEngine) {


    /**
     * Populate levels with calculated value.
     * Base implementation uses compartment height between last level and level before it.
     */
    extend public bool calculatePopulateLevels(MhEngineSnapperEntry lastEntry, MhEngineSnapperEntry prevLastEntry, MhEngineEntry edit, MhEngineEntry[] children) {


    /**
     * Populate levels with populator.
     */
    extend public bool resolvePopulateLevels(MhEngineSnapperEntry lastEntry, MhEngineEntry edit,
                                             MhEngineEntry[] children) {

Changes to clearance spec

The implementation of collisionPrimitives() in MhClearanceSpec which generated collision primitives based on the given shape's bound has been moved into a new method levelClearanceCollisionPrimitives().

    /**
     * Level clearance collision primitives.
     */
    extend public CollisionPrimitive[] levelClearanceCollisionPrimitives(MhSnapperShape shape, Transform t,
                                                                         double elevation, LayerSet classification) {
        CollisionPrimitive[] res(4);
        if (levelClearanceZ) {
            box b = shape.?localBound();
            b.h = levelClearanceZ.v;

            static Layer layer = Layer(layerSet(sClearance, sLevelClearanceZ), layerSet(sLevel, sLevelFrame));
            res << mhGetBoxCollisionPrimitive(b, layer, t);
        }

        return res.any ? res : null;
    }

Introduced a new behavior MhClearanceSpecLevelClearanceBehavior registered as standard global behavior mhClearanceSpecLevelClearanceBehavior. This behavior generates collision primitives based on a level's clearance spec. The intention of this behavior is to be used to enforce a level clearance (generally controlled by MhClearanceSpec.levelClearanceZ) without requiring unit loads. This behavior is called in MhLevelShape.appendLevelClearancePrimitives().

    public mhLazyGlobal MhBehavior mhClearanceSpecLevelClearanceBehavior = MhClearanceSpecLevelClearanceBehavior();


    /**
     * Clearance spec level clearance behavior.
     * Appends just the level clearance collision primitives.
     */
    public class MhClearanceSpecLevelClearanceBehavior extends MhClearanceSpecBehavior {
    }

User selectable categories

All categories registered in the material handling abstract are now user selectable. They will now appear in the Categorization dialog for users to manually assign to snappers.

User selectable categories

MhSnapperMultiApplyAnimation changes

The applyToCandidateGroup() method in MhSnapperMultiApplyAnimation has been broken up into the following methods.

    /**
     * Get SnapperGroup main.
     */
    extend public MhSnapper getGrpMain(MhSnapperGroup grp) {
        if (!grp) return null;
        for (s in grp.snappers)
          if (s.classification.eval(selection.?main.MhSnapper.classification))
            return s;

        return grp.?snappers.get;
    }


    /**
     * Add new snappers.
     */
    extend public MhSnapper[] addNewSnappers(Snapper[] toBeInsert, MhSnapper parent,
                                             box b0, bool rotated, orientation rot, LayerSet classification) {
        MhSnapper[] newSnappers(toBeInsert.count);

        for (MhSnapper s in toBeInsert) {
            if (MhSnapper newSnapper = snapperReplacement(s)) {
                newSnappers << updateNewSnapper(newSnapper, parent,
                                                b0, rotated, rot, classification);
            }
        }
        return newSnappers;
    }


    /**
     * Update new snappers.
     */
    extend public MhSnapper updateNewSnapper(MhSnapper newSnapper, MhSnapper parent,
                                             box b0, bool rotated, orientation rot, LayerSet classification) {
        Space space = parent.space;
        newSnapper.classification = classification;

        point p = getNewSnapperPos(newSnapper, parent, b0, rotated);
        newSnapper.setPos(p);
        orientation r = parent.toSpace(rot);
        newSnapper.setRot(r);
	
        space.?undoableInsert(newSnapper); // child must have space to be adoptable
        parent.tryAdopt(newSnapper);

        return newSnapper;
    }


    /**
     *  LinkNewSnappers.
     */
    extend public void linkNewSnappers(Snapper[] oldChildren, MhSnapper[] newSnappers, MhSnapper parent) {
        for (n in newSnappers, index=i) {
            if (oldChildren.count > i) {
                ?MhSnapper o = oldChildren[i];
                n.invalidateEngineEntry();
                relinkNeighbors(o, n);
                o.invalidateEngineEntry();
            }
        }
    }

Changes to MhBaySizeEditorItem

Added a new method loadWithinBayLimit(). Override and return true in this method to ensure that unit loads stay within the bay's limit.

    /**
     * Return true if load should be within bay limit.
     */
    extend public bool loadWithinBayLimit() {
        return false;
    }

Changes to unit load engine functions

The easyPopulate() method in MhUnitLoadPopulateFunction has had some of its code moved out into a separate method easyPopulateLoads().

    /**
     * Easy populate loads.
     */
    extend public void easyPopulateLoads(MhEngineEntry edit, MhEngineEntry[] imported,
                                         MhEngineEntry[] children, MhPopulator populator,
                                         MhSystemEngineEnvironment env) {
        MhEngineSnapperEntry lastEntry;
        for (MhEngineSnapperEntry entry in imported, reverse=reverse.?v, index=i) {
            if (entry.isUnitLoad()) {
                if (populator.step(entry)) {
                    entry.move(populator.currentPos - entry.pos);
                    lastEntry = entry;
                } else {
                    children.exclude(entry);
                    lastEntry = null;
                }
            }
        }
		
        if (lastEntry and populateMoreLoads) {
            while (populator.step(lastEntry)) {
                MhEngineConstructionEntry e = lastEntry.?toConstructionEntry();
                e.move(populator.currentPos - e.pos);

                if (reverse.?v) {
                    children.insert(0, e);
                } else {
                    children << e;
                }
            }
        }
    }

There are two new methods noOfUnitLoads() and nextPopulatorPos() in MhDeepstorageUnitLoadConstructionalPopulateFunction. noOfUnitLoads() can be overridden to control the number of unit loads used in building up the bedWidths() sequence. nextPopulatorPos() can be overridden for different starting positions of bed width.

    /**
     * Number of unit loads.
     */
    extend public int noOfUnitLoads() {
        return configuration.getValue("noOfUnitLoads").int;
    }

The addUnitLoads() method in MhDeepstorageUnitLoadConstructionalPopulateFunction has had some of its code moved out into a separate method addAdditionalUnitLoads().

    /**
     * Add additional unit loads.
     */
    extend public void addAdditionalUnitLoads(MhEngineEntry[] childEntries, bool update) {
        double[] beds = bedWidths(systemEnvironment);
        MhEngineEntry[] toAddEntries(childEntries.count*beds.count);
        double currX;
        for (b in beds) {
            currX += first ? 0 : b;
            if (!first) {
                for (c in childEntries) {
                    MhEngineEntry cc = copy(c);
                    cc.move((currX, 0, 0), update=update);
                    toAddEntries << cc;
                }
            }
        }

        if (toAddEntries.any) childEntries += toAddEntries;
        mhSystemEngineCache.put(parent.cacheKey, childEntries.copy());
    }

MhDeepstorageUnitLoadConstructionalPopulateFunction now overrides easyPopulateLods() instead of easyPopulate(), and has a new method easyPopulateBeds() to handle some existing logic.

    /**
     * Easy populate beds.
     */
    extend public void easyPopulateBeds(MhEngineEntry edit, MhEngineEntry[] imported, MhEngineEntry[] children, MhPopulator populator, MhSystemEngineEnvironment env, double[] bedWidths, MhBedEngineEntryHolder[] beds) {
        point startPos = populator.currentPos;

        MhEngineSnapperEntry lastEntry;
        for (bedWidth in bedWidths, index=i) {
            if (!first) {
                populator.currentPos = nextPopulatorPos(edit, startPos, bedWidth);
                if (populator as MhStepperPopulator) populator.?currentStep = null;
                startPos = populator.currentPos;
            }

            range bedXRange(startPos.x-bedWidth/2, startPos.x+bedWidth/2);

            if (MhBedEngineEntryHolder bed = getValidBedEntryHolder(beds, bedXRange)) {
                beds.exclude(bed);
                for (MhEngineSnapperEntry entry in bed.list) {
                    if (populator.step(entry)) {
                        entry.move(populator.currentPos - entry.pos);
                        lastEntry = entry;
                    } else {
                        children.exclude(entry);
                        lastEntry = null;
                    }
                }
            }

            if (lastEntry and populateMoreLoads) {
                while (populator.step(lastEntry)) {
                    MhEngineConstructionEntry e = lastEntry.?toConstructionEntry();
                    e.move(populator.currentPos - e.pos);

                    if (reverse.?v) {
                        children.insert(0, e);
                    } else {
                        children << e;
                    }
                }
            }
        }
    }

MhSpreadPatternBehavior has been extended to allow for easier customization of snapper spreading cache keys. In general, we would recommend to utilize the new MhSpreadPatternBehavior interfaces: appendCacheKey and appendAdditionalCacheKey, to customize spread cache key, instead of the previous way of extending every single MhSnapperSpreadPattern to account for every single classification variants.

New: public class MhSpreadToolEnv

// public class MhSnapperSpreadPattern
New: extend public str key()

// public class MhSpreadToolAnimation
New: extend public void appendAnimationSpreadCacheKey(StrBuf buf)
New: extend public MhSnapperSpreadPattern spreadPattern()


// public class MhSpreadPatternBehavior
New: extend public void appendCacheKey(StrBuf buf, MhSnapper main, SnapAlternative alternative, MhSnapperSpreadPattern pattern)
New: extend public void appendAdditionalCacheKey(StrBuf buf, MhSnapper main, SnapAlternative alternative, MhSnapperSpreadPattern pattern)


// public class MhSnapperRemoverVessel
New: extend public void clearSpreadGroupCache()
New: extend public str->SpreadPatternGroup cachedSpreadGroups()
New: extend public SpreadPatternGroup spreadGroup(str key)
New: extend public SpreadPatternGroup cacheSpread(str cacheKey, CoreObject{} candidates)
New: extend public SpreadPatternGroup findSpreadGroup(CoreObject{} snappers)


// public class MhSnapperSpreadVessel
New: extend public void clearSpreadGroupCache()
New: extend public str->SpreadPatternGroup cachedSpreadGroups()
New: extend public SpreadPatternGroup spreadGroup(str key)
New: extend public SpreadPatternGroup cacheSpread(str cacheKey, CoreObject{} candidates)
New: extend public SpreadPatternGroup findSpreadGroup(CoreObject{} snappers)
New: extend public void updateSpreadGroupAfterExplode(Snapper parent, box[] markedInfoBounds, MhSnapper[] explodedChildren, str cacheKey)

cm.core

cm.core.debug

Added a search option in DropDownTreeView and DropDownTreeViewPopup.

cm.win

Added a search option in DropDownTreeView and DropDownTreeViewPopup.

custom.flooring

Flooring now has support for roll products.

Flooring waste reuse is now available.

Flooring cove base allows the user to calculate the length of base board of flooring surfaces.