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.