cm.abstract.dataSymbol

The fields I1 (part base price) and O1 (option price) have been added to SIF exports for parts. OL for option price has been removed.

final package str[] generateConfiguraSifRows(DsPart part, Space space) {
	
	...
	
	// I1= base price (without option upcharges)
	addSifRow(env, "I1", part.basePrice().toS());
	
	...
	
	//Options
	for (DsPDataOption pO in sortedSelOptions, index=index) {
		...
		addSifRow(env, "O1", o.price(this).toS);
		...
	}
	
	...
}

The fields I1 (part base price) and O1 (option price) have been added to SIF exports for parts, leading to these additions in DsFreeformPData:

public str[] generateSifRows(DsPart part, Space space) {
	
	...
	
	// I1= base price (without option upcharges)
	addSifRow(env, "I1", part.basePrice().toS());
	
	...
	
	//Options
	for (pO in sortedSelOptions, index=index) if (pO as DsPDataOption) {
		...
		addSifRow(env, "O1", o.price(this).toS);
		...
	}
	
	...
}

cDsFreeformMeasTypeSubSet now excludes enum types unknown, mirrorPointX, mirrorPointY, and insertElevation. This means these columns are no longer available as columns in the freeform picklist.

cm.abstract.dataSymbol.materialLegend

The following functions have been added to support filtering of materials in DsMaterialLegendSnappers.

DsMaterialLegendSnapper

Added: public ObjectLabelSubSet _filterSubSet;
Added: public void initFilters() {}
Added: extend public ObjectSubSet filterSubSet() {}

The following functions have been added to support length units for COMs in the in DsMaterialLegendItems.

DsMaterialLegendItem
Added: extend public Graph graphLengthUnit(UserTextStyle style) {}

DsMaterialLegendItemBuildEnv 
Added: public constructor(bool buildName=true, bool buildDescription=true,
		       bool buildID=true, bool buildEnterprise=true|, bool buildLengthUnit=false) {}

To support filtering of DsMaterialLegends, ObjFilters are utilized to filter materials in the legend. As a result, the optional bool filtered parameter was added when retrieving the sorted item list for legends..

DsMaterialLegend 
Old: public sorted str->MaterialLegendItem sortedList() {}
New: public sorted str->MaterialLegendItem sortedList(bool filtered=true) {}

cm.abstract.draw

On drawing load, instances of DrawEllipse will be replaced by instances of Draw3DEllipse. The only major difference between them is that Draw3DEllipse optionally supports 3D graphics outside of paper space.

cm.abstract.material

The following overidden getter/setter functions have been added. Now, changes to description on COMPartProxy change the description on the referenced CustomerOwnMaterial.

// get/set now reference CustomerOwnMaterial description
Added: public str description() {}
Added: public str description=(str v) {}

The following functions have been added to support updating a specific MaterialTreeViewItem in the tree view of materials and CustomerOwnMaterial undo operations.

// Updates a specific MaterialTreeViewItem in the tree view
Added: extend public void updateMaterial(Material mat) {}

// returns the COMUndoOp for the CustomerOwnMaterial and the operation being performed
Added: extend public COMUndoOp comUndoOp(CustomerOwnMaterial com, comUndoOperation operation) {}

cm.abstract.materialHandling

MhAfterEngineInitialExportBehavior changes

Change sorting of snapper from locally defined snapper child depth sorting to use newly added mhSnapperFuncRunOrderSort(Snapper a, Snapper b, Object env).

MhEngineRun changes

Change sorting of snapper from locally defined snapper child depth sorting to use newly added mhSnapperFuncRunOrderSort(Snapper a, Snapper b, Object env) during export.

MhRemoverExecuteVessel changes

Change sorting of snapper from locally defined snapper child depth sorting to use newly added mhSnapperFuncRunOrderSort(Snapper a, Snapper b, Object env).

MhSnapperShape changes

Implement sorting of snapper children to use newly added mhSnapperRootXYZPosSort(Snapper a, Snapper b, Object env) during property change.

MhEngineManager

Split run functions into multiple functions for clarity. This means that runFunctions will be a stand alone function and no longer call post engine run and finalize engine run.

        int i;
        while (i <= list.lastIndex) {
            if (MhEngineRun r = list[i]) {
                currentRun = r;
                
                r.beforeEngineRun(e, runEnv);
                
                r.mainRunFunctions(e, runEnv);
                r.postRunFunctions(e, runEnv);
                r.finalizeRunFunctions(e, runEnv);
                
                r.afterEngineRun(e, runEnv);
                
                currentRun = null;
            }

            i++;
        }

For function that called runFunctions outside of engine manager, this will have to change as below.

Example: 

Old:  {
    run.runFunctions(runEngine, env);
}

New: { 
    run.mainRunFunctions(runEngine, env);
    run.postRunFunctions(runEngine, env);
}

Temporary MhConfigRef copy changes

The function mhConfigTempCopy(MhConfigRef ref) will now also set the field id = 0 of MhConfigRef objects where their field isCustom = true.

Old:
public MhConfigRef mhConfigTempCopy(MhConfigRef ref) {
    MhConfigRef cRef = copy(ref);
    cRef.gid = guid();
    
    if (ref.isCustom) {
        if (developMode) if (cRef.id != -1) ptrace("Expected ID to be -1 for custom!".eAngry);
    } else {
        cRef.id = 0;
        cRef.name = "";
    }
    return cRef;
}

New:
public MhConfigRef mhConfigTempCopy(MhConfigRef ref) {
    if (!ref) return null;
    MhConfigRef cRef = copy(ref);
    cRef.gid = guid();
    cRef.id = 0;
    if (!ref.isCustom) cRef.name = "";
    return cRef;
}

This is because MhConfigRef objects are only considered temporary if id == 0.

public class MhConfigRef {

    /**
     * Temp.
     */
    final public bool temp() { return id == 0; }
}

Without resetting the id field back to 0, they will not be treated as temporary config refs by MhConfigManager, stopping those instances from being merged with identical config refs in the MhConfigManager.configs container field.

MhLevelConstructionalPopulateFunction changes

A new field public Int noOfLevels has been added to MhLevelConstructionalPopulateFunction. When a noOfLevels value is passed in from your engine behavior, this function will only populate loads up to the noOfLevels value instead of using the parent bound and populating as much as possible up to the bound height.

cm.abstract.materialHandling.storage

Drawing configurations changes for bay and frame editors

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

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

public class MhStorageEditorDialog extends DialogWindow : abstract {

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

            return drawingConfigsEditable();
        }
        return true;
    }
}

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

public class MhStorageEditorDialog extends DialogWindow : abstract {

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

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

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

public class MhStorageEditorDialog extends DialogWindow : abstract {

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

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

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


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


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


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


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


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


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


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

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

public class MhStorageEditorConfiguration extends MhSystemConfiguration {

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

        return false;
    }
}

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

public class MhStorageEditorDialog extends DialogWindow : abstract {

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


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

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

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

        return true;
    }
}

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

package class MhStorageEditorWorldEvents extends EventViewer {

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


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

Rows break all soft connections

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

public class MhRowShape extends MhSnapperShape {

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

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

public class MhRowStretchAnimation extends MhPropertyStretchAnimation {

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

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

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

        return res + snappers;
    }

MhStorageConfiguration changes

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

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

MhLevelArrangeFunction2, a better and simpler MhLevelArrangeFunction

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

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

MhUnitLoadArrangement, to handle unitload layout on unit load.

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

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

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

MhPopulator

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

- New: public void reset() {

MhCompartmentConfigurationItem

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

cm.abstract.materialHandling.storage.engine

MhSystemExportFunction changes

Change sorting of entry from locally defined entry snapper child depth sorting to use newly added mhEntryFuncRunOrderSort(MhEngineEntry a, MhEngineEntry b, Object env).

mhStorageChildrenBlocks changes

Change sorting of entry from locally defined entry local position sorting to use newly added mhEntryFuncRunOrderSort(MhEngineEntry a, MhEngineEntry b, Object env).

MhSystemImportFunction changes

Move logic that break linking after export to actual import function MhSystemImportFunction

- New: public void afterImport(MhEngineEntry{} imported, MhEngine engine) {

cm.abstract.materialHandling.storage.racking

Add logic to explicitly allowNewSpacer(..) calculated using aisle depth and implemented as always true by default.

cm.abstract.materialHandling.storage.racking.deepstorage

MhDeepstorageUnitLoadConstructionalPopulateFunction changes

MhDeepstorageUnitLoadConstructionalPopulateFunction is now able to populate based on noOfUnitLoadsInDepth that is passed in as an argument. A new flag is added populateBasedOnNoUlDepth, so that you can decide whether to populate unit load based on the noOfUnitLoadsInDepth given or populate as many as possible.

public class MhDeepstorageUnitLoadConstructionalPopulateFunction extends MhUnitLoadConstructionalPopulateFunction {
- New: public Bool populateBasedOnNoUlDepth;
- New: public Int noOfUnitLoadsInDepth;

cm.abstract.part

The field I1 (part base price) has been added to SIF exports for parts, leading to this addition:

extend public str[] internal_generateConfiguraSifRows(Space space) {
	...
	addSifRow("I1", basePrice().toS(), lines);
	...
}

cm.core

cm.core

Core Properties bugfix

'cm/core/coreProperties.cm::build(PreCorePropObj z, symbol{} attributes=null, BuildPropertiesEnv env=null, str->Object args=null)`

Resolved a bug during properties rebuild, with existing properties not getting removed correctly if the rebuilt properties has zero item or only one item left. This may impact developers using workarounds to manually remove properties that are incorrectly left behind.

cm.core.red3D.distributed

In runtime REDRenderJOB work the same as it before.

cm.core.red3D.distributed.redRenderJob

In runtime REDRenderJOB work the same as it before.

cm.core.toolbox

Extension Tabs

With the addition of Extension Tabs (not to be confused with Toolbox Component Tabs), the toolbox card can now contain multiple instances of ToolboxSectionedScrollableSubWindow, one for each tab. To differentiate between these instances, the key of the window can be used instead. The format of a key is $cardKey!$tabKey.

Changes to the toolbox configuration have been made as well. It stores information such as what sections should be hidden, what the order of each section should be, and what was collapsed by the user. If the Component Tab uses Extension Tabs, the key of each tab is prepended to the section keys that are stored into the config.

If your tab does not have any tabs, behaviour is unchanged from previous versions.

New libraryFun for Paper Tools toolbox in facelift

Many functions inside cm.core.toolbox/toolbox.cm uses the name of the function that created a library to find it.

In facelift, they key for the paper tools library is instead faceliftPaperToolsLibrary. So when trying to find the paper tools library, make sure to search for that when useFacelift is true.

Affected functions are:

public bool updateToolboxCheckBox(symbol pkg, str libraryFun, str checkBoxKey, bool check, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateAllTbCheckBoxes(symbol pkg, str libraryFun, str checkBoxKey, bool check, bool invokeCallback=true) {
public bool updateToolboxSubSetDropDown(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool rebuildToolboxSubSetDropDown(symbol pkg, str libraryFun, str dropDownLimbKey, Object initial, Object[] seq, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateAllTbSubSetDropDowns(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, bool invokeCallback=true) {
public bool updateToolboxMaterialTreeDropDown(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, CoreAppWindow appWindow=null, bool invokeCallback=false) {
public bool updateAllTbMaterialTreeDropDowns(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, bool invokeCallback=false) {
public bool updateToolboxEnumDropDown(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateAllTbEnumDropDowns(symbol pkg, str libraryFun, str dropDownLimbKey, str selectItemKey, bool invokeCallback=true) {
public bool updateToolboxDistanceLimb(symbol pkg, str libraryFun, str distanceLimbKey, Object object, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateAllTbDistanceDropDowns(symbol pkg, str libraryFun, str distanceLimbKey, Object object, bool invokeCallback=true) {
public bool updateToolboxButton(symbol pkg, str libraryFun, str buttonKey, CoreAppWindow appWindow=null) {
public bool updateAllTbButtons(symbol pkg, str libraryFun, str buttonKey) {
public bool updateToolboxButtonColor(symbol pkg, str libraryFun, str buttonKey, color c, CoreAppWindow appWindow=null) {
public bool updateAllTbButtonColors(symbol pkg, str libraryFun, str buttonKey, color c) {
public bool updateToolboxDoubleTextField(symbol pkg, str libraryFun, str textFieldKey, Object object, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateToolboxDistanceTextField(symbol pkg, str libraryFun, str textFieldKey, Object object, CoreAppWindow appWindow=null, bool invokeCallback=true) {
public bool updateAllTbDoubleTextFields(symbol pkg, str libraryFun, str textFieldKey, Object object, bool invokeCallback=true) {
public LazyLibraryCard getLibraryCard(symbol pkg, str libraryFun, CoreAppWindow appWindow=null) {
public SubSetLimbDropDown getSubSetDropDown(symbol pkg, str libraryFun, str dropDownLimbKey) {

Possibly other in other packages are also affected. Anything that used stdPaperToolsLibrary as a key and is also supposed to work in facelift, essentially.

cm.import.interop

COM Interop / DotNetInvokerServer potential behavior change

We have fixed an issue with DotNetInvokerServer failing to resolve necessary DLL dependencies. Previously it calls .NET Assembly library's LoadFile which loads the specified assembly, but this method does not resolve dependencies. This means custom COM code that references an external library will not work properly or silently fail.

This has been resolved by calling LoadFrom() instead, which attempts to automatically resolve dependencies, for example it will attempt to search for dependencies in the same folder and load them. However, in rare cases the dependency may resolve to an already loaded version with the same identity but on a different path, which might potentially be an issue in development environments. Please perform a smoke test with your COM interop functionalities to ensure they are working fine.

cm.std.print.template

Through PackageStreamRenamer, the classes below will be replaced as followed when a drawing or favorite is loaded:

DrawTemplate3DCircle     -> Draw3DCircle
DrawTemplate3DRect       -> Draw3DRect
DrawTemplate3DArc        -> Draw3DArc
DrawTemplate3DLine       -> Draw3DLine
DrawTemplate3DHelpLine   -> Draw3DHelpLine
DrawTemplate3D2SidedRect -> Draw3D2SidedRect
DrawTemplate3D3SidedRect -> Draw3D3SidedRect

cm.win

Added new argument to isValid method of G3StatsEntity in cm.win.eventLogG3.cm to allow for different validities for different event actions.

Old: extend public bool isValid()
New: extend public bool isValid(str action)

The following functions have been overritten in Display affecting displays with passive=true.

public void rightClick(pointI p) {
public void rightClick2(pointI p) {
public void rightRelease(pointI p) {
public void rightRelease2(pointI p) {

The setGlobalSkin(Skin) function is now a no-op when CET is in the new UI. This is part of larger plan to remove the Skin concept entirely in a future version of CET.