phetsims / density-buoyancy-common

Common code for the Density and Buoyancy simulations
GNU General Public License v3.0
0 stars 2 forks source link

Improve types for MaterialIdentifier, MaterialName, MaterialNonCustomIdentifier, etc #176

Closed samreid closed 1 month ago

samreid commented 3 months ago

From some REVIEW comments in https://github.com/phetsims/density-buoyancy-common/issues/165

// REVIEW: How do these relate to MaterialName which is the type of material.identifier?
// REVIEW: MK: I don't know how to say this type must be a subset of "keyof Material". Do you?
type MaterialNonCustomIdentifier = 'ALUMINUM' | 'BRICK' | 'COPPER' | 'ICE' | 'PLATINUM' | 'STEEL' | 'STYROFOAM' | 'WOOD' | 'PVC';
type MaterialIdentifier = MaterialNonCustomIdentifier | CustomMaterialName;

const materialToEnum = ( material: Material ): MaterialEnumeration => {
  const enumerationValue = MaterialEnumeration[ ( ( material.identifier as MaterialIdentifier | null ) || CUSTOM_MATERIAL_NAME ) ];

  assert && assert( enumerationValue, 'Unexpected material identifier: ' + material.identifier );
  return enumerationValue;
};

Note that material.identifier is being type asserted as MaterialIdentifier | null even though it is actually MaterialName | null. What is the relationship between MaterialName and MaterialIdentifier? Do we need both? When would that type assertion fail?

samreid commented 2 months ago

@zepumph and I took baby steps in the commit. Next we would like to:

zepumph commented 1 month ago

@samreid and I have a total mess with lots of good progress here in a patch. Upping the priority, and noting that this is a great direction, but exposed underlying problems with the "adjustableMaterial" paradigm for density mystery and B:B explore.

Bugs (that we know about)

  1. Changing materialEnumProperty doesn't udpate customDensityProperty, but changing from the combo box does.
  2. (B:B explore) (a) materialEnumProperty is brick, then phet-io set customDensityProperty to 400 (that of wood). Material stays on Brick. 1. is that buggy in Density Mystery screen (as published in 1.1), 2. what do we do for B:B explore.

next steps:

  1. TODOs
  2. Migration rules
  3. commit point
  4. Factor out bidirectional logic that maps material/materialNames.
```diff Subject: [PATCH] remove unused variable, https://github.com/phetsims/density-buoyancy-common/issues/213 --- Index: js/common/model/MaterialEnumeration.ts =================================================================== diff --git a/js/common/model/MaterialEnumeration.ts b/js/common/model/MaterialEnumeration.ts deleted file mode 100644 --- a/js/common/model/MaterialEnumeration.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ /dev/null (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) @@ -1,29 +0,0 @@ -// Copyright 2024, University of Colorado Boulder - -/** - * This is a subset of the possible Materials that can be used to set the material in Density. - * - * @author Sam Reid (PhET Interactive Simulations) - */ -import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js'; -import Enumeration from '../../../../phet-core/js/Enumeration.js'; -import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; - -export default class MaterialEnumeration extends EnumerationValue { - public static readonly ALUMINUM = new MaterialEnumeration(); - public static readonly BRICK = new MaterialEnumeration(); - public static readonly COPPER = new MaterialEnumeration(); - public static readonly ICE = new MaterialEnumeration(); - public static readonly PLATINUM = new MaterialEnumeration(); - public static readonly STEEL = new MaterialEnumeration(); - public static readonly STYROFOAM = new MaterialEnumeration(); - public static readonly WOOD = new MaterialEnumeration(); - public static readonly PVC = new MaterialEnumeration(); - public static readonly CUSTOM = new MaterialEnumeration(); - - public static readonly enumeration = new Enumeration( MaterialEnumeration, { - phetioDocumentation: 'Material values' - } ); -} - -densityBuoyancyCommon.register( 'MaterialEnumeration', MaterialEnumeration ); \ No newline at end of file Index: js/common/view/MaterialControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/MaterialControlNode.ts b/js/common/view/MaterialControlNode.ts --- a/js/common/view/MaterialControlNode.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/common/view/MaterialControlNode.ts (date 1720460628432) @@ -79,14 +79,14 @@ materials = materials.filter( material => !material.hidden ); } - const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier! ) ]; + const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier ) ]; options.supportCustomMaterial && materialNames.push( CUSTOM_MATERIAL_NAME ); // TODO: https://github.com/phetsims/density-buoyancy-common/issues/163 re-evaluate eliminating or moving this code // after addressing #163 const comboBoxMaterialProperty = new DynamicProperty( new Property( materialProperty ), { bidirectional: true, - map: ( material: Material ) => material.custom ? CUSTOM_MATERIAL_NAME : material.identifier!, + map: ( material: Material ) => material.identifier, inverseMap: ( materialName: MaterialName ): Material => { if ( materialName === CUSTOM_MATERIAL_NAME ) { // Handle our minimum volume if we're switched to custom (if needed) @@ -97,7 +97,7 @@ } ); } else { - return Material[ materialName ]; + return Material.getMaterial( materialName ); } }, reentrant: true, @@ -116,7 +116,7 @@ const materialToItem = ( material: Material ) => { return { - value: material.identifier!, + value: material.identifier, createNode: () => new Text( material.nameProperty, { font: DensityBuoyancyCommonConstants.COMBO_BOX_ITEM_FONT, maxWidth: comboMaxWidth @@ -126,6 +126,7 @@ }; }; + // TODO: parametrize to MaterialName const comboBox = new ComboBox( comboBoxMaterialProperty, [ ...regularMaterials.map( materialToItem ), ...( options.supportCustomMaterial ? [ { Index: js/common/view/BlockControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/BlockControlNode.ts b/js/common/view/BlockControlNode.ts --- a/js/common/view/BlockControlNode.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/common/view/BlockControlNode.ts (date 1720463008089) @@ -12,17 +12,16 @@ import Cuboid from '../model/Cuboid.js'; import MaterialMassVolumeControlNode, { MaterialMassVolumeControlNodeOptions } from './MaterialMassVolumeControlNode.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; -import Material from '../model/Material.js'; +import Material, { CUSTOM_MATERIAL_NAME } from '../model/Material.js'; import NumberControl, { NumberControlOptions } from '../../../../scenery-phet/js/NumberControl.js'; import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js'; import { combineOptions } from '../../../../phet-core/js/optionize.js'; import PrecisionSliderThumb from './PrecisionSliderThumb.js'; import UnitConversionProperty from '../../../../axon/js/UnitConversionProperty.js'; import Range from '../../../../dot/js/Range.js'; -import MaterialEnumeration from '../model/MaterialEnumeration.js'; type SelfOptions = { - mysteryMaterials?: Material[]; + mysteryMaterials: Material[]; // Provide empty list to opt out. }; export type BlockControlNodeOptions = MaterialMassVolumeControlNodeOptions & SelfOptions; @@ -30,11 +29,9 @@ export default class BlockControlNode extends MaterialMassVolumeControlNode { public constructor( cuboid: Cuboid, listParent: Node, numberControlMassPropertyFeatured: boolean, options: BlockControlNodeOptions ) { - // Add mystery materials at the end, if provided. Note custom will appear before mystery materials. This is handled - // elsewhere, see https://github.com/phetsims/density-buoyancy-common/issues/161 - const materials = options.mysteryMaterials ? - Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ) : - Material.SIMPLE_MASS_MATERIALS; + // Add mystery materials at the end. Note custom will appear before mystery materials. This is handled + // by the supertype, see https://github.com/phetsims/density-buoyancy-common/issues/161 + const materials = Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ); super( cuboid.materialProperty, cuboid.massProperty, cuboid.volumeProperty, materials, cubicMeters => cuboid.updateSize( Cube.boundsFromVolume( cubicMeters ) ), listParent, numberControlMassPropertyFeatured, options ); @@ -46,10 +43,6 @@ const densityNumberControlTandem = options.tandem.createTandem( 'densityNumberControl' ); const customDensityProperty = cuboid.customDensityProperty!; - customDensityProperty.lazyLink( () => { - cuboid.materialEnumProperty!.value = MaterialEnumeration.CUSTOM; - } ); - const densityAsLitersProperty = new UnitConversionProperty( customDensityProperty, { factor: 1 / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER } ); Index: js/common/model/Mass.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/Mass.ts b/js/common/model/Mass.ts --- a/js/common/model/Mass.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/common/model/Mass.ts (date 1720463764525) @@ -9,7 +9,6 @@ import BooleanProperty, { BooleanPropertyOptions } from '../../../../axon/js/BooleanProperty.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import Emitter from '../../../../axon/js/Emitter.js'; -import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js'; import NumberProperty, { NumberPropertyOptions } from '../../../../axon/js/NumberProperty.js'; import Property, { PropertyOptions } from '../../../../axon/js/Property.js'; import Matrix3, { Matrix3StateObject } from '../../../../dot/js/Matrix3.js'; @@ -28,7 +27,7 @@ import IOType from '../../../../tandem/js/types/IOType.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import InterpolatedProperty from './InterpolatedProperty.js'; -import Material, { CreateCustomMaterialOptions, CUSTOM_MATERIAL_NAME, CustomMaterialName } from './Material.js'; +import Material, { CreateCustomMaterialOptions, CUSTOM_MATERIAL_NAME, MaterialName } from './Material.js'; import PhysicsEngine, { PhysicsEngineBody } from './PhysicsEngine.js'; import Basin from './Basin.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; @@ -40,21 +39,9 @@ import MassTag, { MassTagStateObject } from './MassTag.js'; import Bounds3 from '../../../../dot/js/Bounds3.js'; import BlendedVector2Property from './BlendedVector2Property.js'; -import MaterialEnumeration from './MaterialEnumeration.js'; import { GuardedNumberProperty, GuardedNumberPropertyOptions } from './GuardedNumberProperty.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; - -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176: How do these relate to MaterialName which is the type of material.identifier? -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176 MK: I don't know how to say this type must be a subset of "keyof Material". Do you? -type MaterialNonCustomIdentifier = 'ALUMINUM' | 'BRICK' | 'COPPER' | 'ICE' | 'PLATINUM' | 'STEEL' | 'STYROFOAM' | 'WOOD' | 'PVC'; -type MaterialIdentifier = MaterialNonCustomIdentifier | CustomMaterialName; - -const materialToEnum = ( material: Material ): MaterialEnumeration => { - const enumerationValue = MaterialEnumeration[ ( ( material.identifier as MaterialIdentifier | null ) || CUSTOM_MATERIAL_NAME ) ]; - - assert && assert( enumerationValue, 'Unexpected material identifier: ' + material.identifier ); - return enumerationValue; -}; +import StringUnionProperty from '../../../../axon/js/StringUnionProperty.js'; // For the Buoyancy Shapes screen, but needed here because setRatios is included in each core type // See https://github.com/phetsims/buoyancy/issues/29 @@ -90,7 +77,7 @@ massPropertyOptions?: NumberPropertyOptions; // Accepted values for the Material Combo Box - materialEnumPropertyValidValues?: MaterialEnumeration[]; + materialEnumPropertyValidValues?: MaterialName[]; minVolume?: number; maxVolume?: number; @@ -132,7 +119,8 @@ public readonly materialProperty: Property; // for phet-io support (to control the materialProperty) - public readonly materialEnumProperty?: Property; + // TODO: rename from "enum"??? + public readonly materialEnumProperty?: StringUnionProperty; // for phet-io support (to control the materialProperty) public readonly customDensityProperty?: NumberProperty; @@ -226,7 +214,18 @@ volumePropertyOptions: {}, massPropertyOptions: {}, - materialEnumPropertyValidValues: MaterialEnumeration.enumeration.values, + materialEnumPropertyValidValues: [ + 'ALUMINUM', + 'BRICK', + 'COPPER', + 'ICE', + 'PLATINUM', + 'STEEL', + 'STYROFOAM', + 'WOOD', + 'PVC', + CUSTOM_MATERIAL_NAME + ], minVolume: 0, maxVolume: Number.POSITIVE_INFINITY @@ -282,12 +281,15 @@ this.adjustableMaterial = options.adjustableMaterial; if ( options.adjustableMaterial ) { - this.materialEnumProperty = new EnumerationProperty( materialToEnum( options.material ), { - tandem: tandem?.createTandem( 'materialEnumProperty' ), + this.materialEnumProperty = new StringUnionProperty( options.material.identifier, { + tandem: tandem?.createTandem( 'materialEnumProperty' ), // TODO: rename? validValues: options.materialEnumPropertyValidValues, phetioFeatured: true, phetioDocumentation: 'Current material of the object. Changing the material will result in changes to the mass, but the volume will remain the same.' } ); + this.materialEnumProperty.lazyLink( x => { + debugger; + } ) this.customDensityProperty = new NumberProperty( options.material.density, { tandem: tandem?.createTandem( 'customDensityProperty' ), phetioFeatured: true, @@ -316,7 +318,7 @@ this.materialProperty.link( ( material, oldMaterial ) => { if ( !enumLock ) { enumLock = true; - this.materialEnumProperty!.value = materialToEnum( material ); + this.materialEnumProperty!.value = material.identifier; enumLock = false; } if ( !densityLock ) { @@ -331,13 +333,29 @@ material.customColor.link( colorListener ); } } ); - Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], ( materialEnum, density, color ) => { + + this.customDensityProperty.lazyLink( currentDensity => { +// TODO: locks? + + if ( this.materialEnumProperty!.value !== CUSTOM_MATERIAL_NAME && + + // if NOT any valid materials match current density + !_.some( options.materialEnumPropertyValidValues, materialName => { + if ( materialName === CUSTOM_MATERIAL_NAME ) { + return false; + } + return Material.getMaterial( materialName ).density === currentDensity; + } ) ) { + this.materialEnumProperty!.value = CUSTOM_MATERIAL_NAME; + } + } ); + Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], materialEnum => { // See if it's an external change if ( !enumLock && !densityLock && !colorLock ) { enumLock = true; densityLock = true; colorLock = true; - if ( materialEnum === MaterialEnumeration.CUSTOM ) { + if ( materialEnum === CUSTOM_MATERIAL_NAME ) { const createMaterialOptions: CreateCustomMaterialOptions = { density: this.customDensityProperty!.value }; @@ -351,8 +369,7 @@ this.materialProperty.value = Material.createCustomSolidMaterial( createMaterialOptions ); } else { - assert && assert( Material.hasOwnProperty( materialEnum.name ), `unexpected material enum: ${materialEnum.name}` ); - this.materialProperty.value = Material[ materialEnum.name as MaterialNonCustomIdentifier ]; + this.materialProperty.value = Material.getMaterial( materialEnum ); } enumLock = false; densityLock = false; Index: js/density/view/DensityIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/density/view/DensityIntroScreenView.ts b/js/density/view/DensityIntroScreenView.ts --- a/js/density/view/DensityIntroScreenView.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/density/view/DensityIntroScreenView.ts (date 1720461280258) @@ -55,7 +55,8 @@ model.secondaryMass, this.popupLayer, { tandem: tandem, - maxCustomMass: 10 + maxCustomMass: 10, + mysteryMaterials: [] } ); const accordionTandem = tandem.createTandem( 'densityAccordionBox' ); Index: js/common/model/Material.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/Material.ts b/js/common/model/Material.ts --- a/js/common/model/Material.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/common/model/Material.ts (date 1720462328425) @@ -28,11 +28,12 @@ import MappedProperty from '../../../../axon/js/MappedProperty.js'; import Range from '../../../../dot/js/Range.js'; import WithRequired from '../../../../phet-core/js/types/WithRequired.js'; +import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js'; const NullableColorPropertyReferenceType = NullableIO( ReferenceIO( Property.PropertyIO( Color.ColorIO ) ) ); type MaterialState = { - identifier: null | keyof typeof Material; + identifier: MaterialName; name: ReferenceIOState; tandemName: string | null; density: number; @@ -96,13 +97,11 @@ export type MaterialName = typeof nonCustomMaterialNames[ number ] | CustomMaterialName; -export type CreateCustomMaterialOptions = MaterialOptions & Required> & { densityRange?: Range }; - export type MaterialOptions = { nameProperty?: TReadOnlyProperty; // If set, this material will be available at Material[ identifier ] as a global - identifier?: MaterialName | null; + identifier: MaterialName; // Used for tandems tandemName?: string | null; @@ -113,6 +112,7 @@ // in SI (Pa * s). For reference a poise is 1e-2 Pa*s, and a centipoise is 1e-3 Pa*s. viscosity?: number; + // TODO: get rid of this? custom?: boolean; // If true, don't show the density in number pickers/readouts @@ -127,11 +127,13 @@ // Used for the color of depth lines added on top of the Material depthLinesColorProperty?: TReadOnlyProperty; }; +type NoIdentifierMaterialOptions = StrictOmit; +export type CreateCustomMaterialOptions = NoIdentifierMaterialOptions & Required> & { densityRange?: Range }; export default class Material { public readonly nameProperty: TReadOnlyProperty; - public readonly identifier: MaterialName | null; + public readonly identifier: MaterialName; public readonly tandemName: string | null; public readonly density: number; // in SI (kg/m^3) public readonly viscosity: number; @@ -145,7 +147,6 @@ const options = optionize()( { nameProperty: new TinyProperty( 'unknown' ), - identifier: null, tandemName: null, density: 1, viscosity: 1e-3, @@ -173,10 +174,11 @@ /** * Returns a custom material that can be modified at will. */ - public static createCustomMaterial( options: MaterialOptions ): Material { + public static createCustomMaterial( options: NoIdentifierMaterialOptions ): Material { return new Material( combineOptions( { nameProperty: DensityBuoyancyCommonStrings.material.customStringProperty, tandemName: 'custom', + identifier: 'CUSTOM', custom: true }, options ) ); } @@ -637,6 +639,7 @@ density: Material.GOLD.density } ); + // TODO: Dynamically construct based on non custom identifier in the constructor, public static readonly MATERIALS = [ Material.AIR, Material.ALUMINUM, @@ -665,6 +668,7 @@ Material.MERCURY, Material.OIL, Material.PLATINUM, + Material.PVC, Material.PYRITE, Material.SAND, Material.SEAWATER, @@ -677,6 +681,18 @@ Material.WOOD ] as const; + public static getMaterial( materialName: MaterialName ): Material { + for ( let i = 0; i < Material.MATERIALS.length; i++ ) { + const aMaterial = Material.MATERIALS[ i ]; + if ( aMaterial.custom || aMaterial.identifier === CUSTOM_MATERIAL_NAME ) { + console.log( aMaterial ); + } + } + const material = _.find( Material.MATERIALS, material => material.identifier === materialName )!; + assert && assert( material, `unknown material name: ${materialName}` ); + return material; + } + public static readonly DENSITY_MYSTERY_MATERIALS = [ Material.WOOD, Material.GASOLINE, @@ -725,7 +741,7 @@ documentation: 'Represents different materials that solids/liquids in the simulations can take, including density (kg/m^3), viscosity (Pa * s), and color.', stateSchema: { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ), - identifier: NullableIO( StringIO ), + identifier: StringIO, // TODO: ?!?!?!?! tandemName: NullableIO( StringIO ), density: NumberIO, viscosity: NumberIO, @@ -744,7 +760,7 @@ return { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ).toStateObject( material.nameProperty ), - identifier: NullableIO( StringIO ).toStateObject( material.identifier ), + identifier: StringIO.toStateObject( material.identifier ) as MaterialName, tandemName: NullableIO( StringIO ).toStateObject( material.tandemName ), density: material.density, viscosity: material.viscosity, @@ -758,10 +774,8 @@ }; }, fromStateObject( obj ): Material { - if ( obj.identifier ) { - const material = Material[ obj.identifier ]; - assert && assert( material, `Unknown material: ${obj.identifier}` ); - return material as Material; + if ( obj.identifier && obj.identifier !== CUSTOM_MATERIAL_NAME ) { // TODO: custom check? + return Material.getMaterial( obj.identifier ); } else { const staticCustomColor = NullableIO( Color.ColorIO ).fromStateObject( obj.staticCustomColor ); @@ -782,5 +796,8 @@ } } ); } +Material.MATERIALS.forEach( x => {( x.custom || x.identifier === CUSTOM_MATERIAL_NAME ) && console.log( x )} ); + +assert && assert( _.uniq( Material.MATERIALS ).length === Material.MATERIALS.length, 'duplicate in Material.MATERIALS' ); densityBuoyancyCommon.register( 'Material', Material ); \ No newline at end of file Index: js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts b/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts --- a/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (date 1720461280264) @@ -70,14 +70,14 @@ this.rightBox = new PrimarySecondaryControlsNode( model.primaryMass, model.secondaryMass, - this.popupLayer, - { + this.popupLayer, { useDensityControlInsteadOfMassControl: true, customKeepsConstantDensity: true, tandem: tandem, minCustomMass: 0.1, maxCustomMass: 15, - supportHiddenMaterial: true + supportHiddenMaterial: true, + mysteryMaterials: [] } ); Index: js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts b/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts --- a/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (date 1720461158638) @@ -12,12 +12,11 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import Cube from '../../common/model/Cube.js'; import DensityBuoyancyModel, { DensityBuoyancyModelOptions } from '../../common/model/DensityBuoyancyModel.js'; -import Material from '../../common/model/Material.js'; +import Material, { CUSTOM_MATERIAL_NAME, MaterialName } from '../../common/model/Material.js'; import Scale, { DisplayType } from '../../common/model/Scale.js'; import TwoBlockMode from '../../common/model/TwoBlockMode.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import MassTag from '../../common/model/MassTag.js'; -import MaterialEnumeration from '../../common/model/MaterialEnumeration.js'; type BuoyancyBasicsExploreModelOptions = DensityBuoyancyModelOptions; @@ -38,17 +37,13 @@ phetioFeatured: true } ); - // This step is required because, for this screen, MaterialEnumeration does not use all of the simple materials. - // So we have to filter out the ones that are not used for phet-io valid values. - const simpleMaterialsIdentifiers = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ) as string[]; - const simpleMaterialsInEnumerationKeys = MaterialEnumeration.enumeration.keys.filter( name => simpleMaterialsIdentifiers.includes( name ) ); - // @ts-expect-error TODO: is this string indexing correct? https://github.com/phetsims/density-buoyancy-common/issues/176 - const validSimpleMaterials = simpleMaterialsInEnumerationKeys.map( x => MaterialEnumeration[ x ] ).concat( [ MaterialEnumeration.CUSTOM ] ); + // B:B Explore has a more limited set of available materials. + const simpleMaterialsIdentifiers: MaterialName[] = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ).concat( [ CUSTOM_MATERIAL_NAME ] ); this.primaryMass = Cube.createWithMass( this.engine, Material.WOOD, new Vector2( -0.2, 0.2 ), 2, { tag: MassTag.PRIMARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockA' ) } ); @@ -56,7 +51,7 @@ this.secondaryMass = Cube.createWithMass( this.engine, Material.ALUMINUM, new Vector2( 0.05, 0.35 ), 13.5, { tag: MassTag.SECONDARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockB' ), visible: false Index: js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts b/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts --- a/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (revision d39e8fd41b3dcdf35d889315ad8672b06eb95abf) +++ b/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (date 1720460344735) @@ -215,7 +215,7 @@ Material.GOLD, Material.PLATINUM ].concat( Material.SIMPLE_MASS_MATERIALS ), - material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density + material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density // TODO: Doesn't the class always make mystery at the end? Material.MATERIAL_V, Material.MATERIAL_W ] ), cubicMeters => model.block.updateSize( Cube.boundsFromVolume( cubicMeters ) ), this.popupLayer, true, {
samreid commented 1 month ago

Slightly different patch from discussion with @zepumph

```diff Subject: [PATCH] remove unused variable, https://github.com/phetsims/density-buoyancy-common/issues/213 --- Index: js/common/model/MaterialEnumeration.ts =================================================================== diff --git a/js/common/model/MaterialEnumeration.ts b/js/common/model/MaterialEnumeration.ts deleted file mode 100644 --- a/js/common/model/MaterialEnumeration.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ /dev/null (revision 98f360f91e355151ac4390930dd3ecb43c554e45) @@ -1,29 +0,0 @@ -// Copyright 2024, University of Colorado Boulder - -/** - * This is a subset of the possible Materials that can be used to set the material in Density. - * - * @author Sam Reid (PhET Interactive Simulations) - */ -import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js'; -import Enumeration from '../../../../phet-core/js/Enumeration.js'; -import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; - -export default class MaterialEnumeration extends EnumerationValue { - public static readonly ALUMINUM = new MaterialEnumeration(); - public static readonly BRICK = new MaterialEnumeration(); - public static readonly COPPER = new MaterialEnumeration(); - public static readonly ICE = new MaterialEnumeration(); - public static readonly PLATINUM = new MaterialEnumeration(); - public static readonly STEEL = new MaterialEnumeration(); - public static readonly STYROFOAM = new MaterialEnumeration(); - public static readonly WOOD = new MaterialEnumeration(); - public static readonly PVC = new MaterialEnumeration(); - public static readonly CUSTOM = new MaterialEnumeration(); - - public static readonly enumeration = new Enumeration( MaterialEnumeration, { - phetioDocumentation: 'Material values' - } ); -} - -densityBuoyancyCommon.register( 'MaterialEnumeration', MaterialEnumeration ); \ No newline at end of file Index: js/common/view/MaterialControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/MaterialControlNode.ts b/js/common/view/MaterialControlNode.ts --- a/js/common/view/MaterialControlNode.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/common/view/MaterialControlNode.ts (date 1720540980341) @@ -18,7 +18,7 @@ import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; -import Material, { CUSTOM_MATERIAL_NAME, MaterialName } from '../model/Material.js'; +import Material, { MaterialName } from '../model/Material.js'; import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; import PickRequired from '../../../../phet-core/js/types/PickRequired.js'; import Range from '../../../../dot/js/Range.js'; @@ -79,16 +79,16 @@ materials = materials.filter( material => !material.hidden ); } - const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier! ) ]; - options.supportCustomMaterial && materialNames.push( CUSTOM_MATERIAL_NAME ); + const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier ) ]; + options.supportCustomMaterial && materialNames.push( 'CUSTOM' ); // TODO: https://github.com/phetsims/density-buoyancy-common/issues/163 re-evaluate eliminating or moving this code // after addressing #163 const comboBoxMaterialProperty = new DynamicProperty( new Property( materialProperty ), { bidirectional: true, - map: ( material: Material ) => material.custom ? CUSTOM_MATERIAL_NAME : material.identifier!, + map: ( material: Material ) => material.identifier, inverseMap: ( materialName: MaterialName ): Material => { - if ( materialName === CUSTOM_MATERIAL_NAME ) { + if ( materialName === 'CUSTOM' ) { // Handle our minimum volume if we're switched to custom (if needed) const volume = Math.max( volumeProperty.value, options.minCustomVolumeLiters / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER ); return Material.createCustomSolidMaterial( { @@ -97,7 +97,7 @@ } ); } else { - return Material[ materialName ]; + return Material.getMaterial( materialName ); } }, reentrant: true, @@ -116,7 +116,7 @@ const materialToItem = ( material: Material ) => { return { - value: material.identifier!, + value: material.identifier, createNode: () => new Text( material.nameProperty, { font: DensityBuoyancyCommonConstants.COMBO_BOX_ITEM_FONT, maxWidth: comboMaxWidth @@ -126,10 +126,11 @@ }; }; + // TODO: parametrize to MaterialName const comboBox = new ComboBox( comboBoxMaterialProperty, [ ...regularMaterials.map( materialToItem ), ...( options.supportCustomMaterial ? [ { - value: CUSTOM_MATERIAL_NAME, + value: 'CUSTOM', createNode: () => new Text( DensityBuoyancyCommonStrings.material.customStringProperty, { font: DensityBuoyancyCommonConstants.COMBO_BOX_ITEM_FONT, maxWidth: comboMaxWidth Index: js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts b/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts --- a/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (date 1720540039250) @@ -221,7 +221,7 @@ Material.GOLD, Material.PLATINUM ].concat( Material.SIMPLE_MASS_MATERIALS ), - material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density + material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density // TODO: Doesn't the class always make mystery at the end? Material.MATERIAL_V, Material.MATERIAL_W ] ), cubicMeters => model.block.updateSize( Cube.boundsFromVolume( cubicMeters ) ), this.popupLayer, true, { Index: js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts b/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts --- a/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (date 1720540039258) @@ -70,14 +70,14 @@ this.rightBox = new ABControlsNode( model.massA, model.massB, - this.popupLayer, - { + this.popupLayer, { useDensityControlInsteadOfMassControl: true, customKeepsConstantDensity: true, tandem: tandem, minCustomMass: 0.1, maxCustomMass: 15, - supportHiddenMaterial: true + supportHiddenMaterial: true, + mysteryMaterials: [] } ); Index: js/common/model/Mass.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/Mass.ts b/js/common/model/Mass.ts --- a/js/common/model/Mass.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/common/model/Mass.ts (date 1720542003605) @@ -9,7 +9,6 @@ import BooleanProperty, { BooleanPropertyOptions } from '../../../../axon/js/BooleanProperty.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import Emitter from '../../../../axon/js/Emitter.js'; -import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js'; import NumberProperty, { NumberPropertyOptions } from '../../../../axon/js/NumberProperty.js'; import Property, { PropertyOptions } from '../../../../axon/js/Property.js'; import Matrix3, { Matrix3StateObject } from '../../../../dot/js/Matrix3.js'; @@ -28,7 +27,7 @@ import IOType from '../../../../tandem/js/types/IOType.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import InterpolatedProperty from './InterpolatedProperty.js'; -import Material, { CreateCustomMaterialOptions, CUSTOM_MATERIAL_NAME, CustomMaterialName } from './Material.js'; +import Material, { CreateCustomMaterialOptions, MaterialName } from './Material.js'; import PhysicsEngine, { PhysicsEngineBody } from './PhysicsEngine.js'; import Basin from './Basin.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; @@ -40,21 +39,9 @@ import MassTag, { MassTagStateObject } from './MassTag.js'; import Bounds3 from '../../../../dot/js/Bounds3.js'; import BlendedVector2Property from './BlendedVector2Property.js'; -import MaterialEnumeration from './MaterialEnumeration.js'; import { GuardedNumberProperty, GuardedNumberPropertyOptions } from './GuardedNumberProperty.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; - -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176: How do these relate to MaterialName which is the type of material.identifier? -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176 MK: I don't know how to say this type must be a subset of "keyof Material". Do you? -type MaterialNonCustomIdentifier = 'ALUMINUM' | 'BRICK' | 'COPPER' | 'ICE' | 'PLATINUM' | 'STEEL' | 'STYROFOAM' | 'WOOD' | 'PVC'; -type MaterialIdentifier = MaterialNonCustomIdentifier | CustomMaterialName; - -const materialToEnum = ( material: Material ): MaterialEnumeration => { - const enumerationValue = MaterialEnumeration[ ( ( material.identifier as MaterialIdentifier | null ) || CUSTOM_MATERIAL_NAME ) ]; - - assert && assert( enumerationValue, 'Unexpected material identifier: ' + material.identifier ); - return enumerationValue; -}; +import StringUnionProperty from '../../../../axon/js/StringUnionProperty.js'; // For the Buoyancy Shapes screen, but needed here because setRatios is included in each core type // See https://github.com/phetsims/buoyancy/issues/29 @@ -90,7 +77,7 @@ massPropertyOptions?: NumberPropertyOptions; // Accepted values for the Material Combo Box - materialEnumPropertyValidValues?: MaterialEnumeration[]; + materialEnumPropertyValidValues?: MaterialName[]; minVolume?: number; maxVolume?: number; @@ -132,7 +119,8 @@ public readonly materialProperty: Property; // for phet-io support (to control the materialProperty) - public readonly materialEnumProperty?: Property; + // TODO: rename from "enum"??? + public readonly materialEnumProperty?: StringUnionProperty; // for phet-io support (to control the materialProperty) public readonly customDensityProperty?: NumberProperty; @@ -226,7 +214,18 @@ volumePropertyOptions: {}, massPropertyOptions: {}, - materialEnumPropertyValidValues: MaterialEnumeration.enumeration.values, + materialEnumPropertyValidValues: [ + 'ALUMINUM', + 'BRICK', + 'COPPER', + 'ICE', + 'PLATINUM', + 'STEEL', + 'STYROFOAM', + 'WOOD', + 'PVC', + 'CUSTOM' + ], minVolume: 0, maxVolume: Number.POSITIVE_INFINITY @@ -282,16 +281,19 @@ this.adjustableMaterial = options.adjustableMaterial; if ( options.adjustableMaterial ) { - this.materialEnumProperty = new EnumerationProperty( materialToEnum( options.material ), { - tandem: tandem?.createTandem( 'materialEnumProperty' ), + this.materialEnumProperty = new StringUnionProperty( options.material.identifier, { + tandem: tandem?.createTandem( 'materialEnumProperty' ), // TODO: rename? validValues: options.materialEnumPropertyValidValues, phetioFeatured: true, phetioDocumentation: 'Current material of the object. Changing the material will result in changes to the mass, but the volume will remain the same.' } ); + this.materialEnumProperty.lazyLink( x => { + debugger; + } ) this.customDensityProperty = new NumberProperty( options.material.density, { tandem: tandem?.createTandem( 'customDensityProperty' ), phetioFeatured: true, - phetioDocumentation: 'Density of the objet when the material is set to “CUSTOM”.', + phetioDocumentation: 'Density of the object when the material is set to “CUSTOM”.', range: new Range( 150, 23000 ), units: 'kg/m^3' } ); @@ -316,7 +318,7 @@ this.materialProperty.link( ( material, oldMaterial ) => { if ( !enumLock ) { enumLock = true; - this.materialEnumProperty!.value = materialToEnum( material ); + this.materialEnumProperty!.value = material.identifier; enumLock = false; } if ( !densityLock ) { @@ -331,13 +333,29 @@ material.customColor.link( colorListener ); } } ); - Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], ( materialEnum, density, color ) => { + +// this.customDensityProperty.lazyLink( currentDensity => { +// // TODO: locks? +// +// if ( this.materialEnumProperty!.value !== 'CUSTOM' && +// +// // if NOT any valid materials match current density +// !_.some( options.materialEnumPropertyValidValues, materialName => { +// if ( materialName === 'CUSTOM' ) { +// return false; +// } +// return Material.getMaterial( materialName ).density === currentDensity; +// } ) ) { +// this.materialEnumProperty!.value = 'CUSTOM'; +// } +// } ); + Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], materialEnum => { // See if it's an external change if ( !enumLock && !densityLock && !colorLock ) { enumLock = true; densityLock = true; colorLock = true; - if ( materialEnum === MaterialEnumeration.CUSTOM ) { + if ( materialEnum === 'CUSTOM' ) { const createMaterialOptions: CreateCustomMaterialOptions = { density: this.customDensityProperty!.value }; @@ -351,8 +369,7 @@ this.materialProperty.value = Material.createCustomSolidMaterial( createMaterialOptions ); } else { - assert && assert( Material.hasOwnProperty( materialEnum.name ), `unexpected material enum: ${materialEnum.name}` ); - this.materialProperty.value = Material[ materialEnum.name as MaterialNonCustomIdentifier ]; + this.materialProperty.value = Material.getMaterial( materialEnum ); } enumLock = false; densityLock = false; Index: js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts b/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts --- a/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (date 1720540980355) @@ -12,12 +12,11 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import Cube from '../../common/model/Cube.js'; import DensityBuoyancyModel, { DensityBuoyancyModelOptions } from '../../common/model/DensityBuoyancyModel.js'; -import Material from '../../common/model/Material.js'; +import Material, { MaterialName } from '../../common/model/Material.js'; import Scale, { DisplayType } from '../../common/model/Scale.js'; import TwoBlockMode from '../../common/model/TwoBlockMode.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import MassTag from '../../common/model/MassTag.js'; -import MaterialEnumeration from '../../common/model/MaterialEnumeration.js'; type BuoyancyBasicsExploreModelOptions = DensityBuoyancyModelOptions; @@ -38,17 +37,13 @@ phetioFeatured: true } ); - // This step is required because, for this screen, MaterialEnumeration does not use all of the simple materials. - // So we have to filter out the ones that are not used for phet-io valid values. - const simpleMaterialsIdentifiers = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ) as string[]; - const simpleMaterialsInEnumerationKeys = MaterialEnumeration.enumeration.keys.filter( name => simpleMaterialsIdentifiers.includes( name ) ); - // @ts-expect-error TODO: is this string indexing correct? https://github.com/phetsims/density-buoyancy-common/issues/176 - const validSimpleMaterials = simpleMaterialsInEnumerationKeys.map( x => MaterialEnumeration[ x ] ).concat( [ MaterialEnumeration.CUSTOM ] ); + // B:B Explore has a more limited set of available materials. + const simpleMaterialsIdentifiers: MaterialName[] = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ).concat( [ 'CUSTOM' ] ); this.massA = Cube.createWithMass( this.engine, Material.WOOD, new Vector2( -0.2, 0.2 ), 2, { tag: MassTag.PRIMARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockA' ) } ); @@ -56,7 +51,7 @@ this.massB = Cube.createWithMass( this.engine, Material.ALUMINUM, new Vector2( 0.05, 0.35 ), 13.5, { tag: MassTag.SECONDARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockB' ), visible: false Index: js/common/model/Material.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/Material.ts b/js/common/model/Material.ts --- a/js/common/model/Material.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/common/model/Material.ts (date 1720542003597) @@ -28,11 +28,12 @@ import MappedProperty from '../../../../axon/js/MappedProperty.js'; import Range from '../../../../dot/js/Range.js'; import WithRequired from '../../../../phet-core/js/types/WithRequired.js'; +import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js'; const NullableColorPropertyReferenceType = NullableIO( ReferenceIO( Property.PropertyIO( Color.ColorIO ) ) ); type MaterialState = { - identifier: null | keyof typeof Material; + identifier: MaterialName; name: ReferenceIOState; tandemName: string | null; density: number; @@ -46,9 +47,6 @@ depthLinesColor: ColorState; }; -export const CUSTOM_MATERIAL_NAME = 'CUSTOM'; -export type CustomMaterialName = typeof CUSTOM_MATERIAL_NAME; - const nonCustomMaterialNames = [ 'ALUMINUM', 'APPLE', @@ -94,15 +92,14 @@ 'MATERIAL_X', 'MATERIAL_Y' ] as const; -export type MaterialName = typeof nonCustomMaterialNames[ number ] | CustomMaterialName; - -export type CreateCustomMaterialOptions = MaterialOptions & Required> & { densityRange?: Range }; +type NonCustomMaterialName = typeof nonCustomMaterialNames[ number ]; +export type MaterialName = NonCustomMaterialName | 'CUSTOM'; export type MaterialOptions = { nameProperty?: TReadOnlyProperty; // If set, this material will be available at Material[ identifier ] as a global - identifier?: MaterialName | null; + identifier: MaterialName; // Used for tandems tandemName?: string | null; @@ -113,6 +110,7 @@ // in SI (Pa * s). For reference a poise is 1e-2 Pa*s, and a centipoise is 1e-3 Pa*s. viscosity?: number; + // TODO: get rid of this? custom?: boolean; // If true, don't show the density in number pickers/readouts @@ -127,14 +125,18 @@ // Used for the color of depth lines added on top of the Material depthLinesColorProperty?: TReadOnlyProperty; }; +type NoIdentifierMaterialOptions = StrictOmit; +export type CreateCustomMaterialOptions = NoIdentifierMaterialOptions & Required> & { densityRange?: Range }; export default class Material { public readonly nameProperty: TReadOnlyProperty; - public readonly identifier: MaterialName | null; + public readonly identifier: MaterialName; public readonly tandemName: string | null; public readonly density: number; // in SI (kg/m^3) public readonly viscosity: number; + + // TODO: Eliminate custom as an orthogonal attribute, it can be determined from identifier. public readonly custom: boolean; public readonly hidden: boolean; public readonly customColor: Property | null; @@ -145,7 +147,6 @@ const options = optionize()( { nameProperty: new TinyProperty( 'unknown' ), - identifier: null, tandemName: null, density: 1, viscosity: 1e-3, @@ -173,10 +174,11 @@ /** * Returns a custom material that can be modified at will. */ - public static createCustomMaterial( options: MaterialOptions ): Material { + public static createCustomMaterial( options: NoIdentifierMaterialOptions ): Material { return new Material( combineOptions( { nameProperty: DensityBuoyancyCommonStrings.material.customStringProperty, tandemName: 'custom', + identifier: 'CUSTOM', custom: true }, options ) ); } @@ -637,6 +639,8 @@ density: Material.GOLD.density } ); + // TODO: Convert to an object literal like{AIR: new Material( ... ), ...} as const + // TODO: Then we can lift the keys for a string union of "NonCustomMaterialName". public static readonly MATERIALS = [ Material.AIR, Material.ALUMINUM, @@ -665,6 +669,7 @@ Material.MERCURY, Material.OIL, Material.PLATINUM, + Material.PVC, Material.PYRITE, Material.SAND, Material.SEAWATER, @@ -677,6 +682,12 @@ Material.WOOD ] as const; + public static getMaterial( materialName: MaterialName ): Material { + const material = _.find( Material.MATERIALS, material => material.identifier === materialName )!; + assert && assert( material, `unknown material name: ${materialName}` ); + return material; + } + public static readonly DENSITY_MYSTERY_MATERIALS = [ Material.WOOD, Material.GASOLINE, @@ -725,7 +736,7 @@ documentation: 'Represents different materials that solids/liquids in the simulations can take, including density (kg/m^3), viscosity (Pa * s), and color.', stateSchema: { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ), - identifier: NullableIO( StringIO ), + identifier: StringIO, // TODO: ?!?!?!?! tandemName: NullableIO( StringIO ), density: NumberIO, viscosity: NumberIO, @@ -744,7 +755,7 @@ return { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ).toStateObject( material.nameProperty ), - identifier: NullableIO( StringIO ).toStateObject( material.identifier ), + identifier: StringIO.toStateObject( material.identifier ) as MaterialName, tandemName: NullableIO( StringIO ).toStateObject( material.tandemName ), density: material.density, viscosity: material.viscosity, @@ -758,10 +769,8 @@ }; }, fromStateObject( obj ): Material { - if ( obj.identifier ) { - const material = Material[ obj.identifier ]; - assert && assert( material, `Unknown material: ${obj.identifier}` ); - return material as Material; + if ( obj.identifier !== 'CUSTOM' ) { + return Material.getMaterial( obj.identifier ); } else { const staticCustomColor = NullableIO( Color.ColorIO ).fromStateObject( obj.staticCustomColor ); @@ -782,5 +791,8 @@ } } ); } +Material.MATERIALS.forEach( x => {( x.custom || x.identifier === 'CUSTOM' ) && console.log( x )} ); + +assert && assert( _.uniq( Material.MATERIALS ).length === Material.MATERIALS.length, 'duplicate in Material.MATERIALS' ); densityBuoyancyCommon.register( 'Material', Material ); \ No newline at end of file Index: js/density/view/DensityIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/density/view/DensityIntroScreenView.ts b/js/density/view/DensityIntroScreenView.ts --- a/js/density/view/DensityIntroScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/density/view/DensityIntroScreenView.ts (date 1720540039284) @@ -55,7 +55,8 @@ model.massB, this.popupLayer, { tandem: tandem, - maxCustomMass: 10 + maxCustomMass: 10, + mysteryMaterials: [] } ); const accordionTandem = tandem.createTandem( 'densityAccordionBox' ); Index: js/common/view/BlockControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/view/BlockControlNode.ts b/js/common/view/BlockControlNode.ts --- a/js/common/view/BlockControlNode.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/js/common/view/BlockControlNode.ts (date 1720542003609) @@ -19,10 +19,9 @@ import PrecisionSliderThumb from './PrecisionSliderThumb.js'; import UnitConversionProperty from '../../../../axon/js/UnitConversionProperty.js'; import Range from '../../../../dot/js/Range.js'; -import MaterialEnumeration from '../model/MaterialEnumeration.js'; type SelfOptions = { - mysteryMaterials?: Material[]; + mysteryMaterials: Material[]; // Provide empty list to opt out. }; export type BlockControlNodeOptions = MaterialMassVolumeControlNodeOptions & SelfOptions; @@ -30,11 +29,9 @@ export default class BlockControlNode extends MaterialMassVolumeControlNode { public constructor( cuboid: Cuboid, listParent: Node, numberControlMassPropertyFeatured: boolean, options: BlockControlNodeOptions ) { - // Add mystery materials at the end, if provided. Note custom will appear before mystery materials. This is handled - // elsewhere, see https://github.com/phetsims/density-buoyancy-common/issues/161 - const materials = options.mysteryMaterials ? - Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ) : - Material.SIMPLE_MASS_MATERIALS; + // Add mystery materials at the end. Note custom will appear before mystery materials. This is handled + // by the supertype, see https://github.com/phetsims/density-buoyancy-common/issues/161 + const materials = Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ); super( cuboid.materialProperty, cuboid.massProperty, cuboid.volumeProperty, materials, cubicMeters => cuboid.updateSize( Cube.boundsFromVolume( cubicMeters ) ), listParent, numberControlMassPropertyFeatured, options ); @@ -46,9 +43,9 @@ const densityNumberControlTandem = options.tandem.createTandem( 'densityNumberControl' ); const customDensityProperty = cuboid.customDensityProperty!; - customDensityProperty.lazyLink( () => { - cuboid.materialEnumProperty!.value = MaterialEnumeration.CUSTOM; - } ); + // customDensityProperty.lazyLink( () => { + // cuboid.materialEnumProperty!.value = 'CUSTOM'; + // } ); const densityAsLitersProperty = new UnitConversionProperty( customDensityProperty, { factor: 1 / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER
zepumph commented 1 month ago

Almost ready for a commit, but need one more migration rule,

```diff Subject: [PATCH] remove unused variable, https://github.com/phetsims/density-buoyancy-common/issues/213 --- Index: density-buoyancy-common/js/common/model/MaterialEnumeration.ts =================================================================== diff --git a/density-buoyancy-common/js/common/model/MaterialEnumeration.ts b/density-buoyancy-common/js/common/model/MaterialEnumeration.ts deleted file mode 100644 --- a/density-buoyancy-common/js/common/model/MaterialEnumeration.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ /dev/null (revision 98f360f91e355151ac4390930dd3ecb43c554e45) @@ -1,29 +0,0 @@ -// Copyright 2024, University of Colorado Boulder - -/** - * This is a subset of the possible Materials that can be used to set the material in Density. - * - * @author Sam Reid (PhET Interactive Simulations) - */ -import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js'; -import Enumeration from '../../../../phet-core/js/Enumeration.js'; -import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; - -export default class MaterialEnumeration extends EnumerationValue { - public static readonly ALUMINUM = new MaterialEnumeration(); - public static readonly BRICK = new MaterialEnumeration(); - public static readonly COPPER = new MaterialEnumeration(); - public static readonly ICE = new MaterialEnumeration(); - public static readonly PLATINUM = new MaterialEnumeration(); - public static readonly STEEL = new MaterialEnumeration(); - public static readonly STYROFOAM = new MaterialEnumeration(); - public static readonly WOOD = new MaterialEnumeration(); - public static readonly PVC = new MaterialEnumeration(); - public static readonly CUSTOM = new MaterialEnumeration(); - - public static readonly enumeration = new Enumeration( MaterialEnumeration, { - phetioDocumentation: 'Material values' - } ); -} - -densityBuoyancyCommon.register( 'MaterialEnumeration', MaterialEnumeration ); \ No newline at end of file Index: density-buoyancy-common/js/common/view/MaterialControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/common/view/MaterialControlNode.ts b/density-buoyancy-common/js/common/view/MaterialControlNode.ts --- a/density-buoyancy-common/js/common/view/MaterialControlNode.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/common/view/MaterialControlNode.ts (date 1720542489200) @@ -18,7 +18,7 @@ import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; -import Material, { CUSTOM_MATERIAL_NAME, MaterialName } from '../model/Material.js'; +import Material, { MaterialName } from '../model/Material.js'; import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; import PickRequired from '../../../../phet-core/js/types/PickRequired.js'; import Range from '../../../../dot/js/Range.js'; @@ -79,16 +79,16 @@ materials = materials.filter( material => !material.hidden ); } - const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier! ) ]; - options.supportCustomMaterial && materialNames.push( CUSTOM_MATERIAL_NAME ); + const materialNames: MaterialName[] = [ ...materials.map( material => material.identifier ) ]; + options.supportCustomMaterial && materialNames.push( 'CUSTOM' ); // TODO: https://github.com/phetsims/density-buoyancy-common/issues/163 re-evaluate eliminating or moving this code // after addressing #163 const comboBoxMaterialProperty = new DynamicProperty( new Property( materialProperty ), { bidirectional: true, - map: ( material: Material ) => material.custom ? CUSTOM_MATERIAL_NAME : material.identifier!, + map: ( material: Material ) => material.identifier, inverseMap: ( materialName: MaterialName ): Material => { - if ( materialName === CUSTOM_MATERIAL_NAME ) { + if ( materialName === 'CUSTOM' ) { // Handle our minimum volume if we're switched to custom (if needed) const volume = Math.max( volumeProperty.value, options.minCustomVolumeLiters / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER ); return Material.createCustomSolidMaterial( { @@ -97,7 +97,7 @@ } ); } else { - return Material[ materialName ]; + return Material.getMaterial( materialName ); } }, reentrant: true, @@ -116,7 +116,7 @@ const materialToItem = ( material: Material ) => { return { - value: material.identifier!, + value: material.identifier, createNode: () => new Text( material.nameProperty, { font: DensityBuoyancyCommonConstants.COMBO_BOX_ITEM_FONT, maxWidth: comboMaxWidth @@ -126,10 +126,11 @@ }; }; + // TODO: parametrize to MaterialName, https://github.com/phetsims/density-buoyancy-common/issues/176 const comboBox = new ComboBox( comboBoxMaterialProperty, [ ...regularMaterials.map( materialToItem ), ...( options.supportCustomMaterial ? [ { - value: CUSTOM_MATERIAL_NAME, + value: 'CUSTOM', createNode: () => new Text( DensityBuoyancyCommonStrings.material.customStringProperty, { font: DensityBuoyancyCommonConstants.COMBO_BOX_ITEM_FONT, maxWidth: comboMaxWidth Index: density-buoyancy-common/js/common/view/BlockControlNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/common/view/BlockControlNode.ts b/density-buoyancy-common/js/common/view/BlockControlNode.ts --- a/density-buoyancy-common/js/common/view/BlockControlNode.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/common/view/BlockControlNode.ts (date 1720542217033) @@ -19,10 +19,9 @@ import PrecisionSliderThumb from './PrecisionSliderThumb.js'; import UnitConversionProperty from '../../../../axon/js/UnitConversionProperty.js'; import Range from '../../../../dot/js/Range.js'; -import MaterialEnumeration from '../model/MaterialEnumeration.js'; type SelfOptions = { - mysteryMaterials?: Material[]; + mysteryMaterials: Material[]; // Provide empty list to opt out. }; export type BlockControlNodeOptions = MaterialMassVolumeControlNodeOptions & SelfOptions; @@ -30,11 +29,9 @@ export default class BlockControlNode extends MaterialMassVolumeControlNode { public constructor( cuboid: Cuboid, listParent: Node, numberControlMassPropertyFeatured: boolean, options: BlockControlNodeOptions ) { - // Add mystery materials at the end, if provided. Note custom will appear before mystery materials. This is handled - // elsewhere, see https://github.com/phetsims/density-buoyancy-common/issues/161 - const materials = options.mysteryMaterials ? - Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ) : - Material.SIMPLE_MASS_MATERIALS; + // Add mystery materials at the end. Note custom will appear before mystery materials. This is handled + // by the supertype, see https://github.com/phetsims/density-buoyancy-common/issues/161 + const materials = Material.SIMPLE_MASS_MATERIALS.concat( options.mysteryMaterials ); super( cuboid.materialProperty, cuboid.massProperty, cuboid.volumeProperty, materials, cubicMeters => cuboid.updateSize( Cube.boundsFromVolume( cubicMeters ) ), listParent, numberControlMassPropertyFeatured, options ); @@ -46,9 +43,9 @@ const densityNumberControlTandem = options.tandem.createTandem( 'densityNumberControl' ); const customDensityProperty = cuboid.customDensityProperty!; - customDensityProperty.lazyLink( () => { - cuboid.materialEnumProperty!.value = MaterialEnumeration.CUSTOM; - } ); + // customDensityProperty.lazyLink( () => { + // cuboid.materialEnumProperty!.value = 'CUSTOM'; + // } ); const densityAsLitersProperty = new UnitConversionProperty( customDensityProperty, { factor: 1 / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER Index: phet-io-sim-specific/repos/density/js/density-migration-processors.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/phet-io-sim-specific/repos/density/js/density-migration-processors.ts b/phet-io-sim-specific/repos/density/js/density-migration-processors.ts --- a/phet-io-sim-specific/repos/density/js/density-migration-processors.ts (revision ad9b9ff60db17bbb04c0a6c24e9af94eddfd1ee8) +++ b/phet-io-sim-specific/repos/density/js/density-migration-processors.ts (date 1720544106748) @@ -458,7 +458,10 @@ new RenamePhetioElement( 'blocksValueControlPanel.toggleNode.elements.massNumberControl', 'blocksValueControlPanel.massNumberControl' ), new RenamePhetioElement( 'blocksValueControlPanel.toggleNode.elements.volumeNumberControl', 'blocksValueControlPanel.volumeNumberControl' ), - new RenamePhetioElement( 'blocksValueControlPanel.toggleNode.elements.densityNumberControl', 'blocksValueControlPanel.densityNumberControl' ) + new RenamePhetioElement( 'blocksValueControlPanel.toggleNode.elements.densityNumberControl', 'blocksValueControlPanel.densityNumberControl' ), + + new ChangeStateKeyIOType( 'MaterialIO', 'identifier', 'StringIO', + materialState => { if ( !materialState.identifier ) {materialState.identifier = 'CUSTOM'; } } ) ] }; Index: density-buoyancy-common/js/common/model/Mass.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/common/model/Mass.ts b/density-buoyancy-common/js/common/model/Mass.ts --- a/density-buoyancy-common/js/common/model/Mass.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/common/model/Mass.ts (date 1720543797546) @@ -9,7 +9,6 @@ import BooleanProperty, { BooleanPropertyOptions } from '../../../../axon/js/BooleanProperty.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import Emitter from '../../../../axon/js/Emitter.js'; -import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js'; import NumberProperty, { NumberPropertyOptions } from '../../../../axon/js/NumberProperty.js'; import Property, { PropertyOptions } from '../../../../axon/js/Property.js'; import Matrix3, { Matrix3StateObject } from '../../../../dot/js/Matrix3.js'; @@ -28,7 +27,7 @@ import IOType from '../../../../tandem/js/types/IOType.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import InterpolatedProperty from './InterpolatedProperty.js'; -import Material, { CreateCustomMaterialOptions, CUSTOM_MATERIAL_NAME, CustomMaterialName } from './Material.js'; +import Material, { CreateCustomMaterialOptions, MaterialName } from './Material.js'; import PhysicsEngine, { PhysicsEngineBody } from './PhysicsEngine.js'; import Basin from './Basin.js'; import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; @@ -40,21 +39,9 @@ import MassTag, { MassTagStateObject } from './MassTag.js'; import Bounds3 from '../../../../dot/js/Bounds3.js'; import BlendedVector2Property from './BlendedVector2Property.js'; -import MaterialEnumeration from './MaterialEnumeration.js'; import { GuardedNumberProperty, GuardedNumberPropertyOptions } from './GuardedNumberProperty.js'; import DensityBuoyancyCommonConstants from '../DensityBuoyancyCommonConstants.js'; - -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176: How do these relate to MaterialName which is the type of material.identifier? -// TODO: https://github.com/phetsims/density-buoyancy-common/issues/176 MK: I don't know how to say this type must be a subset of "keyof Material". Do you? -type MaterialNonCustomIdentifier = 'ALUMINUM' | 'BRICK' | 'COPPER' | 'ICE' | 'PLATINUM' | 'STEEL' | 'STYROFOAM' | 'WOOD' | 'PVC'; -type MaterialIdentifier = MaterialNonCustomIdentifier | CustomMaterialName; - -const materialToEnum = ( material: Material ): MaterialEnumeration => { - const enumerationValue = MaterialEnumeration[ ( ( material.identifier as MaterialIdentifier | null ) || CUSTOM_MATERIAL_NAME ) ]; - - assert && assert( enumerationValue, 'Unexpected material identifier: ' + material.identifier ); - return enumerationValue; -}; +import StringUnionProperty from '../../../../axon/js/StringUnionProperty.js'; // For the Buoyancy Shapes screen, but needed here because setRatios is included in each core type // See https://github.com/phetsims/buoyancy/issues/29 @@ -90,7 +77,7 @@ massPropertyOptions?: NumberPropertyOptions; // Accepted values for the Material Combo Box - materialEnumPropertyValidValues?: MaterialEnumeration[]; + materialEnumPropertyValidValues?: MaterialName[]; minVolume?: number; maxVolume?: number; @@ -132,7 +119,8 @@ public readonly materialProperty: Property; // for phet-io support (to control the materialProperty) - public readonly materialEnumProperty?: Property; + // TODO: rename from "enum"? https://github.com/phetsims/density-buoyancy-common/issues/176 + public readonly materialEnumProperty?: StringUnionProperty; // for phet-io support (to control the materialProperty) public readonly customDensityProperty?: NumberProperty; @@ -226,7 +214,17 @@ volumePropertyOptions: {}, massPropertyOptions: {}, - materialEnumPropertyValidValues: MaterialEnumeration.enumeration.values, + materialEnumPropertyValidValues: [ + 'ALUMINUM', + 'BRICK', + 'COPPER', + 'ICE', + 'PLATINUM', + 'STEEL', + 'STYROFOAM', + 'WOOD', + 'CUSTOM' + ], minVolume: 0, maxVolume: Number.POSITIVE_INFINITY @@ -282,8 +280,8 @@ this.adjustableMaterial = options.adjustableMaterial; if ( options.adjustableMaterial ) { - this.materialEnumProperty = new EnumerationProperty( materialToEnum( options.material ), { - tandem: tandem?.createTandem( 'materialEnumProperty' ), + this.materialEnumProperty = new StringUnionProperty( options.material.identifier, { + tandem: tandem?.createTandem( 'materialEnumProperty' ), // TODO: rename? https://github.com/phetsims/density-buoyancy-common/issues/176 validValues: options.materialEnumPropertyValidValues, phetioFeatured: true, phetioDocumentation: 'Current material of the object. Changing the material will result in changes to the mass, but the volume will remain the same.' @@ -291,7 +289,7 @@ this.customDensityProperty = new NumberProperty( options.material.density, { tandem: tandem?.createTandem( 'customDensityProperty' ), phetioFeatured: true, - phetioDocumentation: 'Density of the objet when the material is set to “CUSTOM”.', + phetioDocumentation: 'Density of the object when the material is set to “CUSTOM”.', range: new Range( 150, 23000 ), units: 'kg/m^3' } ); @@ -316,7 +314,7 @@ this.materialProperty.link( ( material, oldMaterial ) => { if ( !enumLock ) { enumLock = true; - this.materialEnumProperty!.value = materialToEnum( material ); + this.materialEnumProperty!.value = material.identifier; enumLock = false; } if ( !densityLock ) { @@ -331,13 +329,29 @@ material.customColor.link( colorListener ); } } ); - Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], ( materialEnum, density, color ) => { + +// this.customDensityProperty.lazyLink( currentDensity => { +// // TODO: locks? +// +// if ( this.materialEnumProperty!.value !== 'CUSTOM' && +// +// // if NOT any valid materials match current density +// !_.some( options.materialEnumPropertyValidValues, materialName => { +// if ( materialName === 'CUSTOM' ) { +// return false; +// } +// return Material.getMaterial( materialName ).density === currentDensity; +// } ) ) { +// this.materialEnumProperty!.value = 'CUSTOM'; +// } +// } ); + Multilink.lazyMultilink( [ this.materialEnumProperty, this.customDensityProperty, this.customColorProperty ], materialEnum => { // See if it's an external change if ( !enumLock && !densityLock && !colorLock ) { enumLock = true; densityLock = true; colorLock = true; - if ( materialEnum === MaterialEnumeration.CUSTOM ) { + if ( materialEnum === 'CUSTOM' ) { const createMaterialOptions: CreateCustomMaterialOptions = { density: this.customDensityProperty!.value }; @@ -351,8 +365,7 @@ this.materialProperty.value = Material.createCustomSolidMaterial( createMaterialOptions ); } else { - assert && assert( Material.hasOwnProperty( materialEnum.name ), `unexpected material enum: ${materialEnum.name}` ); - this.materialProperty.value = Material[ materialEnum.name as MaterialNonCustomIdentifier ]; + this.materialProperty.value = Material.getMaterial( materialEnum ); } enumLock = false; densityLock = false; Index: density-buoyancy-common/js/density/view/DensityIntroScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/density/view/DensityIntroScreenView.ts b/density-buoyancy-common/js/density/view/DensityIntroScreenView.ts --- a/density-buoyancy-common/js/density/view/DensityIntroScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/density/view/DensityIntroScreenView.ts (date 1720542217046) @@ -55,7 +55,8 @@ model.massB, this.popupLayer, { tandem: tandem, - maxCustomMass: 10 + maxCustomMass: 10, + mysteryMaterials: [] } ); const accordionTandem = tandem.createTandem( 'densityAccordionBox' ); Index: density-buoyancy-common/js/common/model/Material.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/common/model/Material.ts b/density-buoyancy-common/js/common/model/Material.ts --- a/density-buoyancy-common/js/common/model/Material.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/common/model/Material.ts (date 1720542489221) @@ -28,11 +28,12 @@ import MappedProperty from '../../../../axon/js/MappedProperty.js'; import Range from '../../../../dot/js/Range.js'; import WithRequired from '../../../../phet-core/js/types/WithRequired.js'; +import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js'; const NullableColorPropertyReferenceType = NullableIO( ReferenceIO( Property.PropertyIO( Color.ColorIO ) ) ); type MaterialState = { - identifier: null | keyof typeof Material; + identifier: MaterialName; name: ReferenceIOState; tandemName: string | null; density: number; @@ -46,9 +47,6 @@ depthLinesColor: ColorState; }; -export const CUSTOM_MATERIAL_NAME = 'CUSTOM'; -export type CustomMaterialName = typeof CUSTOM_MATERIAL_NAME; - const nonCustomMaterialNames = [ 'ALUMINUM', 'APPLE', @@ -94,15 +92,14 @@ 'MATERIAL_X', 'MATERIAL_Y' ] as const; -export type MaterialName = typeof nonCustomMaterialNames[ number ] | CustomMaterialName; - -export type CreateCustomMaterialOptions = MaterialOptions & Required> & { densityRange?: Range }; +type NonCustomMaterialName = typeof nonCustomMaterialNames[ number ]; +export type MaterialName = NonCustomMaterialName | 'CUSTOM'; export type MaterialOptions = { nameProperty?: TReadOnlyProperty; // If set, this material will be available at Material[ identifier ] as a global - identifier?: MaterialName | null; + identifier: MaterialName; // Used for tandems tandemName?: string | null; @@ -127,14 +124,18 @@ // Used for the color of depth lines added on top of the Material depthLinesColorProperty?: TReadOnlyProperty; }; +type NoIdentifierMaterialOptions = StrictOmit; +export type CreateCustomMaterialOptions = NoIdentifierMaterialOptions & Required> & { densityRange?: Range }; export default class Material { public readonly nameProperty: TReadOnlyProperty; - public readonly identifier: MaterialName | null; + public readonly identifier: MaterialName; public readonly tandemName: string | null; public readonly density: number; // in SI (kg/m^3) public readonly viscosity: number; + + // TODO: Eliminate custom as an orthogonal attribute, it can be determined from identifier. https://github.com/phetsims/density-buoyancy-common/issues/176 public readonly custom: boolean; public readonly hidden: boolean; public readonly customColor: Property | null; @@ -145,7 +146,6 @@ const options = optionize()( { nameProperty: new TinyProperty( 'unknown' ), - identifier: null, tandemName: null, density: 1, viscosity: 1e-3, @@ -173,10 +173,11 @@ /** * Returns a custom material that can be modified at will. */ - public static createCustomMaterial( options: MaterialOptions ): Material { + public static createCustomMaterial( options: NoIdentifierMaterialOptions ): Material { return new Material( combineOptions( { nameProperty: DensityBuoyancyCommonStrings.material.customStringProperty, tandemName: 'custom', + identifier: 'CUSTOM', custom: true }, options ) ); } @@ -637,6 +638,8 @@ density: Material.GOLD.density } ); + // TODO: Convert to an object literal like{AIR: new Material( ... ), ...} as const + // TODO: Then we can lift the keys for a string union of "NonCustomMaterialName". https://github.com/phetsims/density-buoyancy-common/issues/176 public static readonly MATERIALS = [ Material.AIR, Material.ALUMINUM, @@ -665,6 +668,7 @@ Material.MERCURY, Material.OIL, Material.PLATINUM, + Material.PVC, Material.PYRITE, Material.SAND, Material.SEAWATER, @@ -677,6 +681,12 @@ Material.WOOD ] as const; + public static getMaterial( materialName: MaterialName ): Material { + const material = _.find( Material.MATERIALS, material => material.identifier === materialName )!; + assert && assert( material, `unknown material name: ${materialName}` ); + return material; + } + public static readonly DENSITY_MYSTERY_MATERIALS = [ Material.WOOD, Material.GASOLINE, @@ -725,7 +735,7 @@ documentation: 'Represents different materials that solids/liquids in the simulations can take, including density (kg/m^3), viscosity (Pa * s), and color.', stateSchema: { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ), - identifier: NullableIO( StringIO ), + identifier: StringIO, tandemName: NullableIO( StringIO ), density: NumberIO, viscosity: NumberIO, @@ -744,7 +754,7 @@ return { name: ReferenceIO( ReadOnlyProperty.PropertyIO( StringIO ) ).toStateObject( material.nameProperty ), - identifier: NullableIO( StringIO ).toStateObject( material.identifier ), + identifier: material.identifier, tandemName: NullableIO( StringIO ).toStateObject( material.tandemName ), density: material.density, viscosity: material.viscosity, @@ -758,10 +768,8 @@ }; }, fromStateObject( obj ): Material { - if ( obj.identifier ) { - const material = Material[ obj.identifier ]; - assert && assert( material, `Unknown material: ${obj.identifier}` ); - return material as Material; + if ( obj.identifier !== 'CUSTOM' ) { + return Material.getMaterial( obj.identifier ); } else { const staticCustomColor = NullableIO( Color.ColorIO ).fromStateObject( obj.staticCustomColor ); @@ -783,4 +791,8 @@ } ); } +assert && assert( _.every( Material.MATERIALS, material => !( material.custom || material.identifier === 'CUSTOM' ) ), + 'custom materials not allowed in MATERIALS list' ); +assert && assert( _.uniq( Material.MATERIALS ).length === Material.MATERIALS.length, 'duplicate in Material.MATERIALS' ); + densityBuoyancyCommon.register( 'Material', Material ); \ No newline at end of file Index: density-buoyancy-common/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts b/density-buoyancy-common/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts --- a/density-buoyancy-common/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/buoyancy-basics/view/BuoyancyBasicsExploreScreenView.ts (date 1720542217001) @@ -70,14 +70,14 @@ this.rightBox = new ABControlsNode( model.massA, model.massB, - this.popupLayer, - { + this.popupLayer, { useDensityControlInsteadOfMassControl: true, customKeepsConstantDensity: true, tandem: tandem, minCustomMass: 0.1, maxCustomMass: 15, - supportHiddenMaterial: true + supportHiddenMaterial: true, + mysteryMaterials: [] } ); Index: density-buoyancy-common/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts b/density-buoyancy-common/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts --- a/density-buoyancy-common/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/buoyancy-basics/model/BuoyancyBasicsExploreModel.ts (date 1720542216996) @@ -12,12 +12,11 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import Cube from '../../common/model/Cube.js'; import DensityBuoyancyModel, { DensityBuoyancyModelOptions } from '../../common/model/DensityBuoyancyModel.js'; -import Material from '../../common/model/Material.js'; +import Material, { MaterialName } from '../../common/model/Material.js'; import Scale, { DisplayType } from '../../common/model/Scale.js'; import TwoBlockMode from '../../common/model/TwoBlockMode.js'; import densityBuoyancyCommon from '../../densityBuoyancyCommon.js'; import MassTag from '../../common/model/MassTag.js'; -import MaterialEnumeration from '../../common/model/MaterialEnumeration.js'; type BuoyancyBasicsExploreModelOptions = DensityBuoyancyModelOptions; @@ -38,17 +37,13 @@ phetioFeatured: true } ); - // This step is required because, for this screen, MaterialEnumeration does not use all of the simple materials. - // So we have to filter out the ones that are not used for phet-io valid values. - const simpleMaterialsIdentifiers = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ) as string[]; - const simpleMaterialsInEnumerationKeys = MaterialEnumeration.enumeration.keys.filter( name => simpleMaterialsIdentifiers.includes( name ) ); - // @ts-expect-error TODO: is this string indexing correct? https://github.com/phetsims/density-buoyancy-common/issues/176 - const validSimpleMaterials = simpleMaterialsInEnumerationKeys.map( x => MaterialEnumeration[ x ] ).concat( [ MaterialEnumeration.CUSTOM ] ); + // B:B Explore has a more limited set of available materials. + const simpleMaterialsIdentifiers: MaterialName[] = Material.SIMPLE_MASS_MATERIALS.map( x => x.identifier ).concat( [ 'CUSTOM' ] ); this.massA = Cube.createWithMass( this.engine, Material.WOOD, new Vector2( -0.2, 0.2 ), 2, { tag: MassTag.PRIMARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockA' ) } ); @@ -56,7 +51,7 @@ this.massB = Cube.createWithMass( this.engine, Material.ALUMINUM, new Vector2( 0.05, 0.35 ), 13.5, { tag: MassTag.SECONDARY, adjustableMaterial: true, - materialEnumPropertyValidValues: validSimpleMaterials, + materialEnumPropertyValidValues: simpleMaterialsIdentifiers, adjustableColor: false, tandem: blocksTandem.createTandem( 'blockB' ), visible: false Index: density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts b/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts --- a/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (revision 98f360f91e355151ac4390930dd3ecb43c554e45) +++ b/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts (date 1720542489206) @@ -221,7 +221,7 @@ Material.GOLD, Material.PLATINUM ].concat( Material.SIMPLE_MASS_MATERIALS ), - material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density + material => material.density ).concat( [ // Adding Mystery Materials at the end, so they aren't sorted by density // TODO: Doesn't the class always make mystery at the end? https://github.com/phetsims/density-buoyancy-common/issues/176 Material.MATERIAL_V, Material.MATERIAL_W ] ), cubicMeters => model.block.updateSize( Cube.boundsFromVolume( cubicMeters ) ), this.popupLayer, true, {
zepumph commented 1 month ago

Here are the 5 spots that we do some mapping with material names/densities/material properties

ComboNumberControl for fluid selection MaterialControlNode for solid materials adjustableMaterial for density mystery B:B explore using MaterialControlNode + customDensityProperty/numberControl. CompareBlockSetModel.createMaterialProperty uses density to map to material, but there is no string enum complexity. Also it is derivedProperty, there is no bidirectional.

samreid commented 1 month ago

I'm wondering if we should put this issue on hold while investigating https://github.com/phetsims/density-buoyancy-common/issues/256

zepumph commented 1 month ago

https://github.com/phetsims/density-buoyancy-common/issues/256 is looking promising! Catch you over there.

zepumph commented 1 month ago

This has all been improved in that we only have Material now, and each instance is instrumented. Closing

phet-dev commented 1 month ago

Reopening because there is a TODO marked for this issue.

zepumph commented 1 month ago

Still good to think about this one last todo, I'll decrease the priority until other material work is complete.

zepumph commented 1 month ago

https://github.com/phetsims/density-buoyancy-common/blob/005221b1ec7082e36cd5696aca63de89f834144a/js/common/model/Material.ts#L454-L456

samreid commented 1 month ago

I wrote this patch which may be almost as good:

```diff Subject: [PATCH] Delete ScaleIO, see https://github.com/phetsims/density-buoyancy-common/issues/269 --- Index: js/common/model/Material.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/js/common/model/Material.ts b/js/common/model/Material.ts --- a/js/common/model/Material.ts (revision 80259e109b7efdd81b0df2e9ba17cb5ca5ef95f2) +++ b/js/common/model/Material.ts (date 1721697068452) @@ -451,54 +451,6 @@ density: Material.GOLD.density } ); - // TODO: Convert to an object literal like{AIR: new Material( ... ), ...} as const - // TODO: Then we can lift the keys for a string union of "NonCustomMaterialName". https://github.com/phetsims/density-buoyancy-common/issues/176 - public static readonly MATERIALS = [ - Material.ALUMINUM, - Material.APPLE, - Material.BOAT_BODY, - Material.BRICK, - Material.CONCRETE, - Material.COPPER, - Material.DIAMOND, - Material.GLASS, - Material.GOLD, - Material.HUMAN, - Material.ICE, - Material.LEAD, - Material.PLATINUM, - Material.PVC, - Material.PYRITE, - Material.SILVER, - Material.STEEL, - Material.STYROFOAM, - Material.TANTALUM, - Material.TITANIUM, - Material.WOOD, - Material.AIR, - Material.FLUID_A, - Material.FLUID_B, - Material.FLUID_C, - Material.FLUID_D, - Material.FLUID_E, - Material.FLUID_F, - Material.GASOLINE, - Material.HONEY, - Material.MERCURY, - Material.OIL, - Material.SAND, - Material.SEAWATER, - Material.WATER, - Material.MATERIAL_O, - Material.MATERIAL_P, - Material.MATERIAL_R, - Material.MATERIAL_S, - Material.MATERIAL_V, - Material.MATERIAL_W, - Material.MATERIAL_X, - Material.MATERIAL_Y - ] as const; - public static readonly DENSITY_MYSTERY_MATERIALS = [ Material.WOOD, Material.GASOLINE, @@ -582,6 +534,16 @@ } } +// Create a type that is a string literal union pulling all the keys of Material: +type MaterialKeys = { + [K in keyof T]: T[K] extends Material ? K : never; +}[keyof T]; + +type MaterialName = MaterialKeys; + +const x: MaterialName = 'ALUMINUM'; +console.log( x ); + export class CustomLiquidMaterial extends Material { public constructor( tandem: Tandem, providedOptions: MaterialOptions ) { @@ -601,7 +563,4 @@ } } -assert && assert( _.every( Material.MATERIALS, material => !material.custom ), 'custom materials not allowed in MATERIALS list' ); -assert && assert( _.uniq( Material.MATERIALS ).length === Material.MATERIALS.length, 'duplicate in Material.MATERIALS' ); - densityBuoyancyCommon.register( 'Material', Material ); \ No newline at end of file
samreid commented 1 month ago

Also, maybe we don't need this at all since we never look up a Material based on a string name (other than tandems). In that case, we can just delete the list of MATERIALS and close the issue.

zepumph commented 1 month ago

Excellent, thanks.