Multiple developers have discovered that the reverse keyword, used as part of the for syntax, will not always behave as intended. It is the intention of the syntax to cause the loop to reverse iterate over the members of the collection with no other changes/syntaxes/keywords necessary. However, in the current state, Ranges require the developer to also define the collection in reverse order, which shouldn't be necessary.
With this release, this behavior has been fixed and the reverse keyword now functions as intended for Ranges. As a consequnce of the fix, however, existing workarounds will no longer function. If you have used the "define the range in reverse" trick to workaround the problem, you will need un-reverse the range. Failure to make this change will cause the loop to be skipped entirely.
For example, if you have code that is currently written as:
for (i in 3..1, reverse) {
you will need to re-write it as:
for (i in 1..3, reverse) {
In the CET 17.0 release in May 2026, Catalogue Component Tab and Component Tab Creator will reach their End-of-Life (EOL) and be retired completely.
As part of the EOL effort,
All developers are strongly encouraged to migrate to the new Table of Content (ToC) Toolbox introduced in 15.5 Major as the replacement for the Component Tab - (read more and learn how to migrate or build toolbox with ToC here) [https://dev-docs.configura.com/design-system/cet-developer-guides/catalogue-table-of-contents-with-the-new-ui]
Main MR: [https://git.configura.com/cet/external/base/-/merge_requests/42378]
Data symbol parts and picklists now follow the new 17.0 core Ind. Tag model. Stable Ind. Tag identity is based on sourceId(), user-modified tag text is stored through the shared UserTagInfoHolder, and legacy picklist tag data is migrated during load so existing user overrides can be preserved across reset-behavior changes and older drawings.
MR: Ind. Tag migration and restoration changes
Data-symbol product parts also participate in the 17.0 option-special storage split. Before 17.0, DsPart option specials were read from the shared owner-level PartSpecialHolder using data-symbol-specific combined keys. In 17.0, those option specials belong in the per-part OptionSpecialHolder introduced in cm.abstract.part, and DsPartSpecialMigrator exists to translate older DsSpecOption keys into the new holder layout.
If your catalogue package has older drawings that must preserve DsPart option specials or additional options, you need to opt in to that migration flow during load.
cm.abstract.office no longer provides the older panel-frame electrical object framework from electrical.cm.
Instead, the package now depends on cm.abstract.pathing and exposes AOElecNode as a small office-level building block for packages that want to implement a routed electrical system.
The new 17.0 custom.fika electrical system is the reference example for that new routed architecture. Rather than asking PanelFrame and PanelJunctionSnapper for EOPosition or EORange data and applying ElectricalObjects directly, Fika defines electrical snappers, routing nodes, and A-star path finding on top of AOElecNode and cm.abstract.pathing.
For most manufacturers, the lower-effort migration path is not to fully adopt the new Fika routing model. If your package already relies on the old office electrical interfaces, the practical migration path is to copy the removed interfaces and supporting logic into your own package-owned classes and continue using that model there.
The abstract part layer now follows the new core Ind. Tag model. The calculation Ind. Tag column can split rows by actual user-entered tag text, only treats true user overrides as adjusted values, and applies row-level Ind. Tag adjustments to each underlying part. A post-merge hook also repairs pre-17.0 adjustment keys when older drawings are loaded.
MR: Ind. Tag migration and restoration changes
ProdPart has also been expanded with first-class support for option additions, option import, and option-level customization state.
Those changes provide the part foundation for the new query import workflow in cm.abstract.part.query, where imported option items can be dragged into the query dialog and applied as additions or special overrides.
CET 17.0 also changes where option specials live at runtime. Before 17.0, both PartSpecial and OptionSpecial data were stored together in the snapper-level PartSpecialHolder under cSpecialHolderKey, and option-special lookup depended on a combined part-and-option key. In 17.0, option specials move into a separate per-part OptionSpecialHolder, stored on the owner under a key built from optionSpecialHolderPropKey(part) and the part's sourceId(). That split is what makes stable option identity, inserted-option ordering, and per-part option-special migration possible.
Because older drawings still load old option specials from PartSpecialHolder, packages that need backward compatibility must opt in to the new migration helpers such as ProdPartSpecialMigrator and DsPartSpecialMigrator during their load/migration flow.
CET 17.0 adds a new product-part import layer in cm.abstract.part.import. These classes specialize the new core importer framework so SIF, PMX, and OFDA XML imports can create ProdPart instances instead of plain Part instances.
The most important new behavior is in SIF and OFDA XML import. ProdPartSIFImporterEnv can now build SpecOption data during import, and OFDA XML import can now route option tags into ProdPart through new shared option-import hooks.
This package is also the bridge used by the query import UI. cm.abstract.part.query now returns these environments from ProdPartQueryDialogBehavior.getImporterEnv(str suffix), so imported product parts and product options flow through the same importer framework as the core query import path.
cm.abstract.part.query adds the product-part and option-specific part of the new 17.0 query import workflow.
Together with the base dialog changes in cm.core.part.query and the underlying option/customization changes in cm.abstract.part.ProdPart, product-part query dialogs can now import product parts and option items, drag option items into the query grid, and create option additions or overrides in addition to the existing option-special workflow.
CET 17.0 splits the PMX code that used to live entirely in cm.abstract.pmx into two layers.
cm.abstract.pmx is now the abstract/export-facing package: it keeps the PMX order exporter, package initialization, test hooks, and local file-move/debug helpers.
The reusable PMX database wrapper types, PMX data-definition objects, constants, and .NET bridge code have been moved into the new shared package cm.core.pmx.
That means most compile-time migration work is import and type rerouting. If your code previously referenced PMX model/database types through cm.abstract.pmx, update those references to cm.core.pmx instead. If your code used PMX import helpers such as PMXFilePartSource or ItemDataConverter, those APIs were not moved into cm.core.pmx; they now live in cm.core.part.import.
The ItemData option payload also changed shape during that extraction. The old abstract-only optionsSeq storage is gone in 17.0; the shared cm.core.pmx.ItemData uses a single canonical options array for option generation, appending through operator<<, exporter insertion, and part initialization from PMX data.
cm.abstract.projectInfo now stores and restores project-information drop-down selections in a way that preserves the selected item's label as well as its key.
This fixes a visible behavior issue in paper view and other text-driven outputs where fields such as terms of delivery, delivery method, terms of payment, and delivery time could show only the stored numeric/key value instead of the human-readable label.
cm.core.Snapper is also part of the 17.0 migration story for query-related part customization.
During load, Snapper.loaded1(...) now initializes the optional PartSpecialMigrator, and later Snapper.fetchParts(...) invokes migratePartSpecials(localParts) before applying 17.0 additions and overrides.
That makes cm.core.Snapper the package where migrator discovery and execution actually happen. The migrator types themselves are documented under cm.core.part and cm.abstract.part.
We've identified that feature snapping in 3D works poorly for most snappers, and that many extensions do not provide their own snap features. To fix this with minimal migration effort, 3D features are now generated from each snapper's mesh by default.
Core validation now exposes Check Drawing from the Tools menu, normal extension snapper appended in appendInvalidation will be still register here.
Navisworks NWD Export will be removed as an extension from the Marketplace and moved into core.
As part of this the package has changed from cm.core.nwd to cm.core.sceneExport.nwd.
The export will be automatically activated only when using a Material Handling edition
CET 17.0 refactors Ind. Tag identity and storage in cm.core.part. User-entered Ind. Tag text is now stored separately from the default ItemTagInfo key, part identity for Ind. Tags and part annotations is driven by sourceId(), and drawings from 16.5 and earlier are migrated on load so existing user-modified tags, AN/AD annotations, and single-part adjustments continue to work.
MR: Ind. Tag migration and restoration changes
The same stable-identity work also introduces broader Part support for additions, overrides, and customization state. Those APIs are important context for the new import/customization workflows in cm.core.part.query and cm.abstract.part.query, where imported parts are applied as additions or overrides and need to map back to a stable logical part identity.
CET 17.0 also separates part-special storage from option-special storage. PartSpecialHolder remains the shared owner-level container for part specials, overrides, and additional parts, while cm.abstract.part introduces a separate per-part OptionSpecialHolder for option specials and added options. Older drawings may therefore need holder migration in addition to key migration.
CET 17.0 adds a new core import layer for Part data. cm.core.part.import provides the importer classes, the shared importer-environment model, and the default SIF, PMX, and OFDA XML implementations that now back file-based part import workflows.
The main migration impact is compile-time. Packages that want to import custom part types should now plug into this framework by subclassing PartImporterEnv, returning the correct Part subclass from createPart(...), and,
for OFDA XML, overriding the new OFDA import hooks on Part.
PMX import continues to rely on initializePartFromItemData(...), which was already part of the older PMX import path. The truly new Part interfaces in this area are the OFDA import hooks described below.
The query dialog was expanded in 17.0 with a resizable window, a right-side import pane, and drag-and-drop support for imported parts.
Together with the underlying cm.core.part and cm.abstract.part changes, the query workflow now supports importing both parts and
product-part options, and applying them as additions or overrides in addition to the existing special-part workflow.
CET 17.0 extracts the reusable PMX runtime surface from cm.abstract.pmx into the new shared package cm.core.pmx.
This is now the package that owns the PMX data model (ItemData, OptionData, PartDetails, and related records), the PMX database wrapper (Database), the PMX file constants, and the PMXNetObj/DataDefinition bridge layer.
The move makes PMX functionality available to both exporter code and generic part-import code without forcing those consumers to depend on the abstract exporter package. It also broadens the PMX database/export payload to include project-level metadata such as notes, order info, and sold-to/ship-to records.
Because the package is new, migration work is mostly straightforward namespace replacement: code that previously instantiated PMX data/database types from cm.abstract.pmx should now import cm.core.pmx and use the same type names there.
Improved the texture mapping of the basic 3D shapes for rectangles, cylinders and pyramids.
Improved the texture mapping of many wall symbols such as arced walls, windows and curtains.
DcSIFExportEnv no longer has ambiguous constructor overloadscustom.dataCatalog.builder removes the ambiguous DcSIFExportEnv constructor overload pair that previously accepted the same overall argument set with catalogCodeOverride and useInFile in conflicting positions.
This fixes a compile-time problem for downstream subclasses calling super(...), where CET could report an ambiguous function error unless the caller forced the overload with a positional null.
custom.fika is not where the shared migrator framework is defined, but it is an important example package for the 17.0 migration story. It shows how a catalogue package with older custom Part key history can opt in to the new PartSpecialMigrator load flow by exposing a snapper-level partSpecialMigrator prop and returning a package-specific migrator.
In Fika's case, FikaDsPartSpecialMigrator extends DsPartSpecialMigrator and overrides oldPartSpecialsKey(Part part) so older Fika drawings can still find part and option specials that were stored under pre-17.0 key formats.
Definining a literal Range in reverse order as part of the for syntax will now cause the loop to be skipped entirely. Rewrite the Range literal to correct order. The regular expression for.*\(.*[.][.].*reverse can be used to quickly find all places in your code where you are using the reverse keyword with a literal Range.
Method signature changes in MCClient.
Old: final public bool onlineLogin()
new: final public bool onlineLogin(bool useCachedPassword=true)
The license client tester has been removed.
Removed: public void testConfiguraLicenseClient()
Removed login from the license client.
The constructors of DsiConstraintSelPath have been changed.
Old: public constructor auto(path, feature, option); public constructor auto(path, feature, option, checkedV); public constructor auto(path, feature, option, numV);
New: public constructor(str path, SFeature feature, Option option, DsiPDataOption dataOption, DsiPDataOption parentDataOption)
The following interfaces will be removed as part of the Component Tab and Component Tab Creator EOL effort:
ProductCatalogRemoved: extend public void appendToolbox(str url)
The following interfaces will be removed as part of the Component Tab and Component Tab Creator EOL effort:
// Functions: Removed: public bool dsCloneToolboxes(DataCatalog catalog, ProductCatalog productCatalog, DsToolboxCreatorToolboxCards toolboxCards) Removed: public str dsToolboxFileStr(DataCatalog catalog, DsToolboxCreatorToolboxCards cards, DsToolboxCreatorToolboxCard card)
Note: cm.abstract.dataSymbol.ui.toolboxCreator package is completely removed. If your extension has dependency to this package, please remove it.
// Constants Removed: public const str dsToolboxCreatorFileType = ".cmtbxc"; // Functions Removed: public Image DsToolboxCreatorLoadToolboxImage(Url file) Removed: public DsToolboxCreatorExtensionToolboxCard dsToolboxCreatorCreateExtensionToolboxCard(Window parent, symbol pkg, DsToolboxCreatorToolboxCard card, str sortKey, str groupId, str libraryFunctionName=null, Image image=null, str label=null) Removed: public str tbCatCardStatsEntityKey(DsCatalogToolboxData cData) Removed: public void dsToolboxCreatorToolboxCardSetSectionLabelAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetSectionAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetThumbnailViewAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetToolboxSectionWindowAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetHeaderAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetImageSectionAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public void dsToolboxCreatorToolboxCardSetSchemeButtonAnimationEnv(Window window, DsDragAnimationEnv env) Removed: public Library dsToolboxCreatorLibraryFromCard(Card card, symbol pkg) Removed: public void faceliftPreprocess(Library library) Removed: public str->str dsDefaultSchemeDescriptionMap(str{} languages) // Classes Removed: public class DsToolboxCreatorCardPropertyWindow extends SubWindow Removed: public class DsToolboxCreatorCardWindow extends CardWindow Removed: public class DsToolboxCreatorControlWindow extends SubWindow Removed: public class DsToolboxCreatorDataCatalogHeader extends DsDataCatalogHeader Removed: public class DsToolboxCreatorDataCatalogSectionedLabel extends DsDataCatalogSectionedLabel Removed: public class DsToolboxCreatorDataCatalogSectionedScrollableSubWindow extends DsDataCatalogSectionedScrollableSubWindow Removed: public class DsToolboxCreatorDesignWindow extends SubWindow Removed: public class DsToolboxCreatorDialog extends DialogWindow Removed: public class DsToolboxCreatorDeletionSubWindow extends SubWindow Removed: public class DsToolboxCreatorDragAnimation extends DsDragAnimation Removed: public class DsToolboxCreatorDropDownTreeView extends DropDownTreeView Removed: public class DsToolboxCreatorDynamicCard extends DsDynamicCard Removed: public class DsToolboxCreatorExtensionToolboxCard extends ExtensionToolboxCard Removed: public class DsToolboxCreatorFillerInsertDragAnimation extends DsToolboxCreatorThumbnailViewMoveDragAnimation Removed: public class DsToolboxCreatorFillerMoveDragAnimation extends DsToolboxCreatorThumbnailViewMoveDragAnimation Removed: public class DsToolboxCreatorFillerThumbnail extends DsThumbnail Removed: public class DsToolboxCreatorFocusLines Removed: public class DsToolboxCreatorFocusRect Removed: public class DsToolboxCreatorGroupInsertDragAnimation extends DsToolboxCreatorDragAnimation Removed: public class DsToolboxCreatorGroupMoveDragAnimation extends DsToolboxCreatorGroupInsertDragAnimation Removed: public class DsToolboxCreatorHeaderInsertDragAnimation extends DsToolboxCreatorGroupInsertDragAnimation Removed: public class DsToolboxCreatorHeaderLimb extends DsDynamicHeaderLimb Removed: public class DsToolboxCreatorHeaderMoveDragAnimation extends DsToolboxCreatorGroupMoveDragAnimation Removed: public class DsToolboxCreatorHeaderPropertyWindow extends SubWindow Removed: public class DsToolboxCreatorImageSectionInsertDragAnimation extends DsToolboxCreatorDragAnimation Removed: public class DsToolboxCreatorImageSectionMoveDragAnimation extends DsToolboxCreatorImageSectionInsertDragAnimation Removed: public class DsToolboxCreatorImageSectionPropertyWindow extends ShrinkWindow Removed: public class DsToolboxCreatorLibraryLimb extends DsDynamicLibraryLimb Removed: public class DsToolboxCreatorProductCatalogLimbBuilder extends DsProductCatalogLimbBuilder Removed: public class DsToolboxCreatorProductLevelPropertySubWindow extends ShrinkWindow Removed: public class DsToolboxCreatorProductViewInsertDragAnimation extends DsDragAnimation Removed: public class DsToolboxCreatorPropertyWindow extends SubWindow Removed: public class DsToolboxCreatorSchemeButton extends DsDataCatalogSchemeButton Removed: public class DsToolboxCreatorSchemeButtonInsertDragAnimation extends DsToolboxCreatorDragAnimation Removed: public class DsToolboxCreatorSchemeButtonMoveDragAnimation extends DsToolboxCreatorSchemeButtonInsertDragAnimation Removed: public class DsToolboxCreatorSchemeButtonPropertyWindow extends ShrinkWindow Removed: public class DsToolboxCreatorSectionLabelPropertyWindow extends SubWindow Removed: public class DsToolboxCreatorSideBarPropertiesPropertyWindow extends DsInWindowSideBarProperties Removed: public class DsToolboxCreatorThumbnailsLimb extends DsThumbnailsLimb Removed: public class DsToolboxCreatorThumbnailPropertyWindow extends SubWindow Removed: public class DsToolboxCreatorThumbnailViewMoveDragAnimation extends DsThumbnailViewMoveDragAnimation Removed: public class DsToolboxCreatorToolboxCard Removed: public class DsToolboxCreatorToolboxCards Removed: public class DsToolboxCreatorToolboxSectionedScrollableSubWindow extends ToolboxSectionedScrollableSubWindow Removed: public class DsToolboxCreatorToolboxThumbnail extends DsToolboxThumbnail Removed: public class DsToolboxCreatorToolboxThumbnailView extends DsToolboxThumbnailView Removed: public class DsToolboxCreatorTooltip Removed: public class DsToolboxCreatorTreeViewItem extends TreeViewItem Removed: public class DsToolboxCreatorHeaderTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorGroupTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorProductViewTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorFillerTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorImageSectionTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorSchemeButtonTreeViewItem extends DsToolboxCreatorTreeViewItem Removed: public class DsToolboxCreatorUIBuilder extends DsNewUIBuilder Removed: public class DsToolboxCreatorUIGroupBuilder extends DsUIGroupBuilder
DsPartIf your data symbol part subclass previously used itemTagKey() or partSourceId to control Ind. Tag identity, move that logic to sourceId().
Old: public constructor(Snapper snapper, DsPData data, double basePrice, Double optionPriceSum, double qty=1, str partSourceId=null) New: public constructor(Snapper snapper, DsPData data, double basePrice, Double optionPriceSum, double qty=1, str sourceId=null) Old: public constructor(Snapper snapper, DsPData data, str articleCode, str description, double basePrice, Double optionPriceSum, double quantity=1, double inPrice=0, bool flattenable=true, bool group=false, partStyle style=psNone, str flattenableKey=null, bool allowOtherParent=true, str partSourceId=null) New: public constructor(Snapper snapper, DsPData data, str articleCode, str description, double basePrice, Double optionPriceSum, double quantity=1, double inPrice=0, bool flattenable=true, bool group=false, partStyle style=psNone, str flattenableKey=null, bool allowOtherParent=true, str sourceId=null) Old override point: public str itemTagKey() New override point: public str sourceId()
DataSymbolOld: public void setItemTagInfo(Part part, str key, ItemTagInfo info, bool userMod=false) New: public void setItemTagInfo(Part part, str key, ItemTagInfo info)
DsPicklistThe following picklist-local user-tag storage fields have been removed. If your code accessed them directly, migrate to the shared helper APIs in cm.core.part.userTagInfoHolder.cm.
Removed: public str->ItemTagInfo tagInfos(); Removed: public str->str tagInfoKeys();
DsPicklistQuantityGridCellThe freeform picklist quantity cell now uses a custom numeric field instead of relying only on the older default text parsing path.
Old: public class DsPicklistQuantityGridCell extends PicklistInputGridCell New: public class DsPicklistQuantityGridCell extends DsPicklistInputGridCell Old: extend public FormattedTextField getCustomField(Control popup) New: extend public FormattedTextField getCustomField(GridWindow gw, Control popup) Old: public FormattedTextField getCustomField(Control popup) New: public FormattedTextField getCustomField(GridWindow gw, Control popup)
This is the class-level migration surface behind the quantity-input behavior change in Catalogue Explorer freeform picklists.
DsPartDsPart now exposes additional public hooks for the 17.0 option-identity and added-option workflows.
Removed: public str optSpecialKey(PartOptionItem opt) Old: public str getCompanyCode() New: public str companyCode() Old: extend public str catalogCode() New: public str catalogCode() Old: extend public double pkgCount() New: public double pkgCount() Added: extend public void xmlAdditionalOptions(DsiPDataOption dataOpt|, int index, str prefix, XmlStreamBuf buf, bool recurse=true) Added: extend public void xmlAdditionalOptions(SpecOption anchorOpt|, int index, str prefix, XmlStreamBuf buf, bool recurse=true) Added: extend public void xmlAdditionalOptions(str key|, int index, str prefix, XmlStreamBuf buf, bool recurse=true) Added: extend public void generateCustomOptionXmlTag(CustomSpecOption customOpt, int index, str prefix, XmlStreamBuf buf) Added: extend public void generateAdditionalOptions(DsiPDataOption dataOpt|, ItemData itemData, bool recurse=true) Added: extend public void generateAdditionalOptions(SpecOption anchorOpt|, ItemData itemData, bool recurse=true) Added: extend public void generateAdditionalOptions(str key|, ItemData itemData, bool recurse=true)
DsPart pricing behaviorDsPart now participates more directly in the shared 17.0 currency-translation pricing flow.
Changed behavior/important usage: public double basePrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: public Double generateOptionPriceSum()
For data-symbol parts, this means base price and option-price calculations now flow through the shared CET-currency translation behavior more consistently.
The older data-symbol-specific package-count column interfaces have been removed. If your package references them directly, update that code to use the shared core package-count column instead.
Removed: public const DsPackageCountColumn dsPkgCountColumn() Removed: public class DsPackageCountColumn extends BasicPartColumn
Replacement: use cm.core.init.packageCountColumn() / PackageCountColumn.
DsPDataDsPData also gained new hooks for the 17.0 option-export flows.
Added: extend public void generateOptionRows(PartOptionItem opt, DsPart part, SifEnv env, Space space, str featureCode=null, str featureDesc=null) Added: extend public void generateAdditionalOptionRows(DsiPDataOption dataOpt|, DsPart part, SifEnv env, Space space, bool recurse=true) Added: extend public void generateAdditionalOptionRows(SpecOption anchorOpt|, DsPart part, SifEnv env, Space space, bool recurse=true) Added: extend public void generateAdditionalOptionRows(str key|, DsPart part, SifEnv env, Space space, bool recurse=true)
These methods are the entry points for packages that need to participate in SIF export of added options.
DsSpecOptionDsSpecOption should also be considered part of the 17.0 migration surface for data-symbol packages because its key generation feeds directly into option-special identity and migration.
Added/important usage: public class DsSpecOption extends SpecOption Added/important usage: public DsPDataOption option Added/important usage: public str generateKey() Added/important usage: public str adjustmentKey()
For data-symbol options, generateKey() and adjustmentKey() now depend on DsPDataOption.optionToRootPath() when available. generateKey() also includes groupCode when present. That is why data-symbol option-special migration and added-option ordering need their own DsPartSpecialMigrator handling instead of assuming base SpecOption keys are sufficient.
DsPartSpecialMigratorDsPartSpecialMigrator is the public data-symbol-specific migration helper for 17.0 option-special migration.
It extends ProdPartSpecialMigrator and adjusts the legacy-key logic for DsSpecOption and feature-option combinations.
Added: public class DsPartSpecialMigrator extends ProdPartSpecialMigrator Added: public str oldOptSpecialsKey(Part part, PartOptionItem opt) Added: public str oldOptSpecialsKey(PartOptionItem opt)
If your catalogue package historically used custom DsPart option keys, subclass DsPartSpecialMigrator and override those methods. The Fika example does exactly this by returning FikaDsPartSpecialMigrator from the snapper prop and overriding oldPartSpecialsKey(Part part) for its older key histories.
The migrator types are opt-in.
If your snapper package needs to preserve old part or option specials, expose a PartSpecialMigrator "partSpecialMigrator" prop on the snapper and return the right migrator instance for that snapper family.
A downstream example from custom.fika looks like this:
PartSpecialMigrator "partSpecialMigrator" { Object default(..) { return FikaDsPartSpecialMigrator(); } }
The searchable base types involved are:
New/important: class PartSpecialMigrator New/important: class ProdPartSpecialMigrator New/important: class DsPartSpecialMigrator
Changed the length setter function from final to extend.
Old: final public double length=(double v) { New: extend public double length=(double v) { Removed: extend public void lengthChanged() {
Renamed two methods to more clearly convey what they do.
Old: extend public void setP0(point p, Angle forcedAngle=null, bool updateD=true) { New: extend public void setSpaceP0(point p, Angle forcedAngle=null, bool updateD=true) { Old: extend public void setP1(point p, Angle forcedAngle=null) { New: extend public void setSpaceP1(point p, Angle forcedAngle=null) {
Removed the following functions:
Removed: final public void drawHelpFrameIfSelected(GInstance gs) { Removed: extend public void drawMarginHelpFrameIfSelected(GInstance gs, rect mr) { Removed: final public void drawHelpFrameIfSelectedOrZoomedOut(GInstance gs, GTextBox gText) {
Instead, use one of these:
Added: final public void drawHelpFrame(GInstance gs) { Added: final public void drawMarginHelpFrame(GInstance gs) {
Old: extend public void addSuperStretchVessel(point p) { New: extend public void addSuperStretchVessel(point p, angle a) {
Changed the constructor to optionally support custom labels for collections.
Old: public constructor(str collectionName, str dbPath, str namespace, str uom, symbol[] tags, int version, Material m) { New: public constructor(str collectionName, str dbPath, str namespace, str uom, symbol[] tags, int version, Material m, str collectionLabel=null) {
Steal was removed and made inaccessible, not meant to be used from the outside.
Old: extend public void steal(AbsExtrCollection f) { New: extend package void registryCopyContent(AbsExtrCollection f) {
Minor name changes:
Old: final public AbsExtrProfile get(str name) { New: final public AbsExtrProfile get(str id) {
Made member not intended to be changed from the outside private but public readable.
Old: public str id; New: private str id : public readable;
Added a optional construction argument to decide whether the profile shape should be closed or not.
Old: public constructor(str coll, str name, AbsExtrShapeDefItem[] shapeDef) { New: public constructor(str coll, str name, AbsExtrShapeDefItem[] shapeDef, bool closeProfile=true) {
Unused methods in K2PriceListManager removed:
Removed: extend public void updateChannelsFromOfflineData() Removed: extend public void updateChannelsFromOnlineData(str->str response)
This is now handled by K2ChannelManager, see cm/abstract/k2/k2ChannelManager.cm.
Removed old constructor:
Removed: public constructor(str article, str brand=null, str model3DStr=null, str productCode=null, str eanCode=null, str extProductSheetLink=null, str cetType=null, size snapperSize = (0, 0, 0), size nicheSize = (0, 0, 0), double depthOffset=0, double heightOffset=0, fanCm3DType fanType=fanCm3DType.undefined, whitegoodsFrontType front=whitegoodsFrontType.undefined, str accessoriesList=null, str imageStr=null, symbol creatorPkg=#:package : caller eval, bool hidden=false)
Instead use:
public constructor(str article, str brand=null, str model3DStr=null, str productCode=null, str eanCode=null, str extProductSheetLink=null, str cetType=null, size snapperSize = (0, 0, 0), size nicheSize = (0, 0, 0), double depthOffset=0, double heightOffset=0, fanCm3DType fanType=fanCm3DType.undefined, whitegoodsFrontType front=whitegoodsFrontType.undefined, str accessoriesList=null, str imageStr=null, symbol creatorPkg=#:package : caller eval, bool hidden=false, str rsKey=null)
AbsMaterial now includes thumbnailType method, subclasses will need to remove extend keyword if they had previously defined this method.
AbsMaterial public class AbsMaterial extends CachedMaterial { extend public gmThumbnailType thumbnailType() {..} } public class FOMaterial extends OfficeMaterial { Old: extend public gmThumbnailType thumbnailType() {..} New: public gmThumbnailType thumbnailType() {..} }
The abstract class MhSystemConfiguration, and the child classes MhStorageConfiguration and MhStorageEditorConfiguration have had similar methods declared in them.
MhStorageConfiguration.configUserPropertyChanged(str key, Object value, Object oldValue, Object propOwner, MhStorageConfigurationItem item) and MhStorageEditorConfiguration.userPropertyChange(MhStorageEditorItem item, CoreProperty property, Object oldValue) were both doing similar logic and so we have now created a new method in the parent class MhSystemConfiguration that calls this similar functionality. If you had previously overridden either of these two methods, you should now override configUserPropertyChanged(MhSystemConfigurationItem item, CoreProperty property, Object oldValue) instead.
public class MhSystemConfiguration extends CoreObject { Added: /** * Config user property changed. */ extend public void configUserPropertyChanged(MhSystemConfigurationItem item, CoreProperty property, Object oldValue) { if (property.owner != item) { item.?additionalPropChanged(property.key, property.value, oldValue, property.owner); } configUserPropertyChanged(property.key, property.value, oldValue, property.owner, item); } } public class MhStorageConfiguration extends MhSystemConfiguration { Removed: /** * configUserPropertyChanged - user changed property. Adjust configuration accordingly. */ extend public void configUserPropertyChanged(str key, Object value, Object oldValue, Object propOwner, MhStorageConfigurationItem item) { for (it in items) { it.configUserPropertyChanged(..); } } } public class MhStorageEditorConfiguration extends MhSystemConfiguration { Removed: /** * User property changed. */ extend public void userPropertyChange(MhStorageEditorItem item, CoreProperty property, Object oldValue) { if (property.owner != item) { item.additionalPropChanged(property.key, property.value, oldValue, property.owner); } if (space and !skipPushToPreview(..-)) { beforePushPropToPreview(..); pushPropToPreview(space, ..-); afterPushPropToPreview(..); } } }
The function Snapper getNextSingleAisle(MhSnapper snapper, bool top=true) has been removed and a function Snapper getNextAisle(MhSnapper snapper, bool top=true) has been added.
Removed: public Snapper getNextSingleAisle(MhSnapper snapper, bool top=true) { for (r in getAllRows(snapper, top=top, reverse=false)) { if (r == snapper) continue; if (r.isSingle and r.isAisle) return r; } return null; } Added: public Snapper getNextAisle(MhSnapper snapper, bool top=true) { for (r in getAllRows(snapper, top=top, reverse=false)) { if (r == snapper) continue; if ((r.isDouble and r.isAisle and !r.isFlueGap) or (r.isSingle and r.isAisle)) return r; } return null; }
Previously MhEngineEntry has a map field str->Object additionalShapeArgs that allows storing key-value data. This has now been changed to a private field str->Object _additionalShapeArgs and new public methods have been introduced with the same name. This is so that the source of this map can be overridden.
For example, MhEngineConstructionEntry overrides the additionalShapeArgs methods to put/retrieve data from the owned constructionInfo() object instead. The class MhEngineConstructionEntryInfo now has a new field str->Object _additionalShapeArgs and method str->Object additionalShapeArgs(). This allows the map of the info class to be set independently and for the data to later be assigned to a MhEngineConstructionEntry at a later time.
// cm/abstract/materialHandling/engine/mhEngineEntry.cm public class MhEngineEntry extends MhEngineEntryBase { Old: public str->Object additionalShapeArgs; New: private str->Object _additionalShapeArgs; New: extend public str->Object additionalShapeArgs() { } New: extend public str->Object additionalShapeArgs=(str->Object args) { } New: extend public Object getAdditionalShapeArg(str key) { } New: extend public void putAdditionalShapeArg(str key, Object val) { } } // cm/abstract/materialHandling/engine/mhEngineConstructionEntry.cm public class MhEngineConstructionEntryInfo { New: private str->Object _additionalShapeArgs; New: extend public str->Object additionalShapeArgs() { } } // cm/abstract/materialHandling/mhSnapperSpawner.cm public class MhSnapperSpawner extends SnapperSpawner { Old: extend public MhEngineConstructionEntry engineConstructionEntry() { } New: extend public MhEngineConstructionEntry engineConstructionEntry(MhEngineConstructionEntryInfo info=null) { } }
The additionalShapeArgs are primarily used to push property values to spawned snappers on constructions. They can also be used to hold distinct entry values to be used during engine functions.
These additionalShapeArgs are applied in three areas during construction, one during instantiation of an MhEngineConstructionEntry, one during spawning of a snapper, and one more time after export of entry.
1. Instantiation of `MhEngineConstructionEntry` public class MhSnapperSpawner extends SnapperSpawner { /** * Engine construction entry (used by entry layout only). */ extend public MhEngineConstructionEntry engineConstructionEntry(MhEngineConstructionEntryInfo info=null) { MhSnapperShape shape = createShape(); for (key, val in info.?additionalShapeArgs) shape.?changeShape(key, val); ... } } 2. Spawning of a snapper public class MhEngineConstructionEntry extends MhEngineSnapperEntry { /** * Spawn snapper. */ extend public MhSnapper spawnSnapper(MhEngine engine, MhSystemSpawnerSelector spawnerSelector=null) { ... if (MhSnapper s = spawnerSelector.?spawnSnapper(classification, shapeArgs=shapeArgs+additionalShapeArgs, env=this)) { return s; } ... } } 3. After export of entry. public class MhSnapperShape extends CorePropObj { /** * Entry exported. */ extend public void entryExported(MhSnapper snapper, MhEngineEntry entry) { ... // Apply additional shape args on exported as they are not included in entry cache key so export function might have spawned snapper without using correct shape args from mhSystemEngineCache. if (entry as MhEngineConstructionEntry) { for (key, val in entry.additionalShapeArgs()) { changeShape(key, val); } } } }
Several changes have been made for handling level clearances in the abstract. First of all, the class MhClearanceSpecLevelClearanceBehavior and the field levelClearanceZ field in MhClearanceSpec have been removed.
How this would be used is the MhClearanceSpecLevelClearanceBehavior class would call spec.?collisionPrimitives(shape, env) which by default would call MhClearanceSpec.levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) that returns a CollisionPrimitive built based on spec.levelClearanceZ. This would result in a CollisionPrimitive always being built for the level.
Removed: public mhLazyGlobal MhBehavior mhClearanceSpecLevelClearanceBehavior = MhClearanceSpecLevelClearanceBehavior(); Removed: public class MhClearanceSpecLevelClearanceBehavior extends MhClearanceSpecBehavior public class MhClearanceSpec extends CorePropObj { Removed: public Double levelClearanceZ; Removed: Prop cMhLevelClearanceZPK : attributes={#allowPush, #allowPushNull}; }
Instead of always building a CollisionPrimitive for the level to represent the level clearance, we now only append this primitive during the construction of entries.
public class MhLevelShape extends MhBoxSnapperShape { Old: extend public void appendLevelClearancePrimitives(CollisionPrimitive{} prims, MhCollisionFetchEnv env) { if (?MhClearanceBehavior b = owner.?behavior("levelClearance")) { if (!env) env = MhCollisionFetchEnv(); Transform t = env.transform; double elevation; if (t) elevation = t.pos.z; else elevation = owner.toSpaceTransform().pos.z; MhClearanceCollisionFetchEnv clearanceEnv(env, elevation, owner.rootParent.MhSnapper.?classification); b.appendCollisionPrimitives(this, clearanceEnv, prims); } } New: extend public void appendLevelClearancePrimitives(CollisionPrimitive{} prims, MhCollisionFetchEnv env) { } } public class MhLevelSpawner extends MhStorageSpawner { New: /** * Return construction entry collision primitive. */ public CollisionPrimitive engineConstructionCollision(MhSnapperShape shape, MhEngineConstructionEntry entry) { CollisionPrimitive res = super(..); appendLevelClearancePrimitive(res, shape, entry); return res; } New: /** * Append level clearance primitive. * Simple implementation of level clearance when using MhCompartmentType with no unit loads. For custom logic, override either this or MhClearanceSpec.levelClearanceCollision(). */ extend public void appendLevelClearancePrimitive(CollisionPrimitive prim, MhSnapperShape shape, MhEngineConstructionEntry entry) { if (prim as CollisionPrimitiveSet) { if (?MhClearanceBehavior b = statelessBehavior("Clearance")) { MhCollisionFetchEnv env(reason=#construction, entry=entry); b.appendCollisionPrimitives(shape, env, prim.subPrims); } } } }
Due to the above change in behavior, the ownership of a level clearance value has been changed from the clearance spec to the engine entry, more specifically it is stored in the additionalShapeArgs() of MhEngineEntry.
public class MhClearanceSpec extends CorePropObj { Old: /** * Collision primitives based on `levelClearanceZ`. */ extend public CollisionPrimitive[] levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) { CollisionPrimitive[] res(4); if (levelClearanceZ and shape) { box b = shape.localBound(); b.h = levelClearanceZ.v; static Layer layer = Layer(layerSet(sClearance, sLevelClearanceZ), layerSet(sLevel, sLevelFrame)); res << mhGetBoxCollisionPrimitive(b, layer, env.transform); } return res.any ? res : null; } New: /** * Collision primitives for level clearance. */ extend public CollisionPrimitive[] levelClearanceCollision(MhSnapperShape shape, MhCollisionFetchEnv env) { CollisionPrimitive[] res(4); // Append level clearance collision from MhCompartmentType during construction. if (env.?reason == #construction) { MhEngineEntry entry = env.entry; if (?Double levelClearance = entry.?getAdditionalShapeArg("levelClearance")) { box b = shape.?localBound([sLevelFrame]); b.h += levelClearance.v; static Layer layer = Layer(layerSet(sClearance, sLevelClearanceZ), layerSet(sLevel, sLevelFrame)); res << mhGetBoxCollisionPrimitive(b, layer); } } return res.any ? res : null; } } public class MhCollisionFetchEnv { New: public MhEngineEntry entry; Old: public constructor(symbol reason=#none, Transform transform=null, bool includeChildren=false, Object arg=null) New: public constructor(symbol reason=#none, Transform transform=null, bool includeChildren=false, MhEngineEntry entry=null, Object arg=null) }
Changes have been made to support different level entries having different unit load clearances. MhUnitLoadSpawner can pass in different clearance specs for each level entry instead of always using the same clearance spec from the configuration.
public class MhEntryLayoutEnv { New: public MhEngineEntry entry; } public class MhUnitLoadSpawner extends MhStorageSpawner { Old: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, str unitLoadKey, LayerSet rootParentClassification, MhEntryLayoutEnv env) { ... for (MhClearanceSpecBehavior behavior in collection.behaviors) { MhStorageClearanceCollisionFetchEnv clearanceEnv(..., clearanceSpec=config.?clearanceSpec); behavior.appendCollisionPrimitives(loadShape, clearanceEnv, set.subPrims); } } New: extend public void appendAdditionalConstructionCollision(MhEngineConstructionEntry entry, str unitLoadKey, LayerSet rootParentClassification, MhEntryLayoutEnv env) { ... MhClearanceSpec spec; if (?MhEngineConstructionEntry parent = env.entry) { if (?MhClearanceSpec cSpec = parent.getAdditionalShapeArg(cMhClearanceSpecPK)) { spec = cSpec; } } if (!spec) spec = config.?clearanceSpec; for (MhClearanceSpecBehavior behavior in collection.behaviors) { MhStorageClearanceCollisionFetchEnv clearanceEnv(..., clearanceSpec=spec); behavior.appendCollisionPrimitives(loadShape, clearanceEnv, set.subPrims); } } }
We have added further control over the number of unit loads populated during construction.
MhUnitLoadConstructionalPopulateFunction class (unitLoadConstructionalPopulate) had the argument loadCount, which was passed to MhConstructionUnitLoadArrangement to populate unit loads perpendicular to the populate vector. It has been renamed to repeatCount. Additionally, a new argument ulCount has been added to represent the number of unit loads to populate in the direction of population. If left as null, the previous behavior will be used which is to populate as many loads as possible. Similarly the MhConstructionUnitLoadArrangement class now holds fields repeatCount and ulCount in place of the previous field count.
Effectively, previous specification of loadCount will now need to specify repeatCount instead. Make sure to search for loadCount in your extension as engine function execute arguments do not throw compile errors for incorrect arguments.
// cm/abstract/materialHandling/storage/engine/mhUnitLoadConstructionalPopulateFunction.cm public class MhUnitLoadConstructionalPopulateFunction extends MhSystemEngineFunction { Old: public Int loadCount; New: public Int repeatCount; //replaces loadCount New: public int ulCount; } // cm/abstract/materialHandling/storage/mhUnitLoadArrangement.cm public class MhConstructionUnitLoadArrangement extends MhUnitLoadArrangement { Old: public int count; New: public int repeatCount; //replaces count New: public int ulCount; Old: public constructor(MhPopulator populator, MhEngineEntry defaultLoadEntry, int count=1, vector direction=(0, 1, 0), bool spreadEvenly=true) { } New: public constructor(MhPopulator populator, MhEngineEntry defaultLoadEntry, int ulCount=0, int repeatCount=1, vector direction=(0, 1, 0), bool spreadEvenly=true) { } }
The repeatCount and ulCount fields for MhUnitLoadConstructionalPopulateFunction are typically passed in from MhLevelConstructionalPopulateFunction.levelChildConstruction(MhEngineConstructionEntry entry) where it passes the values from entry.constructionInfo. Additionally in MhEngineLevelConstructionEntryInfo, the field noOfUnitLoads was renamed to noOfUnitLoadsX and both noOfUnitLoadsX and noOfUnitLoadsY fields are now boxed Int types instead of primitive int.
// cm/abstract/materialHandling/storage/engine/mhLevelConstructionalPopulateFunction.cm public class MhLevelConstructionalPopulateFunction extends MhSystemEngineFunction { /** * levelChildContruction */ extend public void levelChildConstruction(MhEngineConstructionEntry entry) { ... ?MhEngineLevelConstructionEntryInfo info = entry.constructionInfo; Int ulXCount = info.?noOfUnitLoadsX; Int ulYCount = info.?noOfUnitLoadsY; if (!ulYCount) ulYCount = configuration.unitLoadCountY(); engine.exec("unitLoadConstruction", ..., ulCount=ulXCount, repeatCount=ulYCount, ...); ... } } public class MhEngineLevelConstructionEntryInfo extends MhEngineConstructionEntryInfo { Old: public int noOfUnitLoads; New: public Int noOfUnitLoadsX; Old: public int noOfUnitLoadsY; New: public Int noOfUnitLoadsY; }
Some method and argument names in MhUnitLoadEnsureClearanceFunction2 were updated to more accurately represent that the function operates on populated child entries rather than the imported entries.
// cm/abstract/materialHandling/storage/engine/mhUnitLoadEnsureClearanceFunction.cm public class MhUnitLoadEnsureClearanceFunction2 extends MhSystemEngineFunction { Old: extend public MhUnitLoadArrangement unitLoadArrangement(MhEngineEntry[] imported, MhPopulator populator) New: extend public MhUnitLoadArrangement unitLoadArrangement(MhEngineEntry[] children, MhPopulator populator) Old: extend public MhEngineEntry loadEntryToKeep(MhEngineEntry[] imported) New: extend public MhEngineEntry loadEntryToKeep(MhEngineEntry[] children) Old: extend public bool spreadUnitLoad(MhEngineEntry[] imported) New: extend public bool spreadUnitLoad(MhEngineEntry[] children) Old: extend public MhEngineEntry[] importedChildren(MhEngineEntry level) New: extend public MhEngineEntry[] childEntries(MhEngineEntry level) }
From the MhLevelArrangeFunction2, the field arrangedByOtherBay was removed as it was not used.
// cm/abstract/materialHandling/storage/engine/mhLevelArrangeFunction.cm public class MhLevelArrangeFunction2 extends MhSystemEngineFunction { Removed: public Bool arrangedByOtherBay; }
For doing config comparisons between two snappers, a new strict argument was introduced where it allows a new comparison logic when strict=false. When strict=false, classifications are compared by doing a LayerSet.eval() where one symbol match is required instead of a total match between layers.
// cm/abstract/materialHandling/storage/behavior/mhConfigBehaviors.cm Old: public bool basicConfigEq(MhSnapper a, MhSnapper b, SnapperFilter childFilter=null) New: public bool basicConfigEq(MhSnapper a, MhSnapper b, bool strict=true, SnapperFilter childFilter=null) Old: public bool basicConfigNoChildrenEq(MhSnapper a, MhSnapper b) New: public bool basicConfigNoChildrenEq(MhSnapper a, MhSnapper b, bool strict) Old: public bool basicConfigNoChildrenNoShapeEq(MhSnapper a, MhSnapper b) New: public bool basicConfigNoChildrenNoShapeEq(MhSnapper a, MhSnapper b, bool strict=true)
Undo operation class MhStorageEditorConfigPropModifyUndoOp now stores the Guid to the config instead of the config object itself.
public class MhStorageEditorConfigPropModifyUndoOp extends MhStorageEditorUndoOp { Removed: public MhStorageEditorConfiguration config : copy=reference; New: public Guid configGid; New: public constructor(MhStorageEditorDialog dialog, Guid configGid, str propKey, Object propVal) { }
Removed classes MhStorageEditorDimensionToolbarModelItem and MhBayEditorElevArrowToolbarModelItem. Both functionalities are now implemented in MhStorageEditorConfiguration. Instead of separate toolbar items, they have been combined into one visibility toolbar item.
Removed: public class MhStorageEditorDimensionToolbarModelItem extends ToolbarModelItem { Removed: public class MhBayEditorElevArrowToolbarModelItem extends ToolbarModelItem { Removed: public const str cMhArrowVisibilityStateKey = "arrowVisibilityState"; public class MhStorageEditorConfiguration extends MhSystemConfiguration { New: /** * Allow dimension visibility toggle? */ extend public bool allowDimensionVisibilityToggle(View view) { return false; } New: /** * Allow arrow visibility toggle? */ extend public bool allowArrowVisibilityToggle(View view) { return false; } New: /** * Dimension visibility toggle changed. */ extend public void dimensionVisibilityToggleChanged(bool state, View view) { if (Space space = view.?space) { mhStorageEditorDimensionTogglePutCached(space, value=state); for (s in space.snappers) mhUpdateEditorDimensionVisibility(s); } } New: /** * Arrow visibility toggle changed. */ extend public void arrowVisibilityToggleChanged(bool state, View view) { if (Space space = view.?space) { mhStorageEditorArrowTogglePutCached(space, value=state); insertArrowVessel(space); } } New: /** * Update snappers visibility. */ extend public void updateSnappersVisibility() { for (s in space.snappers) mhUpdateEditorDimensionVisibility(s); insertArrowVessel(space); } }
The dimensions toggle is visible by default for bay editor. The arrow toggle is visible by default for frame editor, and bay editor's 3D view.

MhConfigNameDialog was a dialog specifically used to rename configurations. It was used together with the function str mhConfigMakeNameDialog(Window parent, str label, sizeI size=sizeI(400, 150), str initName=null, str headerName=null, pointI pos=(-1, -1)). Both the class and function have been removed.
Removed: public class MhConfigNameDialog extends NameDialog { Removed: public str mhConfigMakeNameDialog(Window parent, str label, sizeI size=sizeI(400, 150), str initName=null, str headerName=null, pointI pos=(-1, -1)) {
This functionality has been replaced with the class MhValidNameDialog which shares most of the functionality, but instead takes in a sequence of str that is considered invalid. Replace calls of the function with the following code instead:
Old: str name = mhConfigMakeNameDialog(this, "Name", initName=config.label); New: var cont = mhSystemConfigurationManager.getContainer(config); str[] invalidNames(); for (otherConfig in cont.configs) { if (otherConfig == config) continue; invalidNames << otherConfig.label; } str name = mhMakeValidNameDialog(this, $name, initName=config.label, invalidNames=invalidNames);
This class had two methods void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch) and void switchChildren(Snapper oldSnapper, Snapper newSnapper) which have been removed and replaced with void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch=false).
This change is to combine the two methods into one. You can view the 16.5 Major migration guide section "Changes to spread tools switchChildren logic" for more comprehensive information regarding the usage of switchChildren.
public class MhSnapperApplyAnimation extends MhSnapperSpreadToolAnimation { Removed: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch) { Removed: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper) { Added: extend public void switchChildren(Snapper oldSnapper, Snapper newSnapper, bool forceSwitch=false) {
We have undertaken a series of enhancements to the Preconfigurator UI builder and preview system to improve overall performance, increase consistency during navigation, and introduce new features that support additional preview snappers as well as orthographic camera.
// cm/abstract/materialHandling/storage/mhStorageConfiguratorUIBuilder.cm Old: extend public Window propertiesWindow(Window w, MhSystemConfiguration config) New: extend public Window buildPropertiesWindow(Window w, MhSystemConfiguration config)
Additional methods has been introduce for each configurator item to allow preview to be display for each MhCompartmentType.
// Snappers preview for each item. public class MhStorageConfigurationItem extends MhSystemConfigurationItem /** * Snappers preview. */ extend public Snapper{} previewSnappers() { return null; } }
The MhConfigSnapperPreviewWindow class has been replaced with MhConfigViewContainer to better support model‑contextual elements and streamline the overall design.
Additionally, with the introduction of the new orthographic camera in the Preconfigurator, some methods have been deprecated as they are no longer relevant.
A feature from pre-configura has been removed from 17.0. Previously, the previewWindow functionality was intended to support custom 2D graphics—such as supplementary diagrams or detailed visual overlays—but this approach is now superseded by the enhanced capabilities of the orthographic camera system
// cm/abstract/materialHandling/storage/mhConfiguratorPreview3D.cm Old: public class MhConfigSnapperPreviewWindow extends SubWindow New: public class MhConfigViewContainer extends MultiViewContainer // cm/abstract/materialHandling/storage/mhStorageConfigurator.cm Removed: public CardWindow cardWindow; Removed: public MhConfigSnapperPreviewWindow snapperPreviewContainer; Removed: extend public bool changeSnapperPreviewParent(CardWindow cw, SubWindow sub, bool requiresRebuild=false) Removed: extend public bool changeSnapperPreviewParent(MhStorageConfigurationItemUIBuilder itemBuilder, SubWindow sub) // cm/abstract/materialHandling/storage/mhStorageConfiguratorUIBuilder.cm Removed: public MhConfigurationContentSubWindow main; Removed: extend public void zoomToMainComponent(MhStorageConfiguratorDialog dialog) Removed: extend public MhConfigurationContentSubWindow createContentSubWindow(Window w) Removed: extend public Window previewWindow(Window w, MhSystemConfiguration config) Removed: extend public Image previewButtonIcon(str key)

This class MhRowAnimationEngineGfxBehavior is now made abstract and can no longer be instantiated. The logics in this class has been moved out to MhRowAnimationEngineGfxBehaviorG1 which is the replacement of the original class. This was introduce as part of the new MhRowAnimationEngineGfxBehaviorG2 which mean to unified 2D and 3D generation.
Old: public class MhRowAnimationEngineGfxBehavior extends MhGenericEngineGfxBehavior New: public class MhRowAnimationEngineGfxBehavior extends MhGenericEngineGfxBehavior : abstract /** * Row Animation Graphics. * * It ultimately draws what is in the 'groupedEntries', little to no interpretation. * * G1: * creates the rects/boxes for graphics individually for 2D/3D methods. * supports cmSym * collision forces red coloring */ public class MhRowAnimationEngineGfxBehaviorG1 extends MhRowAnimationEngineGfxBehavior /** * Row Animation Graphics. * * It ultimately draws what is in the 'groupedEntries', little to no interpretation. * * Key differences from G1: * unified box creation for 2D and 3D * check for collision passed into the color method for more flexibility * * NOTE: no CmSym Support. */ public class MhRowAnimationEngineGfxBehaviorG2 extends MhGenericEngineGfxBehavior
Additional implementation has been added into the MhTunnelShape class that support fill cross and material for the tunnel graphic.
// cm/abstract/materialHandling/storage/racking/behavior/mhTunnelGfxBehavior.cm Old: extend public APath2D crossShape(box b) New: extend public AShape2D crossShape(MhSnapper owner, box b) Old: extend public APath2D tunnelSymPath(box b) New: extend public APath2D tunnelPath(MhSnapper owner, box b) Old: extend public GMaterial3D material3D() New: extend public Material3D material3D(MhSnapper owner)
The bool shouldReplace now is passed into the class contructor.
public class MhUnderUnitLoadCollisionAlternative extends MhFixedPointCollisionAlternative { Old: /** * Constructor */ public constructor(Snapper snapper, box unitLoadbound, point ip, orientation ir=(0deg, 0deg, 0deg), bool middle=true, double depth=0d, double dualPlacementOffset=0d, Double distanceToSnapper=null) { super(snapper, ip, ir, shouldReplace=shouldReplace, distanceToSnapper=distanceToSnapper); set*(this: middle, depth, dualPlacementOffset); this.unitLoadbound = unitLoadbound; } New: /** * Constructor */ public constructor(Snapper snapper, box unitLoadbound, point ip, orientation ir=(0deg, 0deg, 0deg), bool shouldReplace=false, bool middle=true, double depth=0d, double dualPlacementOffset=0d, Double distanceToSnapper=null) { super(snapper, ip, ir, shouldReplace=shouldReplace, distanceToSnapper=distanceToSnapper); set*(this: middle, depth, dualPlacementOffset); this.unitLoadbound = unitLoadbound; } }
We have removed fields and methods related to the collection of left and right strechers. These stretchers were only used in multibay/multiframe setups. They have been moved into MhMultiRowShape instead in the cm.abstract.materialHandling.storage.multi abstract extension dedicated to multibay/multiframe setups.
public class MhRowShape extends MhSnapperShape { Removed: public Connector[] leftStretchers : stream=null, copy=null; Removed: public Connector[] rightStretchers : stream=null, copy=null; Removed: /** * Init stretchers. */ extend public void initStretcherConnectors() { Removed: /** * Init left stretcher connectors. */ extend public void initLeftStretcherConnectors() { Removed: /** * Init right stretcher connectors. */ extend public void initRightStretcherConnectors() { Removed: /** * Clear stretcher connectors. */ extend public void clearStretcherConnectors() { Removed: /** * Update stretcher connectors. */ extend public void updateStretcherConnectors() { }
The following classes MhAisleRowEngineBehavior, MhFlueGapRowEngineBehavior, and MhBayRowEngineBehavior have had the same interfaces changed for the method shouldRunMultiChildrenLink().
public class MhAisleRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { } public class MhFlueGapRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { } public class MhBayRowEngineBehavior extends MhEngineBehavior { Old: extend public bool shouldRunMultiChildrenLink() { New: extend public bool shouldRunMultiChildrenLink(MhSnapper snapper) { }
The global constant sFrameAccessory has been moved from package cm.abstract.materialHandling.storage.racking to cm.abstract.materialHandling.storage.
Removed field str volumeId and replaced with a private field private symbol volumeId. Replaced methods str volId=(str id) with symbol volId=(symbol id) and str volId() with symbol volId(). Also renamed method AbsMezzDecking holeCandidate(AnimationMouseInfo mi) to `AbsMezzDecking getHoleCandidate(AnimationMouseInfo mi).
public class AbsMezzLevelRectInsertAnimation extends DrawShapeRectAnimation { Old: public str volumeId; New: private symbol volumeId; Old: extend public str volId=(str id) { New: extend public symbol volId=(symbol id) { Old: extend public str volId() { New: extend public symbol volId() { Old: extend public AbsMezzDecking holeCandidate(AnimationMouseInfo mi) { New: extend public AbsMezzDecking getHoleCandidate(AnimationMouseInfo mi) { }
Removed function void mezzInsertRails(APolyline2D poly, AbsMezzDecking{} deckings, Space space). Call function void mezzInsertRailGroup(APolyline2D poly, APolyline2D[] holes, AbsMezzDecking{} deckings, Space space) instead.
Updated function signature:
Old: public (AShape2D->AbsMezzDecking{}) mezzCombinedShapes(AbsMezzDecking{} deckings, bool ignoreTh=false) { New: public (AShape2D->AbsMezzDecking{}) mezzCombinedShapes(AbsMezzDecking{} deckings, bool ignoreTh=false, bool forStructure=false) {
The method Graph get2D() has been removed and replaced with post2D(AbsMezzGraphNode node). It was previously used to create a circle graph to represent a post, but we no longer draw the posts in abstract by default.
public class AbsMezzRail extends Model3DSnapper { Old: extend public Graph get2D() { New: extend public Graph post2D(AbsMezzGraphNode node) { }
The method Primitive3D rail3D(point2D from, point2D to) has been removed and replaced with Primitive3D rail3D(AbsMezzGraphNode n0, AbsMezzGraphNode n1). The method Primitive3D post3D() has been removed and replaced with post3D(AbsMezzGraphNode node)`.
public class AbsMezzRail extends Model3DSnapper { Old: extend public Primitive3D rail3D(point2D from, point2D to) { New: extend public Primitive3D rail3D(AbsMezzGraphNode n0, AbsMezzGraphNode n1) { Old: extend public Primitive3D post3D() { New: extend public Primitive3D post3D(AbsMezzGraphNode node) { }
The following has been removed:
Removed: public const bool dbg_absMezzRailGfx; public class AbsMezzGraphNodeRail3DVisitor extends AbsMezzGraphNodeVisitor { Removed: public AbsMezzGraphNode->(AbsMezzGraphNode-><double, point2D, point2D>[]) processed(); } public class AbsMezzGraphNodeRail2DVisitor extends AbsMezzGraphNodeVisitor { Removed: public AbsMezzGraphNode->(AbsMezzGraphNode-><double, point2D, point2D>[]) processed(); } public class AbsMezzGraphNodeVisitor extends Visitor { Removed: extend public Primitive3D dbgArrow3D(AbsMezzGraphNode from, AbsMezzGraphNode to, double height) { Removed: extend public Graph dbgArrow2D(AbsMezzGraphNode from, AbsMezzGraphNode to) { }
AbsMezzEdge has been made unstreamable as these are meant to be temporary objects representing two AbsMezzGraphNode objects.
Removed field SnapperRef{} rackingRefs in AbsMezzDecking. Use methods SnapperRef{} refs() and SnapperRef{} refs=(SnapperRef{} r) instead.
public class AbsMezzDecking extends DrawShapeSnapper { Removed: public SnapperRef{} rackingRefs; }
The structures snapper has been reworked. Instead of each structures snapper representing a group of lines, we now have a structure snapper for every line.
Removed the following classes:
Removed: public class AbsMezzPurlins extends AbsMezzStructures { Removed: public class AbsMezzColumns extends AbsMezzStructures { Removed: public class AbsMezzBeams extends AbsMezzStructures {
Renamed class AbsMezzStructures to AbsMezzStructure. Field line[] lines is now line ln.
Old: public class AbsMezzStructures extends ModelSnapper { public line[] lines; public constructor(line[] lines) { } } New: public class AbsMezzStructure extends ModelSnapper { public line ln; public constructor(line ln) { } }
Removed and replaced the below methods in AbsMezzDecking.
public class AbsMezzDecking extends DrawShapeSnapper { Old: extend public AbsMezzBeams createBeams() { New: extend public AbsMezzStructure createBeam() { Old: extend public AbsMezzPurlins createPurlins() { New: extend public AbsMezzStructure createSecondary() { Old: extend public AbsMezzColumns createColumns() { New: extend public AbsMezzStructure createColumn() { }
Renamed the following fields:
public class AbsMezzStructureLinesEnv extends PropObj { Old: public line[] purlinLines; New: public line[] secondaryLines; Old: public double purlinMaxDist = 200mm; New: public double secondaryMaxDist = 200mm; Old: public size2D purlinSize = (64mm, 120mm); New: public size2D secondarySize = (64mm, 120mm); }
Updated method signature
public class AbsMezzStructureLinesGenerator { Old: extend public void processGeneratedLinesToBeam(line[] beamLines, line[] purlinLines, line[] columnLines, size2D beamSize, double deckingThickness=0) { New: extend public void processGeneratedLinesToBeam(line[] beamLines, line[] secondaryLines, line[] columnLines, size2D beamSize, double deckingThickness=0) { }
electrical.cmThe old public office electrical framework was removed from cm.abstract.office.
If your package depends on these interfaces today, the expected migration path is to copy the removed
interfaces and any needed implementation from the old office electrical system into your own package, then update your package code to reference those local types instead of the deleted cm.abstract.office ones.
Removed: public class AbstractPointEOPosition extends EOPosition Removed: public class PointEOPosition extends AbstractPointEOPosition Removed: public class LineEOPosition extends EOPosition Removed: public class EORange extends EOPosition Removed: public class PanelFrameElectricals extends CorePropObj Removed: public class InsertElectricalObject extends ToolAnimation Removed: public class ElLinkSnap extends LinkSnap Removed: public class ElectricalConnection extends CorePropObj
PanelFrameThe old panel-frame electrical extension points are gone from cm.abstract.office.
Packages can no longer plug electrical behavior into PanelFrame through the removed ElectricalObject / EOPosition flow unless they recreate that API in their own package.
Removed: extend public PanelFrameElectricals electricalData() Removed: extend public EOPosition[] electricalPositions(ElectricalObject z) Removed: extend public bool apply(ElectricalObject z, EOPosition pos, ElectricalAction action) Removed: extend public bool remove(EOPosition pos, ElectricalAction action) Removed: extend public electricalAction allow(ElectricalObject z, EOPosition pos, bool replace, bool toogle)
PanelJunctionSnapperThe old junction routing hooks were also removed from the shared office package. Packages that still use the old electrical model should move these hooks into their own junction classes.
Removed: extend public PanelJunctionElectricalConnect[] electricalConnects(bool visual=false) Removed: extend public EORange[] routingPositions(ElectricalConnection z)
cm.abstract.office now imports cm.abstract.pathing, and the corresponding abstract extension adds cm.abstract.pathing as a package dependency.
This matters if you choose to adopt the new routed electrical model used by custom.fika, but it is not required just to preserve an older package-owned copy of the removed electrical interfaces.
Added/important usage: use cm.abstract.pathing;
ProdPartProdPart now distinguishes between base part options and all part options, where allPartOptions() includes custom inserted options.
The old spec-option invalidation flow has been broadened accordingly.
Added: extend public PartOption[] allPartOptions() Added: extend public void setPartOptions(PartOption[] options, bool invalidatePartOptions=true) Added: extend public PartOption[] rebuildPartOptions() Added: extend public PartOption[] insertCustomOptions(PartOption[] options) Old: extend public void invalidateSpecOptions(bool invalidateOptionPriceSum=true) New: extend public void invalidatePartOptions(bool invalidateOptionPriceSum=true)
If you extend ProdPart and cache or mutate options, update your code to invalidate part options through invalidatePartOptions(...) and to choose between options() and allPartOptions() appropriately.
ProdPart custom option ordering interfacesProdPart now exposes explicit interfaces for tracking custom inserted options by anchor key.
These interfaces are important if you build, reorder, export, or inspect additional options.
Added: public str->CustomSpecOption anchorKeyToCustom Added: extend public CustomSpecOption[] customSpecOptions(PropObj s=null) Added: extend public str->CustomOptionSpecial getCustomOptionSpecials(PropObj s=null) Added: extend public void putFollowingCustom(str anchorKey, CustomSpecOption custom) Added: extend public CustomSpecOption getFollowingCustom(str anchorKey) Added: extend public CustomSpecOption getFollowingCustom(PartOptionItem anchor)
PartOptionPartOption is part of the 17.0 migration surface because inserted custom options now need a public insertion hook at the option-branch level.
Added: extend public void insertPartOptionItem(int index, PartOptionItem newOption)
If you implement a custom PartOption, add support for insertPartOptionItem(...) so ProdPart.insertCustomOptions(...) and related added-option flows can splice inserted options into the correct branch.
ProdPartOptionProdPartOption is the standard product-part implementation of the new PartOption insertion interface.
Added: public void insertPartOptionItem(int index, PartOptionItem newOption)
The default 17.0 product-part option flow now relies on ProdPartOption.insertPartOptionItem(...) when rebuilding allPartOptions() with inserted custom options.
PartOptionItemPartOptionItem is now an important part of the public migration surface because 17.0 uses option identity more broadly across query import, option-special lookup, and additional-option ordering.
Added/important usage: extend public str key() Added/important usage: extend public void setKey(str value) Added/important usage: extend public str generateKey() Added/important usage: extend public double upcharge(Part owner=null, Space space=null, bool translatePrice=false) Added/important usage: extend public int sequence() Added/important usage: extend public str getUserDefined(str type) Added/important usage: extend public void setUserDefined(str type, str contents)
Implementers should treat key() as the stable option identifier used by query rows, option-special storage, and imported/additional-option workflows.
SpecOptionSpecOption is now more directly part of the migration story because 17.0 relies on its stable key for option identity.
Added/important usage: public str uniqueKey Added/important usage: public str key() Added/important usage: public void setKey(str value) Added/important usage: public str groupCode Added/important usage: public str groupCode() Added/important usage: extend public str adjustmentKey() Added/important usage: public str specialsKey() Old: public constructor(str code, str description, str groupDescription=null, int level=1, bool exportable=true) New: public constructor(str code, str description, str groupCode=null, str groupDescription=null, int level=1, bool exportable=true) Old: public constructor(str code, str description, str groupDescription=null, int level=1, double price=0.0, bool exportable=true) New: public constructor(str code, str description, str groupCode=null, str groupDescription=null, int level=1, double price=0.0, bool exportable=true)
The practical effect is that option-special and query-row identity now follows SpecOption.key() rather than being built from the owning part special key. adjustmentKey() remains the adjustment-facing identity, while specialsKey() now follows the stable option key. groupCode is now first-class option data and is carried through option creation and import flows, rather than existing only as loosely attached metadata.
For custom option rows, this also replaces the older CustomSpecOption key-storage model. The old dedicated _specialsKey field on CustomSpecOption is no longer the source of truth; instead, the special's specialsKey() now maps to the generated option item's PartOptionItem.key() / SpecOption.uniqueKey.
OptionSpecialOptionSpecial now exposes setter interfaces for feature metadata used by the new option export and added-option flows.
Removed: public Part originalPart Removed: public str originalOptStr Removed: extend public Part originalPart=(Part value) Added/important usage: extend public str featureCode=(str value) Added/important usage: extend public str featureDescription=(str value)
CustomOptionSpecialCustomOptionSpecial now exposes explicit anchor and feature-metadata interfaces for 17.0 added-option ordering and export.
Added/important usage: public str anchorOptKey Added/important usage: extend public str anchorOptKey=(str value) Added/important usage: public str featureCode=(str value) Added/important usage: public str featureDescription=(str value)
anchorOptKey identifies which normal option or added option a custom option follows. The feature setters are used when custom options are exported back out as XML, OFDA, PMX, or SIF option data.
OptionSpecialHolderOption specials are no longer expected to live in PartSpecialHolder.specials() for 17.0-created data.
OptionSpecialHolder is the new public holder type for per-part option-special state.
Added: public class OptionSpecialHolder Added: extend public bool any() Added: extend public bool empty() Added: extend public void clear() Added: extend public void put(str key, OptionSpecial special) Added: extend public OptionSpecial get(str key) Added: extend public void remove(str key) Added: extend public void removeOptionSpecials() Added: extend public bool anySpecials() Added: extend public bool emptySpecials() Added: extend public void putAddition(CustomOptionSpecial custom) Added: extend public CustomOptionSpecial getAddition(str key) Added: extend public void removeAddition(str key) Added: extend public void removeAdditions() Added: extend public bool anyAdditions() Added: extend public bool emptyAdditions() Added: extend public CustomSpecOption[] getAdditionalOptions()
The holder is managed through a new shared function surface in cm.abstract.part.optionSpecialHolderFunctions.cm.
These names are useful search targets if your code still assumes all specials live under cSpecialHolderKey.
Added: public const str cOptionSpecialHolder = "optionSpecialHolder"; Added: public str optionSpecialHolderPropKey(Part part) Added: public OptionSpecialHolder initOptionSpecialHolder(PropObj s, Part part) Added: public OptionSpecialHolder getOptionSpecialHolder(PropObj s, Part part) Added: public void removeOptionSpecialHolder(PropObj s, Part part, bool invalidateWorldPrice) Added: public void putOptionSpecial(PropObj s, Part part, str key, OptionSpecial special, bool invalidateWorldPrice) Added: public OptionSpecial getOptionSpecial(PropObj s, Part part, str key) Added: public void removeOptionSpecial(PropObj s, Part part, str key, bool invalidateWorldPrice) Added: public void removeAllOptionSpecials(PropObj s, Part part, bool invalidateWorldPrice) Added: public void putAdditionalOption(PropObj s, Part part, CustomOptionSpecial special, bool invalidateWorldPrice) Added: public CustomOptionSpecial getAdditionalOptionSpecial(PropObj s, Part part, str key) Added: public CustomOptionSpecial getAdditionalOptionSpecial(PropObj s, Part part, PartOptionItem opt) Added: public void removeAdditionalOption(PropObj s, Part part, str key, bool invalidateWorldPrice) Added: public void removeAdditionalOption(PropObj s, Part part, PartOptionItem opt, bool invalidateWorldPrice) Added: public void removeAdditionalOptions(PropObj s, Part part, bool invalidateWorldPrice)
ProdPart option-special and addition APIsProdPart now has a larger public API for option specials, additional options, and option customization state.
These APIs are used by the new product-part query import workflow.
Added: extend public OptionSpecialHolder initOptionSpecialHolder() Added: extend public OptionSpecialHolder optionSpecialHolder(PropObj s=null) Added: extend public OptionSpecial getOptSpecial(str key, PropObj s=null) Old: extend public void putOptSpecial(str key, OptionSpecial special, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void putOptSpecial(str key, OptionSpecial special, bool invalidateWorldPrice, PropObj s=null) Old: extend public void putOptSpecial(PartOptionItem opt, OptionSpecial special, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void putOptSpecial(PartOptionItem opt, OptionSpecial special, bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeOptSpecial(str optSpecialKey, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeOptSpecial(str optSpecialKey, bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeOptSpecial(PartOptionItem opt, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeOptSpecial(PartOptionItem opt, bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeOptSpecials(PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeOptSpecials(bool invalidateWorldPrice, PropObj s=null) Added: extend public bool isAdditionalOption(PartOptionItem opt, PropObj s=null) Added: extend public void putAdditionalOption(CustomOptionSpecial special, bool invalidateWorldPrice, PropObj s=null) Added: extend public CustomOptionSpecial getAdditionalOptionSpecial(str key, PropObj s=null) Added: extend public CustomOptionSpecial getAdditionalOptionSpecial(PartOptionItem opt, PropObj s=null) Added: extend public void removeAdditionalOption(str key, bool invalidateWorldPrice, PropObj s=null) Added: extend public void removeAdditionalOption(PartOptionItem opt, bool invalidateWorldPrice, PropObj s=null) Added: extend public void removeAdditionalOptions(bool invalidateWorldPrice, PropObj s=null) Added: extend public customizationType customizationStatus(PartOptionItem option, PropObj s=null) Added: extend public symbol{} customizationStates(PartOptionItem option, PropObj s=null)
ProdPartSpecialMigratorProdPartSpecialMigrator is the new public migration helper for moving pre-17.0 product-part option specials out of PartSpecialHolder and into the new per-part holder structure.
Added: public class ProdPartSpecialMigrator extends PartSpecialMigrator Added: public void attemptMigrateSpecials(Part part, PropObj s) Added: extend public void attemptMigrateOptionSpecials(Part part, PropObj s) Added: extend public void attemptMigrateCustomOptionsSpecials(Part part, PropObj s) Added: extend public str oldOptSpecialsKey(Part part, PartOptionItem opt) Added: extend public str oldOptSpecialsKey(PartOptionItem opt)
Override oldOptSpecialsKey(...) if your package previously saved option specials with package-specific keys.
ProdPartCreatorProdPartCreator is the product-part-specific creator base used for 17.0 additions and overrides.
It extends PartCreator, generates a ProdPart, replays options through processOptions(...), and includes option codes in the generated flattenable key.
Added: public class ProdPartCreator extends PartCreator Added: public PartOption[] options Added: public constructor(PartData data, PartOption[] options=null) Added: public Part generatePart(PropObj owner=null) Added: public str generateFlattenableKey(Part part) Added: extend public void processOptions(ProdPart part)
ProdPartAdditionProdPartAddition is the new creator type for additional product parts.
Instead of applying its PartOption[] directly to the generated part, it converts them into CustomOptionSpecials and stores them as additional options in OptionSpecialHolder.
Added: public class ProdPartAddition extends ProdPartCreator Added: public void processOptions(ProdPart part) Added: extend public CustomOptionSpecial createCustomOptionSpecial(Part part, PartOptionItem opt)
ProdPartOverrideProdPartOverride is the new creator type for product-part overrides.
It generates a ProdPart and applies the stored PartOption[] directly with setPartOptions(...).
Added: public class ProdPartOverride extends ProdPartCreator Added: public void processOptions(ProdPart part)
These types participate in the core Snapper.fetchParts() customization flow through the owner-level PartCreator APIs documented in cm.core.part.
ProdPart changed signatures and moved flowsSeveral existing ProdPart interfaces changed shape in 17.0.
Old: public Double upcharge() New: public Double upcharge(bool includeChildren=false, Space space=null, bool translatePrice=true, bool invalidate=false) Old: extend public CustomSpecOption[] customSpecOptions() New: extend public CustomSpecOption[] customSpecOptions(PropObj s=null) Old: public bool containsAnySpecials(PropObj s=null) New: public bool containsAnySpecials(bool ignoreStandardSpecial=false, PropObj s=null) Old: public Double upcharge() New: public Double upcharge(bool includeChildren=false, Space space=null, bool translatePrice=true, bool invalidate=false) Old: extend public CustomSpecOption[] customSpecOptions() New: extend public CustomSpecOption[] customSpecOptions(PropObj s=null) Old: extend public void initializePartFromItemData(ItemData item, ProjectInformation projectInfo) New: public void initializePartFromItemData(ItemData item, ProjectInformation projectInfo)
The important runtime consequence is that product-part option upcharge is now expected to translate to the current CET currency by default through the shared translatePrice=true flow. If your manufacturer package overrides option pricing, verify that option rows and option-derived totals are still correct and are not translated twice.
The following shared import/export hooks are also now part of the effective ProdPart surface because 17.0 routes more behavior through common Part logic:
Added/changed usage: public void importOFDASpecItemTag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOptionTag(XmlTag tag, OFDAHeaderContent header=null)
ProdPart option identityOption special keys now use the option's own stable key instead of composing a key from the part special key.
Old: extend public str optSpecialKey(PartOptionItem opt) { return specialsKey() # opt.specialsKey(); } New: extend public str optSpecialKey(PartOptionItem opt) { return opt.?key(); }
If you persisted or compared option special keys manually, update that logic to use the option key directly.
ProdPart public changesAdded: public loadFailedResult loadFailed(ObjectFormatter formatter, LoadFailure failure) Added: public PartInfoTree[] infoTrees() Old: public void removeAllSpecials(PropObj s=null, bool invalidateWorldPrice=true) New: public void removeAllSpecials(bool invalidateWorldPrice, PropObj s=null) Added: public void removeOverrideCreator(bool invalidateWorldPrice, PropObj s=null) Added: public void removeAdditionCreator(bool invalidateWorldPrice, PropObj s=null) Added: public loadFailedResult loadFailed(ObjectFormatter formatter, LoadFailure failure) Added: public PartInfoTree[] infoTrees() Added: public void removeAllSpecials(PropObj s=null, bool invalidateWorldPrice=true) Added: public void removeOverrideCreator(PropObj s=null, bool invalidateWorldPrice=true) Added: public void removeAdditionCreator(PropObj s=null, bool invalidateWorldPrice=true)
ProdPartSIFImporterEnvProdPartSIFImporterEnv is the new SIF environment for importing ProdPart objects and their options.
Added: public class ProdPartSIFImporterEnv extends PartSIFImporterEnv Added: public PartOptionItem currentOpt Added: public PartOptionItem[] currentPartOpts Added: public void beginImport(Url file, Part[] part) Added: public void importKeyValue(str key, str value, Part[] part) Added: public Part createPart(Object data) Added: extend public PartOptionItem createOption(Object data, Part[] part) Added: extend public str headerOptionKey() Added: public void flushPart(Part[] part) Added: extend public void flushOption(Part[] part)
The default SIF option mapping uses ON, OD, O1/OL, OP, and OG to build SpecOption objects before appending them to the imported ProdPart.
In that mapping, OP now imports into SpecOption.groupCode and OG imports into SpecOption.groupDescription.
ProdPartPMXImporterEnvProdPartPMXImporterEnv is the new PMX environment for creating ProdPart instances during PMX import.
Added: public class ProdPartPMXImporterEnv extends PartPMXImporterEnv Added: public Part createPart(Object data)
This environment mainly switches the created part type to ProdPart. The PMX population hook itself remains initializePartFromItemData(...), which was already part of the older PMX path.
ProdPartOFDAImporterEnvProdPartOFDAImporterEnv is the new OFDA XML environment for creating ProdPart instances.
Added: public class ProdPartOFDAImporterEnv extends PartOFDAImporterEnv Added: public Part createPart(Object data)
ProdPart OFDA option import hooksThese ProdPart interfaces are now part of the 17.0 import surface for OFDA XML product-part import.
Added/changed usage: public void importOFDASpecItemTag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOptionTag(XmlTag tag, OFDAHeaderContent header=null)
importOptionTag(...) is the new shared hook for reading <Option> and <CustomOption> tags into SpecOption objects, including nested option tags.
Option special generation now uses the owning Part to convert the amount from calculation currency before saving the special.
This relies on the updated PartMakeSpecialDialog constructor from cm.core.part.query, so custom call sites should pass the owning part when constructing option special dialogs.
ProdPartQueryDialogBehavior now exposes import and option-drag extension points.
New: public SubWindow initImportWindow(QueryDialog dialog, Url url=null) New: public PartImporterEnv getImporterEnv(str suffix) New: public void beginGridDragAnimation(QueryDialog dialog, pointI dragPoint, Object dragValue) New: public range[] getValidDragRanges(QueryDialog dialog, Object dragValue)
The option-special dialog creation path was also updated to pass the owning part into OptionMakeSpecialDialog.
ProdPartQueryDialogDataEnv now includes public APIs for option filtering, option import, option overrides, and additional option specials.
Key additions include:
New: extend public bool acceptPartOpt(Part part, SpecOption opt) New: extend public PartOptionItem getOption(int row) New: extend public Part getOptionParentPart(int row) New: extend public void putOptionSpecial(int row, OptionSpecial special) New: public void putOverride(int row, Object data) New: extend public OptionSpecial createOptionSpecial(Part part, PartOptionItem option, bool priceReplace=true) New: public void putAddition(int row, Object data) New: extend public void putAdditionalOption(int row, PartOptionItem newOption) New: extend public void putAdditionalOptionSpecial(int row, CustomOptionSpecial special) New: extend public CustomOptionSpecial getAdditionalOptionSpecial(int row) New: public void removeAddition(int row) New: extend public void removeAdditionalOption(int row) New: extend public CustomOptionSpecial createCustomOptionSpecial(Part part, PartOptionItem option)
createOptionRowData also changed its row identifier source:
Old: str optRowID = part.optSpecialKey(option) New: str optRowID = option.key()
If you extend the product-part query data environment, review any assumptions about option row identifiers and option insertion/removal behavior.
The following new public classes were added for product-part option import support:
New: public class ProdPartQueryImportWindow extends QueryImportWindow New: public class QueryImportOptionTVI extends QueryImportTVI
These are used to display option items in the import tree and support dragging imported options into the query grid.
New: public customizationType customizationStatus() // on QueryOptionRowData New: extend public void putAddition(CustomOptionSpecial special) // on QueryOptionRowData New: extend public CustomOptionSpecial getAddition() // on QueryOptionRowData New: extend public void removeAddition() // on QueryOptionRowData New: extend public void putAddition(CustomOptionSpecial special) // on QueryProdPartRowData New: public int initialColumnWidth(str columnLabel) // on QueryProdPartGridBuilderEnv New: extend public Brush getStateBrush(customizationType status) // on QueryProdPartGridBuilderEnv
These query-side APIs rely on the option identity, option customization, and additional-option APIs added in cm.abstract.part.ProdPart.
cm.abstract.pmx now depends on cm.core.pmxThe abstract PMX package now imports the shared PMX core package and expects PMX model/database types to come from there.
Old: use cm.abstract.pmx; New: use cm.abstract.pmx; New: use cm.core.pmx;
In practice, packages that only need the order exporter can usually keep use cm.abstract.pmx;. Packages that instantiate PMX records or database objects directly should also import cm.core.pmx.
The following public PMX classes were removed from cm.abstract.pmx and recreated in cm.core.pmx.
Update imports and fully-qualified references accordingly.
Old: cm.abstract.pmx.AttributeData New: cm.core.pmx.AttributeData Old: cm.abstract.pmx.Database New: cm.core.pmx.Database Old: cm.abstract.pmx.ItemData New: cm.core.pmx.ItemData Old: cm.abstract.pmx.OptionData New: cm.core.pmx.OptionData Old: cm.abstract.pmx.PartDetails New: cm.core.pmx.PartDetails Old: cm.abstract.pmx.PictureData New: cm.core.pmx.PictureData Old: cm.abstract.pmx.ProjectInformation New: cm.core.pmx.ProjectInformation
The internal bridge/base helpers also moved with the same package rewrite:
Old: cm.abstract.pmx.DataDefinition New: cm.core.pmx.DataDefinition Old: cm.abstract.pmx.PMXNetObj New: cm.core.pmx.PMXNetObj
cm.core.part.importA few interfaces were removed from cm.abstract.pmx, but they were not moved to cm.core.pmx.
They now belong to the part-import layer.
Old: cm.abstract.pmx.ItemDataConverter New: cm.core.part.import.ItemDataConverter Old: cm.abstract.pmx.registerItemDataConverter(...) New: cm.core.part.import.registerItemDataConverter(...) Old: cm.abstract.pmx.getConverter(...) New: cm.core.part.import.getConverter(...) Old: cm.abstract.pmx.PMXFilePartSource New: cm.core.part.import.PMXFilePartSource
Use cm.core.part.import when your package creates parts from PMX files or registers PMX ItemData converters.
PMXExporter writes the new PMX project metadata recordsPMXExporter now creates and inserts additional PMX records before item export.
If you extend PMXExporter, override its insert flow, or substitute your own PMX Database, account for the extra records and database calls.
Added effective usage in PMXExporter: ShipToInformation shipToInfo Added effective usage in PMXExporter: SoldToInformation soldToInfo Added effective usage in PMXExporter: NotesTable notesInfo Added effective usage in PMXExporter: OrderInformation[] orderInfos Added effective database calls: Database.insertNotesInfo(...) Database.insertShipToInformation(...) Database.insertSoldToInformation(...) Database.insertOrderInformation(...)
ItemData.optionsSeq was removed in favor of optionsThis is a broader API migration than the single exporter line change.
In 16.5, cm.abstract.pmx.ItemData stored PMX options in optionsSeq, ItemData.operator<<(OptionData) appended into optionsSeq, and exporter code iterated item.optionsSeq.
In 17.0, the shared cm.core.pmx.ItemData removes optionsSeq and keeps one option sequence named options.
Update any custom code that builds, mutates, exports, or imports PMX ItemData objects accordingly.
Old field: public OptionData[] optionsSeq New field: public OptionData[] options Old exporter usage: insertOptionData(itemID, item.optionsSeq) New exporter usage: insertOptionData(itemID, item.options)
This also affects code on both sides of PMX conversion:
Export generation in 17.0 writes option rows into itemData.options Import initialization in 17.0 reads option rows from item.options
If your package previously touched optionsSeq directly, switch all such reads and writes to options. If you overrode PMX item-generation or PMX part-initialization flows, verify that they no longer assume two different option collections exist.
ProjectInformationDialogProjectInformationDialog now treats drop-down values as key-and-label pairs instead of only returning a single string value.
Changed behavior/important usage: drop-down field values can now be returned as `<str, str>` key/label pairs Changed behavior/important usage: drop-down field restoration now looks up the saved label first, then falls back to the saved key
If you extend the dialog or depend on the stored value format for project-information drop-downs, review any code that assumes those controls round-trip only a single string.
createElevationSpace() now requires setting setNewGuid explicitly.
Old: extend public ElevSpaceG2 createElevationSpace() { New: extend public ElevSpaceG2 createElevationSpace(bool setNewGuid) {
Removed: public bool dbg_verboseProfilerDump = false; Removed: public void repairBlocks() {
Snappercm.core.Snapper now exposes the public load and fetch hooks that drive 17.0 part-special migration.
Added/important usage: public void loaded1(ObjectFormatter formatter, LoadFailInfo failInfo) Added/important usage: extend public void initPartSpecialMigrator(ObjectFormatter formatter) Added/important usage: extend public PartSpecialMigrator getPartSpecialMigrator(bool init=false) Added/important usage: extend public void migratePartSpecials(Part[] parts)
These names are useful search targets if you are debugging why a package-specific PartSpecialMigrator is or is not being discovered during drawing load.
Removed enum that is not used anywhere in the base repository.
Removed: public enum featureDetail : field access {
The visibility of dropDownImage field has been altered, you will need to use setDropDownImage to change the image.
Old: public Image dropDownImage : copy=reference; New: private Image dropDownImage : copy=reference, public readable; New: final public void setDropDownImage(Image i)
This class has been removed. You can replace it with FaceliftToggle
Old: public class InfoTipToggle extends CheckBox { New: public class FaceliftToggle extends CheckBox {
The following interfaces in SelectLasso2DAnimation have been changed:
Old: extend public bool isTarget(Snapper s, APolyline2D pline=null, bool quickElim=true) { New: extend public bool isTarget(Snapper s, APolyline2D pline) {
The following interfaces in SelectLasso2DAnimation have been removed:
Removed: final public void appendTargets(line2D l) { Removed: extend public bool contained(Snapper s, APolyline2D pline=null) { Removed: extend public bool ridiculous(rect sb, APolyline2D pline) { Removed: extend public bool plausible(rect sb, APolyline2D pline) { Removed: extend public GeometricFinder[] getFinders(APolyline2D pline) { Removed: extend public int snapperBoundInPolyline(Snapper s, APolyline2D pline) { Removed: extend public int graphInPolyline(GraphPrimitive gp, APolyline2D pline, line2D e, bool oneUncontained, bool seen) {
Deprecated functions have been removed.
Old: public void removeOldCetQLParamsFromSnapper(Snapper z) : deprecated { New: public void removeCetQLParamsFromSnapper(Snapper z) {
TaggableSnapperUser-modified state is no longer passed through the setItemTagInfo(..) signature. Update any overrides to use the new three-parameter form.
Old: extend public void setItemTagInfo(Part part, str key, ItemTagInfo info, bool userMod=false) New: extend public void setItemTagInfo(Part part, str key, ItemTagInfo info)
DVariableDVariable now accepts stored drop-down values as key-and-label pairs in addition to plain strings.
Added/important usage: `value` can now be a `?<str, str>` pair, where the stored DVariable value becomes the first item
This supports the 17.0 project-information drop-down fix, where UI code can preserve the display label for output while still storing the underlying key.
PackageCountColumnPackage count is now exposed as a shared core part column instead of only through the older data-symbol-specific column package.
Added: public const PackageCountColumn packageCountColumn() Added: public class PackageCountColumn extends BasicPartColumn
PackageCountColumn reads Part.pkgCount() and is now part of the core part-column registration path.
The compatibility interfaces added for 16.5 were removed in 17.0.
Old: extend public str displayName() New: extend public str displayName(bool showSummationLabel=true) Old: extend public void setCells(GridWindow gw, int row, int last, Space space) New: extend public void setCells(GridWindow gw, int row, int last, Space space, bool showSummationLabel=true)
If you override or extend these methods on custom GlobalPartAdjustmentSum classes, update your signatures to include the new showSummationLabel argument and use it when rendering the sum line label.
The following overloads for getSummaryLines were changed in 17.0 (cm/core/calc/globalPartAdjustment.cm).
Most call sites can continue to omit the new argument because it has a default value. If your code depended on the removed two-argument overloads as distinct interfaces, update it to call the three-argument version instead.
Old: public SummaryLine[] getSummaryLines(Space space, SummaryPriceInfo pInfo) New: public SummaryLine[] getSummaryLines(Space space, SummaryPriceInfo pInfo, bool showSummationLabels=true) Old: public SummaryLine[] getSummaryLines(AdditionalGlobalPartAdjustment[] items, SummaryPriceInfo pInfo) New: public SummaryLine[] getSummaryLines(AdditionalGlobalPartAdjustment[] items, SummaryPriceInfo pInfo, bool showSummationLabels=true)
isLoadable() now passes the CollFile as an argument. Useful to check if the file is loadable using CollFile's data.
Old: extend public bool isLoadable() { New: extend public bool isLoadable(CollFileG3 file) {
Function names have been retained, no changes beyond the following are needed.
Old: use cm.core.nwd; New: use cm.core.sceneExport.nwd;
Old: use cm.core.nwd.export; New: use cm.core.sceneExport.nwd.export;
PartIf your custom Part subclass overrides Ind. Tag identity or ItemTagInfo key generation, move that logic to the new override points below.
Old: extend public str itemTagKey() New: final public str itemTagKey() New override point: extend public str sourceId() Old: extend public str itemTagInfoKey() New: final public str itemTagInfoKey() New override point: extend public str defaultItemTagInfoKey() Old: extend public ItemTagInfo setItemTagInfo(str text) New: extend public ItemTagInfo setUserItemTagInfo(str text)
PartProxyPartProxy now has a matching hook for the final flattenable-key phase.
Added: extend public void finalizeFlattenbleKey(Part part)
This gives proxy-based part implementations a dedicated extension point for adjusting the part after the flattenable key has been finalized.
The constructor argument previously named partSourceId is now sourceId in core Part and in downstream part subclasses that forward into it.
Old: public constructor(PartData data, str partSourceId=null) New: public constructor(PartData data, str sourceId=null) Old: public constructor(Snapper snapper, str articleCode, str description, double basePrice, Double optionPriceSum, ..., str partSourceId=null) New: public constructor(Snapper snapper, str articleCode, str description, double basePrice, Double optionPriceSum, ..., str sourceId=null) Old: public constructor(Snapper snapper, str articleCode, str description, double listPrice, ..., str partSourceId=null) New: public constructor(Snapper snapper, str articleCode, str description, double listPrice, ..., str sourceId=null) Added: extend public void setSourceId(str id) Added: extend public str sourceId() Added: extend public str{} sourceIds()
Part customization APIsPart now exposes public APIs for additions, overrides, and overall customization state.
These are used by the new query import workflow.
Added: extend public bool isOverride(PropObj s=null) Added: extend public void putOverrideCreator(PartCreator overrideSpecial, bool invalidateWorldPrice, PropObj s=null) Added: extend public PartCreator getOverrideCreator(PropObj s=null) Added: extend public void removeOverrideCreator(bool invalidateWorldPrice, PropObj s=null) Added: extend public bool isAddition(PropObj s=null) Added: extend public void putAdditionCreator(PartCreator additionSpecial, bool invalidateWorldPrice, PropObj s=null) Added: extend public PartCreator getAdditionCreator(PropObj s=null) Added: extend public void removeAdditionCreator(bool invalidateWorldPrice, PropObj s=null) Added: extend public customizationType customizationStatus(PropObj s=null) Added: extend public symbol{} customizationStates(PropObj s=null)
Part special and standard-special APIsPart now separates user-visible specials from internal standard specials, and several special-management signatures now require the invalidateWorldPrice argument before PropObj s.
Old: extend public bool containsSpecial(PropObj s=null) New: extend public bool containsSpecial(bool ignoreStandardSpecial=false, PropObj s=null) Old: extend public bool containsAnySpecials(PropObj s=null) New: extend public bool containsAnySpecials(bool ignoreStandardSpecial=false, PropObj s=null) Old: extend public PartSpecial getSpecial(PropObj s=null) New: extend public PartSpecial getSpecial(bool ignoreStandardSpecial=false, PropObj s=null) Old: extend public PartSpecial getSpecial(str id, PropObj s=null) New: extend public PartSpecial getSpecial(str id, bool ignoreStandardSpecial=false, PropObj s=null) Old: extend public void putSpecial(PartSpecial special, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void putSpecial(PartSpecial special, bool invalidateWorldPrice, PropObj s=null) Old: extend public void putSpecial(str id, PartSpecial special, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void putSpecial(str id, PartSpecial special, bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeSpecial(str id, PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeSpecial(str id, bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeSpecial(PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeSpecial(bool invalidateWorldPrice, PropObj s=null) Old: extend public void removeAllSpecials(PropObj s=null, bool invalidateWorldPrice=true) New: extend public void removeAllSpecials(bool invalidateWorldPrice, PropObj s=null) Added: extend public PartSpecial getStandardSpecial(PropObj s=null) Added: extend public PartSpecial getStandardSpecial(str id, PropObj s=null) Added: extend public void putStandardSpecial(PartSpecial special, bool invalidateWorldPrice, PropObj s=null) Added: extend public void putStandardSpecial(str id, PartSpecial special, bool invalidateWorldPrice, PropObj s=null) Added: extend public void removeStandardSpecial(str id, bool invalidateWorldPrice, PropObj s=null) Added: extend public void removeStandardSpecial(bool invalidateWorldPrice, PropObj s=null)
Standard specials are a new lower-priority internal-special system. getSpecial(...) now falls back to getStandardSpecial(...) unless ignoreStandardSpecial=true is passed. Implementers should also expect standard specials to be treated differently from user specials in user-facing state such as highlighting and customization-status checks.
PartSpecialThe PartSpecial constructor is now more permissive and can be called without explicitly passing all fields.
Old: public constructor(str partNum, str descr, bool priceReplace, double amount) New: public constructor(str partNum=null, str descr=null, bool priceReplace=true, double amount=0)
PartSpecialHolderPartSpecialHolder remains part of the public cm.core.part surface, but in 17.0 it is more clearly the holder for part-level customization data.
If your code previously assumed option specials also lived here forever, that assumption is no longer valid.
Changed meaning: public class PartSpecialHolder Added: public str->PartSpecial standardSpecials() Added: public str->PartCreator overrides() Added: private PartCreator[] additions() Added: private str->int additionKeyToIdx() Added: extend public PartSpecial getStandardSpecial(str key) Added: extend public void putStandardSpecial(str key, PartSpecial special) Added: extend public void removeStandardSpecial(str key) Added: extend public PartCreator getOverrideCreator(str key) Added: extend public void putOverrideCreator(str key, PartCreator special) Added: extend public void removeOverrideCreator(str key) Added: extend public void removeAllOverrides() Added: extend public void putAdditionCreator(PartCreator custom) Added: extend public PartCreator getAdditionCreator(int index) Added: extend public PartCreator getAdditionCreator(str key) Added: extend public void removeAdditionCreator(int index) Added: extend public void removeAdditionCreator(str additionKey) Added: extend public void removeAllAdditions() Added: extend public Part[] getAdditionalParts(PropObj s) Added: extend public void appendAdditionKeyToIdx(str key) Added: extend public void rebuildAdditionKeyToIdx()
The shared helper functions in cm.core.part.partSpecialHolderFunctions.cm also changed shape in 17.0. These names are useful search targets if your code manages owner-level part specials directly.
Old: public void removeSpecialHolder(PropObj s, bool invalidateWorldPrice=true) New: public void removeSpecialHolder(PropObj s, bool invalidateWorldPrice) Old: public void putSpecial(PropObj s, str key, PartSpecial special, bool invalidateWorldPrice=true) New: public void putSpecial(PropObj s, str key, PartSpecial special, bool invalidateWorldPrice) Old: public void removeSpecial(PropObj s, str key, bool invalidateWorldPrice=true) New: public void removeSpecial(PropObj s, str key, bool invalidateWorldPrice) Old: public void removeAllSpecials(PropObj s, bool invalidateWorldPrice=true) New: public void removeAllSpecials(PropObj s, bool invalidateWorldPrice) Added: public bool containsStandardSpecial(PropObj s) Added: public PartSpecial getStandardSpecial(PropObj s, str key) Added: public void putStandardSpecial(PropObj s, str key, PartSpecial special, bool invalidateWorldPrice) Added: public void removeStandardSpecial(PropObj s, str key, bool invalidateWorldPrice) Added: public void removeAllStandardSpecials(PropObj s, bool invalidateWorldPrice) Added: public void putOverrideCreator(PropObj s, str key, PartCreator creator, bool invalidateWorldPrice) Added: public void removeOverrideCreator(PropObj s, str key, bool invalidateWorldPrice) Added: public void removeAllOverrides(PropObj s, bool invalidateWorldPrice) Added: public void putAdditionCreator(PropObj s, PartCreator custom, bool invalidateWorldPrice) Added: public void removeAdditionCreator(PropObj s, int index, bool invalidateWorldPrice) Added: public void removeAdditionCreator(PropObj s, str key, bool invalidateWorldPrice) Added: public void removeAllAdditions(PropObj s, bool invalidateWorldPrice)
PartSpecialMigratorPartSpecialMigrator is the new base migration helper for packages that need to remap old special keys to 17.0 sourceId()-based keys.
Added: public class PartSpecialMigrator Added: public bool pendingMigration Added: public str{} migrated Added: public Version version Added: extend public void migrateSpecials(Part[] parts, PropObj s) Added: extend public void beforeMigration(Part[] parts, PropObj s) Added: extend public void attemptMigrateSpecials(Part part, PropObj s) Added: extend public str oldPartSpecialsKey(Part part) Added: extend public void afterMigration(Part[] parts, PropObj s)
If your package used a non-default pre-17.0 part-special key, override oldPartSpecialsKey(...) in a subclass.
PartSpecialMigrator is discovered during loadThe migrator lookup happens from cm.core.Snapper, which is important if you are trying to wire a package-specific migrator into drawing load.
Important load APIs on Snapper: extend public void initPartSpecialMigrator(ObjectFormatter formatter) extend public PartSpecialMigrator getPartSpecialMigrator(bool init=false) extend public void migratePartSpecials(Part[] parts)
At a high level, Snapper.loaded1() calls initPartSpecialMigrator(formatter). If the snapper already has a non-empty PartSpecialHolder and the loaded package version is older than the current runtime, CET resolves this."partSpecialMigrator" through getPartSpecialMigrator(init=true), initializes it from the prop default if needed, and marks pendingMigration = true with the loaded package version. Later, migratePartSpecials(parts) checks that flag and calls migrateSpecials(parts, this).
That means a migrator is not found by package scanning or global registration. It is found by the snapper exposing a partSpecialMigrator prop/default that returns the right PartSpecialMigrator instance.
PartCreatorPartCreator is the new base customization object used for part additions and overrides.
It stores PartData, generates a stable creator key, lazily creates a Part, and can replay extended adjustments onto the generated part.
Added: public class PartCreator Added: public constructor(PartData data) Added: extend public str key() Added: extend public str generateKey() Added: extend public Part part(PropObj owner=null) Added: extend public void invalidate() Added: extend public Part generatePart(PropObj owner=null) Added: extend public str generateFlattenableKey(Part part) Added: extend public void setPartDataValues(Part part, PropObj owner=null) Added: extend public void applyExtendedAdjustments(Part part, Space space)
If your extension needs custom addition or override generation, subclass PartCreator instead of treating additions and overrides as raw Part instances.
Part metadata APIsCompany and catalog access are now part of the core Part API instead of only being available through higher-level part types.
Added: extend public str companyCode() Added: extend public str catalog() Added: extend public str catalogCode() Added: extend public double pkgCount() Added: extend public void generatePartDetails(PartDetails partDetails)
If you previously depended on AbsPart-specific metadata accessors, prefer the new core Part interfaces where possible. pkgCount() is now part of the shared core part surface and feeds both column/UI output and exported part details.
Part pricing APIsThe shared core pricing APIs now consistently honor CET currency translation in more places.
Changed behavior/important usage: extend public double basePrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: extend public double optionPriceSum(bool includeChildren=false, Space space=null, bool translatePrice=true, bool invalidate=false) Changed behavior/important usage: extend public Double upcharge(bool includeChildren=false, Space space=null, bool translatePrice=true, bool invalidate=false) Changed behavior/important usage: final public double listPrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: final public double validListPrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: extend public double totalBasePrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: extend public double totalOptionPriceSum(bool includeChildren=false, Space space=null, bool translatePrice=true, bool invalidate=false) Changed behavior/important usage: extend public double totalListPrice(bool includeChildren=false, Space space=null, bool translatePrice=true) Changed behavior/important usage: extend public double totalValidListPrice(bool includeChildren=false, Space space=null, bool translatePrice=true)
The signatures are mostly unchanged, but 17.0 changes the shared runtime behavior so these methods now translate prices to the current CET currency more consistently. If your package overrides pricing logic, verify that your override neither skips the new translation path nor translates prices twice.
PartDataPart data objects now expose package count as part of the shared core data contract.
Added: extend public double pkgCount() Added: extend public double setPkgCount(double value)
BasicPartDataBasicPartData now stores package count directly.
Added: public double _pkgCount = 1 Added: public double pkgCount() Added: public double setPkgCount(double value)
PartDetailsPMX/export part details now include package count.
Added: final public int packageCount() Added: final public int packageCount=(int value)
PartImporterPartImporter is the new abstract base class for file-based part importers.
Added: public class PartImporter : abstract Added: public PartImporterEnv env Added: public constructor() Added: public constructor auto() Added: extend public Part[] import(Url file, PartImporterEnv env=null) Added: extend public str{} supportedFileSuffixes() Added: extend public bool validFile(Url file)
PartImporterEnvPartImporterEnv is the new abstract environment type paired with PartImporter. Override it to control record import, part creation, and import lifecycle hooks.
Added: public class PartImporterEnv : abstract Added: public bool silent Added: public constructor auto() Added: public constructor() Added: extend public void beginImport(Url file, Part[] parts) Added: extend public void import(Object data, Part[] parts) Added: extend public Part createPart(Object data) Added: extend public void endImport(Url file, Part[] parts)
PartSIFImporter, PartPMXImporter, and PartOFDAImporterThese are the new concrete importers for the built-in file formats.
Added: public class PartSIFImporter extends PartImporter Added: public constructor(bool silent=false) Added: public constructor(PartSIFImporterEnv env) Added: public str{} supportedFileSuffixes() Added: public Part[] import(Url file, PartImporterEnv env=null) Added: public class PartPMXImporter extends PartImporter Added: public constructor(bool silent=false) Added: public constructor(PartPMXImporterEnv env) Added: public str{} supportedFileSuffixes() Added: public Part[] import(Url file, PartImporterEnv env=null) Added: public class PartOFDAImporter extends PartImporter Added: public OFDAHeaderContent header Added: public constructor(bool silent=false) Added: public constructor(PartOFDAImporterEnv env) Added: public str{} supportedFileSuffixes() Added: public Part[] import(Url file, PartImporterEnv env=null) Added: extend public void importHeader(XmlTag root) Added: extend public void importOrderLineItems(XmlTag root, Part[] data, PartImporterEnv env) Added: extend public XmlTag[] getOrderLineItemTags(XmlTag current)
PartSIFImporterEnv, PartPMXImporterEnv, and PartOFDAImporterEnvThese new environments implement the default import behavior for each file type.
Added: public class PartSIFImporterEnv extends PartImporterEnv Added: public Part currentPart Added: public void beginImport(Url file, Part[] parts) Added: public void import(Object data, Part[] parts) Added: extend public void importKeyValue(str key, str value, Part[] parts) Added: extend public void importPartTag(str key, str value) Added: public Part createPart(Object data) Added: public void endImport(Url file, Part[] parts) Added: extend public str headerPartKey() Added: extend public void flushPart(Part[] parts) Added: public class PartPMXImporterEnv extends PartImporterEnv Added: public Part currentPart Added: public void beginImport(Url file, Part[] parts) Added: public void import(Object data, Part[] parts) Added: public Part createPart(Object data) Added: public void endImport(Url file, Part[] parts) Added: extend public void flushPart(Part[] parts) Added: public class PartOFDAImporterEnv extends PartImporterEnv Added: public Part currentPart Added: public void beginImport(Url file, Part[] parts) Added: public void import(Object data, Part[] parts) Added: extend public void importOrderLineItemTag(XmlTag tag, Part[] parts, OFDAHeaderContent header=null) Added: public void endImport(Url file, Part[] part) Added: public Part createPart(Object data) Added: extend public void flushPart(Part[] parts)
Part OFDA import hooksThese are the new import-facing Part interfaces added in 17.0. They are the main Part APIs that packages need to override for the new OFDA XML importer.
Added: extend public void importOFDAOrderLineItemTag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOFDAOrderLineItemChildTag(XmlTag child, XmlTag oliTag, OFDAHeaderContent header=null) Added: extend public void importOFDATag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOFDAPriceTag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOFDASpecItemTag(XmlTag tag, OFDAHeaderContent header=null) Added: extend public void importOFDACatalogTag(XmlTag tag, OFDAHeaderContent header=null)
The PMX hook initializePartFromItemData(ItemData item, ProjectInformation projectInfo) is still used by the new importer package, but that interface was already part of the older PMX import path and is not new for 17.0.
QueryDialog now extends ResizableDialogWindow instead of DialogWindow.
The dialog also exposes a new right-side subwindow and resize-related API.
Old: public class QueryDialog extends DialogWindow New: public class QueryDialog extends ResizableDialogWindow New: public SubWindow rightWindow New: public event clientBoundChangedEvent New: public event resizeEndEvent New: public event resizingEvent New: extend public SubWindow rightWindow() New: public sizeI minimalSize() New: public sizeI maximalSize()
If you subclass QueryDialog or depend on its window lifecycle or layout behavior, verify that your code still works with a resizable frame and with the additional rightWindow region.
The constructor now requires the owning Part in addition to the original PartSpecial.
Old: public constructor(Window parent, PartSpecial original, str label=$specialsDialog) New: public constructor(Window parent, Part part, PartSpecial original, str label=$specialsDialog)
Update custom call sites to pass the owning part when opening the special dialog.
QueryControlWindow has a broader public interface for import support, state-driven button updates, and help UI.
Old: public QueryButton specialsButton New: public Button specialsButton Old: public QueryButton removeSpecialsButton New: public Button removeSpecialsButton Old: public QueryButton exportButton New: public Button exportButton New: public SelectButton importButton New: public event specialsBtnClicked New: public event removeSpecialsBtnClicked New: public event exportBtnClicked New: public event importBtnClicked New: public event helpBtnClicked New: public str getHelpText() New: extend public void setSpecialControlEnablement(bool enable=true, bool refresh=true) New: extend public void update(customizationType state)
If you customized the top control window, update any code that assumed the controls were QueryButtons and consider hooking the new events instead of direct button callbacks.
QuerySubWindow now inherits constructors from SubWindow and adds generic control and help-dialog hooks.
Old: public class QuerySubWindow extends SubWindow New: public class QuerySubWindow extends SubWindow : inherit constructors New: public Button helpButton New: public void querySubWindowControlEventCB(Control c) New: extend public void showHelpDialog() New: extend public str getHelpText() New: extend public void queryControlEventCB(Control c)
Use queryControlEventCB when building subwindows that route child control callbacks through the shared querySubWindowControlEventCB function. Override getHelpText() if your subwindow exposes the new help button.
The following new public classes were added for query import support:
New: public class QueryImportWindow extends QuerySubWindow New: public class QueryImportTreeView extends AxTreeView New: public class QueryImportTVI extends AxColumnsTVI New: public class QueryImportPartTVI extends QueryImportTVI New: public class QueryDragEventArgs New: public class QueryGridOptionDragAnimation extends QueryGridDragAnimation New on QueryImportWindow: public str getHelpText()
These provide extension points for file import UI, imported-part tree views, popup menu handling, help dialogs, and drag/drop behavior into the query grid.
QueryDialogBehavior now includes public extension points for import-window lifecycle and import/drag handling.
Key additions include:
New: extend public SubWindow initImportWindow(QueryDialog dialog, Url url=null) New: extend public void initImportWindowEvents(SubWindow importWindow) New: extend public SubWindow rightWindow(QueryDialog dialog) New: extend public void openImportWindow(QueryDialog dialog, bool showFilePrompt=true, Url file=null) New: extend public void closeImportWindow(QueryDialog dialog, bool dispose=false) New: extend public str[] importFileFilters() New: extend public <str, str>[] getImportFileLabelsAndSuffixes() New: extend public PartImporter getImporter(str suffix) New: extend public PartImporterEnv getImporterEnv(str suffix) New: extend public void updateGridDragAnimation(QueryDialog dialog, pointI dragPoint, Object dragValue) New: extend public void beginGridDragAnimation(QueryDialog dialog, pointI dragPoint, Object dragValue) New: extend public range[] getValidDragRanges(QueryDialog dialog, Object dragValue) New: extend public bool validateImportedData(Object data, QueryDialog dialog) New: extend public bool validateImportedPart(Part part, QueryDialog dialog) New: extend public bool validatePricing(Part part, QueryDialog dialog) New: extend public void cleanImportedData(Object data)
If you provide a custom QueryDialogBehavior, review these new hooks and add overrides where needed to support import sources, validation rules, or custom drag/drop behavior.
QueryRowData and QueryPartRowData now expose customization-state and creator-management helpers.
New: extend public customizationType customizationStatus() New on QueryPartRowData: extend public void putAddition(PartCreator creator) extend public PartCreator getAddition() extend public void removeAddition() extend public void putOverride(PartCreator creator) extend public PartCreator getOverride() extend public void removeOverride()
These APIs are used by the new import workflow and are backed by the stable identity and customization APIs added in cm.core.part.Part.
New: public void closeAllQueryDialogs() New: extend public Brush getStateBrush(customizationType status) New: public int initialColumnWidth(str columnLabel) New: public str toolTipText() // on EditGridCell New: public const color ultraLightGreenColor = color(160, 190, 160)
cm.core.pmxThe PMX core/runtime interfaces now live in a new package. Import it anywhere you construct PMX record objects, talk to the PMX database wrapper, or need PMX constants.
Added: package cm.core.pmx : 17.0.100; Added: use cm.core.pmx;
cm.abstract.pmxThese public types keep the same names but now come from cm.core.pmx.
Old: cm.abstract.pmx.AttributeData New: cm.core.pmx.AttributeData Old: cm.abstract.pmx.Database New: cm.core.pmx.Database Old: cm.abstract.pmx.ItemData New: cm.core.pmx.ItemData Old: cm.abstract.pmx.itemDataStatus New: cm.core.pmx.itemDataStatus Old: cm.abstract.pmx.OptionData New: cm.core.pmx.OptionData Old: cm.abstract.pmx.PartDetails New: cm.core.pmx.PartDetails Old: cm.abstract.pmx.PictureData New: cm.core.pmx.PictureData Old: cm.abstract.pmx.ProjectInformation New: cm.core.pmx.ProjectInformation
The following previously-abstract support types also moved into the new package:
Old: cm.abstract.pmx.DataDefinition New: cm.core.pmx.DataDefinition Old: cm.abstract.pmx.PMXNetObj New: cm.core.pmx.PMXNetObj
cm.core.pmx exposes additional PMX record objects that can now be created and inserted explicitly.
Added: public class NotesTable Added: public class OrderInformation Added: public class ShipToInformation Added: public class SoldToInformation
These are the types now used by cm.abstract.pmx.PMXExporter when exporting project-level PMX metadata.
Database has new schema-update and metadata insertion APIsIf you subclass, wrap, or call Database directly, update your code for the broader 17.0 surface.
Added: extend public void makeDatabaseStructureUpdates() Added: final public bool checkIfColumnExists(str tableName, str columnName) Added: final public void alterProjectInformationTableAddNewColumns(str oldTblName) Added: final public int64 insertNotesInfo(NotesTable in_notesInfo) Added: final public int insertOrderInformation(OrderInformation in_orderInfo) Added: final public int insertShipToInformation(ShipToInformation in_shipToInfo) Added: final public int insertSoldToInformation(SoldToInformation in_soldToInfo)
The constructor now also performs structure updates when opening an existing PMX database file.
ProjectInformation constructor now accepts ProjectInfoProjectInformation still takes the PMX target path, but it now also accepts the source ProjectInfo object so it can populate the expanded project metadata.
Old: public constructor(Url pmxTargetPath) New: public constructor(Url pmxTargetPath, ProjectInfo projInfo=null)
PartDetails has a new package-count propertyPartDetails now carries package-count data.
Added: final public int packageCount() Added: final public int packageCount=(int value)
cm.core.pmxcReconfigCategory was moved out of cm.abstract.pmx and is now public in cm.core.pmx.
The shared PMX file filter is also now exposed from cm.core.pmx for importer/exporter code.
Old: cm.abstract.pmx.cReconfigCategory New: cm.core.pmx.cReconfigCategory Added shared constant in cm.core.pmx: public str[] pmxFileFilter
Any package that only needs PMX constants or the PMX file-extension filter should depend on cm.core.pmx instead of cm.abstract.pmx.
PropsSchemeSpecificQPInfo now takes in a World to be compatible with multiple drawings. So initObjFromScheme also takes in World as an optional argument.
Old: extend public void initObjFromScheme(PropObj obj) { New: extend public void initObjFromScheme(PropObj obj, World world=null) {
Removed: public class FunkySelectionVisualizer extends SelectionVisualizer {
Made showLibHelpText return the constructed window.
Old: public void showLibHelpText(Window parent, str text="No help available in BETA version.", str title=null, str key=null, int width=300, PointI pos=null, symbol pkg=null) { New: public DialogWindow showLibHelpText(Window parent, str text="No help available in BETA version.", str title=null, str key=null, int width=300, PointI pos=null, symbol pkg=null) { Removed: public DialogWindow showLibHelpText2(str pkg="cm.std.tools", Window parent=null, str text="No help available in BETA version.", str key=null, PointI p=null) {
Added a bool suppressZeroFraction argument to the constructor.
Old: public constructor(symbol pkg, str key, str name, alignment textAlignment, distanceUnit unit, int precision1, distanceUnit unit2, int precision2, bool suppressZeroFraction, double arrowHSize, Class arrowHType, Class arrowHType2, double gap, double extLineExt, bool onlyPaperView=false, bool stackDims=false, Bool showDistanceUnit=null) { New: public constructor(symbol pkg, str key, str name, alignment textAlignment, distanceUnit unit, int precision1, distanceUnit unit2, int precision2, bool suppressZeroFraction, double arrowHSize, Class arrowHType, Class arrowHType2, double gap, double extLineExt, bool suppressZeroFeet=false, bool onlyPaperView=false, bool stackDims=false, Bool showDistanceUnit=null) {
Combined all 5 constructors into 1. The default argument values are the same as before in all cases except for the constructor with the bool real argument. There shouldn't have been any reason to use that constructor since "real" width lines have never been supported. But if you want to ensure the same behavior as before, specify widthStyle=lineWidthStyle.variable for any instance that previously set the bool real argument to any value.
In the majority of cases though, all that needs to be done is to add the argument hint for the width (e.g. LineType(.., .., w=5)).
Old: public constructor() { Old: public constructor(color lineColor) { Old: public constructor(color lineColor, lineStyle style, int width=0) { Old: public constructor(color lineColor, double width, bool real=false) { Old: public constructor(color lineColor, lineStyle style, lineWidthStyle widthStyle, double w) { New: public constructor(color lineColor=black, lineStyle style=lineStyle.solid, lineWidthStyle widthStyle=lineWidthStyle.unit, int w=0) {
Also consider if you should be using the LineType constructor at all. As long as the LineType instance does not need to be modified, it is recommended to get it using the lineType(..)-function instead, which returns a cached copy for improved performance.
Change the width argument from a double to an integer, which is what the value has always been converted to internally. If you have previously passed it a fractional value and is unsure what to replace it with, you can just cast it to an integer (e.g. myVar.int) to maintain the same behaviour as before.
Old: public str lineCacheKey(color c, lineStyle style, lineWidthStyle widthStyle, double width) : inline { New: public str lineCacheKey(color c, lineStyle style, lineWidthStyle widthStyle, int width) : inline { Old: public LineType lineType(color c, lineStyle style=lineStyle.solid, lineWidthStyle widthStyle=lineWidthStyle.unit, double width=0) { New: public LineType lineType(color c, lineStyle style=lineStyle.solid, lineWidthStyle widthStyle=lineWidthStyle.unit, int width=0) {
The interfaces to getStateContaining and getInfoContaining have been changed to allow bypassing the pkgStateCache:
Old: final public ExtensionState getStateContaining(symbol pkg) New: final public ExtensionState getStateContaining(symbol pkg, bool skipCache=false) Old: final public ExtensionInfo getInfoContaining(symbol pkg) New: final public ExtensionInfo getInfoContaining(symbol pkg, bool skipCache=false)
The constructor now requires you to specify the start point and the end point of the line, in world coordinates.
Removed: public constructor(pointF ipWC, int lineIndex) { Added: public constructor(pointF ipWC, pointF p0WC, pointF p1WC, int lineIndex) {
Renamed the atan functions that take 2 arguments to be correctly called atan2.
Old: public angle atan(double y, double x) : nonfglue = ang_atan2; New: public angle atan2(double y, double x) : nonfglue = ang_atan2; Old: public double atand(double y, double x) : nonfglue = ang_atan2d; New: public double atan2Rad(double y, double x) : nonfglue = ang_atan2d; Old: public double atanquick(double y, double x) : nonfglue = ang_qatan2d; New: public double quickAtan2Rad(double y, double x) : nonfglue = ang_qatan2d;
Renamed the atan functions that take 2 arguments to be correctly called atan2.
Old: public angle atan(float y, float x) : nonfglue = angF_atan2; New: public angle atan2(float y, float x) : nonfglue = angF_atan2; Old: public float atand(float y, float x) : nonfglue = angF_atan2f; New: public float atan2Rad(float y, float x) : nonfglue = angF_atan2f; Old: public float atanquick(float y, float x) : nonfglue = angF_qatan2f; New: public float quickAtan2Rad(float y, float x) : nonfglue = angF_qatan2f;
createLogFile() will now be package to prevent others from rotating log files accidentally.
Use cmWritable("log.cmtxt") instead to fetch the current log file.
Old: public Url createLogFile(CallStack stack=null, str msg="") { New: package Url createLogFile(CallStack stack=null, str msg="") {
Moved classes AEVEventReciever,AEVButton and AEVDialog from cm.statistics.reporter.test to cm.statistics.reporter. This also moves the methods public void startAnalyticsEventViewer() and public void stopAnalyticsEventViewer().
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library stdArchitecturalLibrary() {
A new public field was added to control whether printed summary sum lines include summation type labels.
New: public bool showSummationLabels = true
If you construct or configure QuotationListChapterCreator instances in code, you can now set showSummationLabels explicitly to control whether labels such as Sell, Buy, List, or Profit are shown on sum lines.
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library stdWallLibrary() {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library stdPaperToolsLibrary() { Removed: public class ViewportSnapperLimbVisibility extends LibraryLimbVisibility { Removed: public class XCLipLimbVisibility extends LibraryLimbVisibility {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library stdWallLibrary() {
Replaced the thickness field with a width and depth component.
Old: public double thickness; New: public double width; New: public double depth; Removed: public const DistanceRange pillarThicknessDomain(1cm, 100m);
Removed: extend public void drawGraphsLocal(Snapper snapper, LayerBuffer lb) {
Added caller eval to the testPackage(..) package-argument, which causes the package argument to be set from the call location rather than the function definition location. This means that if the function is called for example from the package "cm.core.dwg.test", it will now run all tests in that same package even if you don't pass any arguments to the function.
Old: public void testPackage(symbol pkg=#:package, bool verbose=false, bool hideErrors=false) { New: public void testPackage(symbol pkg=#:package : caller eval, bool verbose=false, bool hideErrors=false) { Removed: public void testPackage(symbol pkg=#:package : caller eval) {
For example, if you previously called the function like so:
testPackage(#:package, verbose=true);
You can now call it like this and it will still behave the same:
testPackage(verbose=true);
Some classes in getWindowInstruction.cm has been moved to their own files: See getParentWindowInstruction.cm and getChildInstruction.cm.
Two deprecated constructors have been removed:
Removed: public constructor(str windowKey, str outputKey, SrcRef src=#:src) { Removed: public constructor(Class windowClass, str outputKey, SrcRef src=#:src) {
Instead, use one of these constructors:
public constructor(str windowKey, str outputKey, bool ensureFound=true, bool ensureUnique=true, bool ensureValid=true, bool ensureVisible=true, SrcRef src=#:src) { public constructor(Class windowClass, str outputKey, bool ensureFound=true, bool ensureUnique=true, bool ensureValid=true, bool ensureVisible=true, SrcRef src=#:src) {
Two deprecated constructors have been removed:
Removed: public constructor(str windowKey, str parentKey, str outputKey, SrcRef src=#:src) { Removed: public constructor(Class windowClass, str parentKey, str outputKey, SrcRef src=#:src) {
Instead, use one of these constructors:
public constructor(str windowKey, str parentKey|, str outputKey, bool ensureFound=true, bool ensureUnique=true, bool ensureValid=true, bool ensureVisible=true, SrcRef src=#:src) { public constructor(Class windowClass, str parentKey|, str outputKey, bool ensureFound=true, bool ensureUnique=true, bool ensureValid=true, bool ensureVisible=true, SrcRef src=#:src) {
Changes that have been done:
DibImage instead of MemoryImage. For more details, check the run-time/behavior changes section.bool newSvgRenderer now defaults to true for icon loading functions.Old: public Image icon(str name, symbol key=#default, bool debug=false) Old: public Image icon(str name, symbol key=#default, bool newSvgRenderer=false, bool debug=false) New: public Image icon(str name, symbol key=#default, bool newSvgRenderer=true, bool debug=false) Old: public Image icon(str name, bool enabled, symbol key=#default) New: public Image icon(str name, bool enabled, symbol key=#default, bool newSvgRenderer=true) Old: public Image disabledIcon(str name, symbol key=#default) New: public Image disabledIcon(str name, symbol key=#default, bool newSvgRenderer=true) Old: public Image dibIcon(str name, symbol key=#default, bool debug=false) Old: public Image dibIcon(str name, symbol key=#default, bool newSvgRenderer=false, bool debug=false) New: public Image dibIcon(str name, symbol key=#default, bool newSvgRenderer=true, bool debug=false) Old: public Image disabledDibIcon(str name, symbol key=#default) New: public Image disabledDibIcon(str name, symbol key=#default, bool newSvgRenderer=true)
The following fields and methods have been removed:
public str->DibImage dibIconLookup(); final public Image getDib(str name, bool debug=false) final public Image getDib(str name, bool newSvgRenderer=false, bool debug=false) final public Image getDisabledDib(str name, bool debug=false)
The following methods have been updated:
Old: final public Image get(str name, bool debug=false) Old: final public Image get(str name, bool newSvgRenderer=false, bool debug=false) New: final public Image get(str name, bool newSvgRenderer=true, bool debug=false)
The following methods have been updated to remove bool dib=false:
Old: final public Image get(str name, symbol key, bool dib=false, bool debug=false) Old: final public Image get(str name, symbol key, bool dib=false, bool newSvgRenderer=false, bool debug=false) New: final public Image get(str name, symbol key, bool newSvgRenderer=true, bool debug=false) Old: final public Image getDisabled(str name, symbol key, bool dib=false) New: final public Image getDisabled(str name, symbol key)
Old: public constructor(Url imageFile, sizeI size=(-1, -1), SrcRef src=#:src) Old: public constructor(Url imageFile, bool newSvgRenderer, sizeI size=(-1, -1) New: public constructor(Url imageFile, bool newSvgRenderer=true, sizeI size=(-1, -1) Old: public SvgImage vectorImage(Url url, bool use=false, SrcRef src=#:src) Old: public SvgImage vectorImage(Url url, bool newSvgRenderer, bool use=false, SrcRef src=#:src) New: public SvgImage vectorImage(Url url, bool newSvgRenderer=true, bool use=false, SrcRef src=#:src)
New argument str key in constructor.
Old: public constructor(Window parent, Font font=underlineSystemFont, Brush brush=null, FrameStyle frameStyle=noFrame, str label="", color labelColor=color(0, 0, 240), alignment textSide=undefinedAlignment, Image image=null, Image disabledImage=null, color color=nocolor, sizeI size=(-1, -1), pointI margins=(0, 0), alignment align=middle, str link=null, bool showUnderline=false, function(Control button) callback=null, SrcRef src=#:src) { New: public constructor(Window parent, Font font=underlineSystemFont, Brush brush=null, FrameStyle frameStyle=noFrame, str label="", color labelColor=color(0, 0, 240), alignment textSide=undefinedAlignment, Image image=null, Image disabledImage=null, color color=nocolor, sizeI size=(-1, -1), pointI margins=(0, 0), alignment align=middle, str key=null, str link=null, bool showUnderline=false, function(Control button) callback=null, SrcRef src=#:src) {
The function showForeignWindow no longer restores the window when setting its visibility.
Use restoreForeignWindow if you want the old behavior of restoring the window.
Old: public void showForeignWindow2(mwnd hwnd) = win_showForeignWindow; New: public void showForeignWindow(mwnd hwnd) = win_showForeignWindow; Old: public void showForeignWindow(mwnd hwnd) = win_restoreForeignWindow; New: public void restoreForeignWindow(mwnd hwnd) = win_restoreForeignWindow;
The following functions have been removed.
public bool faceliftDeveloper() : deprecated { return staffan or prismDeveloper; } public bool isAlphaLegacy(char c) { return spellChecker.isAlpha(c); }
AppWindow
Old: final public pointI dropDownShow(pointI p, bool autoSize=true) New: final public pointI dropDownShow(pointI p, bool autoSize=true, bool enforceMinWidth=true)
Window
Old: extend public pointI showDropDownMenu(pointI p, bool pInScreenCoords=false, bool autoSize=true) New: extend public pointI showDropDownMenu(pointI p, bool pInScreenCoords=false, bool autoSize=true, bool enforceMinWidth=true)
We have added a new argument bool establishSocketConnection=false into the constructor.
Old: public constructor(str key, str url, int port=-1, bool launch=false, str label="CET", bool showPageTitle=false, bool showNavigation=true, bool alwaysOnTop=false, bool hideUrl=false, pointI initPos=(-1, -1), sizeI initSize=(-1, -1), bool resizable=false, sizeI minSize=(-1, -1), sizeI maxSize=(-1, -1), bool devMode=false, webViewCB msgCallback=null) New: public constructor(str key, str url, int port=-1, bool launch=false, str label="CET", bool showPageTitle=false, bool showNavigation=true, bool alwaysOnTop=false, bool hideUrl=false, pointI initPos=(-1, -1), sizeI initSize=(-1, -1), bool resizable=false, sizeI minSize=(-1, -1), sizeI maxSize=(-1, -1), bool devMode=false, webViewCB msgCallback=null, bool establishSocketConnection=false)
The sizeI size argument has been changed to int width.
Old: public constructor(Window parent, str message, Image image, str key=null, sizeI size=(0, 0), int internalMargin=7, Brush brush=ultraLightGrayBrush, FrameStyle frameStyle=lightGrayPenFrame, color textColor=black, color linkColor=primary600, color linkHoverColor=primary600, int textSize=12, str fontFace=null, function(Control button, str key):bool linkCallback=null, SrcRef src=#:src) { New: public constructor(Window parent, str message, Image image, str key=null, int width=0, int internalMargin=7, Brush brush=ultraLightGrayBrush, FrameStyle frameStyle=lightGrayPenFrame, color textColor=black, color linkColor=primary600, color linkHoverColor=primary600, int textSize=12, str fontFace=null, function(Control button, str key):bool linkCallback=null, SrcRef src=#:src) {
This MessageWindow constructor now takes in argument int width.
Old: public constructor(Window parent, str message, messageType msgType=messageType.undefined, str key=null, int internalMargin=7, function(Control button, str key):bool linkCallback=null, SrcRef src=#:src) { New: public constructor(Window parent, str message, messageType msgType=messageType.undefined, str key=null, int width=0, int internalMargin=7, function(Control button, str key):bool linkCallback=null, SrcRef src=#:src) {
statsMainExtensionPackage now accepts argument bool allowGuess to try guessing the main extension package via the provided package.
Old: public symbol statsMainExtensionPackage(symbol pkg) New: public symbol statsMainExtensionPackage(symbol pkg, bool allowGuess=false)
getMainExtensionPackage now accepts argument bool skipCache to allow bypassing cached results
Old: public symbol getMainExtensionPackage(symbol pkg) New: public symbol getMainExtensionPackage(symbol pkg, bool skipCache=false)
The following classes have been removed as film strip view functionality has been removed.
Removed: public class FilmStripView extends ThumbNailView { Removed: public class PreviewWindow extends SubWindow {
The following fields and methods have been removed in ExploreDialog
Removed: public PreviewWindow previewWin; Removed: extend public PreviewWindow buildPreviewWin() { Removed: extend public void refreshPreviewWin() { Removed: extend public void setPreviewContent(Image image=null, str label=null) { Removed: extend public void updatePreviewButtons(bool disablePrev, bool disableNext) { Removed: extend public sizeI previewImageSize() { Removed: extend public bool filmStripMode() {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library genericElectricalLibrary() { Old: public Library faceliftElectricalLibrary() { New: private Library faceliftElectricalLibrary() {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library classicStdLightsLibrary() { Old: public Library faceliftStdLightsLibrary() { New: private Library faceliftStdLightsLibrary() {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library medicalLibrary() { Old: public Library faceliftMedicalLibrary() { New: private Library faceliftMedicalLibrary() {
Removed the classic library. Update references to the facelifted library or remove them.
Removed: public Library stdPlantsLibrary() { Old: public Library faceliftPlantsLibrary() { New: private Library faceliftPlantsLibrary() {
The following interfaces will be removed as part of the Component Tab and Component Tab Creator EOL effort:
// Functions Removed: public void dcShowToolboxCreatorVendorChangeWarning(DataCatalog catalog)
// Functions Removed: public DcToolboxCreatorDialog dcToolboxCreatorDialog() Removed: public DcToolboxCreatorDialog dcShowToolboxCreatorDialog() Removed: public DcToolboxCreatorDialog dcShowToolboxCreatorDialog(bool setFocus) Removed: public void dcCloseToolboxCreatorDialog() // Classes Removed: public class DcToolboxCreatorDialog extends DsToolboxCreatorDialog Removed: public class DcAnimationTreeViewAnimationEnv extends DsDragAnimationEnv Removed: public class DcDataCatalogLimbEnv extends DsDataCatalogLimbEnv Removed: public class DcToolboxCreatorToolboxCards extends DsToolboxCreatorToolboxCards
// Classes Removed: public class DcProductCatalogToolboxProductReferencesEnv extends DcProductCatalogReferencesValidationEnv { Removed: public class DcProductCatalogToolboxReferencesEnv extends DcValidationEnv Removed: public class DcProductCatalogToolboxProductOptionsEnv extends DcValidationEnv
class DcProductLevelEditSubWindow
Removed: extend public void updateActivation()
class DcTableOfContentsSubWindow
// Field public NoteWindow noteWindow; // Methods Removed: extend public void openToolboxCreator() Removed: extend public void buildNoteWindow() Removed: extend public void updateNote()
class DcTableOfContentToolboxSubWindow
Removed: public DsButton toolboxCreatorBtn; Removed: extend public void updateActivation()
class DcTableOfContentsTreeView
Removed: public bool keyCode(Window originator, KeyCode keyCode) Removed: public bool acceptTVI(TreeViewItem item)
// Classes: Removed: public class DcTbOptionsValidationUndoOp extends DcUndoOp Removed: public class DcProductCatalogAddToolboxesUndoOp extends DcUndoOp Removed: public class DcProductCatalogRemoveToolboxesUndoOp extends DcProductCatalogAddToolboxesUndoOp
// Classes Removed: public class DcPsLayoutReferencesValidationEnv extends DcValidationEnv
// Classes Removed: public class DcCompTabValidationResult extends DcValidationResult Functions: Removed: public void dcNavigateToToolboxCreator() Removed: public void dcNavigateToToolboxCreatorCard(str code)
DcSIFExportEnvThe duplicate public constructor shape has been removed. The remaining constructor places catalogCodeOverride last, after fileRule3D and fileRule2D.
Removed: public constructor(str baseFileName, str[] priceLists, Url outputDir, DataCatalog catalog, str preferredLanguage=null, bool allowAlternateLanguage=true, str productCatalogKey=null, str catalogCodeOverride=null, bool filterProductsByCatalog=true, bool exportMaterials=false, bool renderGMaterials=false, bool exportMaterialResolutions=false, bool exportModels=false, bool ignoreUndefined=true, bool includeOFDAParams=false, bool useInFile=false, bool generateSifSafeFeatures=false, bool fixProductRootMultiGraphics=false, <str, bool, str, bool> fileRule3D=null, <str, bool, str, bool> fileRule2D=null) Supported constructor: public constructor(str baseFileName, str[] priceLists, Url outputDir, DataCatalog catalog, str preferredLanguage=null, bool allowAlternateLanguage=true, str productCatalogKey=null, bool filterProductsByCatalog=true, bool exportMaterials=false, bool renderGMaterials=false, bool exportMaterialResolutions=false, bool exportModels=false, bool ignoreUndefined=true, bool includeOFDAParams=false, bool useInFile=false, bool generateSifSafeFeatures=false, bool fixProductRootMultiGraphics=false, <str, bool, str, bool> fileRule3D=null, <str, bool, str, bool> fileRule2D=null, str catalogCodeOverride=null)
If your subclass constructor previously passed a positional null only to disambiguate catalogCodeOverride, you can remove that workaround and call the remaining constructor shape directly.
Added 2 new parameters to updateProductDD to flag that first/last product is selected.
Old: extend public void updateProductDD(bool updateCard=true) New: extend public void updateProductDD(bool updateCard=true, bool selFirstProd=false, bool selLastProd=false)
Added 2 new parameters to updateSelProd to flag that first/last product is selected.
Old: extend public void updateSelProd(int start) New: extend public void updateSelProd(int start, bool selFirstProd=false, bool selLastProd=false)
Added 1 new parameter to 'dcBuildAddProductTreeViewItem' to keep track of the additional product's parent product.
Old: public DcAddProductTreeViewItem dcBuildAddProductTreeViewItem(DsiAddProductRefType prod, CatalogTreeViewEnv env) New: public DcAddProductTreeViewItem dcBuildAddProductTreeViewItem(DsiAddProductRefType prod, CatalogTreeViewEnv env, DsProductType parentProd)
FikaDsPartSpecialMigratorFikaDsPartSpecialMigrator is the package-specific migrator used by Fika snappers.
Added: public class FikaDsPartSpecialMigrator extends DsPartSpecialMigrator Added: public str oldPartSpecialsKey(Part part)
oldPartSpecialsKey(Part part) handles multiple historical key formats:
creatorKey()super(..)partSpecialMigratorFika snappers now opt in to migration by exposing a PartSpecialMigrator "partSpecialMigrator" prop/default.
Added/important usage: PartSpecialMigrator "partSpecialMigrator" { Object default(..) { return FikaDsPartSpecialMigrator(); } }
If your package has older custom key history, this is the pattern to copy: provide the prop on the snapper family and return the right subclass for that package.
CET no longer remembers the user's credentials. The responsibility of authenticating the user is now delegated to our Single Sign-on service.
DsAPIPData which is the data used for snappers imported from stage has been removed. Stage snappers will now use DsPData which is used by regular catalogue symbols to be more consistent with CET catalogue symbols.
As part of the EOL effort, the Component Tab file format (.cmtbxt) will also be retired and no longer be loadable as the streamed Component Tab class packages are removed. Ideally the Component Tab interfaces should only be streamed as part of the cmtbxt file format, and should not (and is not supposed to) be streamed with other cm file format (e.g., drawings, favorites). However, if the classes were somehow streamed as part of other file format, a new PackageStreamRenamer class is introduced to redirect these missing classes to a temporary placeholder class. Do note that this class serves no meaningful purpose, as it merely exists to suppress or bypass any load failure/errors caused by missing Component Tab related packages.
In developMode, the renamer will print extra info and a short stack dump so that it won't go fully unnoticed.
In cm.abstract.dataSymbol.renamer.cm:
/** * Stream renamer for removed toolbox creator package (cm.abstract.dataSymbol.ui.toolboxCreator). * which was permanently removed starting 17.0. * * Handles and redirect the streamed objs to a dummy class so it does not crash CET. */ private class DsToolboxCreatorRenamer extends PackageStreamRenamer { public void type(version v, Symbol pkg, Str name, Str fileName=null) { if (pkg.v == "cm.abstract.dataSymbol.ui.toolboxCreator") { if (developMode) { pln("Attempting to load Component Tab interfaces!".eAngry; #pkg; #name); stackDump(3); } pkg.v = "cm.abstract.dataSymbol"; name.v = "DsDummyTBCreatorStreamPlaceHolder"; } } /** * Deprecate old packages. */ final private void deprecateOldPackages() { // Used to deprecate packages info stored in drawings to avoid load warnings about missing packages if (StreamRenamer r = globalStreamRenamer) { r.deprecatePkg((#"cm.abstract.dataSymbol.ui.toolboxCreator")); } } } /** * Placeholder for streamed classes from 'cm.abstract.dataSymbol.ui.toolboxCreator'. */ public class DsDummyTBCreatorStreamPlaceHolder : unstreamable { /** * Load failure event. */ public loadFailedResult loadFailed(ObjectFormatter formatter, LoadFailure failure) { return loadFailedResult.ignore; } }
sourceId()DsPart.sourceId() now resolves a stable identity in this order: super(), DsPData.dataId, DsPData.styleNr(), then article code. This is the identity used by the new core Ind. Tag and user-tag migration system.
DsPart.uniqueKey() no longer includes user-modified ItemTagInfo state. If your extension relied on customized Ind. Tag text to make data symbol parts unique, provide a stable sourceId() instead.
DsPicklist.loadFailed(..) now migrates the removed tagInfos and tagInfoKeys storage into the shared UserTagInfoHolder. This also reconstructs user-modified tag text for tags that were saved but were not actively materialized when the drawing was last open.
When a DsPicklistItem is removed, related user-modified Ind. Tag info is cleared from the shared holder instead of lingering on the picklist.
The quantity column in the freeform Catalogue Explorer picklist now accepts both , and . as decimal separators through the custom numeric input field used by DsPicklistQuantityGridCell.
This aligns the picklist quantity editor more closely with Calculations. Entering a value such as 1,2 should now preserve the decimal quantity instead of truncating everything after the comma.
DsPart option specials now belong in per-part OptionSpecialHolderAt runtime, DsPart option specials are no longer supposed to remain in the owner snapper's shared PartSpecialHolder.
They now belong in the per-part OptionSpecialHolder keyed from the part's sourceId(), just like other ProdPart option specials.
This matters for data-symbol packages because legacy DsSpecOption keys were not the same as base SpecOption keys. If you load older drawings without opting in to the right migrator, option specials or added options can be lost even though part specials still survive.
DsPart exports now include added options through anchor traversalDsPart.xmlOptions(...), DsPart.xmlAdditionalOptions(...), and DsPart.generateAdditionalOptions(...) now work with the 17.0 added-option model. Instead of assuming only catalogue-selected options exist, these flows follow the option anchor chain and include inserted custom options in XML and PMX item data.
If your package overrides export behavior, make sure it does not silently ignore added options that now live behind getFollowingCustom(...).
Root-level added options are anchored with cRootKey, so export code that only looks for options following normal catalogue options can still miss valid added options.
Data-symbol OFDA/XML export now participates in the shared Part.xmlUserDefined(...) price-export flow. OfdaXMLOrderLineDsProxy writes the part base price through cBasePriceTag, and DsPart continues to export option data through the 17.0 added-option-aware XML helpers.
For core data-symbol parts, base price and option pricing now follow the current CET currency setting more consistently. That affects shared BOM-visible outputs such as Calculations, query dialogs, print/report flows, article-view style UI, SIF export, and OFDA/XML export where those values are shown or exported.
This is a migration point for manufacturers. Packages that previously relied on untranslated data-symbol prices, or that added their own manual currency conversion, should verify pricing in both default-currency and non-default-currency scenarios.
The older data-symbol-specific package-count column registration is removed in favor of the shared core PackageCountColumn. Data-symbol parts still provide the actual count through DsPart.pkgCount(), but OFDA/XML and part-detail export now rely more on the shared core package-count path.
Older saved references to cm.abstract.dataSymbol.partColumns package-count and preview column types are redirected through the renamer to the shared core column types during load.
DsPartSpecialMigrator handles legacy data-symbol option keysDsPartSpecialMigrator translates the older data-symbol option-special key formats into the new per-part holder layout.
That includes the older DsSpecOption key forms and the feature-option combinations that were previously flattened into the shared holder key.
During load, the discovery path is:
Snapper.loaded1() calls initPartSpecialMigrator(formatter)initPartSpecialMigrator(...) checks whether the snapper already has a non-empty PartSpecialHolder and whether the loaded package version is older than the current runtimegetPartSpecialMigrator(init=true) then resolves this."partSpecialMigrator", calling the prop default if neededpendingMigration = true and the loaded package versionSnapper.migratePartSpecials(parts) calls migrateSpecials(parts, this)Because the migrator is opt-in, catalogue packages should verify older drawings that contain:
DsPart key historiesDsPData now has explicit added-option export hooksDsPData.generateOptionRows(...) and generateAdditionalOptionRows(...) are the new SIF-export helpers for both normal and added options.
If your package customizes SIF export, review those hooks so that added options remain visible in exported data.
IFC export was using a snapper's sym representation to generate the IFC geometry of the IFC symbol. This was because sym supports geometries such as extrusions, which can be exported as solids. However, due to how certain SymNodes were generated, this resulted in compound solids which cause the geometries to go missing when viewed in certain IFC viewer softwares (such as Solibri and Revit) due to the rendering rules of those softwares. Defaulted this now to export using the snapper's mesh instead to improve the consistency of the visibility of IFC symbol geometries in IFC viewer softwares. However, if a manufacturer wishes to still export their snapper using the sym based representation (possibly to export as a solid), they can override extend public bool useSymBasedRepresentations() method in IfcExportCoreObjectFactory to return true.
In 17.0 Major we have fixed an issue involving level snappers inserted without any unit loads. They do not have a MhSnapperBehavior in its stateBehaviorCollection field after insertion. The problem is that after inserting a UnitLoad onto a level, it does not get absorbed as a MhSnapperInfo due to the level missing the MhSnapperBehavior, and this behavior does not get initialized for existing levels.
We have since fixed it to start initializing MhSnapperBehavior when needed, and to ensure old drawings are handled we added this load code. If you have a custom level shape class that does not extend from MhLevelShape, you will need to copy the below code into your class.
public class MhLevelShape extends MhBoxSnapperShape { public void loaded1(ObjectFormatter formatter, LoadFailInfo failInfo) { super(..); mhUnstreamStoredSpec(formatter); // CETC-134365. if (formatter.version(#:package) < version(17, 0, 0)) { if (owner and !owner.snapperInfosBehavior()) { if (MhSystemConfiguration config = owner.configuration()) { forChildren(MhSnapper child in owner) { if (config.exportAsBehavior(child.classification)) { owner.initSnapperInfosBehavior(); break; } } } } } } }
The following spawners have had the mhRowChildSelectionBehavior added to them. This changes their group selection behavior such that the row is no longer included in the selection when the accessory is selected. You may need to exclude this behavior if your custom class already appends a selection behavior.
The bay editor has a material override applied to the frames. The key used for this override has changed.
Old: private void overrideFrameMaterial(Snapper snapper, REDOverrideMaterial mat) { ... const str key2D = "overrideMaterialVessel2D"; ... const str key3D = "overrideMaterialVessel3D"; ... } New: private void overrideFrameMaterial(Snapper snapper, REDOverrideMaterial mat) { ... const str key2D = "mhElevSpaceOverrideMaterialVessel2D"; ... const str key3D = "mhElevSpaceOverrideMaterialVessel3D"; ... }
Packages that previously relied on the old InsertElectricalObject animation and PanelFrame callbacks will not get equivalent behavior automatically in 17.0.
If you do nothing, that shared behavior is simply gone.
For existing manufacturer packages, the expected behavior-preserving migration is usually:
custom.fika shows a different, more structural 17.0 option:
FODataElecSnapper exposes appendElectricalAlternatives(...), getSnappingNodes(...), getAvailableNodes(), getNeighborNodes(...), appendNodesToPath(...), and alreadyRoutedTo(...)FOElecNode subclasses AOElecNode and delegates those routing decisions back to the owning electrical snapperFOElecPathAnimation and FOElecPathFinder replace the old insert/apply/remove workflow with routed path creationThat Fika approach is useful when you want the new routed electrical system, but it should be treated as a redesign path, not as the default migration expectation for every manufacturer package.
AbsTagPartColumnThe Ind. Tag column is now splittable by user-modified Ind. Tag text. This keeps calculation rows separated when the underlying part number is the same but the user override text differs.
value(..) now uses Part.itemTagText() and isAdjusted(..) now checks Part.isUsrModItemTagInfo(), so only explicit user overrides are treated as adjusted values.
This split/merge behavior is accomplished through the updated cm.core.part.Part.finalFlattenableKey() behavior, not just column logic. Because part adjustments are tied to the final flattenable identity used during merge, any extension with saved or custom part-adjustment flows should test those workflows with user-modified Ind. Tags.
indTagAdjustmentHook(..) is now registered through partRowAdjustmentApplyHooks. When the adjusted column is AbsTagPartColumn, the current value is applied to every part in the row and the temporary PartColumnAdjustment is removed afterward.
fixCET17Adjustments(..) now runs from appendPostFinalizeAfterMergeHook(..) and restores older single-part adjustments that were saved with the pre-17.0 flattenable key format. This is mainly relevant when opening 16.5-and-earlier drawings that contain user-modified Ind. Tags or other single-part adjustments.
ProdPartWhen calculation or ExtendedPartData imports contain an indTag, ProdPart now routes that value through setUserItemTagInfo(..) so it participates in the new shared user-tag storage and reset-behavior system.
allPartOptions() now includes custom inserted options and is used by more export and query flows. This means code that previously assumed options() and the effective option sequence were the same may now see different behavior when custom options have been added.
OptionSpecialHolderBefore 17.0, both part specials and option specials were stored together in the owner snapper's PartSpecialHolder, and option-special keys were composed from the part key and option key.
In 17.0, each part instead gets its own OptionSpecialHolder, stored on the owner under optionSpecialHolderPropKey(part), which includes the part's sourceId().
The practical effect is:
PartSpecialHolder remains the owner-level holder for part specials, overrides, and part additionsOptionSpecialHolder.specials() now stores only that part's default-option specialsOptionSpecialHolder.additions() now stores only that part's additional CustomOptionSpecialskey()If your extension still inspects the snapper-level PartSpecialHolder.specials() map for option specials, migrate that code to OptionSpecialHolder and the ProdPart helper APIs.
When loading pre-17.0 drawings, old option specials can still exist in the shared PartSpecialHolder under legacy combined keys.
ProdPartSpecialMigrator exists to read those old entries, move them into the new per-part holder, and then remove the old keys.
This migration is not automatic for every package. If your package used package-specific part keys or option keys, or if it persists older drawings that must preserve option specials and additional options, you need to opt in to the migrator flow and override the key-conversion methods as needed. In practice, CET discovers the migrator through the owner snapper's partSpecialMigrator prop/default and then invokes it from the snapper load path.
anchorKeyToCustom now drives ordering for added optionsAdded options are no longer just an unordered bag of CustomOptionSpecials.
CustomOptionSpecial.anchorOptKey identifies which option or added option a custom option follows, and ProdPart.anchorKeyToCustom caches that relationship after insertCustomOptions(...) rebuilds the effective option sequence.
This is important because the cache is used by downstream export/import and query flows to preserve added-option order:
anchorOptKeyDsPart OFDA/XML export uses getFollowingCustom(...) to emit custom options after the correct anchor optionDsPData SIF and PMX export use getFollowingCustom(...) to generate additional option rows in the correct orderIf you create or reorder additional options yourself, make sure you invalidate and rebuild part options so the anchor cache is refreshed before exporting or reading back option order.
The 17.0 OFDA/XML export flow now uses Part.xmlUserDefined(...), ProdPart.xmlUserDefined(...), and package-specific overrides such as DsPart to write additional pricing data into exported XML.
For product parts, option-list prices are exported through cOptionListTag. Base price export is handled through the shared cBasePriceTag path in Part.xmlUserDefined(...).
As part of the 17.0 OptionSpecial cleanup, the old originalPart / originalOptStr flattenable-key path was removed. A practical consequence is that some older drawings that contain both a part adjustment and an option special on the same effective part can lose the part adjustment after load because the persisted adjustment key no longer resolves the same way.
This is most relevant for packages that saved older drawings with mixed part-adjustment and option-special state. Those flows should be tested explicitly when validating 17.0 migration behavior.
ProdPartAddition and ProdPartOverride are applied during Snapper.fetchParts()The creator objects are stored on the owner first, but they do not affect calculations until the owner later calls fetchParts().
At that point the core flow calls applyCustomParts(this, localParts) after getParts(...) and afterGetParts(...), which is where additions and overrides become real generated parts.
For product parts, that means:
ProdPartAddition.part(owner) generates a ProdPart and turns its stored PartOption[] into additional CustomOptionSpecials in that part's OptionSpecialHolderProdPartOverride.part(owner) generates a ProdPart and applies its stored PartOption[] directly with setPartOptions(...)This matters if you are debugging calculations, query dialogs, or exports and expect a stored creator to have immediate effect before fetchParts() has run.
Custom option additions are now stored and ordered explicitly through anchor keys. This allows imported or user-added options to be inserted after specific existing options and preserved in query/import workflows.
If you rely on option order, option export, or custom option persistence, test those flows with added options and anchor-key updates.
At runtime, a product-part option can now report:
specialadditionoverride when the owning part is overriddenThis affects query-grid highlighting and any extension logic that uses option customization state to decide visibility or behavior.
The abstract import layer changes visible import behavior by producing ProdPart instances and option data through the shared query import workflow.
ProdPartQueryDialogBehavior.getImporterEnv(str suffix) now returns:
ProdPartSIFImporterEnv(silent=false) for .sifProdPartPMXImporterEnv(silent=false) for .pmxProdPartOFDAImporterEnv(silent=false) for .xmlThat means package-specific ProdPart import behavior should usually be implemented by subclassing one of these environments or by overriding the relevant ProdPart import hook, not by bypassing the query import flow.
ProdPart options during importProdPartSIFImporterEnv tracks both the current part and the current option while parsing the file. When it sees headerOptionKey() (ON by default), it starts a new option, collects option fields into a SpecOption, and appends all flushed options to the part in flushPart(...).
If your older import code assumed SIF import only produced base part data, update that assumption. In 17.0 the abstract import layer can produce product-part option data directly from the imported file.
For imported OFDA XML, ProdPartOFDAImporterEnv creates ProdPart instances and ProdPart.importOFDASpecItemTag(...) now routes <Option> and <CustomOption> children into importOptionTag(...).
This is the shared path that now builds imported SpecOption objects from OFDA XML, including nested options. Packages that need to customize OFDA option import should override importOptionTag(...) or extend importOFDASpecItemTag(...) accordingly.
Product-part query dialogs now support importing option items as well as parts. Imported option items can be shown in the import pane and dragged into valid option rows in the query grid as additional options or special overrides.
This is the abstract-part side of the broader query import workflow introduced together with cm.core.part.query.
Option rows are now filtered through acceptPartOpt(...).
Options that are top-level features (have a level of 0) are filtered out of the Query Dialog.
Options that are special, additional, or part of an overridden Part are kept visible.
Option and product-part rows now use customization-based highlighting similar to the core query package:
If you rely on previous option-row visibility or row-color assumptions, verify the updated behavior.
The data environment now supports creating, anchoring, reordering, and removing additional option specials (CustomOptionSpecial) directly from query import and drag/drop operations.
Removing an added option can also update anchor keys on neighboring added options.
If you have custom code that inspects or persists option special ordering, verify it against the new anchor-key update behavior.
OptionMakeSpecialDialog now converts the amount shown in the dialog from calculation currency back to the part's main currency before storing the final OptionSpecial or CustomOptionSpecial.
If you compare displayed amounts to stored option-special values, account for this conversion.
When a project-information field is backed by a drop-down, CET now prefers the selected item's label when presenting that value in paper view and similar text output.
That means fields such as:
can now render the expected text instead of only the stored numeric or internal key value.
Snapper.loaded1() now initializes PartSpecialMigratorFor packages that opt in to the new migration flow, the discovery path starts in cm.core.Snapper:
Snapper.loaded1() calls initPartSpecialMigrator(formatter)initPartSpecialMigrator(...) checks whether legacy specials exist and whether the loaded package version is older than the current runtimegetPartSpecialMigrator(init=true) resolves the snapper's partSpecialMigrator prop/defaultmigratePartSpecials(parts)This is the runtime entry point that downstream packages such as custom.fika depend on when they provide FikaDsPartSpecialMigrator() through the snapper prop.
The drawFeatures(..) method has been changed so that features for the 3D view are now generated from the snappers 3D mesh instead of using the same features as for the 2D view, which were often not accurate in 3D space. The method can be overriden to define a custom behaviour.
drawFeatures → Use the overridedrawFeatures → Take features based on drawGraphs (2D graphics)drawFeatures → Use the overridedrawFeatures → Add features based on 3D meshdrawFeatures → Use the overridedrawFeatures → Take features based on drawGraphs (2D graphics)drawFeatures → Use the overridedrawFeatures → Take features based on drawGraphs (2D graphics)Some core snappers override drawFeature in order to retain the old behavior. The reasons for this vary, but one example is lines where we want it to snap to the mathematical (infinitely small) line rather than to the cylindrical mesh of the line.
The following snappers have overriden the behavior:
drawFeatures to return drawGraphs(..).drawFeatures and define your own more accurate features.drawFeatures or systemDrawFeatures:
In general, it's recommended to test the feature snapping of your extension and ensure it's working as expected. For example, check if the measure tool can be used to accurately measure the dimensions your objects.
The System Validation dialog is now exposed as Check Drawing from the Tools menu and is backed by the new cm.core.invalidListCB() callback.
CET 17.0 introduces a new standard-special system in cm.core.part. Standard specials are internal specials rather than user-created specials, so part rows that only contain standard specials are not highlighted as user-special rows in Calculations.
This follows the new Part.containsAnySpecials(ignoreStandardSpecial=true) behavior used by calculation row highlighting. Implementers should treat that as part of the intended standard-special behavior, not as a loss of ordinary special highlighting.
Calculations now has support for showing or hiding summation type labels on summary sum lines in 17.0. This affects calculation summaries, summary templates, article view preview, and printouts.
Sum lines now use a user-facing summary setting to decide whether labels such as Sell, Buy, List, or Profit are appended to the sum name.
For 17.0, this setting defaults to enabled so existing summaries and printouts will continue to show summation labels unless explicitly turned off.
This behavior is controlled through ArticleViewSummarySettings.enabledSettings using the key cSumLabelSettingKey ("summationLabels").
Older saved summary settings that do not contain this field load successfully and use the 17.0 default of true.
If you have custom code that compares summary labels literally, parses printed summary text, or depends on the exact displayed name of a GlobalPartAdjustmentSum, verify that it still works when summation labels are shown or when users disable them.
Extensions that create part tags and part tag categories on a new drawing using hooks such as createWorldHooks() and selectWorldHooks() are required to assign a fixed unique ID onto the Extension's default created custom PartTags and PartTagCategorys. Otherwise, loading CollabPro projects might result in duplicated part tag categories.
As cm.core.nwd has been removed as a seperate extension, it is nolonger possible to list it as an extension dependency.
If your extension.xml includes a dependency on cm.core.nwd this should be removed.
The shared core pricing flow now translates base prices and option upcharge prices to the current CET currency more consistently. In practice, this affects BOM-visible areas such as Calculations, query dialogs, print/report flows, article-view style part presentations, and order/export paths that rely on the shared pricing APIs.
This can require migration for manufacturers. If your extension overrides basePrice(...), optionPriceSum(...), upcharge(...), listPrice(...), totalListPrice(...), supportsDefaultCurrency(...), or related export helpers, validate that prices still come out correctly when the CET currency differs from the part's default currency.
The new core part settings support three reset modes:
Switching between these modes converts existing user-modified Ind. Tags to the new keying structure so user overrides are preserved.
When drawings from 16.5 and earlier are loaded, CET now:
sourceId()finalFlattenableKey() now reflects Ind. Tag split stateThe new Ind. Tag split/merge behavior is backed by changes to Part.finalFlattenableKey(). User-modified Ind. Tag state now affects the final key used by core merge and adjustment logic, which means it can change how rows merge and which key is used for single-part adjustments.
Older saved adjustments are remapped through legacyFinalFlattenableKey() where possible, but extension developers should still test any part-adjustment workflows that depend on persisted adjustment keys, especially when user-modified Ind. Tags are present.
If your part logic is implemented through PartProxy, note that the new finalizeFlattenbleKey() hook is now part of that flattenable-key flow. Proxy code can participate directly in the finalization step instead of only relying on earlier key-generation behavior.
Package count is now treated as shared core part metadata instead of only being surfaced by package-specific part types. Part.pkgCount(), PartData.pkgCount(), and Part.generatePartDetails(...) now carry that value through core column logic and PMX/export detail generation.
If your package previously treated package count as package-specific behavior, review any custom part-data or export code that should now participate in the shared core package-count flow.
User-defined tag text is now stored off the default ItemTagInfo key instead of replacing it directly. If your extension parses or persists user-tag keys manually, update that logic to use cItemTagInfoDelim = ":::" and the UserTagInfoHolder APIs instead of the old inline key format.
UserTagInfoHolder improves restoration behaviorUser-modified Ind. Tag info is now centralized on the snapper through UserTagInfoHolder. This gives CET a stable place to preserve explicit overrides while resetting Ind. Tags, switching reset behaviors, and migrating older drawings, which is much cleaner than the previous restoration flow that relied on pure key matching against globally stored modifications.
sourceId() is now the stable identity used by multiple migration pathsThe same sourceId() value now drives:
itemTagKey())specialsKey())cm.abstract.partIf your custom Part subclass needs stable user-modified Ind. Tags or annotations across part-number changes, or participates in query import/customization flows, make sure sourceId() returns a stable per-logical-part identifier.
Snapper.fetchParts()The new addition/override workflow is applied during the owner's fetchParts() call.
The important order in cm.core.Snapper.fetchParts(...) is:
beforeGetParts(env)getParts(env, visited)afterGetParts(env)migratePartSpecials(localParts) // NEWapplyCustomParts(this, localParts) // NEWapplyCustomParts(...) then does two things for the current owner:
getAdditionalParts(this), which generates each added part from its PartCreatorlocalPartsOverrides are matched by stable part identity. The replacement loop starts from each part's sourceId(), sets overridePart.setOveriddenPartKey(part.sourceId()), and can continue while another override exists for the replacement key.
The practical migration consequence is that additions and overrides only affect the current snapper's local parts before child-part collection. If you are debugging why a customization does or does not appear in calculations, this fetchParts() ordering is the first place to check.
CET 17.0 introduces standard specials as a separate internal-special concept alongside ordinary user specials. customizationStates(...) only treats true user specials as the special customization state, and callers can use ignoreStandardSpecial=true in containsSpecial(...) / containsAnySpecials(...) when they want user-facing state such as row highlighting to ignore standard specials.
Related UI such as Calculations and the query grid do not highlight rows just because a standard special exists.
For 17.0-created data, PartSpecialHolder should now be thought of as the owner-level holder for:
PartSpecialOlder drawings can still load legacy option specials from this holder until a package-specific migrator moves them into OptionSpecialHolder, but new code should not treat PartSpecialHolder.specials() as the long-term storage location for option specials.
PartSpecialMigrator provides the shared migration pattern, but packages with custom historical key formats still need to opt in and provide the right subclass or override logic through the snapper's partSpecialMigrator prop/default. If your extension changed part identity before 17.0, test loading older drawings with specials, overrides, additions, and option specials rather than assuming the default oldPartSpecialsKey(...) implementation is sufficient.
The new importer packages are mostly compile-time additions, but they also change how query import resolves formats and where format-specific customization now plugs in.
The query import flow in cm.core.part.query now asks QueryDialogBehavior.getImporter(str suffix) for a PartImporter and QueryDialogBehavior.getImporterEnv(str suffix) for an optional custom environment. The default mapping is:
sif -> PartSIFImporterpmx -> PartPMXImporterxml -> PartOFDAImporterIf your package needs a different part type or different per-record behavior, override getImporterEnv(...) and return a custom PartImporterEnv subclass instead of replacing the whole UI flow.
All three built-in importers follow the same environment lifecycle:
validFile(file) checks readability and suffix support.beginImport(file, parts) resets environment state.import(data, parts) is called for each parsed record.endImport(file, parts) flushes any remaining state.That means format-specific customization should normally live in the environment type, especially in createPart(...), record import overrides, and flush/finalization hooks.
Part hooksPartOFDAImporter parses OFDA XML into <OrderLineItem> tags, and PartOFDAImporterEnv calls Part.importOFDAOrderLineItemTag(...) for each imported part. From there, Part dispatches to the more specific OFDA hooks such as importOFDAPriceTag(...), importOFDASpecItemTag(...), and importOFDACatalogTag(...).
Packages that need custom OFDA import behavior should therefore override the relevant Part import hook instead of trying to reimplement the XML traversal from scratch.
The query dialog now opens as a resizable window with minimum and maximum bounds. A right-side import pane can be opened from the top controls, and the dialog layout now adapts to top, data, bottom, and right subwindows.
If you have automation, screenshots, or tests that assume the old fixed layout, verify them against the new resizable dialog.
The query dialog now supports importing parts from supported file types and dragging them into the grid.
Depending on the drop location and modifier state, imported parts can be inserted as additions or applied as overrides.
For product-part option import and option-row behavior, see the corresponding cm.abstract.part.query migration notes.
Rows are also highlighted by customization type:
If you have custom code around row selection, grid rendering, or part customization state, verify that it behaves correctly with added and overridden rows.
The query dialog and import panel now expose help buttons backed by QuerySubWindow.showHelpDialog() and per-window getHelpText() implementations. If you replace or subclass QueryControlWindow or QueryImportWindow, verify that your layout and control callbacks account for the new cHelpBtnKey / helpButton path.
When an imported part has inconsistent pricing data, the user can now be prompted to either use the calculated list price or apply the imported list price as a manual adjustment. If you have workflows that depend on imported list prices being accepted without prompting, review the new validation behavior.
The imported-part workflow relies on Part.sourceId()-based identity and the new Part addition/override APIs from cm.core.part.
If your custom parts do not provide a stable source id, imported additions, overrides, specials, or annotations may not map back to the intended logical part.
PartMakeSpecialDialog now converts the amount shown in the dialog to calculation currency for display, and converts it back when generating the saved PartSpecial.
If you compare displayed amounts to stored part-main-currency values, account for this conversion.
Extensions that create part tags and part tag categories on a new drawing using hooks such as createWorldHooks() and selectWorldHooks() are required to assign a fixed unique ID onto the Extension's default created custom PartTags and PartTagCategorys. Otherwise, loading CollabPro projects might result in duplicated part tag categories.
Improved UV mapping so that textures get applied uniformly across the mesh at a UV-to-world scale of 1 UV unit = 1m. This change should generally not require any migration effort, unless your extension has taken measure to counteract the previously incorrect UV mapping. Please note that this change does not affect ClosedCylinder3D.
Below are comparisons of how it looked before (left) and how it looks after the change (right).
{ width=400px }
{ width=400px }
Improved UV mapping so that textures get applied uniformly across the mesh at a UV-to-world scale of 1 UV unit = 1m. This change should generally not require any migration effort, unless your extension has taken measure to counteract the previously incorrect UV mapping.
Below are comparisons of how it looked before (left) and how it looks after the change (right).
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
Corrected the UV mapping of Rect3D's constructed with the type rect3DtypeWH. Previously, the width and depth component of the UV coordinates were swapped. This change should generally not require any migration effort, unless your extension has taken measure to counteract the previously incorrect UV mapping.
Below is a comparison of how it looked before (left) and how it looks after the change (right).
{ width=400px }
Previously, rayMeshToleranceIntersect and cpp_rayMeshToleranceIntersect returned the point on the mesh that is closest to rayOrigin. It now returns the point on the mesh that is closest to the infinite line starting in rayOrigin with direction rayDir. If multiple points on the mesh have the same distance to that line, it will now choose among those the one that is closest to rayOrigin. This change affects, e.g., View3D.looseObjectIntersectionsAt, used in FuzzyAnimationHeapCandidatePicker.
Previously, the inchesS(double v, bool showUnit, lcid local, int decimals, unitMagnitude magnitude) function would disregard the decimals value. This behavior has been updated to respect that argument.
Previously, extend public int compare(REDPick a, REDPick b) { would compare a and b solely by their distance to the camera.
In the new version, it first compares the distance to the pick line; if those distances are equal, it falls back to the previous behavior of comparing their distance to the camera.
This new distance in REDPick, public double raySqrD = -1; is -1 by default and needs to be set manually if you call the REDPick constructor directly. Picks that have not set this value will be compared to other picks by old behavior.
The location of saved preferences has changed from "
When loading saved UserSettings (such as RtSettings and CoreSettings) CET looks for the file in the folder in "\CET Preferences\
Changed the origin from the bottom-left corner to the bottom-center of the cube.
Print Reports now has a summation-label setting for printed summary sum lines. The quotation list print UI includes a new checkbox for enabling or disabling summation type labels, and print/preview flows now pass that setting through when building summary lines. This applies both to quotation list printing and to calculation article view preview/print flows.
For 17.0, the setting defaults to enabled:
QuotationListChapterCreator.showSummationLabels defaults to trueview.summarySettings.getEnabled(cSumLabelSettingKey, default=true)As a result, existing printouts or preview-based tests may show different summary labels than before unless the setting is explicitly turned off. If your code or tests compare printed summary text literally, update them to account for the label suffixes or disable the setting where appropriate.
Improved UV mapping of many snappers in the wall package. In general, all symbols should now apply textures more uniformly across the mesh at a UV-to-world scale of 1 UV unit = 1m. However, smaller inaccuracies may still exist.
This change affect the following symbols (and possibly more):
Below are some sample comparisons of how it looked before (left) and how it looks after the change (right).
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
{ width=400px }
Now also triggers the snapper's click animation to more closely mimic an actual user click. Tests that start failing due to this are recommended be repeated by hand to figure out whether it's a false negative or not.
In 16.5, we introduced dibIcon(..) which loads icon as DibImage instead of MemoryImage. For 17.0, we are replacing the existing icon(..) to behave exactly as dibIcon(..).
Benefits of DibImage:
For existing code that calls icon(..) and expects a MemoryImage, migration will be required.
Common symptoms are:
How to resolve this:
In core, we had 2 common scenarios.
// Previous logic only handles MemoryImage. byte beforeBlend = 255; if (image as MemoryImage) { beforeBlend = image.blend; image.blend = 100; } image.transparentDraw(c.hdc, imgPos); if (image as MemoryImage) image.blend = beforeBlend; // New logic now handles MemoryImage, DibImage and SvgImage. byte beforeBlend = image.blend; image.blend = 100; image.transparentDraw(c.hdc, imgPos); image.blend = beforeBlend;
// Previous logic only handles MemoryImage and Icon if (limb as SnapperLimb) { if (limb.image in MemoryImage or limb.image in Icon) { button = snapperImage(window, label, limb, limb.src); } } else if (limb as AnimationLimb or limb.image in Icon) { if (limb.image in MemoryImage) { button = animationImage(window, label, limb, limb.src); } } // New logic now handles MemoryImage, Icon, DibImage and SvgImage if (limb as SnapperLimb) { if (limb.image in MemoryImage or limb.image in Icon or limb.image in DibImage or limb.image in SvgImage) { button = snapperImage(window, label, limb, limb.src); } } else if (limb as AnimationLimb) { if (limb.image in MemoryImage or limb.image in Icon or limb.image in DibImage or limb.image in SvgImage) { button = animationImage(window, label, limb, limb.src); } }
In 16.5 Minor:
For 17.0 Major:
If you spot issues loading your svg icons, you can pass newSvgRenderer=false when you call icon(..)
// New svg renderer causing issues icon("myIcon", #:pkg) icon("myIcon", false, #:pkg) vectorImage(url); // Fallback to svg renderer used in CET 16.5 icon("myIcon", #:pkg, newSvgRenderer=false) icon("myIcon", false, #:pkg, newSvgRenderer=false) vectorImage(url, newSvgRenderer=false);
As we now have better SVG rendering capabilities in CET, IconFinder will search for SVG first before other suffixes.
Old: public str[] suffixSearchPriority = [".png", ".bmp", ".cmpng", ".cmbmp", ".svg"]; New: public str[] suffixSearchPriority = [".svg", ".png", ".bmp", ".cmpng", ".cmbmp"];
Fika's migration support depends on the shared cm.core.Snapper discovery path:
Snapper.loaded1() calls initPartSpecialMigrator(formatter)getPartSpecialMigrator(init=true) resolves the snapper's partSpecialMigrator propFikaDsPartSpecialMigrator is marked pending for older drawingsSnapper.migratePartSpecials(parts) invokes the actual migrationIf the snapper does not expose the partSpecialMigrator prop, older Fika part or option specials saved under historical keys will not be migrated just because FikaDsPartSpecialMigrator exists in the package.
Starting from 17.0, CET login now uses Single Sign-on (SSO), which is a browser based login. Also, login credentials are now shared across all CET developer workspaces, so logging in to one workspace will automatically authenticate you in the other workspaces.
Previously, CET stored user credentials in runtime settings—an approach we are now improving as part of our move to SSO. With the introduction of SSO, authentication is transitioning from user-managed preferences to Auth0.
For now, legacy login remains in use for automated testing, as well as build and upload steps in CI/CD pipelines. As part of phase 1 of the SSO rollout, we are standardizing how credentials are provided by requiring the use of CI/CD environment variables instead of preference files.
What’s changing?
CI/CD runners must now use environment variables for CET login:

Who is affected?
Any CI/CD runners that currently rely on stored CET credentials for:
What you need to do
Update your CI/CD configuration to provide credentials via environment variables (MCAUTOU and MCAUTOP) instead of preference files.
As part of our continuing effort to improve background installs we no longer populate DsCtlgUserData during background installs (runningInInstallAndCloseMode) as it has the potential to cause concurrency issues with the main CET process of the user. This should have no tangible effects as catalog data is handled by the main CET process. See https://support.configura.com/hc/en-us/articles/360053487154-extension-cm for more details. But as good practice extensions should continue to avoid initializing, registering, or accessing catalog data during extension initialize, but if absolutely needed should filter these operations using the runningInInstallAndCloseMode flag.
Previously DsPData used G2 properties by default for its base class. This has now been changed so that subclasses of DsPData will also use G2 properties by default as well.
Old: extend public bool usingG2() { if (blockDataProxy == 0 and proxy) return proxy.usingG2(DsPDataProxyEnv(this)); return (this.class.toS in {"DsPData", "DcPData", "DcPRenderData"} and (!proxy or proxy.class.toS == "DsPDataProxy")); }
New: extend public bool usingG2() { if (blockDataProxy == 0 and proxy) return proxy.usingG2(DsPDataProxyEnv(this)); return true; }
To override the default behavior, override the usingG2 method in your DsPData subclass, or register a DsPDataProxy and override the public bool usingG2(DsPDataProxyEnv env) method to return false.
Previously IFC import did not account for the mapping target of an IfcMappedRepresentation (only the mapping source's mapping origin) when calculating the IFC snapper's transform which resulted in multiple snappers being placed in the same location on import. Updated this so now the mapping target transform is applied onto the mapping source's mapping origin's transform on import to determine its position relative to the IFC project.
Improved IFC export to include mezzanines, and user placed floors when the "Include architectural objects on exporting" setting is enabled.
This change is closely related to the new electrical system introduced in custom.fika, but the reusable office-side surface in 17.0 is intentionally much smaller: cm.abstract.office now provides the shared node type, while product packages own the actual electrical-system behavior.
CET 17.0 introduces a new standard-special system in cm.core.part. Standard specials are internal specials rather than user-created specials, so part rows that only contain standard specials are not highlighted as user-special rows in Calculations.
This follows the new Part.containsAnySpecials(ignoreStandardSpecial=true) behavior used by calculation row highlighting. Implementers should treat that as part of the intended standard-special behavior, not as a loss of ordinary special highlighting.
Summary templates now persist enabled/disabled summary settings in addition to the summary adjustment sequence.
When creating a new summary template from a view, the summationLabels setting is copied into the saved template.
The quotation list chapter UI now exposes a new showSummationLabels checkbox.
The control is enabled only when showSummary is enabled.