There will also be a text file for each autosave in the cmWritable/autosave folder
. This file lists the resources used by the corresponding autosave.
Inside cmWritable/externalFormatterResources
, resources are grouped by drawing name so that resources can be shared by all the autosaves for that drawing, eliminating redundant resaving of the same resources.
Now that new calculations are performed when obtaining the width of a LineType
, developers might have to tune the width values used in their non-unit width style LineType
s (eg. variable
and real
).
In cm/io/util/urlCheck.cm, use checkWindowsFilename
instead of the deprecated isValidPath
Interfaces related to Vx expression validation takes additional vendor
argument.
// DsiExprValidationEnv (dsiExprValidationEnv.cm) Old: public constructor(VxRuntime rt, dsiConstraintExpressionType exprType, str expr, DataCatalog cat) New: public constructor(VxRuntime rt, dsiConstraintExpressionType exprType, str expr, DataCatalog cat, str vendor) //dsiVariants.cm Old: public bool dsiValidateConstraintExpr(DataCatalog cat, dsiConstraintExpressionType exprType, str expr, StrBuf error=null) New: public bool dsiValidateConstraintExpr(DataCatalog cat, dsiConstraintExpressionType exprType, str expr, str vendor, StrBuf error=null)
Constraints-related Cards (Tabs) in Catalog Creator are placed between Geometry and Validation Cards. Take note of the sort keys used.
Geometry // 700 (no change) Constraints // 720 (added) ConstraintTables // 725 (added) VariantsTables // 727 (added) Validation // 745 (no change)
Interfaces related to Vx expression validation takes additional vendor
argument.
// DcDBBuilderConstraintsCard (dcDBBuilderConstraintsCard.cm) Old: extend public bool validExpression(dsiConstraintExpressionType constraintExpType, str exp, StrBuf error) New: extend public bool validExpression(dsiConstraintExpressionType constraintExpType, str exp, str vendor, StrBuf error)
Previously deprecated Catalog Creator Constraints Tab classes and interfaces are now removed. New classes and interfaces are found in the package.
// dcDBuilderConstraintsCard.cm Removed: public dcBuildEditConstraintsCard(DcDBBuilderCardWindow cw, int index=700) Removed: public DcDBBuilderEditConstraintsCard dcDBBuilderEditConstraintsCard(Window this=null) Removed: public class DcDBBuilderEditConstraintsCard // dcDBuilderConstraintsCells.cm Removed: public class DcConstraintColumn Removed: public class DcConstraintGridCell Removed: public class DcConstraintExpColumn Removed: public class DcConstraintExpGridCell Removed: public class DcConstraintCodeColumn Removed: public class DcConstraintCodeGridCell Removed: public class DcConstraintExpTypeColumn Removed: public class DcConstraintExpTypeGridCell // dcDBuilderConstraintsGridWindow.cm Removed: public DcDBBuilderConstraintsSS dcConstraintsGridWindow Removed: public class DcDBBuilderConstraintsSS // dcDBuilderConstraintsShrink.cm Removed: public class DcFramelessExpressionShrink
Removed previously deprecated field for Variants Table UI class for Catalog Creator
// DcVariantsTableGridSubWindow (dcVariantsTableGridSubWindow.cm) Removed: public DsButton importBtn
ItemTags generated from DsPart will now use the data's dataId (if set) for the ItemTag key. If this isn't set, it will fallback to the previous behavior of using the styleNr and then the articleCode.
The cached parts field on DsPData is now copy=null to avoid undo crashes.
Previews for cabinet inserts are now cached on a fuzzy basis: A cabinet with similar shape to one already cached will reuse the preview.
Two cabinets count as similar if their shapes have the same previewCacheKey()
.
Note that the user can always clear their cache through the "Clear cached images" button in the Essential Kitchen EU library.
Due to a bug, alternatives would spread through any snappers it existed on before the update (regardless of their auto adoption flags), if it had at least one snapper next to it that was auto adopting. It now respects snappers auto adoption state as it should.
This will also cause alternatives to not spread to snappers unless they've been invalidated. Verify that your alternatives (custom implemented moldings/worktops/soffits for example) updates as they should when invalidated through e.g. a #connect
event.
If your code relied on this bug to force snappers to update, you might need to make sure that more of your snappers than before are invalidated. Call <snapper>.invalidateBehaviorsIfRequired(...)
with #connect
or #propagatedConnect
as the argument to invalidate them. See cm/abstract/k2/molding/k2AutoMoldingBehavior.cm
for an example of this.
K2WtBuiltInPlacementBehavior used to automatically add K2AutoWorktopBuiltInPosBehavior to snappers spawned by its spawner. This no longer happens automatically.
In K2Creator, please add a basic K2AutoWorktopBuiltInPosBehavior behavior to each spawner that uses K2WtBuiltInPlacementBehavior. This is needed for its graphics to update properly as alignment is changed.
K2CategoryBehavior is now added to all snappers by default from K2SnapperSpawner.gatherBehaviors(). If it has been manually added in K2Creator it needs to be removed from the catalog.
For appliances we now support selecting inserts from multiple assortments. To utilize this feature the properties for the insert need to be on the root insert snapper and the insert needs to manage the connection to the catalog for those properties. To make the front responsible for its properties it needs a behavior that has
Removed updateSymByRebuild from K2CabinetGfxBehavior::userPropertyChanged
. Should no longer be needed since updateSym gets called after every shape prop change.
XpLengthPartQuantity
has been renamed to XpCutRunningMeterPartQuantity
and XpRunningMeterPartQuantity
has been added.
XpRunningMeterPartQuantity
gives a quantity equal to the length of the measure.
XpCutRunningMeterPartQuantity
gives a quantity equal to the number of articles of a given length that are needed.
We have identified several scenarios where material legends using MaterialLegendController were displaying excess materials. The solution is to register a pre-fetch part hook to clear temporary legend materials.
Old:
public FOMatLegendController foMatLegendController("FODataMaterialLegend"); /** * Append a SymbolMaterial to the DsMaterialLegend for the specified world. */ public void foDataAppendLegendMaterial(Material m, Snapper owner) { once { appendPostFinalizeHook(function foDataPostFinalizeLegendHook); } foMatLegendController.appendLegendMaterial(m, owner, foDataMLCreator); }
New:
public FOMatLegendController foMatLegendController("FODataMaterialLegend") : keep; /** * Append a SymbolMaterial to the DsMaterialLegend for the specified world. */ public void foDataAppendLegendMaterial(Material m, Snapper owner) { once { appendPreFetchPartsHook(function foDataPreFetchPartsLegendHook); // new line appendPostFinalizeHook(function foDataPostFinalizeLegendHook); } foMatLegendController.appendLegendMaterial(m, owner, foDataMLCreator); } /** * Pre fetch part hook for clearing temp legend materials. */ private void foDataPreFetchPartsLegendHook(Space space) { MaterialLegendController controller = foMatLegendController; if (controller) controller.clearTempLegendMaterials(); }
For a more generic code you can refer to cm/abstract/material/hooks.cm
OfficeMaterialLeg
with MaterialLegendControllerMaterialLegendController cannot be constructed using "OfficeMaterialLeg"
as its auxillary key. If you are affected, you can migrate your old legends by extending MaterialLegendController and override previouslyUsingLegacyMLKey()
to return true
.
Old:
public MaterialLegendController foMatLegendController("OfficeMaterialLeg") : keep;
New:
public FOMatLegendController foMatLegendController("FOMaterialLegend") : keep; /** * FO MaterialLegendController. */ public class FOMatLegendController extends MaterialLegendController { /** * Previously using OfficeMaterialLeg? */ public bool previouslyUsingLegacyMLKey() { return true; } }
public class MhStorageSpawnerSelector extends MhSystemSpawnerSelector public SnapperSpawner spawner(LayerSet classification, Object env=null) { Bugfix: Fixed spawner selection to prioritize exact match, over an equally-attractive-but-partial match. There were cases where an exact-match come later due to us iterating in alphabetical order (through allSorted) and we would return only a partial match. MhStorageEditorDialog - Issues with having leftover `Windows` leaking UI controls in `MhStorageEditorDialog` and `MhStorageConfigurator` has been fixed. You may need to double check your UI behaviors if you do any update or removal of UI controls dynamically. - `buildPropertiesSubWindow()` for building `MhStorageEditorItem` now use `item.class.toS` instead of `item.label` as key for the `Card`. MhSnapperShape - Note: There are two versions of the localBound method which can be easily confused: localBound() and localBound(symbol[]) - localBound() now checks for owner before executing the localBoundWithChildren() case, and will return localBound(null) which returns the geometry's bound in other cases. This fixes the inconsistent bound returned during contexts in which the shape have not obtained an owner yet.
The freeSpaceAbove
property is no longer visible when there is no level above the currently selected snapper.
Old: /** * Show height prop. */ extend public void showHeightProperties() { for (k, _ in getPropsWithAttribute(#height)) if (corePropertyHidden(k)) showCoreProperty(k); } New: /** * Show height prop. */ extend public void showHeightProperties(MhSnapper owner, MhSnapper toSnapper) { for (k, _ in getPropsWithAttribute(#height)) if (corePropertyHidden(k) and propIsVisible(k, owner, toSnapper)) showCoreProperty(k); } /** * Property is visible? */ extend public bool propIsVisible(str key, MhSnapper owner, MhSnapper toSnapper) { if (key == "freeSpaceAbove") { <MhSnapper firstAbove, MhSnapper firstBelow> = getFirstAboveAndBelowSnappers(owner, toSnapper, owner.pos.z); if (!firstAbove) return false; } return true; }
These methods have been introduced to allow more control over the bounds used to calculate freeSpaceAbove
and freeSpaceBelow
. Override geoSymbolsForCalculation()
to control what geometries should be included in the bound.
/** * Free spaces domains. * v0 = freeSpaceAbove domain. * v1 = freeSpaceBelow domain. */ extend public <SubSet, SubSet> freeSpacesDomains(MhSnapper firstAbove, MhSnapper firstBelow, MhSnapper owner, MhSnapper toSnapper, Object env=null) { return <coreProperty("freeSpaceAbove").?domain, coreProperty("freeSpaceBelow").?domain>; } /** * Get snapper bound used for calculation. */ extend public box boundForCalculation(MhSnapper snapper, bool toSpace=false) { Box res = cachedBounds.get(snapper); if (!res) { res = shapeBoundWithChildren(snapper, geoSymbolsForCalculation()); cachedBounds.put(snapper, res); } if (toSpace) return res.v.transformed(snapper.toSpaceTransform); return res.v; } /** * Symbols used to retrieve geometries for snapper bound used for calculation. */ extend public symbol[] geoSymbolsForCalculation() { return [sLevel, sUnitLoad, sTunnel]; }
calculateFreeSpaceAboveAndBelow()
has been updated to use boundForCalculation()
.
Old: /** * Calculate free space above and below. */ extend public <Double, Double> calculateFreeSpaceAboveAndBelow(Snapper owner, Snapper toSnapper, double insertH, Snapper specificAboveSnapper=null, Snapper specificBelowSnapper=null) { box b = owner.localBound(); box b0 = toSnapper.localBound; Double above = b0.h - insertH; Double below = insertH b.p0.z; <Snapper firstAbove, Snapper firstBelow> = getFirstAboveAndBelowSnappers(owner, toSnapper, insertH); if (firstAbove) { above = firstAbove.pos.z - insertH - b.h; } if (firstBelow) { below = insertH - firstBelow.pos.z - firstBelow.localBound.h; } if (specificAboveSnapper and specificAboveSnapper != firstAbove) above = null; if (specificBelowSnapper and specificBelowSnapper != firstBelow) below = null; return <above, below>; } New: /** * Calculate free space above and below. */ extend public <Double, Double> calculateFreeSpaceAboveAndBelow(MhSnapper owner, MhSnapper toSnapper, double insertH, Snapper specificAboveSnapper=null, Snapper specificBelowSnapper=null) { box ownerB = boundForCalculation(owner); box b0 = toSnapper.localBound; Double above = b0.h - insertH; Double below = insertH ownerB.p0.z; <MhSnapper firstAbove, MhSnapper firstBelow> = getFirstAboveAndBelowSnappers(owner, toSnapper, insertH); if (firstAbove) { above = boundForCalculation(firstAbove, toSpace=true).p0.z - insertH - ownerB.p1.z; } if (firstBelow) { below = insertH ownerB.p0.z - boundForCalculation(firstBelow, toSpace=true).p1.z; } if (specificAboveSnapper and specificAboveSnapper != firstAbove) above = null; if (specificBelowSnapper and specificBelowSnapper != firstBelow) below = null; return <above, below>; }
The updateHeightProperties()
method has been broken up into multiple methods to make it easier to override. These methods have also had behavioral changes.
insertHeight
property is unlocked if the locked height is no longer valid (e.g. switching candidates to a different bay with a different height domain).Old: if (heightProp.?locked and heightProp == currentProp) { p.z = heightProp.value.safeDouble; aboveProp.?unlock(); belowProp.?unlock(); } New: /** * Check insert height locked. */ extend public void checkInsertHeightLocked(point& p, SubSet heightDomain) { CoreProperty heightProp = coreProperty("insertHeight"); if (heightProp.?locked) { // Height is locked, maintain current height. if (heightProp.value !in heightDomain) { heightProp.unlock(); } else { p.z = heightProp.value.safeDouble; coreProperty("freeSpaceAbove").?unlock(); coreProperty("freeSpaceBelow").?unlock(); } } }
freeSpaceAbove
property is unlocked if the locked value is no longer valid or if there is no longer a snapper above the selected snapper. The z-position calculation has also been modified to utilize the boundForCalculation()
method.Old: if (aboveProp.?locked and aboveProp == currentProp) { p.z = (firstAbove ? firstAbove.pos.z - b.h : maxH) - aboveProp.value.safeDouble; heightProp.?unlock(); belowProp.?unlock(); } New: /** * Check free space above locked. */ extend public void checkFreeSpaceAboveLocked(point& p, SubSet aboveDomain, MhSnapper firstAbove, MhSnapper owner, MhSnapper toSnapper) { CoreProperty aboveProp = coreProperty("freeSpaceAbove"); if (aboveProp.?locked) { if (aboveProp.value !in aboveDomain or !firstAbove) { aboveProp.unlock(); } else { p.z = (firstAbove ? firstAbove.pos.z boundForCalculation(firstAbove).p0.z - boundForCalculation(owner).p1.z : toSnapper.localBound.h) - aboveProp.value.safeDouble; coreProperty("insertHeight").?unlock(); coreProperty("freeSpaceBelow").?unlock(); } } }
freeSpaceBelow
property is unlocked if the locked value is no longer valid. The z-position calculation has also been modified to utilize the boundForCalculation()
method.Old: if (belowProp.?locked and belowProp == currentProp) { p.z = firstBelow.MhSnapper.?localBound().p1.z (firstBelow? firstBelow.pos.z : 0) belowProp.value.safeDouble - b.p0.z; heightProp.?unlock(); aboveProp.?unlock(); } New: /** * Check free space below locked. */ extend public void checkFreeSpaceBelowLocked(point& p, SubSet belowDomain, MhSnapper firstBelow, MhSnapper owner, MhSnapper toSnapper) { CoreProperty belowProp = coreProperty("freeSpaceBelow"); if (belowProp.?locked) { if (belowProp.value !in belowDomain) { belowProp.unlock(); } else { p.z = (firstBelow ? boundForCalculation(firstBelow, toSpace=true).p1.z : 0) belowProp.value.safeDouble - boundForCalculation(owner).p0.z; coreProperty("insertHeight").?unlock(); coreProperty("freeSpaceAbove").?unlock(); } } }
freeSpaceAbove
and freeSpaceBelow
properties will now be refreshed if their values change but insertHeight
did not.Old: double oldInsertHeight = insertHeight; if (owner.pos != p) owner.setPos(p); insertHeight = p.z; <Double above, Double below> = calculateFreeSpaceAboveAndBelow(owner, toSnapper, insertHeight); freeSpaceAbove = above.?v; freeSpaceBelow = below.?v; if (oldInsertHeight != insertHeight) { heightProp.?refreshProperty(); aboveProp.?refreshProperty(); belowProp.?refreshProperty(); // Calling diff rebuild instead causes the prop UI to constantly flicker. if (domainsUpdated) { for (prop in [heightProp, aboveProp, belowProp]) { for (CoreDistanceField ctrl in prop.controls) { ctrl.validSubSet = prop.domain; break; } } } } New: /** * Update height and space values. */ extend public void updateHeightAndSpaceValues(point p, MhSnapper owner, MhSnapper toSnapper, bool domainsUpdated) { double oldInsertHeight = insertHeight; if (owner.pos != p) owner.setPos(p); insertHeight = p.z; <Double above, Double below> = calculateFreeSpaceAboveAndBelow(owner, toSnapper, insertHeight); double oldFreeSpaceAbove = freeSpaceAbove; double oldFreeSpaceBelow = freeSpaceBelow; freeSpaceAbove = above.?v; freeSpaceBelow = below.?v; CoreProperty heightProp = coreProperty("insertHeight"); CoreProperty aboveProp = coreProperty("freeSpaceAbove"); CoreProperty belowProp = coreProperty("freeSpaceBelow"); if (oldFreeSpaceAbove != freeSpaceAbove) aboveProp.?refreshProperty(); if (oldFreeSpaceBelow != freeSpaceBelow) belowProp.?refreshProperty(); if (oldInsertHeight != insertHeight) heightProp.?refreshProperty(); // Calling diff rebuild instead causes the prop UI to constantly flicker. if (domainsUpdated) { for (prop in [heightProp, aboveProp, belowProp]) { for (CoreDistanceField ctrl in prop.controls) { ctrl.validSubSet = prop.domain; break; } } } }
MhBracingPattern
now inherits from MhStorageSingleton
and should be treated as singletons.
The following functions have been added to retrieve the generic bracing patterns available in the abstract.
/** * Bracing pattern singletons. */ public MhDBracePattern mhDBracePattern() { static MhDBracePattern pattern; if (!pattern) ?pattern = mhStorageSingleton(MhDBracePattern()); return pattern; } public MhKBracePattern mhKBracePattern() { static MhKBracePattern pattern; if (!pattern) ?pattern = mhStorageSingleton(MhKBracePattern()); return pattern; } public MhXBracePattern mhXBracePattern() { static MhXBracePattern pattern; if (!pattern) ?pattern = mhStorageSingleton(MhXBracePattern()); return pattern; } public MhZBracePattern mhZBracePattern() { static MhZBracePattern pattern; if (!pattern) ?pattern = mhStorageSingleton(MhZBracePattern()); return pattern; }
applySnappers()
previously only returned the main snapper of the editor space selection. It has been changed to now return all level snappers and unit load snappers in the selection.
Old: /** * Apply snappers. */ public MhSnapper{} applySnappers(Space space) { if (SpaceSelection selection = space.selection) { ?MhSnapper main = selection.main; if (main.isLevel) return {MhSnapper: main}; } return super(..); } New: /** * Apply snappers. */ public MhSnapper{} applySnappers(Space space) { if (SpaceSelection selection = space.selection) { MhSnapper{} res(); for (MhSnapper snapper in selection.snappers) { if (snapper.isLevel or snapper.isUnitLoad) { res << snapper; } } return res; } return super(..); }
Previously only 1 aisle would be generated for the pre configurator preview snappers. It is now possible to override this to generate different number of aisles based on the selected row layout. We now also generate 2 aisles for when the selected row layout is of class MhRowSingleEndLayout
.
Old: /** * Setup row animation info for preview population. */ extend public MhRowAnimationInfo rowAnimationInfo(MhStorageConfiguration config, MhSnapper row) { ... info.crossAisleDist = config.calcWidthFromAisles(info, 1); return info; } New: /** * Setup row animation info for preview population. */ extend public MhRowAnimationInfo rowAnimationInfo(MhStorageConfiguration config, MhSnapper row) { ... info.crossAisleDist = config.calcWidthFromAisles(info, rowNumAisles(info.rowLayout)); return info; } /** * Row number of aisles for preview population. */ extend public int rowNumAisles(MhRowLayout layout) { if (layout in MhRowSingleEndLayout) return 2; return 1; }
There are now new MhStorageConfigurationPreview
classes to handle number of aisles for the MhRowSingleEndDoubleDeepLayout
or MhCantileverSingleEndRowLayout
layouts.
New: /** * Racking configuration preview. */ public class MhRackingConfigurationPreview extends MhStorageConfigurationPreview { /** * Row number of aisles for preview population. */ public int rowNumAisles(MhRowLayout layout) { if (layout in MhRowSingleEndDoubleDeepLayout) return 2; return super(..); } } /** * Cantilever configuration preview. */ public class MhCantileverConfigurationPreview extends MhRackingConfigurationPreview { /** * Row number of aisles for preview population. */ public int rowNumAisles(MhRowLayout layout) { if (layout in MhCantileverSingleEndRowLayout) return 2; return super(..); } } Use: public class MhRackingConfiguration extends MhStorageRowConfiguration { /** * Preview behavior. */ public MhStorageConfigurationPreview configurationPreview() { if (!preview) preview = MhRackingConfigurationPreview(); return preview; } }
uprightDepth
is now meant to be included in frameDepth
by default, so we no longer manually add that value when retrieving the total frame depth.
Old: public class MhCantileverSinglePopulator extends MhCantileverRowPopulator { public double cantileverFrameDepth() { double uprightD = uprightDepth; double frameD = frameDepth; return frameD uprightD; } } New: public class MhCantileverSinglePopulator extends MhCantileverRowPopulator { public double cantileverFrameDepth() { return frameDepth; } } Old: public class MhCantileverDoublePopulator extends MhCantileverRowPopulator { public double cantileverFrameDepth() { double uprightD = uprightDepth; double frameD = frameDepth; return frameD * 2 uprightD; } } New: public class MhCantileverDoublePopulator extends MhCantileverRowPopulator { public double cantileverFrameDepth() { return frameDepth*2 - uprightDepth; } } Old: public class MhCantileverRowPopulator extends MhStorageRowPopulator : abstract { extend public double cantileverBayDepth() { return frameDepth; } } New: public class MhCantileverRowPopulator extends MhStorageRowPopulator : abstract { extend public double cantileverBayDepth() { return frameDepth - uprightDepth; } }
The center bay has a new symbol added to it's classification sBayCenter
.
Old: public MhEngineEntry createRackingRowEntry() { ... LayerSet centerBayLayer = layerSet(sBay, sFlueGap); } New: public MhEngineEntry createRackingRowEntry() { ... LayerSet centerBayLayer = layerSet(sBay, sFlueGap, sBayCenter); }
Further changes have been made to construct and populate arm snappers on frame snappers.
public class MhCantileverLevelConstructionalPopulateFunction extends MhLevelConstructionalPopulateFunction { /** * Populator */ public MhPopulator populator(box populateBox) { MhPopulator res = super(..); if (res as MhStepperPopulator) { point p = populateBox.p0; p.x += populateBox.w/2; res.startPos = p; res.currentPos = p; } return res; } /** * Execute. */ public Object exec() { ?MhStorageConfiguration config = configuration(); if (!config) return false; if (isFrame(parent)) { double uprightD = config.frameUprightDepth().safeDouble(); if (isDouble(parent)) { // Populate arms front and back. MhEngineConstructionEntry[] children; str key = parent.cacheKey(); ?children = mhSystemEngineCache.get(key).?obj.copy(); if (children.empty) { box b = parent.localBound; b.d = (b.d - uprightD)/2; box b1 = b.moved((b.p0.x, b.p1.y uprightD, b.p0.z)); MhEngineConstructionEntry[] front = populateLevels(0, b); MhEngineConstructionEntry[] back = populateLevels(0, b1, 0deg); children = front back; mhSystemEngineCache.put(key, children.copy()); } for (c in children) parent << c; return true; } // Single frame. box b = parent.localBound(); b.d = b.d - uprightD; addLevels(0, b); return true; } return super(..); } /** * EntryLayout. * Why call it a bayEntry when it can be any entry? */ public MhBayEntryLayout entryLayout() { if (MhSystemSpawnerSelector ss = getSpawnerSelector) { ?MhSnapperSpawner frame = getSpawnerSelector.?spawner(layerSet(sFrame)); return frame.?entryLayout(ss).?MhBayEntryLayout; } return null; } /** * Default entry. */ public MhEngineConstructionEntry defaultLevelEntry(box b, LayerSet classification) { if (isFrame(parent)) return super(..); return null; }
public class MhCantileverFrameEngineBehavior extends MhFrameEngineBehavior { /** * Put engine function to run. */ public void putEngineRunFunctions(MhSnapper snapper, symbol event="", Object env=null) { if (!snapper) return; MhEngine engine = engine(snapper); switch(event) { case sShapeChange, sShapeAndSymChange : { if (env as MhSnapperChangedEnv) { bool stealing = false; if (snapper != env.owner) { if (MhSnapper stealSource = getStealSource(env, snapper)) { stealing = true; mhPutEngineRunFunction(engine, "rowChildStealConfig", snapper=snapper, source=stealSource); } } Object holeZDomain = snapper.domain("holeZDomain"); CollisionPrimitive additionalPrim = additionalPrim(snapper); if (env.translatedKey in ["uprightH", "h"] and !stealing) { if (levelPopulate(snapper)) { // level height changed mhPutEngineRunFunction(engine, "levelPopulate", snapper=snapper, holeZDomain=holeZDomain, loadWithinLimit=loadWithinLimit(snapper), revertAutoULPopulate=revertAutoULPopulate(snapper), additionalPrim=additionalPrim, configKey=snapper.configName); } } if (env.translatedKey in ["d", "h"] and !stealing) { // Run level arrange, since cantilever levels are children to frames. mhPutEngineRunFunction(engine, "levelArrange", snapper=snapper, holeZDomain=holeZDomain, additionalPrim=additionalPrim); } } } case sSnapperInserted: { Object holeZDomain = snapper.domain("holeZDomain"); CollisionPrimitive additionalPrim = additionalPrim(snapper); // Run level arrange, since cantilever levels are children to frames. mhPutEngineRunFunction(engine, "levelArrange", snapper=snapper, holeZDomain=holeZDomain, additionalPrim=additionalPrim); } default : { } }; super(..); } /** * Collect engine function to run. */ public void fetchEngineFunctionsRunG2(MhEngineFunctionRun[] functions, MhSnapper snapper, symbol event="", Object env=null) { super(..); switch (event) { case sShapeChange, sShapeAndSymChange : { functions << MhEngineFunctionRun("rowChildStealConfig"); functions << MhEngineFunctionRun("levelPopulate"); functions << MhEngineFunctionRun("levelArrange"); } case sSnapperInserted: { functions << MhEngineFunctionRun("levelArrange"); } default :; }; } /** * Gather function args. */ public str->Object engineFunctionArgsG2(MhSnapper snapper, MhEngineFunctionRun func, symbol event="", MhPreprocessArgsEnv preprocessArgs=null, Object env=null) { switch (func.name) { case "levelPopulate" : { return props { snapper=snapper, holeZDomain=snapper.domain("holeZDomain"), loadWithinLimit=loadWithinLimit(snapper), revertAutoULPopulate=revertAutoULPopulate(snapper), additionalPrim=additionalPrim(snapper), configKey=snapper.configName }; } case "levelArrange" : { return props { snapper=snapper, holeZDomain=snapper.domain("holeZDomain"), additionalPrim=additionalPrim(snapper) }; } default :; }; return super(..); } /** * Accepts function run. */ public bool acceptFunctionRunG2(MhSnapper snapper, MhEngineFunctionRun func, symbol event="", Object env=null) { switch (func.name) { case "levelPopulate" : { if (env as MhSnapperChangedEnv) { if (snapper != env.owner) { if (MhSnapper stealSource = getStealSource(env, snapper)) { return false; } } return env.translatedKey in ["uprightH", "h"] and levelPopulate(snapper); } return false; } case "levelArrange" : { if (event in [sShapeChange, sShapeAndSymChange]) { if (env as MhSnapperChangedEnv) { if (snapper != env.owner) { if (MhSnapper stealSource = getStealSource(env, snapper)) { return false; } } return env.translatedKey in ["d", "h"]; } return false; } else if (event == sSnapperInserted) { return true; } } default :; }; return super(..); } /** * Invalidate and return true if required. */ public bool invalidateIfRequired(Object owner, symbol k, MhSnapperChangedEnv env=null) { if (k == sAfterInitialExport) return true; return super(..); }
OfficeSnapperManagerRedView3D now will call getDialog().updateCoreProperties() when the selection is changed in the view.
Made fixes/improvements to the dialog and OfficeSnapperManager in regards to its support for G2 quick properties.
Updated how last values are handled for PanelSkinInsertAnimation.
Fix a graphical issue with the PanelSkinArrowVessel where it wasn't properly rotated
beginSideStretch(Connector c) now calls pickedUp() after stretchDisconnectInGroup().
If you have used SIF rs in your extension and they are now missing, you have the following options:
putNextRsPkg("custom.yourExtension.sif", "cm.abstract.sif")
cm.abstract.sif
package.Old: $sifFilesFilter; New: getRs("sifFilesFilter", "cm.abstract.sif");
cm.abstract.office
will no longer always startup for users that do not require it, as part of core package dependency cleanup and performance optimization. Previously, starting cm.abstract.dataSymbol
will always result in the startup of cm.abstract.office
.
This may result in some PartColumns
from cm.abstract.part
not being registered automatically at all times as before, manifesting in the form of a crash for extensions that do not directly depend on cm.abstract.office
.
Suggested fix is to explicitly declare and register the part columns you need in your custom extensions by calling registerPartColumn()
. You may get warnings if the column have already been registered by other extensions, but it should able to be ignored safely.
AOComponent now has it's propdefs set as cached=true.
Fixed a bug where you couldn't set an AOComponent's parent to null via the propdef.
Fixed a bug where spanned stack frames could cause panels to go below the floor due to snapping lower then expected.
In an effort to lessen drawing file size, CustomerOwnMaterials
are no longer stored in World.auxiliaryData
and have been moved
to World.cachedData
.
Note: This means unused COMs are no longer saved with the drawing.
For direct access to a COM, we advise usage of cm.abstract.material.customerOwnMaterials(World world)
Example:
if (CustomerOwnMaterial com = customerOwnMaterials(mainWorld()).?find(myKey)) { // do something with your COM }
Updated the base constructor to call initialize.
Old: public constructor() { /** Default */ } New: public constructor() { super(null); initialize(); }
Added a bool to allow for code to check whether the animation is in the doSuperStretch() method.
Added a call to droped() when it inserts snappers from super stretch
setDefaultViewModes()
now acts on mainWorld()
instead of activeWorld()
, so it is no longer dependent on the last view that was made active.
Old: /** * Set default view modes. */ public void setDefaultViewModes() { if (World world = activeWorld()) { if ((ViewMode[]) viewModes = defaultViewModes()) { world.setViewModes(viewModes); } } } New: /** * Set default view modes. */ public void setDefaultViewModes() { if (World world = mainWorld(selectWorldIfNull=false)) { if ((ViewMode[]) viewModes = defaultViewModes()) { world.setViewModes(viewModes); } } }
Added new fields to calculateWorldPrice
for BOM analytics purpose.
Old: public double calculateWorldPrice(Space space, bool ignoreGlobalAdjustments=false, bool abortable=false, Int partCount=null) { New: public double calculateWorldPrice(Space space, bool ignoreGlobalAdjustments=false, bool abortable=false, Int partCount=null, Timespan timelimit=null, Datetime deadline=null, bool flushSomePrices=false) {
An issue was fixed in the DoubleRange and DoubleSubRange classes which could potientially cause an infinite loop.
old: public Object next(Object o) { (...) if (res > maxV) return maxV; (...) } New: public Object next(Object o) { (...) if (res > maxV) return null; (...) } Old: public Object previous(Object o) { (...) if (res < minV) return minV; (...) } New: public Object previous(Object o) { (...) if (res < minV) return null; (...) }
Added: public class ToolTipGProp extends GProp {}
Before 14.5 the collection of lights in a block space was stored incorretly in the FetchLightEnv3D. It tryed to store etch individual light per snapper in the entries map. This hade the side effect that etch light over write the privies light so only on light would be stored per snapper. This has now been change to store a Light3D[] instead for etch snapper. This means that any code that uses the FetchLightEnv3D entries field need to change to use a Light3D[] insted of Light3D.
Old: public constructor(PhotoLabDialog dialog, Space space, Window parent, View3D peekAtView3D) { view2D = REDView2D(space, "lab2DView", parent); (...) New: public constructor(PhotoLabDialog dialog, Space space, Window parent, View3D peekAtView3D) { view2D = FilteredLabView2D(space, "lab2DView", parent); (...)
Method processProtocol() has been updated to accurately calculate job transfer progress. If buffer size is used to calculate progress and the number of bytes packed is less than buffer size, progress will not be calculated correctly. Instead we compare the number of bytes packed.
Old: extend package rpcStatus processProtocol() { (...) if (zcall.exec() == 0) { if (job) job.packageProgress(packageToSend.first, packageToSend.last, factory.bufferSize, factory.totalSize); (...) } New: extend package rpcStatus processProtocol() { (...) if (zcall.exec() == 0) { if (job) job.packageProgress(packageToSend.first, packageToSend.last, factory.bytesPacked.int, factory.totalSize); (...) }
Calling cm.core.ui.showWorldPropertiesDialog(World, Window)
will no longer show the World Properties dialog as that functionality has been moved to the Info Page.
Similarly, setting the showWorldPropertiesAtStart
boolean in CoreSettings will no longer work to show the dialog.
Changed the subclass of PaperDraw3DPolyReferencePointInsertAnimation
from Draw3DReferencePointInsertAnimation
to Draw3DPolyReferencePointInsertAnimation
. This changes the insert animation the "Grouped help points" button in the "Drawing (paper)" library in paperspace from inserting a line to inserting a polyline.
// cm.std.draw3D.draw3DReferencePoint.cm Modified: public class PaperDraw3DPolyReferencePointInsertAnimation extends Draw3DPolyReferencePointInsertAnimation
Some of the props added now support tooltips.
Old: public GProp[] initProps() { GProp[] seq(); DoubleRange span(-1, 1); seq << GProp("toneMapping", $toneMappingLabel, Bool(true)); seq << GProp("toneMapBackground", $toneMapBackgroundLabel, Bool(true)); seq << GProp("opendenoise", $openDenoiseLabel, Bool(true)); (...) New: public GProp[] initProps() { GProp[] seq(); DoubleRange span(-1, 1); seq << TooltipGProp("toneMapping", $toneMappingLabel, Bool(true), $toneMappingTip); seq << TooltipGProp("toneMapBackground", $toneMapBackgroundLabel, Bool(true), null, $toneMapBackgroundTip); seq << TooltipGProp("opendenoise", $openDenoiseLabel, Bool(true), $openDenoiseTip); (...)
The methods Window.exit
and Window.mouseLeftChild
can now get null passed into the to
argument.
extend public void exit(pointI p, Window to) { ... } extend public void mouseLeftChild(Window child, Window to) { ... }
This happens when the mouse cursor moves from a CET window and outside of CET. It means that code in these methods needs to be null safe in regards to the to
window.