Compile Time Changes

class CustomSpecOption

See cm.abstract.part section in 16.5 New Features migration guide for new CustomSpecOption class documentation .

class AbsPart

Old Pricing API

The following functions are now part of the old pricing system. They are still available for backwards compatibility (except basePrice which has been overloaded in core Part). Migration to the new pricing API should happen as soon as possible. The new pricing API is introduced in v16.5 and documented in the cm.core.part.Part documentation.

public double customListPrice(bool includeChildren=false, str currency=null, Space space=null) {}
extend public double specialListPrice(double listPrice) {}
extend public Double baseOptionPrice() {}
extend public Double specialOptionPrice() {}
extend public double optionSpecialUpcharge() {}
extend public Double upcharge() {}

Old: extend public double basePrice() {}
New: extend public double basePrice(bool includeChildren=false, Space space=null, bool translatePrice=true) {}

class CustomOptionSpecial

See cm.abstract.part section in 16.5 New Features migration guide for new CustomOptionSpecial class documentation .

class SpecOption

Changed: extend public double upcharge(ProdPart owner=null) {}

The upcharge method for SpecOption has been extended to support:

  • A broader Part owner parameter (instead of only ProdPart).
  • Currency translation via a new translatePrice flag and Space context.
Old: extend public double upcharge(ProdPart owner=null) {}
New: extend public double upcharge(Part owner=null, Space space=null, bool translatePrice=false) {}

Old → New behavior

Old:

  • Logic:
    • Started from the option’s price.
    • Applied any applicable OptionSpecial adjustments (replace or add).
    • Returned the resulting value.
  • Only accepted ProdPart as the owner.
  • No currency translation was possible.

New:

  • Logic:
    • Same base calculation (price + special adjustments).
    • If translatePrice=true and the owner uses a non-default currency, the result is converted using owner.translatePrice(...).
  • No longer assumes ProdPart owner.

class OptionSpecial

The following fields and methods have been added and changed in the OptionSpecial class:

// FIELDS
Added: public int level;
Added: public int sequence;

// METHODS
Added: public void copy(PartSpecial special) {}
Added: extend public str optStr=(str value) {}
Added: extend public str optDescStr=(str value) {}
Added: extend public int level=(int value) {}
Added: extend public int sequence=(int value) {}
Added: extend public Part originalPart=(Part value) {}
Added: extend public str originalOptStr=(str value) {}

// UPDATED
Old: public constructor(str partNum, str descr, bool priceReplace, double amount|, str optStr, str optDescStr, Part originalPart, str originalOptStr) {}
New: public constructor(str partNum, str descr, bool priceReplace, double amount|, str optStr, str optDescStr, Part originalPart=null, str originalOptStr=null, int level=0, int sequence=-1) {}

public str flattenableKey() {
	StrBuf res(super());
	res << optStr 
		<< optDescStr 
New:	<< level 
New:	<< sequence
		<< originalPart 
		<< originalPart.specialsKey();
	return res.toS();
}

class ProdPart

Changed: final public SpecOption[] specOptions=(SpecOption[] newOptions) {} and extend public void appendSpecOptions(SpecOption[] newOptions) {}

Both specOptions and appendSpecOptions now accept a new boolean parameter invalidateOptionPrice (default: true). This works with the new option price caching mechanism by allowing the system to selectively invalidate the cached option price sum when spec options are modified.

Old: final public SpecOption[] specOptions=(SpecOption[] newOptions) {}
New: final public SpecOption[] specOptions=(SpecOption[] newOptions, bool invalidateOptionPrice=true) {}

Old: extend public void appendSpecOptions(SpecOption[] newOptions) {}
New: extend public void appendSpecOptions(SpecOption[] newOptions, bool invalidateOptionPrice=true) {}
Old → New behavior

Old:

  • Option prices were not cached.
  • Every time option prices were accessed, they were recalculated on the fly based on current spec options.
  • No invalidation step was needed.

New:

  • Option price totals are now cached
  • When spec options are modified, invalidateOptionPriceSum() is triggered to ensure the cache stays correct
  • Developers can pass invalidateOptionPrice=false to skip cache invalidation if they plan to manage it manually or delay recalculation.
Impact
  • Performance improvement: frequent option price sum lookups no longer require full recalculation
  • Cache management: introduces the concept of invalidating/regenerating option price sums as options change.
  • Default safety: with the default true, cache stays consistent automatically — existing usage behaves as expected
  • Advanced optimization: developers can set false during bulk option appends/updates, then explicitly trigger invalidation once, avoiding multiple regenerations.
  • Migration note: developers should be aware that option pricing is no longer computed “live” — results are now cache-backed.

Other ProdPart Changes

  • Added optional bool invalidateWorldPrice parameter to special functions
    • Allows control over invalidation of world pricing when making a special change
    • Originally invalidated world price on every special change
  • Expanded special interfaces to accept PartOptionItems
    • Previously was exclusively accepted SpecOptions
Interfaces
Added: extend public void putOptSpecial(PartOptionItem opt, OptionSpecial special, PropObj s=null, bool invalidateWorldPrice=true) {}

Old: extend public void generateOptAdjustmentSifRows(SpecOption opt, str[] lines) {}
New: extend public void generateOptAdjustmentSifRows(PartOptionItem opt, str[] lines) {}

Old: extend public str optSpecialKey(SpecOption opt) {}
New: extend public str optSpecialKey(PartOptionItem opt) {}

Old: extend public OptionSpecial getOptSpecial(SpecOption opt, PropObj s=null) {}
New: extend public OptionSpecial getOptSpecial(PartOptionItem opt, PropObj s=null) {}

Old: extend public void removeOptSpecial(SpecOption opt, PropObj s=null) {}
New: extend public void removeOptSpecial(PartOptionItem opt, PropObj s=null, bool invalidateWorldPrice=true) {}

Old: extend public void removeOptSpecials(PropObj s=null) {}
New: extend public void removeOptSpecials(PropObj s=null, bool invalidateWorldPrice=true) {}

Old: extend public void removeAllSpecials(PropObj s=null) {}
New: extend public void removeAllSpecials(PropObj s=null, bool invalidateWorldPrice=true) {}

class ProdPartGridBuilderEnv

Added: extend public GridCell getOptionRowCell(PartOptionItem opt, int columnIdx, Part part=null) {}

  • Gets option row cell for parameter values
  • Parameters:
    • PartOptionItem opt: Option for row to get cell for (can be null)
    • int columnIdx: Column index to get cell for
    • Part part: Part owning option row to get cell for (can be null)
      • mainly utilized for acquiring option specials data
Behavior
  • If opt is null or column index is out of bounds, returns getDefaultCell
  • Otherwise, returns a cell for the option data associated with the column index
    • Assigns a background color to the cell by calling getCellBrush

Changed: extend public GridCell[] getOptionRowCells(SpecOption opt, ProdPart part=null) {}

Old → New behavior

Old:

  • Directly returned a sequence of GridCells

New:

  • Loops through columns, calling getOptionRowCell for each column and appending it to the returned GridCell sequence
  • Allows for more flexibility when building option row cells

Runtime/Behavior Changes

class ProdPartGridBuilder

Old → New Constructor

Old:

  • Simply assigned the provided env.
  • If env was null, the instance had no default environment.
Old:
public constructor(PartGridBuilderEnv env=null) {
    this.env = env;
}

New:

  • If no environment is passed, it now creates a default ProdPartGridBuilderEnv with buildTotalsRow = true.
  • Ensures a baseline configuration is always available, even without explicit input.
New:
public constructor(PartGridBuilderEnv env=null) {
    this.env = env ?? ProdPartGridBuilderEnv(buildTotalsRow=true);
}

class CustomSpecOption

See cm.abstract.part section in 16.5 New Features migration guide for new CustomSpecOption class documentation .

class AbsPart

Changed: extend public PartInfoTree createInfoTree(SpecOption option, PartInfoTree parent=null) {}

  • AbsPart now returns a SpecOptionInfoTree by default when generating info trees
extend public PartInfoTree createInfoTree(SpecOption option, PartInfoTree parent=null) {
Old:	return null;
New:	return SpecOptionInfoTree(this, option, parent=parent);
}

Old Pricing API

The following functions are now part of the old pricing system. They are still available for backwards compatibility (except basePrice which has been overloaded in core Part). Migration to the new pricing API should happen as soon as possible. The new pricing API is introduced in v16.5 and documented in the cm.core.part.Part documentation.

public double customListPrice(bool includeChildren=false, str currency=null, Space space=null) {}
extend public double specialListPrice(double listPrice) {}
extend public Double baseOptionPrice() {}
extend public Double specialOptionPrice() {}
extend public double optionSpecialUpcharge() {}
extend public Double upcharge() {}

Old: extend public double basePrice() {}
New: extend public double basePrice(bool includeChildren=false, Space space=null, bool translatePrice=true) {}

class AbsBasePricePartColumn

Changed: public Object value(Part part, Space space) {}

The AbsBasePricePartColumn value now calls the new basePrice(..) function on core Part.

Old → New behavior

Old:

  • Checked if part was an AbsPart
  • Returned part.basePrice() if so
  • Returned null if not

New:

  • Returns new basePrice(..) function value directly from core Part

class AbsUpchargePartColumn

Changed: public Object value(Part part, Space space) {}

The AbsUpchargePartColumn value now calls the new optionPriceSum(..) function on core Part.

Old → New behavior

Old:

  • Checked if part was an AbsPart
  • Returned part.upcharge() if so
  • Returned null if not

New:

  • Returns new optionPriceSum(..) function value directly from core Part

class SpecOptionInfoTreeColumn

Changed: public GridCell gridCell(PartInfoTree item, Space space, symbol view=null) {}

Old → New behavior

Old:

  • If there is no value for the column, returns null
  • Otherwise, returns a NameGridCell with the value

New:

  • If there is no value for the column, returns null
  • If the item is a SpecOptionInfoTree, returns a SpecOptionNameGridCell with the items associated value, part, and option
  • Otherwise, returns a NameGridCell with the value
public GridCell gridCell(PartInfoTree item, Space space, symbol view=null) {
	str value = output(item, space);
	if (value.empty()) return null;
	
New:	if (item as SpecOptionInfoTree) {
New:		return SpecOptionNameGridCell(value, item.part, item.specOption, align=left);
New:	}

	return NameGridCell(value, left);
}

class CustomOptionSpecial

See cm.abstract.part section in 16.5 New Features migration guide for new CustomOptionSpecial class documentation .

class SpecOptionInfoTree

Changed: public PartColumn[] columns() {}

Old → New behavior

Old:

  • If _columns field was set, its value was returned
  • Otherwise, super() was returned (which is null)
Old:
public PartColumn[] columns() {
	return _columns ?? super();
}

New:

  • If _columns field was set, its value was returned
  • Otherwise, returns a default set of SpecOption columns
    • specOptionInfoColumn (option code)
    • specOptionDescColumn (option description)
    • specOptionUpchargeColumn (option price)
New:
public PartColumn[] columns() {
	return _columns ?? [PartColumn: specOptionInfoColumn,
						specOptionDescColumn,
						specOptionUpchargeColumn];
}

class ProdPart

Constructor Migration: List Price → Base Price + Option Price Sum

The ProdPart class constructors have been expanded and split to support the new part pricing system (introduced in 16.5). New constructors now accept basePrice and optionPriceSum separately.

Old constructors that use list price remain available but are marked for deprecation in a future version.

Old Constructor
  • constructor(Snapper snapper, str articleCode, str description, double listPrice, …)
  • This constructor uses list price (a single cached price) instead of separating base + options.
  • Still supported in 16.5 for compatibility.
New Constructors (preferred, introduced in 16.5)
  • constructor(Snapper snapper, str articleCode, str description, double basePrice, Double optionPriceSum, …)
  • This constructor uses separate base price and option prices instead of utilizing a single cached list price
  • Preferred in v16.5+
Impact
  • If you use list price constructors today → update to the new basePrice + optionPriceSum pattern
  • See new pricing API documentation in cm.core.part.Part compile-time section for more migration tips

Added: public Double generateOptionPriceSum() {}

Introduces a new method to compute the total option price, explicitly aggregating all SpecOption upcharges, including specials.

Behavior
  • Before:
    • No direct method existed for calculating the total option price.
    • Special handling was inconsistent and often spread across different code paths.
  • Now:
    • Provides a single method that:
      • Iterates through all specOptions().
      • Calls each option’s upcharge(this) for contextual pricing.
      • Returns the summed total as a Double.
Impact
  • Centralizes option price calculation logic.
  • Ensures specials are accounted for in a standardized way.
  • Reduces duplication across pricing code paths.
  • Improves clarity: developers can call one method to retrieve the full option pricing.
  • If you have custom option price logic, override generateOptionPriceSum() in your extended Part class.
  • Invalidate the cache (invalidateOptionPriceSum()) when option state changes to trigger regeneration.

Added: extend public void invalidateSpecOptions(bool invalidateOptionPriceSum=true) {}

The invalidateSpecOptions method is introduced alongside the new SpecOptions caching mechanism.

  • Before: SpecOptions sequence was always rebuilt when accessed.
  • Now: SpecOptions are cached for performance reasons, and this method allows developers to explicitly invalidate (reset) the cache.
  • It also optionally invalidates the cached option price sum, ensuring related derived data stays in sync.
Behavior
  • Sets _specOptions to null, clearing the cache so the sequence will be recomputed on next access.
  • If invalidateOptionPriceSum is true (default), also clears the cached option price sum.
Impact
  • New responsibility for developers:
    • Since SpecOptions are now cached, any change to underlying data that affects them must be followed by a call to invalidateSpecOptions() to prevent stale values
  • Performance improvement:
    • Accessing SpecOptions is now faster due to caching
    • Rebuild happens only when invalidated (see rebuildSpecOptions documentation)
  • Backward compatibility:
    • Code that relied on automatic rebuild will now see cached values
    • Developers must add explicit invalidation calls in update flows where rebuild is required
  • Migration requirement:
    • Review any code paths where SpecOptions can change (e.g., product configuration updates).
    • Insert calls to invalidateSpecOptions() in those paths to ensure cache consistency.
    • If option price sums are affected by the same changes, let the default true flag handle both invalidations. Otherwise, explicitly pass false.

Changed: extend public void appendSpecOptions(SpecOption[] newOptions) {} and extend public void appendSpecOption(SpecOption newOption) {}

Both appendSpecOptions and appendSpecOption now accept a new boolean parameter invalidateOptionPrice (default: true). This works with the new option price caching mechanism by allowing the system to selectively invalidate the cached option price sum when spec options are modified.

Old: extend public void appendSpecOptions(SpecOption[] newOptions) {}
New: extend public void appendSpecOptions(SpecOption[] newOptions, bool invalidateOptionPrice=true) {}

Old: extend public void appendSpecOption(SpecOption newOption) {}
New: extend public void appendSpecOption(SpecOption newOption, bool invalidateOptionPrice=true) {}
Old → New behavior

Old:

  • Option prices were not cached.
  • Every time option prices were accessed, they were recalculated on the fly based on current spec options.
  • No invalidation step was needed.

New:

  • Option price totals are now cached
  • When spec options are modified, invalidateOptionPriceSum() is triggered to ensure the cache stays correct
  • Developers can pass invalidateOptionPrice=false to skip cache invalidation if they plan to manage it manually or delay recalculation.
Impact
  • Performance improvement: frequent option price sum lookups no longer require full recalculation
  • Cache management: introduces the concept of invalidating/regenerating option price sums as options change.
  • Default safety: with the default true, cache stays consistent automatically — existing usage behaves as expected
  • Advanced optimization: developers can set false during bulk option appends/updates, then explicitly trigger invalidation once, avoiding multiple regenerations.
  • Migration note: developers should be aware that option pricing is no longer computed “live” — results are now cache-backed.

Cache SpecOption Changes

With the introduction of custom options in the query dialog, the following changes were made in ProdPart:

  • specOptions() now inserts custom options
    • Inserted at value of CustomSpecOptions sequence value
    • Inserts to end if sequence is -1 (default for CustomSpecOption)
  • Introduced nonCustomSpecOptions()
    • Provides all SpecOptions except CustomSpecOptions
    • Filters out any CustomSpecOption instances while collecting
  • Introduced customSpecOptions()
    • Provides all CustomSpecOptions only.
    • Uses part specials key + PartSpecialHolder lookup to gather user-defined options.
  • Added caching for SpecOptions
    • New field private SpecOption[] _specOptions : stream=null
    • Purpose: cache collects spec options to avoid repeated recollection and insertion
    • Must be invalidated when _partOptions changes
  • specOptions() now cached
    • Old: dynamically built a fresh list of options every call.
    • New: checks _specOptions, rebuilds only if null, otherwise returns cached.
  • Introduced rebuildSpecOptions()
    • Centralizes SpecOption sequence rebuilding
    • Ensures both built-in options and dynamic custom ones are aggregated in a single place
Impact
  • Performance: caching improves efficiency when parts/options are queried frequently
  • Extensibility: system now supports user-defined freeform options (CustomSpecOptions) in addition to standard ones
Interfaces
Added: private SpecOption[] _specOptions : stream=null;
Added: final public SpecOption[] nonCustomSpecOptions() {}
Added: extend public void insertCustomOptions(SpecOption[] ops) {}
Added: extend public SpecOption[] rebuildSpecOptions() {}
Added: extend public void invalidateSpecOptions() {}
Added: extend public CustomSpecOption[] customSpecOptions() {}
Added: extend public CustomOptionSpecial[] getCustomOptionSpecials(PropObj s=null) {}

Exporting Part Attributes via New Interface

With the addition of the new Attribute Note and Description columns in the Calculations dialog, the following changes have been made to ProdPart:

  • Overrode new partToOwnerKey function
    • Uniquely identifies a part to it's owner
      • Ex. table legs with a table top owner could have part-to-owner keys like "leg0", "leg1", etc.
    • Behavior:
      • returns the partSourceID value appended to the super() value
  • Introduced generateSifAnnotationRows function
    • Happens during Configura SIF exports
    • Behavior:
      • loops through Parts PartAnnotations
      • adds SIF lines for each PartAnnotation
        • "AN" for the PartAnnotations note value
        • "AD" for the PartAnnotations description value
  • Introduced generateAttributeData function
    • Happens during PMX exports
    • Behavior:
      • loops through Parts PartAnnotations
      • generates AttributeData objects for each PartAnnotation
      • adds the AttributeData objects to the ItemDatas attributes sequence
  • Introduced initializeAnnotationsFromItemData
    • Happens during PMX imports
    • Behavior:
      • loops through an ItemDatas attributes sequence
      • generates PartAnnotation objects from the AttributeDatas
      • adds the PartAnnotationobjects to thePart`s annotations
// Part-to-Owner key
New: public str partToOwnerKey() {}

// SIF
New: extend public void generateSifAnnotationRows(str[] lines) {}

// PMX
New: extend public void generateAttributeData(ItemData itemData) {}

//PMX Import
New: extend public void initializeAnnotationsFromItemData(ItemData item, ProjectInformation projectInfo) {}

class SpecOptionUpchargeInfoTreeColumn

A new SpecOptionUpchargeInfoTreeColumn has been added to display price values associated with SpecOptions in a PartInfoTree

  • Provides formatting and display logic for the price of each option.
  • Defaults to being hidden in the UI (initialVisibility = #none).
  • Uses a SpecOptionNameGridCell