Canvas and Device

Canvas

Canvas implementations that subclass Canvas directly, instead of a more specific implementation like GdiCanvas will find many things not working, since many methods have been moved to ToPixelCanvas. Try subclassing GdiCanvas or ToPixelCanvas instead.

CollabG3 (CollabPro)

Although a local file containing CollUFOFileType is never created, bug() will now be triggered as an additional enforcement in the event it is mistakenly created.

Compiler

Daylight-savings (DST) handling

Time conversion functions throughout various parts of the system were incorrectly handling daylight-savings transitions. This has been fixed.

Object::setField now follows the type rules

The setField method used to permit almost any value type so long as the type of the destination field was assignable to Object. This is a severe violation of the type system's rules and thus in the new version, setField ensures that the type of the instance passed in is assignable to the type of the destination field.

Basically, the same rules that apply to normal field assignment (instance.field = value) now apply to setField (instance.setField("field", value)). There is an exception for unboxing -- for example, you can use setField to set an int field, even though you're passing in an Int, since setField takes an Object.

Example

public class Foo {
    public constructor() {}
    public str->str map_field;
}

{
    Foo f();
    f.setField("map_field", new str->str());    // ok
    f.setField("map_field", new str->Object()); // forbidden in 14.0
}

Changes to severe internal error handling (bail out)

When CM has a severe internal error, it will perform a "bail out". In some circumstances, the bail out may itself cause further errors. In 14.0, many of those situations are now handled by "fast fail" instead. If minidump generation or JIT debugging are configured, they will be invoked, otherwise the application will terminate ungracefully.

Some extremely severe errors (e.g. garbage collector state corruption) may go directly to "fast fail" without a "bail out" attempt, leaving no trace in log files.

Data Variables

When calling a DRecord method that takes the path to a StrArray variable, an additional indexing operator ";;" can now be specified, followed with an integer, to access the value stored at that index.

DRecord root();
root.assign("test.var", StrArray(["one", "two"]));
pln(root.lookupStr("test.var;;1")); // output: two

As a result of this change, the substring ";;" can no longer appear in any part of the path to the variable, and can only appear when accessing a StrArray.

DRecord root();
root.assign("test.var;;1", null); // not allowed
root.assign("test.v;;ar", 3); // not allowed

Floating-point Rounding Behavior

Rounding of floating-point numbers

This is not something introduced in 14.0. This is something introduced in Windows 10 version 2004 (build 19041) which was released May 2020. This is now covered here because it directly affects how floating-point numbers are rounded and has not been mentioned in any migration guide yet.

This update changes the way the fprint-family formats floating-point numbers. Before the mentioned build, floating-point numbers ending in '5' always rounded up. This was changed in May 2020 to always round to the closest even digit (known as Banker's rounding). This to be more in line with IEEE 754.

This affects all methods which in converts a floating-point number into a string.

For example:

Before May 2020:

   formatted(1.45, precision=1)=1.5
   formatted(1.35, precision=1)=1.4

After May 2020 (currently):

   formatted(1.45, precision=1)=1.4
   formatted(1.35, precision=1)=1.4

For more information, read Microsoft's Documentation

Variants Constraints

Certain tags stored in XML will be removed/added in ofdaImport() and ofdaExport(). db3 import and export behaviours will also be affected similarly. Hence, forward compatibility would not be supported i.e., importing a newer XML/db3 file into an older version of CET may not have the correct apply style applied to the catalog.

The following classes are affected:

DataCatalog

Tags removed:

  • ConstraintsApplyStyle
  • ConstraintType

DsRuleType

Tags added:

  • ConstraintExpressionType

cm.abstract

The global private bool _blockedUndoHookCB has been removed from photoSnapper and instead added as a public field in the drawRect parent class. This was done since this value needs to be checked during the drawRectUndoHook. Although it is only used by photoSnapper, it was moved to the parent class to avoid drawRect from needing a dependecy on photoSnapper. In order for the photoSnapper subclass to be able to read and modify its value, it was changed to a public variable.

old: cm/std/photo/photoSnapper.cm

private bool _blockUndoHookCB = false;

new: cm/abstract/draw/drawRect.cm

    /**
     * Block undo hook.
     */
    public bool blockUndoHookCB = false;

As a result we now instead modify this public field in cm/std/photo/photoSnapper.cm quickPropertyChanged():

old:

_blockUndoHookCB = true;
...
_blockUndoHookCB = false;

new:

blockUndoHookCB = true;
...
blockUndoHookCB = false;

As mentioned, the previously named photoSnapperUndoHook has been moved from PhotoSnapper to its parent class DrawRect and renamed drawRectUndoHook. As a result the hook registration has also been moved to the parent class:

cm/std/photo/photoSnapper.cm removed:

/**
 * Photo snapper undo hook.
 */
private void photoSnapperUndoHook(World w, UndoStep step) {
    if (_blockUndoHookCB) return;

    //double start = microTime();
    if (step and step.contains(UndoModifyOp)) {
	if (!w.isUndoRecording and !isUndoRestoreInProgress) {
	    Space{} affected(4);
	    for (PhotoSnapper s in step.modified) {
		if (s.space !in affected and s.space.isPaperSpace() and step.affects(s.space)) {
		    affected << s.space;
		}
	    }

	    if (affected.any) {
		View{} visited(4);
		for (space in affected) {
		    for (REDPaperView view in space.views, index=i) {
			if (view !in visited) {
			    view.invalidatePaperShape();
			    visited << view;
			}
		    }
		}
	    }
	}
    }
    //pln("Invalidate paper shape took: ", us(microTime - start));
}


old:

init {
    putAlternativesChangedHook("cm.std.photo.photoSnapper", function changedAlternativeHook);
    registerUndoHook("photoSnapperUndoHook", function photoSnapperUndoHook);
}

new:

init {
    putAlternativesChangedHook("cm.std.photo.photoSnapper", function changedAlternativeHook);
}

cm/abstract/draw/drawRect.cm added:

use cm.application;

added:

/**
 * Draw rect undo hook.
 */
private void drawRectUndoHook(World w, UndoStep step) {
    if (step and step.contains(UndoModifyOp)) {
	if (!w.isUndoRecording and !isUndoRestoreInProgress) {
	    Space{} affected(4);
	    for (DrawRect s in step.modified) {
		if (s.space !in affected and s.space.isPaperSpace() and step.affects(s.space) and s.drawLayer == bottomLayer and !s.blockUndoHookCB) {
		    affected << s.space;
		}
	    }

	    if (affected.any) {
		View{} visited(4);
		for (space in affected) {
		    for (REDPaperView view in space.views, index=i) {
			if (view !in visited) {
			    view.invalidatePaperShape();
			    visited << view;
			}
		    }
		}
	    }
	}
    }
}

added:

init {
    registerUndoHook("drawRectUndoHook", function drawRectUndoHook);
}

AccessoryEnv

Commit ae48deba In createAccessory() now checks to see if the AccessoryEnv accepts the location instead making a new accessory and asking the location if it accepts the accessory.

Old : 
extend public bool createAccessory(AccGenBehavior behavior, AccLoc loc, CoreObject{} existing) {
 CoreObject acc = generateAccessory(behavior, loc);
  
 if (loc.acceptAccessory(acc)) {
  behavior.updateAccessory(acc, loc);
 ...
 }
 
New :
extend public bool createAccessory(AccGenBehavior behavior, AccLoc loc, CoreObject{} existing) {
 if (acceptLocation(loc)) {
   CoreObject acc = generateAccessory(behavior, loc);
   
   behavior.updateAccessory(acc, loc); 
 ...
 }

COMmaterials

Creating a COMaterial will always create a GMaterial3D. It will use a PBRMatSpec as its spec. Selecting a texture from the COMaterial dialog will set the baseTextureUrl of the spec, while selecting a color from the dialog will set the baseColorFactor of the spec.

cm/abstract/material/comMaterialDialog.cm and cm/abstract/industry/comMaterialDialog.cm:

Constructor:

old:
	    if (!tempMat)
	      tempMat = CustomerOwnMaterial("COMTEMP", "", colorMaterial3D( 125, 125, 125 ));

new:
	    if (!tempMat)
	      tempMat = CustomerOwnMaterial("COMTEMP", "", GMaterial3D());

cm/abstract/material/comMaterialDialog.cm

old:
    extend public void chooseBtnColorCB() {
	color c = chooseColorDialog(parent, tempMat.color2D);
	if (c.isColor) {
	    Material3D m = colorMaterial3D(c);
	    tempMat.updateMaterial3D(m);
	    setControlsEnable();
	    updateImage();
	}

new:

    extend public void chooseBtnColorCB() {
	color c = chooseColorDialog(parent, tempMat.color2D);
	if (c.isColor) {
	    PBRMatSpec spec();
	    spec.baseColorFactor = colorToColorF(c);
	    GMaterial3D m = GMaterial3D(spec);
	    tempMat.updateGMaterial3D(m);
	    setControlsEnable();
	    updateImage();
	}

old:

      extend public void browseUrl() {
	Url url = coreSettings.get("COMaterialDialog.wrappedImageFile").Url;
	int imageFileFilterIdx = coreSettings.safeGetInt("COMaterialDialog.fileFilterIdx");
	
	Int fileFilterIdx(imageFileFilterIdx);
	url = getOpenFileName(this, url, imageImportFilter, filterIndex=fileFilterIdx);
	
	if (url and url.isReadable) {
	    setLastUrlAndIndex(url, fileFilterIdx.v);
	    Material3D m = wrappedImageMaterial3D(url);
	
	    tempMat.updateMaterial3D(m);
	
	    updateImage();
	}

new:

      extend public void browseUrl() {
	Url url = coreSettings.get("COMaterialDialog.wrappedImageFile").Url;
	int imageFileFilterIdx = coreSettings.safeGetInt("COMaterialDialog.fileFilterIdx");
	
	Int fileFilterIdx(imageFileFilterIdx);
	url = getOpenFileName(this, url, imageImportFilter, filterIndex=fileFilterIdx);
	
	if (url and url.isReadable) {
	    setLastUrlAndIndex(url, fileFilterIdx.v);
	    PBRMatSpec spec();
	    spec.baseTextureUrl = url;
	    Material3D m = GMaterial3D(spec);
	
	    tempMat.updateGMaterial3D(m);
	
	    updateImage();
	}

cm/abstract/industry/comMaterialDialog.cm

old:
      extend public void chooseBtnColorCB() {
	 color c = chooseColorDialog(parent, tempMat.color2D);
	 if (c.isColor) {
	     tempMat.invalidate();
	     Material3D m = colorMaterial3D(c);
	     tempMat.updateMaterial3D(m);
	     tempMat._color2D = c;
	 
	     updateImage();
	 }


new:

       extend public void chooseBtnColorCB() {
	 color c = chooseColorDialog(parent, tempMat.color2D);
	 if (c.isColor) {
	     tempMat.invalidate();
	     PBRMatSpec spec();
	     spec.baseColorFactor = colorToColorF(c);
	     GMaterial3D m = GMaterial3D(spec);
	     tempMat.updateGMaterial3D(m);
	 
	     updateImage();
	 }

old:

    extend public void browseUrl() {
	 Url url = coreSettings.get("IndCOMaterialDialog.wrappedImageFile").Url;
	 int imageFileFilterIdx = coreSettings.safeGetInt("IndCOMaterialDialog.fileFilterIdx");

	 Int fileFilterIdx(imageFileFilterIdx);
	 url = getOpenFileName(this, url, imageImportFilter, filterIndex=fileFilterIdx);

	 if (url and url.isReadable) {
	     setLastUrlAndIndex(url, fileFilterIdx.v);
	     Material3D m = wrappedImageMaterial3D(url);
	     tempMat.updateMaterial3D(m);

	     updateImage();
	 }
     }


new:

     extend public void browseUrl() {
	 Url url = coreSettings.get("IndCOMaterialDialog.wrappedImageFile").Url;
	 int imageFileFilterIdx = coreSettings.safeGetInt("IndCOMaterialDialog.fileFilterIdx");

	 Int fileFilterIdx(imageFileFilterIdx);
	 url = getOpenFileName(this, url, imageImportFilter, filterIndex=fileFilterIdx);

	 if (url and url.isReadable) {
	     setLastUrlAndIndex(url, fileFilterIdx.v);
	     PBRMatSpec spec();
	     spec.baseTextureUrl = url;
	     Material3D m = GMaterial3D(spec);
	
	     tempMat.updateGMaterial3D(m);

	     updateImage();
	 }
     }

added:
private str[] gMatImportFilter() {
    return [$fileFilterGMatStr, $fileFilterGMatFilter, 
	    $allFilesFilter, "*.*"];
}

cm/abstract/industry/customerOwnMaterials.cm

added:
   extend public void updateGMaterial3D(Material3D mat) {
	material3D = mat;
	_color2D = nocolor;
    }

cm/std/assetTools/material/lab/mlGMaterial3D.cm

old:
    final public str{} textureLinksToBase() {
	if (?(str{}) links = misc.get("textureLinksToBase")) {
	    return links;
	}
	return null;
    }

new:
   final public str{} textureLinksToBase() {
	if (?(str{}) links = misc.?get("textureLinksToBase")) {
	    return links;
	}
	return null;
    }

cm.abstract.dataSymInterface

Target extension package

Introduced a additional field targetExtensions to store information regarding content pack's extension that being provided by the webservice. This target extension is currently used to check for the extension dependencies and will automatically enabled when user toogle the catalog's state.

New : public str[] targetExtensions();

dsUILevel card item

Additional enum value for dsUILevel is added in 14.0.

public enum dsUILevel : field access {
    ...
    card = 4; // Added for 14.0
}

cm.abstract.dataSymbol

DsPicklist Item Creation

DsPicklistSubWindow now sends all item creation and freeform data creation to the item manager. Previously the item manager would handle some item management and the sub window would handle others.

The following methods/functions were modified.

public void endDrag(pointI p)
public void addFreeformItem(DsFreeformItem freeform) 
public void copyItem()
private void dragAnimationDropCB(DsDragAnimation animation, Window window, pointI p)

cm.abstract.draw

A DrawSurfaceSnapper will now not display path snap points if the member count is 500 or over. This was done to prevent a bug where only some of the snap points would appear if the count was very great.

cm.abstract.k2

K2Creator changes

XpWorktopTypeBehavior has been deprecated* Instead, the thickness property should be defined on the worktop shape, and the material should be defined on the worktop graphics behavior.

K2Worktop2DBehavior, k2Worktop3DBehavior and K2Backsplash2DBehavior have all been deprecated*. Instead a new behavior called K2WorktopGfxBehavior will replace all of them.

*Deprecated behaviors will remain in 14.0 and should keep functioning. But they are planned to be entirely removed in 14.5.

cm.abstract.materialHandling

Engine export

There is now a devmode warning during system export that is printed when an entry is deemed to have failed a consistency check.

    /**
     * Check for consistency. Return true if exported is consistent with entry.
     */
    extend public bool exportFailed(MhEngineEntry entry, box exportedBound) {
        box eb = exportedBound;
        return eb.w != entry.w or eb.d != entry.d or eb.h != entry.h;
    }
    extend public void exportEntry(Space space, MhEngineConstructionEntry entry,
				   MhSnapper parent=null, Snapper{} visited=null) {
        ...
        box eb = info ? info.shape.?engineEntryBound() : s.?engineEntryBound();
        if (exportFailed(entry, eb)) {
            if (developMode) pln("Export failed for ".eRed; entry; entry.classification);
            if (removeFailedEntry.?v) return;
        }
        ...
    }

Unstreaming clearance spec

When loading MhLevelShape and unstreaming the saved MhClearanceSpec, we check existing clearance specs in the MhClearanceSpecContainer of a world. Previously this always checked mainWorld's clearance specs, but this has now been changed to check against the shape owner's world. This change addresses checking against incorrect clearance specs when loading a drawing.

    extend public void unstreamStoredSpec(ObjectFormatter formatter) {
        ...
        Old : if (MhClearanceSpecContainer container = clearanceSpecContainer()) {
        New : if (MhClearanceSpecContainer container = clearanceSpecContainer(owner.?space.world)) {

Changes to MhLevelPopulateFunction

Usage of the limitZ field in MhLevelPopulateFunction has been updated. In 13.5 the accepts() method would immediately check against the limitZ value if it exists, ignoring the maxZ() method. In 14.0 the accepts() method will always call maxZ(), and maxZ() now returns limitZ if it exists.

    extend public bool accepts(MhEngineEntry parent, MhEngineEntry newLevel) {
        ...
        double maxZ = maxZ(parent, prim);
        ...
    }


    extend public double maxZ(MhEngineEntry levelParentEntry, CollisionPrimitive prim) {
        if (limitZ) return limitZ.safeDouble();
    }

Updated the behavior of the accepts() method. It now returns highestZ <= maxZ instead of highestZ < maxZ. Additionally the highestZ calculation when loadWithinLimit=true has been updated.

    Old :
        if (loadWithinLimit.?v) {
            double max = 0;

            if (CollisionPrimitive prim = newLevel.collisionPrim(engine.MhEngine)) {
                max = prim.bound.h;
            } else {
                for (c in newLevel.children(engine.MhEngine)) {
                    double h = c.localBound.h;
                    max = max(max, h);
                }
            }
            highestZ += max;
        }


    New :
        if (loadWithinLimit.?v) {
            double max = 0;
            for (c in newLevel.children(engine.MhEngine)) {
                if (!c.isUnitLoad) continue;
                double h = c.localBound.h;
                max = max(max, h);
            }

            highestZ += max;
        }

Mobile behavior keys

MhMobileFloorUpdateBehavior now has the following key "updateMobileFloorAfterInitialExport". MhMobilePlatformUpdateBehavior now has the following key "updateMobilePlatformAfterInitialExport".

Frame accessory classification

The following spawners have had the sFrameAccessory layer added to their classifications.

  • MhCornerProtectorSpawner
  • MhFlankProtectorSpawner
  • MhUprightProtectorSpawner

MhSnapperMultiApplyAnimation changes

applyToCandidateGroup() no longer always selects the first snapper in the given MhSnapperGroup. It now also checks for and returns the first snapper in the MhSnapperGroup that matches the classification of the selection's main snapper.

    Old:
    extend public void applyToCandidateGroup(MhSnapperGroup grp) {
        if (MhSnapper grpMain = grp.?snappers.get) {
    }


    new:
    extend public void applyToCandidateGroup(MhSnapperGroup grp) {
        if (MhSnapper grpMain = getGrpMain(grp)) {
    }


    /**
     * 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;
    }

bottomStorageLevel changes

The bottomStorageLevel field in MhBayShape is now only used when the bay has a floor level.

public class MhBayShape extends MhBoxSnapperShape {
    /**
     * HoleZDomain
     */
    extend public SubSet holeZDomain() {
        ...
        if (!hasFloorLevel()) res.minV = res.closestSucceeding(bottomStorageLevel).safeDouble;
        return res;
    }


    /**
     * Append Bottom Storage Primitives
     */
    extend public void appendBottomStoragePrimitives(CollisionPrimitive{} prims, Transform t=null) {
        double floorZ = -1mm; // Under the floor to ensure collition with first level
        Bool actualHasFloorLevel = get("configLoadOnFloor").?Bool;
        if (!actualHasFloorLevel) actualHasFloorLevel = hasFloorLevel();
        double bottomZ = (!actualHasFloorLevel.v ? bottomStorageLevel : 0);
        ...
    }
}


public class MhStorageConfiguration extends MhSystemConfiguration {
    /**
     * HoleZDomain
     */
    extend public SubSet holeZDomain() {
        ...
        double bottomZ = !addFloorLevel() ? bottomStorageLevel.safeDouble : 0;
        res.minV = res.closestSucceeding(bottomZ).safeDouble;
        return res;
    }

Additionally, MhBaySpawner now also returns a loadOnFloor value in shapeCreationProps() from the current configuration.

    /**
     * ShapeCreationProps
     */
    public str->Object shapeCreationProps() {
        if (currentConfig) {
            str->Object res = props { ...
                                      loadOnFloor=currentConfig.addFloorLevel()
        ...
    }

Changes to MhCollisionResolver

MhCollisionResolver now always unrolls collision primitives when they are appended to the resolver. This change was made as collision primitives currently do not support resolving collisions with a roll or pitch angle.

    /**
     * Append fixed.
     */
    public void appendFixed(CollisionPrimitive z) {
        CollisionPrimitive[] list();
        mhUnrollCollisionPrim(z, list);
        for (p in list) fixed << p;
    }


    /**
     * Append.
     */
    public void append(CollisionPrimitive z) {
        CollisionPrimitive[] list();
        mhUnrollCollisionPrim(z, list);
        for (p in list) prims << p;
    }

All previous abstract code that initialized CollisionResolver have been replaced with MhCollisionResolver.

public class MhCollisionAlternative extends MhTrySnapAlternative {
    extend public CollisionResolver collisionResolver(Snapper z) {
        if (!_resolver) _resolver = MhCollisionResolver();
        return _resolver;
    }
}


public class MhSnapperSpreadPattern : abstract {
    extend public SnapAlternative[] snapAlternatives(Snapper candidate, MhTrySnapAlternative mainAlt, MhSnapBehavior[] behaviors) {
        ...
        MhCollisionResolver cr();
        ...
    }
}


// cm/abstract/materialHandling/mhResolverFunctions.cm
public Point resolveNextTo(CollisionPrimitive prim, CollisionPrimitive fixedPrim,
                           CollisionPrimitive additionalPrim, CollisionVectorOption option) {
    ...
    MhCollisionResolver resolver();
    resolver.append(option);
    ...
}
public bool primConflicts(CollisionPrimitive prim, CollisionPrimitive fixedPrim) {
    MhCollisionResolver resolver();
    ...
}

Changes to system configuration

The logic in the load() method (to load a configuration from a file) has been modified. It now calls setItemProps() instead of initItems(). With this change, it will no longer set owner of items, create MhConfigurationItemGroup for items, and it will no longer call item.afterInit(). Additionally there is a new call to afterLoad(), which calls afterConfigLoad() in all items.

    /**
     * After load.
     */
    extend public void afterLoad(RobustFormatter formatter) {
        for (item in _items) {
            item.afterConfigLoad(formatter);
        }
    }

If there is some logic for classes extending from MhSystemConfigurationItem that needs to be done after loading a configuration from a file, you may have previously added that code to afterInit(). You should now override the new method afterConfigLoad() instead.

    /**
     * After configuration load.
     */
    extend public void afterConfigLoad(RobustFormatter formatter) { }

Changes to deep storage level engine behavior

MhDeepstorageLevelEngineBehavior now responds different to the sUnitLoadChanged and sChildShapeChanged events. It will now run "unitLoadPopulate" regardless of what spread pattern is used, so it will no longer run "unitLoadEnsureClearance" instead.

Changes to row selection behavior

MhRowSelectionBehavior has been updated to use the MhRowBackToBackFilter for flue gap rows (excluding double-deep flue gaps). With this change, selecting flue gap rows will now also select the directly connected top and bottom rows.

Changes to MhSnapper rotatable method

MhSnapperShape now has a new method rotatable().

    /**
     * Return true if s is a 'rotation snap'.
     */
    extend public bool rotatable(MhSnapper snapper, Connector s) {
        return true;
    }

This is called from MhSnapper.rotatable().

    /**
     * Return true if s is a 'rotation snap'.
     */
    public bool rotatable(Connector s) {
        if (parent) return false;
        return super(..) and shape.?rotatable(this, s);
    }

Spread pattern changes

  • public class SpreadPatternGroup is now unstreamable.
  • MhSnapper.spreadFilter() now returns null if no MhSpreadSnapperFilterBehavior has been defined. Consider defining the behavior if required.
  • MhTrySnapAlternative now sets lastAlternativeSelected before candidateChanged and candidateChosen to ensure the animation acts on the current information.
  • MhUnitLoadSpreadPatternBehavior.spreadPatternDomain() now returns a ClassSubSet domain of MhStorageXXXXXXXSpreadPattern (old: MhUnitLoadXXXXXXXXSpreadPattern).

cm.abstract.office

AODataSkinFrame

Added code to modifyCopy(CopyEnv cEnv) and to restored(Snapper s) to address an issue where the ModifyEnv system wasn't updating tiles.

cm.application

Changes to cm/application/paperSelectionPanel.cm

old:

final public bool loadPaper(Url path=null) {

	...

	if (selectedSpace) {
	    // Assign the same group to the new space in order to ensure that the paper ordering is correct.
	    if (world.isCollWorldG2()) newSpace.group = selectedSpace.group;
	    if (!world.isCollWorld()) newSpace.setOrderNr(selectedSpace.orderNr);
	}

	createSmallPagePreviewControl(newSpace);

	...
   }

new:

 final public bool loadPaper(Url path=null) {

	...

	if (selectedSpace) {
	    // Assign the same group to the new space in order to ensure that the paper ordering is correct.
	    newSpace.group = selectedSpace.group;
	    newSpace.setOrderNr(selectedSpace.orderNr);
	}

	clearAndRebuild();
	...
   }

The following change makes sure the selected papers are only cloned once:

old:

private void clonePaperCB(Control c) {
    if (PaperSelectionPanel selector = c.container(PaperSelectionPanel).PaperSelectionPanel) {
	logEvent("clonePaper", "paperview");
	PaperSpace[] toClone();
	for (z in selector.inMultiSelect) {
	    toClone << z;
	}
	selector.clonePaper();

	for (z in toClone) {
	    selector.select(z);
	    selector.clonePaper();
	}
    }
}

new:

private void clonePaperCB(Control c) {
    if (PaperSelectionPanel selector = c.container(PaperSelectionPanel).PaperSelectionPanel) {
	logEvent("clonePaper", "paperview");
	PaperSpace[] toClone();
	for (z in selector.inMultiSelect) {
	    toClone << z;
	}

	if (toClone.empty()) selector.clonePaper();

	for (z in toClone) {
	    selector.select(z);
	    selector.clonePaper();
	}
    }
}

Finally, the below line in cm/application/paperUI.cm was changed in order to make tesing easier:

old:

final private SubWindow buildActionPanel(Window parent) {

 ...

 groupButton.hide();

 ...

}

new:

final private SubWindow buildActionPanel(Window parent) {

 ...

 if (!developMode) groupButton.hide(); //Show only for testing.

 ...

}

cm.core

cm.core

In order to keep the changes of the last paperspace, we need to access the most recent world. In multi-drawing mode, this is straightforward and can be done by accessing the previous world:

cm/std/print/paperspace/paperSpaces.cm

old:
public PaperSpace[] allPaperSpaces(World world, bool createIfNone=true) {
    PaperSpace[] spaces();
    if (!world or !world.spaces) return spaces;

    if (world.isCollWorldG3) createIfNone=false;
    for (space in world.spaces) if (space as PaperSpace) spaces << space;

    if (createIfNone and spaces.count() == 0) {
	spaces << PaperSpace(world, $paperDefaultName, group=null);
	if (DocumentCreator z = currentDocumentCreatorIfAny()) z.visibilityChanged();
    }

    spaces.sort(function paperSpaceDifference, null);

    return spaces;
}


new:
public PaperSpace[] allPaperSpaces(World world, bool createIfNone=true) {
    PaperSpace[] spaces();
    if (!world or !world.spaces) return spaces;

    if (world.isCollWorldG3) createIfNone=false;
    for (space in world.spaces) if (space as PaperSpace) spaces << space;

    if (createIfNone and spaces.count() == 0) {
	PaperSpace newPaperSpace(world, $paperDefaultName, group=null);

	World prevWorld = session.prevWorld();

	if (prevWorld and (PaperSpace prevPaper = prevWorld.getLastPaperSpace())) {
	    newPaperSpace.pageSetup.?assimilate(prevPaper.pageSetup);
	}

	spaces << newPaperSpace;
	if (DocumentCreator z = currentDocumentCreatorIfAny()) z.visibilityChanged();
    }

    spaces.sort(function paperSpaceDifference, null);

    return spaces;
}

In single drawing mode some changes were required in order to be able to access the previous world, which would have otherwise been destroyed before the new paperspace had been created: cm/core/multiWorldManager.cm >>>>>>> b892a94b2ab38fe1f04775d1f9eee139bfae2fd8

old:
    extend public World create(str id=null) {
	// old-tech from before LARGEADDRESSAWARE and 64-bits
	//if (virtualSize() > 1.5GB) { pln(eLtRed, "large virtualSize, ", virtualSize.byteFormat, ", run full gc..."); gc(); pln("done", ePop); }

	if (dbg_undoRestoreInProgress) {
	    ptrace("force reset undo state for interaction");
	    dbg_undoRestoreInProgress = false;
	}

	bool anyWorld = session.anySelectableWorld;
	if (singleDrawingMode and anyWorld) close();

	World world = lowLevelCreateWorld(id=id);
	initialize(world);
	return world;
    }

new:
    extend public World create(str id=null) {
	// old-tech from before LARGEADDRESSAWARE and 64-bits
	//if (virtualSize() > 1.5GB) { pln(eLtRed, "large virtualSize, ", virtualSize.byteFormat, ", run full gc..."); gc(); pln("done", ePop); }

	if (dbg_undoRestoreInProgress) {
	    ptrace("force reset undo state for interaction");
	    dbg_undoRestoreInProgress = false;
	}

	World worldToClose;
	bool anyWorld = session.anySelectableWorld;

	if (singleDrawingMode and anyWorld) {
	    worldToClose = session.mainWorld;
	    worldToClose.?setViewMode(normalViewMode, alert=false);

	    for (v in session.views) {
		v.unlock();
		v.removeClipping();
	    }
	}

	World world = lowLevelCreateWorld(id=id);
	initialize(world);

	if (worldToClose) close(worldToClose);

	return world;
    }

ItemTagInfo

Before 14.0, item tag info map was streamed with world.auxillary under the key "LITINFO". The map has been moved to world.cachedData which is not streamed with the drawing. The itemTags have a reference to their info hence the infos are already streamed with the drawing. The cache is rebuilt on load. Reason for this change is to avoid infos getting stuck in the drawing causing dependencies to extensions.

Block and Category Change

The #1 issue from users at CETX last year was the ability to filter individual items in blocks (for example hiding all objects without the #panel category).

To accomplish this a new category was added cCoreBlockCat that is currently set to #block. This category resides on BlockSnappers. All view modes needs to add the cCoreBlockCat to its categories. Most uses are covered by a change to categoryViewMode(..) and other standard ViewModes were modified to always add the cCoreBlockCat, to ensure as few changes to be done as possible on the manufacturer side. However, do keep these changes in mind as you are migrating and testing for 14.0.

While the category 'cCoreBlockCat` is still available, a last-minute change to 14.0 disabled the category to no longer serve a purpose. This may be revisited.

=======

cm.core.debug

Added check of active when calling profilerStop() which prevents a crash if you are stopping before starting.

cm.core.xclip

We introduce a flag which will be set each frame to indicate if the content should be rebuild or not:

cm/core/xclip/xclipWormholeSnapper2D.cm

    added:
    /**
     * Invalidate clipped snappers.
     */
     public bool inv_clippedGraphicSnappers : copy=null, stream=null, ignore modify notice;


    added:
    /**
     * Update 'scene' to show 'objs'.
     */
    public void updateContent(XClipScene scene, Object{} objs) {
	if (inv_clippedGraphicSnappers) {
	    content2D.?invalidateClippedGraphicsSnappers();
	    inv_clippedGraphicSnappers = false;
	}
	super(..);
    }

We also changed the invalidation to simply set this flag instead of running a costly method each frame:

    old: public void invalidate_clipContent() { inv_clipContent = true; content2D.?invalidateClippedGraphicsSnappers(); inv_beforeMaster	 = true; }


    new: public void invalidate_clipContent() { inv_clipContent = true; inv_clippedGraphicSnappers = true; inv_beforeMaster = true;}

cm.subset

DistanceRange

The closest(Object o) method has been updated to return 'v' instead of 'o' if 'o' was a Number.

Old:
/**
 * Return the closest member to 'o' in this subset.
 */
public Object closest(Object o) {
	...
	
	if (o as Number) {
	    distance v = o.double.distance;
	    if (v < minV) return minV;
	    if (v > maxV) return maxV;
	    return o;
	}
	
	...
}

New:
/**
 * Return the closest member to 'o' in this subset.
 */
public Object closest(Object o) {
	...
	
	if (o as Number) {
	    distance v = o.double.distance;
	    if (v < minV) return minV;
	    if (v > maxV) return maxV;
	    return v;
	}
	
	...
}

cm.win

PaneTreeNode

Nullcheck in PaneTreeNode.toS() preventing access violation.

Loading icons behavior changed in developMode to reflect releaseMode.

If the directory path used to access icons from file didn't have '/' at the end they would appear to work in developMode, but fail in releaseMode. They now fail in developMode as well, to prevent surprises at build.

/**
 * Put icon database finder extension directory if necessary.
 * 
 * k   - Key differentiating icons of different systems.
 *       Typically this is the package of the extension
 *	 that is registering a directory.
 * dir - Path where icons are located.
 *       Typically this looks something like
 *	 "custom/extension-name/res/images/".
 *	 
 *	 NOTE: The directory must end with "/" or it isn't
 *	 going to work in releaseMode.
 */
public void putIconDbExtensionDir(symbol k, str dir) {
    if (k !in iconDB) {

	// This will make the behavior like releaseMode.
	if (developMode and !dir.endsWith('/')) return;

	Url[] locations = cmInstalledAll(dir);
	if (locations.any) {
	    for (l in locations) {
	} else {
	    iconDB.finder(k, cmNative(dir));
	}
	
	//Url completeDir = iconDbExtensionDir(dir);
	//iconDB.finder(k, completeDir);
    }
}

custom.dataCatalog

Added: private str getTruncatedPortDesc(str portfolioDesc);

custom.reconfigs

Part import

Before 14.0, when a drawing's parts were loaded into the Reconfiguration tool, their owners were removed. In 14.0 this has changed to keeping the original owner. Because the Reconfiguration Tool is creating a drawing in the background and extracting the parts to the ReconfigSpace, the owner's dead space have also been replaced with the current ReconfigSpace.

This change was made because of the parts' need of providing a space, which they do by returning their owner's space. Without a space, the user might experience loss of information while doing things like exporting. Without a correct space, things like sorting the article view on adjusted values stop to work.

While working with parts in the Reconfiguration Tool, all parts can be relied on to have the current ReconfigSpace set as their space.

The following code was/is called after a list of parts are imported into the Reconfiguration Tool:

New:
/**
* Post processing part list.
*/
extend private void postProcessList(PartList list) {
    if (!list) return;
    for (p in list.parts) {
	if (list) for (p in list.parts) {
	   if (p.owner) {
	       // Replace the temporary space the parts got when created to the current ReconfigSpace.
	       // The parts need to have their space set to the current ReconfigSpace
	       // for the user to for example be able to sort on adjusted columns.
	       p.owner.space = reconfigCalcSpace();
	   }
	}
    }
    return list;
}

Removed:
/***********************************************************************
 * Post processing
 ***********************************************************************/

/**
 * Post process result parts.
 */
extend public void postProcessResultParts() {
    //ptrace(#newParts.count);
    for (p in newParts.parts) {
	postProcessOrderPart(p, null);
    }

    //ptrace(#reused.count);
    for (k, list in reused) for (row in list.rows) for (p in row.parts) {
	postProcessReusePart(p, null);
    }

    //ptrace(#excess.count);
    for (k, list in excess) for (row in list.rows) for (p in row.parts) {
	postProcessExcessPart(p, null);
    }
}


/**
 * Post process order part, with recursion.
 */
extend public void postProcessOrderPart(Part part, Space orderPartSpace) {
    //ptrace(#part);
    removeOwners(part);
}


/**
 * Post process reuse part, with recursion.
 */
extend public void postProcessReusePart(Part part, Space reusePartSpace) {
    //ptrace(#part);
    removeOwners(part);
}


/**
 * Post process excess part, with recursion.
 */
extend public void postProcessExcessPart(Part part, Space excessPartSpace) {
    //ptrace(#part);
    removeOwners(part);
}


/**
 * Remove owners.
 */
final public void removeOwners(Part part) {
    if (part) {
	// Remove owners in children
	for (c in part.children) if (c) removeOwners(c);

	// Remove in part
	part.data.setOwner(null);
	if ((Snapper{}) set = part.data.owners) set.clear();
    }
}