diff --git a/js/common/model/BaseModel.ts b/js/common/model/BaseModel.ts index d8ddd282..3dafb219 100644 --- a/js/common/model/BaseModel.ts +++ b/js/common/model/BaseModel.ts @@ -23,10 +23,11 @@ import optionize from '../../../../phet-core/js/optionize.js'; import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import Stopwatch from '../../../../scenery-phet/js/Stopwatch.js'; import TimeSpeed from '../../../../scenery-phet/js/TimeSpeed.js'; -import Tandem from '../../../../tandem/js/Tandem.js'; import gasProperties from '../../gasProperties.js'; import GasPropertiesConstants from '../GasPropertiesConstants.js'; import TimeTransform from './TimeTransform.js'; +import PickRequired from '../../../../phet-core/js/types/PickRequired.js'; +import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; // constants const MODEL_VIEW_SCALE = 0.040; // number of pixels per pm @@ -41,7 +42,7 @@ type SelfOptions = { stopwatchPosition?: Vector2; }; -export type BaseModelOptions = SelfOptions; +export type BaseModelOptions = SelfOptions & PickRequired; export default class BaseModel implements TModel { @@ -64,7 +65,7 @@ export default class BaseModel implements TModel { public readonly stopwatch: Stopwatch; - protected constructor( tandem: Tandem, providedOptions?: BaseModelOptions ) { + protected constructor( providedOptions: BaseModelOptions ) { const options = optionize()( { @@ -87,11 +88,11 @@ export default class BaseModel implements TModel { } ); this.isPlayingProperty = new BooleanProperty( true, { - tandem: tandem.createTandem( 'isPlayingProperty' ) + tandem: options.tandem.createTandem( 'isPlayingProperty' ) } ); this.timeSpeedProperty = new EnumerationProperty( TimeSpeed.NORMAL, { - tandem: tandem.createTandem( 'timeSpeedProperty' ) + tandem: options.tandem.createTandem( 'timeSpeedProperty' ) } ); this.timeTransform = TimeTransform.NORMAL; @@ -107,7 +108,7 @@ export default class BaseModel implements TModel { range: new Range( 0, GasPropertiesConstants.MAX_TIME ), units: 'ps' }, - tandem: tandem.createTandem( 'stopwatch' ) + tandem: options.tandem.createTandem( 'stopwatch' ) } ); } diff --git a/js/common/model/IdealGasLawModel.ts b/js/common/model/IdealGasLawModel.ts index 24ebcb1b..ad251b08 100644 --- a/js/common/model/IdealGasLawModel.ts +++ b/js/common/model/IdealGasLawModel.ts @@ -38,12 +38,21 @@ import PressureModel from './PressureModel.js'; import TemperatureModel from './TemperatureModel.js'; type SelfOptions = { - leftWallDoesWork?: boolean; // does the container's left wall do work on particles? - holdConstant?: HoldConstant; + + // Does the container's left wall do work on particles? + leftWallDoesWork?: boolean; + + // Whether the screen has a collision counter. hasCollisionCounter?: boolean; + + // Whether the screen has the 'Hold Constant' feature. + hasHoldConstantFeature?: boolean; + + // Initial value for holdConstantProperty. + holdConstant?: HoldConstant; }; -type IdealGasLawModelOptions = SelfOptions; +export type IdealGasLawModelOptions = SelfOptions & BaseModelOptions; type OopsEmitters = { @@ -99,54 +108,55 @@ export default class IdealGasLawModel extends BaseModel { // named oopsEmitters. public readonly oopsEmitters: OopsEmitters; - public constructor( tandem: Tandem, providedOptions?: IdealGasLawModelOptions ) { + public constructor( providedOptions: IdealGasLawModelOptions ) { const options = optionize()( { // SelfOptions leftWallDoesWork: false, - holdConstant: 'nothing', - hasCollisionCounter: true + hasCollisionCounter: true, + hasHoldConstantFeature: false, + holdConstant: 'nothing' }, providedOptions ); - super( tandem ); + super( options ); this.holdConstantProperty = new StringUnionProperty( options.holdConstant, { validValues: HoldConstantValues, - tandem: tandem.createTandem( 'holdConstantProperty' ), + tandem: options.hasHoldConstantFeature ? options.tandem.createTandem( 'holdConstantProperty' ) : Tandem.OPT_OUT, phetioReadOnly: true, phetioDocumentation: 'Determines which quantity will be held constant.' } ); this.heatCoolFactorProperty = new NumberProperty( 0, { range: new Range( -1, 1 ), - tandem: tandem.createTandem( 'heatCoolFactorProperty' ), - phetioReadOnly: true, + tandem: options.tandem.createTandem( 'heatCoolFactorProperty' ), + phetioReadOnly: options.hasHoldConstantFeature, // With the Hold Constant feature, the sim animates this Property. phetioDocumentation: 'Amount of heat or cool applied to particles in the container. ' + '-1 is maximum cooling, +1 is maximum heat, 0 is off' } ); this.particleParticleCollisionsEnabledProperty = new BooleanProperty( true, { - tandem: tandem.createTandem( 'particleParticleCollisionsEnabledProperty' ), + tandem: options.tandem.createTandem( 'particleParticleCollisionsEnabledProperty' ), phetioDocumentation: 'Determines whether collisions between particles are enabled.' } ); this.container = new IdealGasLawContainer( { leftWallDoesWork: options.leftWallDoesWork, - tandem: tandem.createTandem( 'container' ) + tandem: options.tandem.createTandem( 'container' ) } ); this.particleSystem = new ParticleSystem( () => this.temperatureModel.getInitialTemperature(), this.particleParticleCollisionsEnabledProperty, this.container.particleEntryPosition, - tandem.createTandem( 'particleSystem' ) + options.tandem.createTandem( 'particleSystem' ) ); this.temperatureModel = new TemperatureModel( this.particleSystem.numberOfParticlesProperty, // N () => this.particleSystem.getAverageKineticEnergy(), // KE - tandem.createTandem( 'temperatureModel' ) + options.tandem.createTandem( 'temperatureModel' ) ); this.pressureModel = new PressureModel( @@ -155,7 +165,7 @@ export default class IdealGasLawModel extends BaseModel { this.container.volumeProperty, // V this.temperatureModel.temperatureProperty, // T () => { this.container.blowLidOff(); }, - tandem.createTandem( 'pressureModel' ) + options.tandem.createTandem( 'pressureModel' ) ); this.collisionDetector = new CollisionDetector( @@ -168,7 +178,7 @@ export default class IdealGasLawModel extends BaseModel { if ( options.hasCollisionCounter ) { this.collisionCounter = new CollisionCounter( this.collisionDetector, { position: new Vector2( 40, 15 ), // view coordinates! determined empirically - tandem: tandem.createTandem( 'collisionCounter' ), + tandem: options.tandem.createTandem( 'collisionCounter' ), visible: true } ); } diff --git a/js/common/view/BaseScreenView.ts b/js/common/view/BaseScreenView.ts index 3c1dfa95..bb0a4ebc 100644 --- a/js/common/view/BaseScreenView.ts +++ b/js/common/view/BaseScreenView.ts @@ -11,17 +11,17 @@ import optionize from '../../../../phet-core/js/optionize.js'; import { Node } from '../../../../scenery/js/imports.js'; import ResetAllButton from '../../../../scenery-phet/js/buttons/ResetAllButton.js'; import TimeControlNode from '../../../../scenery-phet/js/TimeControlNode.js'; -import Tandem from '../../../../tandem/js/Tandem.js'; import gasProperties from '../../gasProperties.js'; import GasPropertiesColors from '../GasPropertiesColors.js'; import GasPropertiesConstants from '../GasPropertiesConstants.js'; import BaseModel from '../model/BaseModel.js'; +import PickRequired from '../../../../phet-core/js/types/PickRequired.js'; type SelfOptions = { hasSlowMotion?: boolean; }; -export type BaseScreenViewOptions = SelfOptions; +export type BaseScreenViewOptions = SelfOptions & PickRequired; export default abstract class BaseScreenView extends ScreenView { @@ -33,7 +33,7 @@ export default abstract class BaseScreenView extends ScreenView { // subclass is responsible for pdomOrder protected readonly resetAllButton: Node; - protected constructor( model: BaseModel, tandem: Tandem, providedOptions?: BaseScreenViewOptions ) { + protected constructor( model: BaseModel, providedOptions?: BaseScreenViewOptions ) { const options = optionize()( { @@ -41,8 +41,7 @@ export default abstract class BaseScreenView extends ScreenView { hasSlowMotion: false, // ScreenViewOptions - isDisposable: false, - tandem: tandem + isDisposable: false }, providedOptions ); super( options ); @@ -77,7 +76,7 @@ export default abstract class BaseScreenView extends ScreenView { } } }, - tandem: tandem.createTandem( 'timeControlNode' ), + tandem: options.tandem.createTandem( 'timeControlNode' ), phetioEnabledPropertyInstrumented: false // Controlled by the sim. } ); this.addChild( this.timeControlNode ); @@ -87,7 +86,7 @@ export default abstract class BaseScreenView extends ScreenView { listener: () => { this.reset(); }, right: this.layoutBounds.maxX - GasPropertiesConstants.SCREEN_VIEW_X_MARGIN, bottom: this.layoutBounds.maxY - GasPropertiesConstants.SCREEN_VIEW_Y_MARGIN, - tandem: tandem.createTandem( 'resetAllButton' ) + tandem: options.tandem.createTandem( 'resetAllButton' ) } ); this.addChild( this.resetAllButton ); } diff --git a/js/common/view/IdealGasLawScreenView.ts b/js/common/view/IdealGasLawScreenView.ts index c4149d18..24c37ba0 100644 --- a/js/common/view/IdealGasLawScreenView.ts +++ b/js/common/view/IdealGasLawScreenView.ts @@ -85,7 +85,6 @@ export default class IdealGasLawScreenView extends BaseScreenView { protected constructor( model: IdealGasLawModel, particleTypeProperty: StringUnionProperty, widthVisibleProperty: Property, - tandem: Tandem, providedOptions?: IdealGasLawScreenViewOptions ) { const options = optionize()( { @@ -95,7 +94,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { oopsDialogsTandem: null }, providedOptions ); - super( model, tandem, options ); + super( model, options ); const containerViewPosition = model.modelViewTransform.modelToViewPosition( model.container.position ); @@ -161,12 +160,12 @@ export default class IdealGasLawScreenView extends BaseScreenView { model.holdConstantProperty, this.visibleBoundsProperty, { resizeGripColor: options.resizeGripColor, resizeHandleIsPressedListener: resizeHandleIsPressedListener, - tandem: tandem.createTandem( 'containerNode' ) + tandem: options.tandem.createTandem( 'containerNode' ) } ); // Return Lid button const returnLidButton = new ReturnLidButton( model.container, { - tandem: tandem.createTandem( 'returnLidButton' ) + tandem: options.tandem.createTandem( 'returnLidButton' ) } ); returnLidButton.boundsProperty.link( bounds => { returnLidButton.right = model.modelViewTransform.modelToViewX( model.container.right - model.container.openingRightInset ) - 30; @@ -179,7 +178,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { visibleProperty: widthVisibleProperty } ); - const bicyclePumpNodeTandem = tandem.createTandem( 'bicyclePumpNode' ); + const bicyclePumpNodeTandem = options.tandem.createTandem( 'bicyclePumpNode' ); // Radio buttons for selecting particle type const particleTypeRadioButtonGroup = new ParticleTypeRadioButtonGroup( particleTypeProperty, @@ -234,7 +233,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { // Thermometer const thermometerNode = new GasPropertiesThermometerNode( model.temperatureModel.thermometer, thermometerListboxParent, { - tandem: tandem.createTandem( 'thermometerNode' ) + tandem: options.tandem.createTandem( 'thermometerNode' ) } ); thermometerNode.boundsProperty.link( bounds => { thermometerNode.centerX = containerNode.right - 50; @@ -246,7 +245,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { // Pressure Gauge const pressureGaugeNode = new PressureGaugeNode( model.pressureModel.pressureGauge, pressureGaugeListboxParent, { - tandem: tandem.createTandem( 'pressureGaugeNode' ) + tandem: options.tandem.createTandem( 'pressureGaugeNode' ) } ); pressureGaugeNode.boundsProperty.link( bounds => { pressureGaugeNode.left = containerNode.right - 2; @@ -275,14 +274,14 @@ export default class IdealGasLawScreenView extends BaseScreenView { model.temperatureModel.temperatureProperty, { left: heaterCoolerNodeLeft, bottom: this.layoutBounds.bottom - GasPropertiesConstants.SCREEN_VIEW_Y_MARGIN, - tandem: tandem.createTandem( 'heaterCoolerNode' ) + tandem: options.tandem.createTandem( 'heaterCoolerNode' ) } ); // Button to erase all particles from container const eraseParticlesButton = new EraseParticlesButton( model.particleSystem, { right: containerNode.right, top: containerWidthNode.bottom + 5, - tandem: tandem.createTandem( 'eraseParticlesButton' ) + tandem: options.tandem.createTandem( 'eraseParticlesButton' ) } ); // Common parent for all tools, so we can move the selected tool to the front without affecting other things. @@ -293,7 +292,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { if ( model.collisionCounter ) { const collisionCounterListboxParent = new Node(); collisionCounterNode = new CollisionCounterNode( model.collisionCounter, collisionCounterListboxParent, this.visibleBoundsProperty, { - tandem: tandem.createTandem( 'collisionCounterNode' ) + tandem: options.tandem.createTandem( 'collisionCounterNode' ) } ); toolsParent.addChild( collisionCounterNode ); toolsParent.addChild( collisionCounterListboxParent ); @@ -302,7 +301,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { // Stopwatch const stopwatchNode = new GasPropertiesStopwatchNode( model.stopwatch, { dragBoundsProperty: this.visibleBoundsProperty, - tandem: tandem.createTandem( 'stopwatchNode' ) + tandem: options.tandem.createTandem( 'stopwatchNode' ) } ); toolsParent.addChild( stopwatchNode ); @@ -346,7 +345,7 @@ export default class IdealGasLawScreenView extends BaseScreenView { // OopsDialog when maximum temperature is exceeded. const oopsMaximumTemperatureDialog = new GasPropertiesOopsDialog( GasPropertiesStrings.oopsMaximumTemperatureStringProperty, { - tandem: ( options.oopsDialogsTandem || tandem ).createTandem( 'oopsMaximumTemperatureDialog' ), + tandem: ( options.oopsDialogsTandem || options.tandem ).createTandem( 'oopsMaximumTemperatureDialog' ), phetioDocumentation: 'Displayed when the maximum Temperature is reached. To recover, all particles are removed. ' + 'If Hold Constant was set to anything other than None or Volume, it is set to None.' } ); diff --git a/js/diffusion/model/DiffusionModel.ts b/js/diffusion/model/DiffusionModel.ts index d0431b46..062a57c0 100644 --- a/js/diffusion/model/DiffusionModel.ts +++ b/js/diffusion/model/DiffusionModel.ts @@ -76,13 +76,15 @@ export default class DiffusionModel extends BaseModel { public constructor( tandem: Tandem ) { - super( tandem, { + super( { // Offset of the model's origin, in view coordinates. Determines where the container's bottom-right corner is. modelOriginOffset: new Vector2( 670, 520 ), // Stopwatch initial position (in view coordinates!), determined empirically. - stopwatchPosition: new Vector2( 60, 50 ) + stopwatchPosition: new Vector2( 60, 50 ), + + tandem: tandem } ); this.particles1 = []; diff --git a/js/diffusion/view/DiffusionScreenView.ts b/js/diffusion/view/DiffusionScreenView.ts index ce06c80a..ad60382d 100644 --- a/js/diffusion/view/DiffusionScreenView.ts +++ b/js/diffusion/view/DiffusionScreenView.ts @@ -36,8 +36,9 @@ export default class DiffusionScreenView extends BaseScreenView { public constructor( model: DiffusionModel, tandem: Tandem ) { - super( model, tandem, { - hasSlowMotion: true // add Normal/Slow radio buttons to the time controls + super( model, { + hasSlowMotion: true, // add Normal/Slow radio buttons to the time controls + tandem: tandem } ); const viewProperties = new DiffusionViewProperties( tandem.createTandem( 'viewProperties' ) ); diff --git a/js/energy/model/EnergyModel.ts b/js/energy/model/EnergyModel.ts index 721f5037..1ba0f4a0 100644 --- a/js/energy/model/EnergyModel.ts +++ b/js/energy/model/EnergyModel.ts @@ -22,9 +22,10 @@ export default class EnergyModel extends IdealGasLawModel { public constructor( tandem: Tandem ) { - super( tandem, { + super( { holdConstant: 'volume', - hasCollisionCounter: false + hasCollisionCounter: false, + tandem: tandem } ); // In case clients attempt to use this feature of the base class diff --git a/js/energy/view/EnergyScreenView.ts b/js/energy/view/EnergyScreenView.ts index bf0f1cec..2ae2f8fb 100644 --- a/js/energy/view/EnergyScreenView.ts +++ b/js/energy/view/EnergyScreenView.ts @@ -34,7 +34,9 @@ export default class EnergyScreenView extends IdealGasLawScreenView { // view-specific Properties const viewProperties = new EnergyViewProperties( tandem.createTandem( 'viewProperties' ) ); - super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, tandem ); + super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, { + tandem: tandem + } ); // Group panels and accordion boxes in the Studio tree. const panelsTandem = tandem.createTandem( 'panels' ); diff --git a/js/explore/model/ExploreModel.ts b/js/explore/model/ExploreModel.ts index 1d267969..96de1ce5 100644 --- a/js/explore/model/ExploreModel.ts +++ b/js/explore/model/ExploreModel.ts @@ -14,9 +14,10 @@ export default class ExploreModel extends IdealGasLawModel { public constructor( tandem: Tandem ) { - super( tandem, { + super( { holdConstant: 'nothing', - leftWallDoesWork: true // moving the left wall does work on particles + leftWallDoesWork: true, // moving the left wall does work on particles + tandem: tandem } ); // In case clients attempt to use this feature of the base class diff --git a/js/explore/view/ExploreScreenView.ts b/js/explore/view/ExploreScreenView.ts index a22a71b7..831e820d 100644 --- a/js/explore/view/ExploreScreenView.ts +++ b/js/explore/view/ExploreScreenView.ts @@ -25,7 +25,9 @@ export default class ExploreScreenView extends IdealGasLawScreenView { // view-specific Properties const viewProperties = new ExploreViewProperties( tandem.createTandem( 'viewProperties' ) ); - super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, tandem ); + super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, { + tandem: tandem + } ); const collisionCounter = model.collisionCounter!; assert && assert( collisionCounter ); diff --git a/js/ideal/IdealScreen.ts b/js/ideal/IdealScreen.ts index a939a566..f6594c93 100644 --- a/js/ideal/IdealScreen.ts +++ b/js/ideal/IdealScreen.ts @@ -19,8 +19,8 @@ import IdealScreenView from './view/IdealScreenView.js'; type SelfOptions = { - // Whether the sim has the panel titled 'Hold Constant' - hasHoldConstantPanel?: boolean; + // Whether the sim has the 'Hold Constant' feature. + hasHoldConstantFeature?: boolean; }; type IdealScreenOptions = SelfOptions & PickOptional; @@ -32,7 +32,7 @@ export default class IdealScreen extends GasPropertiesScreen()( { // SelfOptions - hasHoldConstantPanel: true, + hasHoldConstantFeature: true, // GasPropertiesScreenOptions name: GasPropertiesStrings.screen.idealStringProperty, @@ -41,9 +41,13 @@ export default class IdealScreen extends GasPropertiesScreen new IdealModel( tandem.createTandem( 'model' ) ); - const createView = ( model: IdealModel ) => new IdealScreenView( model, tandem.createTandem( 'view' ), { - hasHoldConstantPanel: options.hasHoldConstantPanel + const createModel = () => new IdealModel( { + hasHoldConstantFeature: options.hasHoldConstantFeature, + tandem: tandem.createTandem( 'model' ) + } ); + const createView = ( model: IdealModel ) => new IdealScreenView( model, { + hasHoldConstantFeature: options.hasHoldConstantFeature, + tandem: tandem.createTandem( 'view' ) } ); super( createModel, createView, options ); diff --git a/js/ideal/model/IdealModel.ts b/js/ideal/model/IdealModel.ts index 1f03b0cb..67718583 100644 --- a/js/ideal/model/IdealModel.ts +++ b/js/ideal/model/IdealModel.ts @@ -7,14 +7,18 @@ * @author Chris Malley (PixelZoom, Inc.) */ -import Tandem from '../../../../tandem/js/Tandem.js'; -import IdealGasLawModel from '../../common/model/IdealGasLawModel.js'; +import IdealGasLawModel, { IdealGasLawModelOptions } from '../../common/model/IdealGasLawModel.js'; import gasProperties from '../../gasProperties.js'; +import { EmptySelfOptions } from '../../../../phet-core/js/optionize.js'; + +type SelfOptions = EmptySelfOptions; + +type IdealModelOptions = SelfOptions & IdealGasLawModelOptions; export default class IdealModel extends IdealGasLawModel { - public constructor( tandem: Tandem ) { - super( tandem ); + public constructor( providedOptions: IdealModelOptions ) { + super( providedOptions ); } } diff --git a/js/ideal/view/IdealScreenView.ts b/js/ideal/view/IdealScreenView.ts index 14c1f140..14867d03 100644 --- a/js/ideal/view/IdealScreenView.ts +++ b/js/ideal/view/IdealScreenView.ts @@ -8,7 +8,6 @@ import optionize from '../../../../phet-core/js/optionize.js'; import { VBox } from '../../../../scenery/js/imports.js'; -import Tandem from '../../../../tandem/js/Tandem.js'; import GasPropertiesColors from '../../common/GasPropertiesColors.js'; import GasPropertiesConstants from '../../common/GasPropertiesConstants.js'; import GasPropertiesOopsDialog from '../../common/view/GasPropertiesOopsDialog.js'; @@ -22,7 +21,9 @@ import IdealViewProperties from './IdealViewProperties.js'; import IdealToolsPanel from './IdealToolsPanel.js'; type SelfOptions = { - hasHoldConstantPanel?: boolean; + + // Whether the sim has the 'Hold Constant' feature. + hasHoldConstantFeature?: boolean; }; type IdealScreenViewOptions = SelfOptions & IdealGasLawScreenViewOptions; @@ -31,14 +32,14 @@ export default class IdealScreenView extends IdealGasLawScreenView { private readonly viewProperties: IdealViewProperties; - public constructor( model: IdealModel, tandem: Tandem, providedOptions?: IdealScreenViewOptions ) { + public constructor( model: IdealModel, providedOptions: IdealScreenViewOptions ) { - const oopsDialogsTandem = tandem.createTandem( 'oopsDialogs' ); + const oopsDialogsTandem = providedOptions.tandem.createTandem( 'oopsDialogs' ); const options = optionize()( { // SelfOptions - hasHoldConstantPanel: true, + hasHoldConstantFeature: false, // IdealScreenViewOptions resizeGripColor: GasPropertiesColors.idealResizeGripColorProperty, @@ -46,20 +47,20 @@ export default class IdealScreenView extends IdealGasLawScreenView { }, providedOptions ); // view-specific Properties - const viewProperties = new IdealViewProperties( tandem.createTandem( 'viewProperties' ) ); + const viewProperties = new IdealViewProperties( options.tandem.createTandem( 'viewProperties' ) ); - super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, tandem, options ); + super( model, viewProperties.particleTypeProperty, viewProperties.widthVisibleProperty, options ); const collisionCounter = model.collisionCounter!; assert && assert( collisionCounter ); // Group panels and accordion boxes in the Studio tree. - const panelsTandem = tandem.createTandem( 'panels' ); + const panelsTandem = options.tandem.createTandem( 'panels' ); const panels = []; let holdConstantPanel; - if ( options.hasHoldConstantPanel ) { + if ( options.hasHoldConstantFeature ) { holdConstantPanel = new HoldConstantPanel( model.holdConstantProperty, model.particleSystem.numberOfParticlesProperty,