openstudio-ee 0.12.3 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.coverage +0 -0
  3. data/.github/workflows/test-with-openstudio.yml +109 -74
  4. data/.gitignore +21 -0
  5. data/.rubocop.yml +15 -2
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +7 -8
  8. data/README.md +1 -0
  9. data/WORKFLOW_CHANGES.md +74 -0
  10. data/lib/measures/AddDaylightSensors/measure.rb +79 -79
  11. data/lib/measures/AddDaylightSensors/measure.xml +4 -4
  12. data/lib/measures/AddOverhangsByProjectionFactor/measure.rb +38 -41
  13. data/lib/measures/AddOverhangsByProjectionFactor/measure.xml +4 -4
  14. data/lib/measures/EnableDemandControlledVentilation/measure.rb +37 -40
  15. data/lib/measures/EnableDemandControlledVentilation/measure.xml +4 -4
  16. data/lib/measures/EnableEconomizerControl/measure.rb +36 -37
  17. data/lib/measures/EnableEconomizerControl/measure.xml +4 -4
  18. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.rb +27 -41
  19. data/lib/measures/GLHEProExportLoadsforGroundHeatExchangerSizing/measure.xml +4 -4
  20. data/lib/measures/GLHEProGFunctionImport/measure.rb +11 -15
  21. data/lib/measures/GLHEProGFunctionImport/measure.xml +4 -4
  22. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.rb +5 -9
  23. data/lib/measures/GLHEProSetupExportLoadsforGroundHeatExchangerSizing/measure.xml +3 -3
  24. data/lib/measures/ImproveFanBeltEfficiency/measure.rb +78 -95
  25. data/lib/measures/ImproveFanBeltEfficiency/measure.xml +6 -6
  26. data/lib/measures/ImproveMotorEfficiency/measure.rb +75 -100
  27. data/lib/measures/ImproveMotorEfficiency/measure.xml +6 -6
  28. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.rb +137 -130
  29. data/lib/measures/IncreaseInsulationRValueForExteriorWalls/measure.xml +4 -4
  30. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.rb +114 -115
  31. data/lib/measures/IncreaseInsulationRValueForExteriorWallsByPercentage/measure.xml +3 -3
  32. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.rb +137 -130
  33. data/lib/measures/IncreaseInsulationRValueForRoofs/measure.xml +4 -4
  34. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.rb +114 -115
  35. data/lib/measures/IncreaseInsulationRValueForRoofsByPercentage/measure.xml +3 -3
  36. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.rb +69 -63
  37. data/lib/measures/ReduceElectricEquipmentLoadsByPercentage/measure.xml +6 -6
  38. data/lib/measures/ReduceLightingLoadsByPercentage/measure.rb +77 -66
  39. data/lib/measures/ReduceLightingLoadsByPercentage/measure.xml +6 -6
  40. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.rb +45 -43
  41. data/lib/measures/ReduceNightTimeElectricEquipmentLoads/measure.xml +4 -4
  42. data/lib/measures/ReduceNightTimeLightingLoads/measure.rb +45 -43
  43. data/lib/measures/ReduceNightTimeLightingLoads/measure.xml +4 -4
  44. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.rb +58 -52
  45. data/lib/measures/ReduceSpaceInfiltrationByPercentage/measure.xml +6 -6
  46. data/lib/measures/ReduceVentilationByPercentage/measure.rb +49 -46
  47. data/lib/measures/ReduceVentilationByPercentage/measure.xml +6 -6
  48. data/lib/measures/add_variable_speed_rtu_control_logic/measure.rb +31 -23
  49. data/lib/measures/add_variable_speed_rtu_control_logic/measure.xml +4 -4
  50. data/lib/measures/create_variable_speed_rtu/measure.rb +166 -174
  51. data/lib/measures/create_variable_speed_rtu/measure.xml +6 -6
  52. data/lib/measures/fan_assist_night_ventilation/measure.rb +33 -32
  53. data/lib/measures/fan_assist_night_ventilation/measure.xml +4 -4
  54. data/lib/measures/nze_hvac/measure.rb +72 -62
  55. data/lib/measures/nze_hvac/measure.xml +4 -4
  56. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.rb +16 -19
  57. data/lib/measures/replace_water_heater_mixed_with_thermal_storage_chilled_water/measure.xml +4 -4
  58. data/lib/measures/window_enhancement/LICENSE.md +14 -0
  59. data/lib/measures/window_enhancement/README.md +112 -0
  60. data/lib/measures/window_enhancement/docs/.gitkeep +0 -0
  61. data/lib/measures/window_enhancement/measure.py +386 -0
  62. data/lib/measures/window_enhancement/measure.xml +128 -0
  63. data/lib/measures/window_enhancement/resources/EC3_lookup.py +321 -0
  64. data/lib/measures/window_enhancement/resources/Test_API.py +32 -0
  65. data/lib/measures/window_enhancement/resources/__pycache__/EC3_lookup.cpython-39.pyc +0 -0
  66. data/lib/measures/window_enhancement/resources/__pycache__/Original_EC3_lookup.py +322 -0
  67. data/lib/measures/window_enhancement/resources/__pycache__/Test_API.cpython-39.pyc +0 -0
  68. data/lib/measures/window_enhancement/resources/calculate_perimeter.py +39 -0
  69. data/lib/measures/window_enhancement/test_output.log +39 -0
  70. data/lib/openstudio/ee_measures/version.rb +1 -1
  71. data/openstudio-ee.gemspec +10 -8
  72. data/test-workflow-locally.sh +152 -0
  73. metadata +64 -35
  74. data/Jenkinsfile +0 -11
@@ -9,17 +9,17 @@
9
9
  class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
10
10
  # define the name that a user will see
11
11
  def name
12
- return 'Add Daylight Sensor at the Center of Spaces with a Specified Space Type Assigned'
12
+ 'Add Daylight Sensor at the Center of Spaces with a Specified Space Type Assigned'
13
13
  end
14
14
 
15
15
  # human readable description
16
16
  def description
17
- return 'This measure will add daylighting controls to spaces that that have space types assigned with names containing the string in the argument. You can also add a cost per space for sensors added to the model.'
17
+ 'This measure will add daylighting controls to spaces that that have space types assigned with names containing the string in the argument. You can also add a cost per space for sensors added to the model.'
18
18
  end
19
19
 
20
20
  # human readable description of modeling approach
21
21
  def modeler_description
22
- return "Make an array of the spaces that meet the criteria. Locate the sensor x and y values by averaging the min and max X and Y values from floor surfaces in the space. If a space already has a daylighting control, do not add a new one and leave the original in place. Warn the user if the space isn't assigned to a thermal zone, or if the space doesn't have any translucent surfaces. Note that the cost is added to the space not the sensor. If the sensor is removed at a later date, the cost will remain."
22
+ "Make an array of the spaces that meet the criteria. Locate the sensor x and y values by averaging the min and max X and Y values from floor surfaces in the space. If a space already has a daylighting control, do not add a new one and leave the original in place. Warn the user if the space isn't assigned to a thermal zone, or if the space doesn't have any translucent surfaces. Note that the cost is added to the space not the sensor. If the sensor is removed at a later date, the cost will remain."
23
23
  end
24
24
 
25
25
  # define the arguments that the user will input
@@ -40,14 +40,15 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
40
40
  # looping through sorted hash of model objects
41
41
  space_type_args_hash.sort.map do |key, value|
42
42
  # only include if space type is used in the model
43
- if !value.spaces.empty?
43
+ unless value.spaces.empty?
44
44
  space_type_handles << value.handle.to_s
45
45
  space_type_display_names << key
46
46
  end
47
47
  end
48
48
 
49
49
  # make a choice argument for space type
50
- space_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('space_type', space_type_handles, space_type_display_names, true)
50
+ space_type = OpenStudio::Measure::OSArgument.makeChoiceArgument('space_type', space_type_handles,
51
+ space_type_display_names, true)
51
52
  space_type.setDisplayName('Add Daylight Sensors to Spaces of This Space Type')
52
53
  args << space_type
53
54
 
@@ -144,7 +145,7 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
144
145
  om_frequency.setDefaultValue(1)
145
146
  args << om_frequency
146
147
 
147
- return args
148
+ args
148
149
  end
149
150
 
150
151
  # define what happens when the measure is run
@@ -152,9 +153,7 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
152
153
  super(model, runner, user_arguments)
153
154
 
154
155
  # use the built-in error checking
155
- if !runner.validateUserArguments(arguments(model), user_arguments)
156
- return false
157
- end
156
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
158
157
 
159
158
  # assign the user inputs to variables
160
159
  space_type = runner.getOptionalWorkspaceObjectChoiceValue('space_type', user_arguments, model)
@@ -181,13 +180,11 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
181
180
  runner.registerError("The selected space type with handle '#{handle}' was not found in the model. It may have been removed by another measure.")
182
181
  end
183
182
  return false
183
+ elsif !space_type.get.to_SpaceType.empty?
184
+ space_type = space_type.get.to_SpaceType.get
184
185
  else
185
- if !space_type.get.to_SpaceType.empty?
186
- space_type = space_type.get.to_SpaceType.get
187
- else
188
- runner.registerError('Script Error - argument not showing up as space type.')
189
- return false
190
- end
186
+ runner.registerError('Script Error - argument not showing up as space type.')
187
+ return false
191
188
  end
192
189
 
193
190
  # check the setpoint for reasonableness
@@ -238,17 +235,15 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
238
235
  if (expected_life < 1) && (expected_life > 100)
239
236
  runner.registerError('Choose an integer greater than 0 and less than or equal to 100 for Expected Life.')
240
237
  end
241
- if om_frequency < 1
242
- runner.registerError('Choose an integer greater than 0 for O & M Frequency.')
243
- end
238
+ runner.registerError('Choose an integer greater than 0 for O & M Frequency.') if om_frequency < 1
244
239
 
245
240
  # short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure
246
241
  def neat_numbers(number, roundto = 2) # round to 0 or 2)
247
- if roundto == 2
248
- number = format '%.2f', number
249
- else
250
- number = number.round
251
- end
242
+ number = if roundto == 2
243
+ format '%.2f', number
244
+ else
245
+ number.round
246
+ end
252
247
  # regex to add commas
253
248
  number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
254
249
  end
@@ -259,14 +254,12 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
259
254
  objects.each do |object|
260
255
  object_LCCs = object.lifeCycleCosts
261
256
  object_LCCs.each do |object_LCC|
262
- if (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
263
- if object_LCC.yearsFromStart == 0
264
- counter += object_LCC.totalCost
265
- end
266
- end
257
+ next unless (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
258
+
259
+ counter += object_LCC.totalCost if object_LCC.yearsFromStart == 0
267
260
  end
268
261
  end
269
- return counter
262
+ counter
270
263
  end
271
264
 
272
265
  # unit conversion from IP units to SI units
@@ -297,15 +290,14 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
297
290
  spaces_using_space_type.each do |space_using_space_type|
298
291
  if space_using_space_type.daylightingControls.empty?
299
292
  space_zone = space_using_space_type.thermalZone
300
- if !space_zone.empty?
293
+ if space_zone.empty?
294
+ runner.registerWarning("Space '#{space_using_space_type.name}' is not associated with a thermal zone. It won't be part of the EnergyPlus simulation.")
295
+ else
301
296
  space_zone = space_zone.get
302
297
  if space_zone.primaryDaylightingControl.empty? && space_zone.secondaryDaylightingControl.empty?
303
298
  spaces_using_space_type_in_zones_without_sensors << space_using_space_type
304
- elsif
305
- runner.registerWarning("Thermal zone '#{space_zone.name}' which includes space '#{space_using_space_type.name}' already had a daylighting sensor. No sensor was added to space '#{space_using_space_type.name}'.")
299
+ elsif runner.registerWarning("Thermal zone '#{space_zone.name}' which includes space '#{space_using_space_type.name}' already had a daylighting sensor. No sensor was added to space '#{space_using_space_type.name}'.")
306
300
  end
307
- else
308
- runner.registerWarning("Space '#{space_using_space_type.name}' is not associated with a thermal zone. It won't be part of the EnergyPlus simulation.")
309
301
  end
310
302
  else
311
303
  runner.registerWarning("Space '#{space_using_space_type.name}' already has a daylighting sensor. No sensor was added.")
@@ -323,9 +315,11 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
323
315
  has_ext_nat_light = false
324
316
  space.surfaces.each do |surface|
325
317
  next if surface.outsideBoundaryCondition != 'Outdoors'
318
+
326
319
  surface.subSurfaces.each do |sub_surface|
327
320
  next if sub_surface.subSurfaceType == 'Door'
328
321
  next if sub_surface.subSurfaceType == 'OverheadDoor'
322
+
329
323
  has_ext_nat_light = true
330
324
  end
331
325
  end
@@ -338,6 +332,7 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
338
332
  floors = []
339
333
  space.surfaces.each do |surface|
340
334
  next if surface.surfaceType != 'Floor'
335
+
341
336
  floors << surface
342
337
  end
343
338
 
@@ -373,16 +368,20 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
373
368
  starting_lcc_counter = space.lifeCycleCosts.size
374
369
 
375
370
  # adding new cost items
376
- lcc_mat = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Mat - #{sensor.name}", space, material_cost, 'CostPerEach', 'Construction', expected_life, years_until_costs_start)
371
+ lcc_mat = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Mat - #{sensor.name}", space,
372
+ material_cost, 'CostPerEach', 'Construction', expected_life, years_until_costs_start)
377
373
  if demo_cost_initial_const
378
- lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space, demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start)
374
+ lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space,
375
+ demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start)
379
376
  else
380
- lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space, demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start + expected_life)
377
+ lcc_demo = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_Demo - #{sensor.name}", space,
378
+ demolition_cost, 'CostPerEach', 'Salvage', expected_life, years_until_costs_start + expected_life)
381
379
  end
382
- lcc_om = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_OM - #{sensor.name}", space, om_cost, 'CostPerEach', 'Maintenance', om_frequency, 0)
380
+ lcc_om = OpenStudio::Model::LifeCycleCost.createLifeCycleCost("LCC_OM - #{sensor.name}", space, om_cost,
381
+ 'CostPerEach', 'Maintenance', om_frequency, 0)
383
382
 
384
383
  if space.lifeCycleCosts.size - starting_lcc_counter == 3
385
- if !warning_cost_assign_to_space
384
+ unless warning_cost_assign_to_space
386
385
  runner.registerInfo('Cost for daylight sensors was added to spaces. The cost will remain in the model unless the space is removed. Removing only the sensor will not remove the cost.')
387
386
  warning_cost_assign_to_space = true
388
387
  end
@@ -424,48 +423,47 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
424
423
  end
425
424
  end
426
425
 
427
- if !zone_spaces_with_new_sensors.empty?
428
- # need to identify the two largest spaces
429
- primary_area = 0
430
- secondary_area = 0
431
- primary_space = nil
432
- secondary_space = nil
433
- three_or_more_sensors = false
434
-
435
- # dfg temp - need to add another if statement so only get spaces with sensors
436
- zone_spaces_with_new_sensors.each do |zone_space|
437
- zone_space_area = zone_space.floorArea
438
- if zone_space_area > primary_area
439
- primary_area = zone_space_area
440
- primary_space = zone_space
441
- elsif zone_space_area > secondary_area
442
- secondary_area = zone_space_area
443
- secondary_space = zone_space
444
- else
445
- # setup flag to warn user that more than 2 sensors can't be added to a space
446
- three_or_more_sensors = true
447
- end
448
- end
449
-
450
- if primary_space
451
- # setup primary sensor
452
- sensor_primary = new_sensor_objects[primary_space.name.to_s]
453
- zone.setPrimaryDaylightingControl(sensor_primary)
454
- zone.setFractionofZoneControlledbyPrimaryDaylightingControl(fraction_zone_controlled * primary_area / (primary_area + secondary_area))
426
+ next if zone_spaces_with_new_sensors.empty?
427
+
428
+ # need to identify the two largest spaces
429
+ primary_area = 0
430
+ secondary_area = 0
431
+ primary_space = nil
432
+ secondary_space = nil
433
+ three_or_more_sensors = false
434
+
435
+ # dfg temp - need to add another if statement so only get spaces with sensors
436
+ zone_spaces_with_new_sensors.each do |zone_space|
437
+ zone_space_area = zone_space.floorArea
438
+ if zone_space_area > primary_area
439
+ primary_area = zone_space_area
440
+ primary_space = zone_space
441
+ elsif zone_space_area > secondary_area
442
+ secondary_area = zone_space_area
443
+ secondary_space = zone_space
444
+ else
445
+ # setup flag to warn user that more than 2 sensors can't be added to a space
446
+ three_or_more_sensors = true
455
447
  end
448
+ end
456
449
 
457
- if secondary_space
458
- # setup secondary sensor
459
- sensor_secondary = new_sensor_objects[secondary_space.name.to_s]
460
- zone.setSecondaryDaylightingControl(sensor_secondary)
461
- zone.setFractionofZoneControlledbySecondaryDaylightingControl(fraction_zone_controlled * secondary_area / (primary_area + secondary_area))
462
- end
450
+ if primary_space
451
+ # setup primary sensor
452
+ sensor_primary = new_sensor_objects[primary_space.name.to_s]
453
+ zone.setPrimaryDaylightingControl(sensor_primary)
454
+ zone.setFractionofZoneControlledbyPrimaryDaylightingControl(fraction_zone_controlled * primary_area / (primary_area + secondary_area))
455
+ end
463
456
 
464
- # warn that additional sensors were not used
465
- if three_or_more_sensors == true
466
- runner.registerWarning("Thermal zone '#{zone.name}' had more than two spaces with sensors. Only two sensors were associated with the thermal zone.")
467
- end
457
+ if secondary_space
458
+ # setup secondary sensor
459
+ sensor_secondary = new_sensor_objects[secondary_space.name.to_s]
460
+ zone.setSecondaryDaylightingControl(sensor_secondary)
461
+ zone.setFractionofZoneControlledbySecondaryDaylightingControl(fraction_zone_controlled * secondary_area / (primary_area + secondary_area))
462
+ end
468
463
 
464
+ # warn that additional sensors were not used
465
+ if three_or_more_sensors == true
466
+ runner.registerWarning("Thermal zone '#{zone.name}' had more than two spaces with sensors. Only two sensors were associated with the thermal zone.")
469
467
  end
470
468
  end
471
469
 
@@ -483,9 +481,11 @@ class AddDaylightSensors < OpenStudio::Measure::ModelMeasure
483
481
  yr0_capital_totalCosts = get_total_costs_for_objects(spaces_using_space_type)
484
482
 
485
483
  # reporting final condition of model
486
- runner.registerFinalCondition("Added daylighting controls to #{sensor_count} spaces, covering #{area_ip}. Initial year costs associated with the daylighting controls is $#{neat_numbers(yr0_capital_totalCosts, 0)}.")
484
+ runner.registerFinalCondition("Added daylighting controls to #{sensor_count} spaces, covering #{area_ip}. Initial year costs associated with the daylighting controls is $#{neat_numbers(
485
+ yr0_capital_totalCosts, 0
486
+ )}.")
487
487
 
488
- return true
488
+ true
489
489
  end
490
490
  end
491
491
 
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>add_daylight_sensors</name>
5
5
  <uid>62babdc7-c81e-4dfd-96e8-af1628a55167</uid>
6
- <version_id>a544f8df-9af8-4b82-9806-bae6d49c009d</version_id>
7
- <version_modified>2025-08-08T15:15:56Z</version_modified>
6
+ <version_id>39be2777-6303-4810-8fd0-aaae1dfa515b</version_id>
7
+ <version_modified>2025-09-25T15:33:42Z</version_modified>
8
8
  <xml_checksum>0AC58119</xml_checksum>
9
9
  <class_name>AddDaylightSensors</class_name>
10
10
  <display_name>Add Daylight Sensor at the Center of Spaces with a Specified Space Type Assigned</display_name>
@@ -216,13 +216,13 @@
216
216
  <filename>measure.rb</filename>
217
217
  <filetype>rb</filetype>
218
218
  <usage_type>script</usage_type>
219
- <checksum>D8B26643</checksum>
219
+ <checksum>22008D9E</checksum>
220
220
  </file>
221
221
  <file>
222
222
  <filename>AddDaylightSensors_Test.rb</filename>
223
223
  <filetype>rb</filetype>
224
224
  <usage_type>test</usage_type>
225
- <checksum>91062DC5</checksum>
225
+ <checksum>2F7FEB8D</checksum>
226
226
  </file>
227
227
  <file>
228
228
  <filename>ModelForDaylightSensors.osm</filename>
@@ -9,17 +9,17 @@
9
9
  class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
10
10
  # define the name that a user will see
11
11
  def name
12
- return 'Add Overhangs by Projection Factor'
12
+ 'Add Overhangs by Projection Factor'
13
13
  end
14
14
 
15
15
  # human readable description
16
16
  def description
17
- return 'Add overhangs by projection factor to specified windows. The projection factor is the overhang depth divided by the window height. This can be applied to windows by the closest cardinal direction. If baseline model contains overhangs made by this measure, they will be replaced. Optionally the measure can delete any pre-existing space shading surfaces.'
17
+ 'Add overhangs by projection factor to specified windows. The projection factor is the overhang depth divided by the window height. This can be applied to windows by the closest cardinal direction. If baseline model contains overhangs made by this measure, they will be replaced. Optionally the measure can delete any pre-existing space shading surfaces.'
18
18
  end
19
19
 
20
20
  # human readable description of modeling approach
21
21
  def modeler_description
22
- return "If requested then delete existing space shading surfaces. Then loop through exterior windows. If the requested cardinal direction is the closest to the window, then add the overhang. Name the shading surface the same as the window but append with '-Overhang'. If a space shading surface of that name already exists, then delete it before making the new one. This measure has no life cycle cost arguments. You can see the economic impact of the measure by costing the construction used for the overhangs."
22
+ "If requested then delete existing space shading surfaces. Then loop through exterior windows. If the requested cardinal direction is the closest to the window, then add the overhang. Name the shading surface the same as the window but append with '-Overhang'. If a space shading surface of that name already exists, then delete it before making the new one. This measure has no life cycle cost arguments. You can see the economic impact of the measure by costing the construction used for the overhangs."
23
23
  end
24
24
 
25
25
  # define the arguments that the user will input
@@ -64,18 +64,19 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
64
64
  # looping through sorted hash of constructions
65
65
  construction_args_hash.sort.map do |key, value|
66
66
  # only include if construction is not used on surface
67
- if !value.isFenestration
67
+ unless value.isFenestration
68
68
  construction_handles << value.handle.to_s
69
69
  construction_display_names << key
70
70
  end
71
71
  end
72
72
 
73
73
  # make an argument for construction
74
- construction = OpenStudio::Measure::OSArgument.makeChoiceArgument('construction', construction_handles, construction_display_names, false)
74
+ construction = OpenStudio::Measure::OSArgument.makeChoiceArgument('construction', construction_handles,
75
+ construction_display_names, false)
75
76
  construction.setDisplayName('Optionally Choose a Construction for the Overhangs')
76
77
  args << construction
77
78
 
78
- return args
79
+ args
79
80
  end
80
81
 
81
82
  # define what happens when the measure is run
@@ -83,9 +84,7 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
83
84
  super(model, runner, user_arguments)
84
85
 
85
86
  # use the built-in error checking
86
- if !runner.validateUserArguments(arguments(model), user_arguments)
87
- return false
88
- end
87
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
89
88
 
90
89
  # assign the user inputs to variables
91
90
  projection_factor = runner.getDoubleArgumentValue('projection_factor', user_arguments)
@@ -117,29 +116,30 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
117
116
  return false
118
117
  end
119
118
 
119
+ elsif !construction.get.to_Construction.empty?
120
+ construction = construction.get.to_Construction.get
120
121
  else
121
- if !construction.get.to_Construction.empty?
122
- construction = construction.get.to_Construction.get
123
- else
124
- runner.registerError('Script Error - argument not showing up as construction.')
125
- return false
126
- end
122
+ runner.registerError('Script Error - argument not showing up as construction.')
123
+ return false
127
124
  end
128
125
 
129
126
  # helper to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure.
130
127
  def neat_numbers(number, roundto = 2) # round to 0 or 2)
131
- if roundto == 2
132
- number = format '%.2f', number
133
- else
134
- number = number.round
135
- end
128
+ number = if roundto == 2
129
+ format '%.2f', number
130
+ else
131
+ number.round
132
+ end
136
133
  # regex to add commas
137
134
  number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
138
135
  end
139
136
 
140
137
  # helper to make it easier to do unit conversions on the fly. The definition be called through this measure.
141
138
  def unit_helper(number, from_unit_string, to_unit_string)
142
- converted_number = OpenStudio.convert(OpenStudio::Quantity.new(number, OpenStudio.createUnit(from_unit_string).get), OpenStudio.createUnit(to_unit_string).get).get.value
139
+ converted_number = OpenStudio.convert(
140
+ OpenStudio::Quantity.new(number,
141
+ OpenStudio.createUnit(from_unit_string).get), OpenStudio.createUnit(to_unit_string).get
142
+ ).get.value
143
143
  end
144
144
 
145
145
  # helper that loops through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0
@@ -148,14 +148,12 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
148
148
  objects.each do |object|
149
149
  object_LCCs = object.lifeCycleCosts
150
150
  object_LCCs.each do |object_LCC|
151
- if (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
152
- if object_LCC.yearsFromStart == 0
153
- counter += object_LCC.totalCost
154
- end
155
- end
151
+ next unless (object_LCC.category == 'Construction') || (object_LCC.category == 'Salvage')
152
+
153
+ counter += object_LCC.totalCost if object_LCC.yearsFromStart == 0
156
154
  end
157
155
  end
158
- return counter
156
+ counter
159
157
  end
160
158
 
161
159
  # counter for year 0 capital costs
@@ -201,17 +199,18 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
201
199
  next if s.subSurfaceType == 'TubularDaylightDiffuser'
202
200
 
203
201
  # get the absoluteAzimuth for the surface so we can categorize it
204
- absoluteAzimuth = OpenStudio.convert(s.azimuth, 'rad', 'deg').get + s.space.get.directionofRelativeNorth + model.getBuilding.northAxis
202
+ absoluteAzimuth = OpenStudio.convert(s.azimuth, 'rad',
203
+ 'deg').get + s.space.get.directionofRelativeNorth + model.getBuilding.northAxis
205
204
  absoluteAzimuth -= 360.0 until absoluteAzimuth < 360.0
206
205
 
207
206
  if facade == 'North'
208
- next if !((absoluteAzimuth >= 315.0) || (absoluteAzimuth < 45.0))
207
+ next unless (absoluteAzimuth >= 315.0) || (absoluteAzimuth < 45.0)
209
208
  elsif facade == 'East'
210
- next if !((absoluteAzimuth >= 45.0) && (absoluteAzimuth < 135.0))
209
+ next unless (absoluteAzimuth >= 45.0) && (absoluteAzimuth < 135.0)
211
210
  elsif facade == 'South'
212
- next if !((absoluteAzimuth >= 135.0) && (absoluteAzimuth < 225.0))
211
+ next unless (absoluteAzimuth >= 135.0) && (absoluteAzimuth < 225.0)
213
212
  elsif facade == 'West'
214
- next if !((absoluteAzimuth >= 225.0) && (absoluteAzimuth < 315.0))
213
+ next unless (absoluteAzimuth >= 225.0) && (absoluteAzimuth < 315.0)
215
214
  else
216
215
  runner.registerError('Unexpected value of facade: ' + facade + '.')
217
216
  return false
@@ -238,23 +237,19 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
238
237
  if new_overhang.empty?
239
238
  ok = runner.registerWarning('Unable to add overhang to ' + s.briefDescription +
240
239
  ' with projection factor ' + projection_factor.to_s + ' and offset ' + offset.to_s + '.')
241
- return false if !ok
240
+ return false unless ok
242
241
  else
243
242
  new_overhang.get.setName("#{s.name} - Overhang")
244
243
  runner.registerInfo('Added overhang ' + new_overhang.get.briefDescription + ' to ' +
245
244
  s.briefDescription + ' with projection factor ' + projection_factor.to_s +
246
245
  ' and offset ' + '0' + '.')
247
- if construction_chosen
248
- if !construction.to_Construction.empty?
249
- new_overhang.get.setConstruction(construction)
250
- end
251
- end
246
+ new_overhang.get.setConstruction(construction) if construction_chosen && !construction.to_Construction.empty?
252
247
  overhang_added = true
253
248
  end
254
249
  end
255
250
  end
256
251
 
257
- if !overhang_added
252
+ unless overhang_added
258
253
  runner.registerAsNotApplicable("The model has exterior #{facade.downcase} walls, but no windows were found to add overhangs to.")
259
254
  return true
260
255
  end
@@ -268,9 +263,11 @@ class AddOverhangsByProjectionFactor < OpenStudio::Measure::ModelMeasure
268
263
  final_shading_groups.each do |shading_group|
269
264
  number_of_final_space_shading_surf += shading_group.shadingSurfaces.size
270
265
  end
271
- runner.registerFinalCondition("The final building has #{number_of_final_space_shading_surf} space shading surfaces. Initial capital costs associated with the improvements are $#{neat_numbers(yr0_capital_totalCosts, 0)}.")
266
+ runner.registerFinalCondition("The final building has #{number_of_final_space_shading_surf} space shading surfaces. Initial capital costs associated with the improvements are $#{neat_numbers(
267
+ yr0_capital_totalCosts, 0
268
+ )}.")
272
269
 
273
- return true
270
+ true
274
271
  end
275
272
  end
276
273
 
@@ -3,8 +3,8 @@
3
3
  <schema_version>3.1</schema_version>
4
4
  <name>add_overhangs_by_projection_factor</name>
5
5
  <uid>1e29e117-3916-4368-b24d-75da54045956</uid>
6
- <version_id>680a7548-4ba3-4bde-9a58-f8873dd8d535</version_id>
7
- <version_modified>2025-08-08T15:15:58Z</version_modified>
6
+ <version_id>7de48598-b984-4a31-b8b7-bedb9a80e9c2</version_id>
7
+ <version_modified>2025-09-25T15:33:44Z</version_modified>
8
8
  <xml_checksum>6DE831F7</xml_checksum>
9
9
  <class_name>AddOverhangsByProjectionFactor</class_name>
10
10
  <display_name>Add Overhangs by Projection Factor</display_name>
@@ -127,13 +127,13 @@
127
127
  <filename>measure.rb</filename>
128
128
  <filetype>rb</filetype>
129
129
  <usage_type>script</usage_type>
130
- <checksum>330F0C00</checksum>
130
+ <checksum>5C47B13D</checksum>
131
131
  </file>
132
132
  <file>
133
133
  <filename>AddOverhangsByProjectionFactor_Test.rb</filename>
134
134
  <filetype>rb</filetype>
135
135
  <usage_type>test</usage_type>
136
- <checksum>18A9E740</checksum>
136
+ <checksum>E5AFF7A2</checksum>
137
137
  </file>
138
138
  <file>
139
139
  <filename>OverhangTestModel_01.osm</filename>
@@ -19,11 +19,11 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
19
19
  # define the name that a user will see, this method may be deprecated as
20
20
  # the display name in PAT comes from the name field in measure.xml
21
21
  def name
22
- return 'Enable Demand Controlled Ventilation'
22
+ 'Enable Demand Controlled Ventilation'
23
23
  end
24
24
 
25
25
  # define the arguments that the user will input
26
- def arguments(model)
26
+ def arguments(_model)
27
27
  args = OpenStudio::Measure::OSArgumentVector.new
28
28
 
29
29
  # make choice argument economizer control type
@@ -35,7 +35,7 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
35
35
  dcv_type.setDisplayName('DCV Type')
36
36
  args << dcv_type
37
37
 
38
- return args
38
+ args
39
39
  end
40
40
 
41
41
  # define what happens when the measure is cop
@@ -43,9 +43,7 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
43
43
  super(model, runner, user_arguments)
44
44
 
45
45
  # use the built-in error checking
46
- if !runner.validateUserArguments(arguments(model), user_arguments)
47
- return false
48
- end
46
+ return false unless runner.validateUserArguments(arguments(model), user_arguments)
49
47
 
50
48
  # assign the user inputs to variables
51
49
  dcv_type = runner.getStringArgumentValue('dcv_type', user_arguments)
@@ -59,11 +57,11 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
59
57
 
60
58
  # short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure
61
59
  def neat_numbers(number, roundto = 2) # round to 0 or 2)
62
- if roundto == 2
63
- number = format '%.2f', number
64
- else
65
- number = number.round
66
- end
60
+ number = if roundto == 2
61
+ format '%.2f', number
62
+ else
63
+ number.round
64
+ end
67
65
  # regex to add commas
68
66
  number.to_s.reverse.gsub(/([0-9]{3}(?=([0-9])))/, '\\1,').reverse
69
67
  end
@@ -76,35 +74,34 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
76
74
  # find AirLoopHVACOutdoorAirSystem on loop
77
75
  air_loop.supplyComponents.each do |supply_component|
78
76
  hVACComponent = supply_component.to_AirLoopHVACOutdoorAirSystem
79
- if !hVACComponent.empty?
80
- hVACComponent = hVACComponent.get
81
-
82
- # get ControllerOutdoorAir
83
- controller_oa = hVACComponent.getControllerOutdoorAir
84
-
85
- # get ControllerMechanicalVentilation
86
- controller_mv = controller_oa.controllerMechanicalVentilation
87
-
88
- if dcv_type == 'EnableDCV'
89
- # check if demand control is enabled, if not, then enable it
90
- if controller_mv.demandControlledVentilation == true
91
- runner.registerInfo("#{air_loop.name} already has DCV enabled.")
92
- else
93
- controller_mv.setDemandControlledVentilation(true)
94
- runner.registerInfo("Enabling DCV for #{air_loop.name}.")
95
- air_loops_changed << air_loop
96
- end
97
- elsif dcv_type == 'DisableDCV'
98
- # check if demand control is disabled, if not, then disabled it
99
- if controller_mv.demandControlledVentilation == false
100
- runner.registerInfo("#{air_loop.name} already has DCV disabled.")
101
- else
102
- controller_mv.setDemandControlledVentilation(false)
103
- runner.registerInfo("Disabling DCV for #{air_loop.name}.")
104
- air_loops_changed << air_loop
105
- end
106
- end
77
+ next if hVACComponent.empty?
78
+
79
+ hVACComponent = hVACComponent.get
107
80
 
81
+ # get ControllerOutdoorAir
82
+ controller_oa = hVACComponent.getControllerOutdoorAir
83
+
84
+ # get ControllerMechanicalVentilation
85
+ controller_mv = controller_oa.controllerMechanicalVentilation
86
+
87
+ if dcv_type == 'EnableDCV'
88
+ # check if demand control is enabled, if not, then enable it
89
+ if controller_mv.demandControlledVentilation == true
90
+ runner.registerInfo("#{air_loop.name} already has DCV enabled.")
91
+ else
92
+ controller_mv.setDemandControlledVentilation(true)
93
+ runner.registerInfo("Enabling DCV for #{air_loop.name}.")
94
+ air_loops_changed << air_loop
95
+ end
96
+ elsif dcv_type == 'DisableDCV'
97
+ # check if demand control is disabled, if not, then disabled it
98
+ if controller_mv.demandControlledVentilation == false
99
+ runner.registerInfo("#{air_loop.name} already has DCV disabled.")
100
+ else
101
+ controller_mv.setDemandControlledVentilation(false)
102
+ runner.registerInfo("Disabling DCV for #{air_loop.name}.")
103
+ air_loops_changed << air_loop
104
+ end
108
105
  end
109
106
  end
110
107
  end
@@ -118,7 +115,7 @@ class EnableDemandControlledVentilation < OpenStudio::Measure::ModelMeasure
118
115
  # Report final condition of model
119
116
  runner.registerFinalCondition("#{air_loops_changed.size} air loops now have demand controlled ventilation enabled.")
120
117
 
121
- return true
118
+ true
122
119
  end
123
120
  end
124
121