openstudio-standards 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (378) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +33 -0
  3. data/data/inventory/thermal_bridging.csv +90 -0
  4. data/data/standards/OpenStudio_Standards-deer-comstock.xlsx +0 -0
  5. data/data/standards/manage_OpenStudio_Standards.rb +1 -1
  6. data/data/standards/test_performance_expected_dd_results.csv +2014 -1891
  7. data/lib/openstudio-standards/btap/analysis.rb +8 -8
  8. data/lib/openstudio-standards/btap/bridging.rb +664 -645
  9. data/lib/openstudio-standards/btap/btap.model.rb +14 -14
  10. data/lib/openstudio-standards/btap/btap.rb +7 -7
  11. data/lib/openstudio-standards/btap/btap_result.rb +1 -1
  12. data/lib/openstudio-standards/btap/economics.rb +23 -23
  13. data/lib/openstudio-standards/btap/envelope.rb +8 -8
  14. data/lib/openstudio-standards/btap/equest.rb +1 -1
  15. data/lib/openstudio-standards/btap/geometry.rb +2 -2
  16. data/lib/openstudio-standards/btap/mpc.rb +7 -7
  17. data/lib/openstudio-standards/btap/schedules.rb +1 -1
  18. data/lib/openstudio-standards/btap/simmanager.rb +4 -4
  19. data/lib/openstudio-standards/btap/spaceloads.rb +26 -26
  20. data/lib/openstudio-standards/btap/utilities.rb +6 -6
  21. data/lib/openstudio-standards/btap/vintagizer.rb +1 -1
  22. data/lib/openstudio-standards/constructions/information.rb +83 -0
  23. data/lib/openstudio-standards/constructions/materials/modify.rb +72 -0
  24. data/lib/openstudio-standards/constructions/modify.rb +80 -0
  25. data/lib/openstudio-standards/create_typical/create_typical.rb +983 -0
  26. data/lib/openstudio-standards/create_typical/enumerations.rb +484 -0
  27. data/lib/openstudio-standards/create_typical/space_type_blend.rb +791 -0
  28. data/lib/openstudio-standards/create_typical/space_type_ratios.rb +494 -0
  29. data/lib/openstudio-standards/daylighting/space.rb +47 -0
  30. data/lib/openstudio-standards/geometry/create.rb +801 -0
  31. data/lib/openstudio-standards/geometry/create_bar.rb +2170 -0
  32. data/lib/openstudio-standards/geometry/information.rb +462 -0
  33. data/lib/openstudio-standards/geometry/modify.rb +48 -0
  34. data/lib/openstudio-standards/hvac/air_loop/information.rb +79 -0
  35. data/lib/openstudio-standards/hvac/cbecs_hvac.rb +616 -0
  36. data/lib/openstudio-standards/hvac/setpoint_managers/information.rb +91 -0
  37. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
  38. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.AirTerminalSingleDuctVAVReheat.rb +1 -1
  39. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
  40. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Model.rb +1 -1
  41. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
  42. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +2 -2
  43. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.hvac_systems.rb +1 -1
  44. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
  45. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +4 -36
  46. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.hvac_systems.rb +1 -1
  47. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +4 -36
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +3 -3
  50. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.hvac_systems.rb +1 -1
  51. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirTerminalSingleDuctVAVReheat.rb +1 -1
  52. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.elevators.rb +1 -1
  53. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirTerminalSingleDuctVAVReheat.rb +1 -1
  54. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.CoilHeatingGas.rb +1 -1
  55. data/lib/openstudio-standards/prototypes/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.elevators.rb +1 -1
  56. data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
  57. data/lib/openstudio-standards/prototypes/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
  58. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.College.rb +7 -7
  59. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Courthouse.rb +8 -8
  60. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.FullServiceRestaurant.rb +14 -14
  61. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.HighRiseApartment.rb +9 -9
  62. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Hospital.rb +16 -16
  63. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Laboratory.rb +7 -7
  64. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterHighITE.rb +8 -8
  65. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeDataCenterLowITE.rb +8 -8
  66. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeHotel.rb +11 -11
  67. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOffice.rb +7 -7
  68. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.LargeOfficeDetailed.rb +9 -9
  69. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOffice.rb +8 -8
  70. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MediumOfficeDetailed.rb +11 -11
  71. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.MidriseApartment.rb +9 -9
  72. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Outpatient.rb +19 -19
  73. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.PrimarySchool.rb +10 -10
  74. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.QuickServiceRestaurant.rb +13 -13
  75. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStandalone.rb +6 -6
  76. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.RetailStripmall.rb +6 -6
  77. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SecondarySchool.rb +9 -9
  78. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterHighITE.rb +8 -8
  79. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallDataCenterLowITE.rb +8 -8
  80. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallHotel.rb +8 -8
  81. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOffice.rb +8 -8
  82. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SmallOfficeDetailed.rb +11 -11
  83. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperMarket.rb +10 -10
  84. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.SuperTallBuilding.rb +19 -19
  85. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.TallBuilding.rb +18 -18
  86. data/lib/openstudio-standards/prototypes/common/buildings/Prototype.Warehouse.rb +6 -6
  87. data/lib/openstudio-standards/prototypes/common/do_not_edit_metaclasses.rb +957 -957
  88. data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirConditionerVariableRefrigerantFlow.rb +1 -1
  89. data/lib/openstudio-standards/prototypes/common/objects/Prototype.AirTerminalSingleDuctVAVReheat.rb +1 -1
  90. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilCoolingWaterToAirHeatPumpEquationFit.rb +84 -16
  91. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingDXSingleSpeed.rb +1 -1
  92. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingGas.rb +1 -1
  93. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoilHeatingWaterToAirHeatPumpEquationFit.rb +61 -10
  94. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ControllerWaterCoil.rb +1 -1
  95. data/lib/openstudio-standards/prototypes/common/objects/Prototype.CoolingTower.rb +1 -1
  96. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Fan.rb +1 -1
  97. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanConstantVolume.rb +1 -1
  98. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanOnOff.rb +1 -1
  99. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanVariableVolume.rb +1 -1
  100. data/lib/openstudio-standards/prototypes/common/objects/Prototype.FanZoneExhaust.rb +1 -1
  101. data/lib/openstudio-standards/prototypes/common/objects/Prototype.HeatExchangerAirToAirSensibleAndLatent.rb +2 -2
  102. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.exterior_lights.rb +4 -4
  103. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.hvac.rb +4 -4
  104. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.rb +43 -30
  105. data/lib/openstudio-standards/prototypes/common/objects/Prototype.Model.swh.rb +1 -1
  106. data/lib/openstudio-standards/prototypes/common/objects/Prototype.ServiceWaterHeating.rb +18 -11
  107. data/lib/openstudio-standards/prototypes/common/objects/Prototype.SizingSystem.rb +1 -1
  108. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +774 -117
  109. data/lib/openstudio-standards/prototypes/common/objects/Prototype.radiant_system_controls.rb +340 -481
  110. data/lib/openstudio-standards/prototypes/common/objects/Prototype.refrigeration.rb +3 -3
  111. data/lib/openstudio-standards/prototypes/common/objects/Prototype.utilities.rb +3 -3
  112. data/lib/openstudio-standards/prototypes/common/prototype_metaprogramming.rb +22 -22
  113. data/lib/openstudio-standards/prototypes/deer/deer.Model.rb +1 -1
  114. data/lib/openstudio-standards/qaqc/calibration.rb +131 -0
  115. data/lib/openstudio-standards/qaqc/create_results.rb +983 -0
  116. data/lib/openstudio-standards/qaqc/envelope.rb +399 -0
  117. data/lib/openstudio-standards/qaqc/eui.rb +213 -0
  118. data/lib/openstudio-standards/qaqc/hvac.rb +1943 -0
  119. data/lib/openstudio-standards/qaqc/internal_loads.rb +568 -0
  120. data/lib/openstudio-standards/qaqc/reporting.rb +141 -0
  121. data/lib/openstudio-standards/qaqc/schedules.rb +129 -0
  122. data/lib/openstudio-standards/qaqc/service_water_heating.rb +273 -0
  123. data/lib/openstudio-standards/qaqc/weather_files.rb +497 -0
  124. data/lib/openstudio-standards/qaqc/zone_conditions.rb +278 -0
  125. data/lib/openstudio-standards/schedules/create.rb +364 -0
  126. data/lib/openstudio-standards/schedules/information.rb +169 -0
  127. data/lib/openstudio-standards/schedules/modify.rb +445 -0
  128. data/lib/openstudio-standards/standards/Standards.AirLoopHVAC.rb +110 -71
  129. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctParallelPIUReheat.rb +3 -3
  130. data/lib/openstudio-standards/standards/Standards.AirTerminalSingleDuctVAVReheat.rb +4 -4
  131. data/lib/openstudio-standards/standards/Standards.BoilerHotWater.rb +2 -1
  132. data/lib/openstudio-standards/standards/Standards.ChillerElectricEIR.rb +16 -10
  133. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +4 -4
  134. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXTwoSpeed.rb +1 -1
  135. data/lib/openstudio-standards/standards/Standards.CoilCoolingWaterToAirHeatPumpEquationFit.rb +1 -1
  136. data/lib/openstudio-standards/standards/Standards.CoilDX.rb +4 -4
  137. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXMultiSpeed.rb +1 -1
  138. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +5 -5
  139. data/lib/openstudio-standards/standards/Standards.CoilHeatingGas.rb +1 -1
  140. data/lib/openstudio-standards/standards/Standards.CoilHeatingGasMultiStage.rb +1 -1
  141. data/lib/openstudio-standards/standards/Standards.CoilHeatingWaterToAirHeatPumpEquationFit.rb +1 -1
  142. data/lib/openstudio-standards/standards/Standards.Construction.rb +17 -18
  143. data/lib/openstudio-standards/standards/Standards.CoolingTower.rb +6 -6
  144. data/lib/openstudio-standards/standards/Standards.CoolingTowerSingleSpeed.rb +1 -1
  145. data/lib/openstudio-standards/standards/Standards.CoolingTowerTwoSpeed.rb +1 -1
  146. data/lib/openstudio-standards/standards/Standards.CoolingTowerVariableSpeed.rb +1 -1
  147. data/lib/openstudio-standards/standards/Standards.Fan.rb +6 -12
  148. data/lib/openstudio-standards/standards/Standards.FanVariableVolume.rb +2 -2
  149. data/lib/openstudio-standards/standards/Standards.FluidCooler.rb +1 -1
  150. data/lib/openstudio-standards/standards/Standards.HeaderedPumpsVariableSpeed.rb +1 -1
  151. data/lib/openstudio-standards/standards/Standards.HeatExchangerSensLat.rb +3 -3
  152. data/lib/openstudio-standards/standards/Standards.Model.rb +411 -261
  153. data/lib/openstudio-standards/standards/Standards.PlanarSurface.rb +2 -2
  154. data/lib/openstudio-standards/standards/Standards.PlantLoop.rb +94 -29
  155. data/lib/openstudio-standards/standards/Standards.Pump.rb +2 -2
  156. data/lib/openstudio-standards/standards/Standards.ScheduleConstant.rb +2 -2
  157. data/lib/openstudio-standards/standards/Standards.ScheduleRuleset.rb +14 -14
  158. data/lib/openstudio-standards/standards/Standards.Space.rb +37 -30
  159. data/lib/openstudio-standards/standards/Standards.SpaceType.rb +38 -29
  160. data/lib/openstudio-standards/standards/Standards.SubSurface.rb +7 -7
  161. data/lib/openstudio-standards/standards/Standards.Surface.rb +13 -13
  162. data/lib/openstudio-standards/standards/Standards.ThermalZone.rb +109 -66
  163. data/lib/openstudio-standards/standards/Standards.WaterHeaterMixed.rb +11 -4
  164. data/lib/openstudio-standards/standards/Standards.ZoneHVACComponent.rb +6 -6
  165. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.AirLoopHVAC.rb +1 -1
  166. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.PlantLoop.rb +1 -1
  167. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/ashrae_90_1_2004.Space.rb +1 -1
  168. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/ashrae_90_1_2007.Space.rb +1 -1
  169. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirLoopHVAC.rb +4 -4
  170. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.AirTerminalSingleDuctVAVReheat.rb +1 -1
  171. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/ashrae_90_1_2010.Space.rb +4 -4
  172. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirLoopHVAC.rb +4 -4
  173. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.AirTerminalSingleDuctVAVReheat.rb +1 -1
  174. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.CoolingTowerVariableSpeed.rb +1 -1
  175. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Model.rb +5 -21
  176. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.Space.rb +4 -4
  177. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.WaterHeaterMixed.rb +1 -1
  178. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/ashrae_90_1_2013.ZoneHVACComponent.rb +1 -1
  179. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirLoopHVAC.rb +36 -4
  180. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.AirTerminalSingleDuctVAVReheat.rb +1 -1
  181. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.CoolingTowerVariableSpeed.rb +1 -1
  182. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Space.rb +4 -4
  183. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.ZoneHVACComponent.rb +1 -1
  184. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/comstock_ashrae_90_1_2016/comstock_ashrae_90_1_2016.AirLoopHVAC.rb +26 -0
  185. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +53 -10
  186. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirTerminalSingleDuctVAVReheat.rb +1 -1
  187. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.CoolingTowerVariableSpeed.rb +1 -1
  188. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Space.rb +6 -6
  189. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.ZoneHVACComponent.rb +2 -2
  190. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/comstock_ashrae_90_1_2019/comstock_ashrae_90_1_2019.AirLoopHVAC.rb +26 -0
  191. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.curves.json +211 -211
  192. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/data/doe_ref_1980_2004.economizers.json +14 -14
  193. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.AirLoopHVAC.rb +4 -4
  194. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.Model.rb +1 -1
  195. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_1980_2004/doe_ref_1980_2004.PlantLoop.rb +1 -1
  196. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/data/doe_ref_pre_1980.economizers.json +14 -14
  197. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.AirLoopHVAC.rb +4 -4
  198. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.Model.rb +1 -1
  199. data/lib/openstudio-standards/standards/ashrae_90_1/doe_ref_pre_1980/doe_ref_pre_1980.PlantLoop.rb +1 -1
  200. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirLoopHVAC.rb +6 -6
  201. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.AirTerminalSingleDuctVAVReheat.rb +1 -1
  202. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.CoolingTowerVariableSpeed.rb +1 -1
  203. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.PlantLoop.rb +1 -1
  204. data/lib/openstudio-standards/standards/ashrae_90_1/nrel_zne_ready_2017/nrel_zne_ready_2017.Space.rb +4 -4
  205. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirLoopHVAC.rb +6 -6
  206. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.AirTerminalSingleDuctVAVReheat.rb +1 -1
  207. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.CoolingTowerVariableSpeed.rb +1 -1
  208. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.PlantLoop.rb +1 -1
  209. data/lib/openstudio-standards/standards/ashrae_90_1/ze_aedg_multifamily/ze_aedg_multifamily.Space.rb +4 -4
  210. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirLoopHVAC.rb +22 -28
  211. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctParallelPIUReheat.rb +1 -1
  212. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.AirTerminalSingleDuctVAVReheat.rb +2 -2
  213. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.BoilerHotWater.rb +1 -74
  214. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ChillerElectricEIR.rb +7 -59
  215. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXSingleSpeed.rb +1 -1
  216. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilCoolingDXTwoSpeed.rb +1 -1
  217. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilDX.rb +1 -1
  218. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingDXSingleSpeed.rb +1 -1
  219. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoilHeatingGas.rb +1 -21
  220. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.DesignSpecificationOutdoorAir.rb +101 -0
  221. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanConstantVolume.rb +1 -1
  222. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanOnOff.rb +1 -1
  223. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.FanVariableVolume.rb +1 -1
  224. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.HeatExchangerSensLat.rb +1 -1
  225. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Model.rb +643 -526
  226. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlanarSurface.rb +8 -2
  227. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.PlantLoop.rb +17 -77
  228. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Space.rb +74 -16
  229. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.SpaceType.rb +96 -44
  230. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.Surface.rb +6 -6
  231. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ThermalZone.rb +18 -6
  232. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.ZoneHVACComponent.rb +1 -1
  233. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.rb +328 -74
  234. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.Model.rb +0 -118
  235. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/ashrae_90_1_prm_2019.rb +2 -1
  236. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.heat_rejection.json +1 -1
  237. data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/baseline_outdoor_air.md +35 -0
  238. data/lib/openstudio-standards/standards/ashrae_90_1_prm/docs/set_plug_load_measures.md +1 -1
  239. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/ashrae_90_1_prm.UserData.rb +228 -0
  240. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_enums.rb +131 -0
  241. data/lib/openstudio-standards/standards/ashrae_90_1_prm/userdata_csv/userdata_space.csv +1 -1
  242. data/lib/openstudio-standards/standards/cbes/cbes.AirLoopHVAC.rb +5 -5
  243. data/lib/openstudio-standards/standards/cbes/cbes.Model.rb +1 -1
  244. data/lib/openstudio-standards/standards/cbes/cbes.PlantLoop.rb +1 -1
  245. data/lib/openstudio-standards/standards/cbes/cbes.Space.rb +1 -1
  246. data/lib/openstudio-standards/standards/cbes/cbes_t24_2005/cbes_t24_2005.Space.rb +1 -1
  247. data/lib/openstudio-standards/standards/cbes/cbes_t24_2008/cbes_t24_2008.Space.rb +1 -1
  248. data/lib/openstudio-standards/standards/deer/deer.AirLoopHVAC.rb +109 -27
  249. data/lib/openstudio-standards/standards/deer/deer.Space.rb +1 -1
  250. data/lib/openstudio-standards/standards/deer/deer_1985/data/deer_1985.economizers.json +246 -4
  251. data/lib/openstudio-standards/standards/deer/deer_1996/data/deer_1996.economizers.json +246 -4
  252. data/lib/openstudio-standards/standards/deer/deer_2003/data/deer_2003.economizers.json +246 -4
  253. data/lib/openstudio-standards/standards/deer/deer_2003/deer_2003.ThermalZone.rb +18 -18
  254. data/lib/openstudio-standards/standards/deer/deer_2007/data/deer_2007.economizers.json +246 -4
  255. data/lib/openstudio-standards/standards/deer/deer_2007/deer_2007.ThermalZone.rb +18 -18
  256. data/lib/openstudio-standards/standards/deer/deer_2011/data/deer_2011.economizers.json +246 -4
  257. data/lib/openstudio-standards/standards/deer/deer_2011/deer_2011.ThermalZone.rb +18 -18
  258. data/lib/openstudio-standards/standards/deer/deer_2014/data/deer_2014.economizers.json +248 -6
  259. data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.Space.rb +3 -3
  260. data/lib/openstudio-standards/standards/deer/deer_2014/deer_2014.ThermalZone.rb +18 -18
  261. data/lib/openstudio-standards/standards/deer/deer_2015/data/deer_2015.economizers.json +248 -6
  262. data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.Space.rb +3 -3
  263. data/lib/openstudio-standards/standards/deer/deer_2015/deer_2015.ThermalZone.rb +18 -18
  264. data/lib/openstudio-standards/standards/deer/deer_2017/data/deer_2017.economizers.json +248 -6
  265. data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.Space.rb +3 -3
  266. data/lib/openstudio-standards/standards/deer/deer_2017/deer_2017.ThermalZone.rb +18 -18
  267. data/lib/openstudio-standards/standards/deer/deer_2020/data/deer_2020.economizers.json +248 -6
  268. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.AirLoopHVAC.rb +18 -5
  269. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.FanVariableVolume.rb +1 -1
  270. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.Space.rb +3 -3
  271. data/lib/openstudio-standards/standards/deer/deer_2020/deer_2020.ThermalZone.rb +18 -18
  272. data/lib/openstudio-standards/standards/deer/deer_2025/data/deer_2025.economizers.json +248 -6
  273. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.AirLoopHVAC.rb +3 -3
  274. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.FanVariableVolume.rb +1 -1
  275. data/lib/openstudio-standards/standards/deer/deer_2025/deer_2025.Space.rb +3 -3
  276. data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.economizers.json +248 -6
  277. data/lib/openstudio-standards/standards/deer/deer_2030/data/deer_2030.heat_pumps.json +2 -2
  278. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.AirLoopHVAC.rb +3 -3
  279. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.FanVariableVolume.rb +1 -1
  280. data/lib/openstudio-standards/standards/deer/deer_2030/deer_2030.Space.rb +3 -3
  281. data/lib/openstudio-standards/standards/deer/deer_2035/data/deer_2035.economizers.json +248 -6
  282. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.AirLoopHVAC.rb +3 -3
  283. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.FanVariableVolume.rb +1 -1
  284. data/lib/openstudio-standards/standards/deer/deer_2035/deer_2035.Space.rb +3 -3
  285. data/lib/openstudio-standards/standards/deer/deer_2040/data/deer_2040.economizers.json +248 -6
  286. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.AirLoopHVAC.rb +3 -3
  287. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.FanVariableVolume.rb +1 -1
  288. data/lib/openstudio-standards/standards/deer/deer_2040/deer_2040.Space.rb +3 -3
  289. data/lib/openstudio-standards/standards/deer/deer_2045/data/deer_2045.economizers.json +260 -0
  290. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.AirLoopHVAC.rb +3 -3
  291. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.FanVariableVolume.rb +1 -1
  292. data/lib/openstudio-standards/standards/deer/deer_2045/deer_2045.Space.rb +3 -3
  293. data/lib/openstudio-standards/standards/deer/deer_2050/data/deer_2050.economizers.json +248 -6
  294. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.AirLoopHVAC.rb +3 -3
  295. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.FanVariableVolume.rb +1 -1
  296. data/lib/openstudio-standards/standards/deer/deer_2050/deer_2050.Space.rb +3 -3
  297. data/lib/openstudio-standards/standards/deer/deer_2055/data/deer_2055.economizers.json +248 -6
  298. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.AirLoopHVAC.rb +3 -3
  299. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.FanVariableVolume.rb +1 -1
  300. data/lib/openstudio-standards/standards/deer/deer_2055/deer_2055.Space.rb +3 -3
  301. data/lib/openstudio-standards/standards/deer/deer_2060/data/deer_2060.economizers.json +248 -6
  302. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.AirLoopHVAC.rb +3 -3
  303. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.FanVariableVolume.rb +1 -1
  304. data/lib/openstudio-standards/standards/deer/deer_2060/deer_2060.Space.rb +3 -3
  305. data/lib/openstudio-standards/standards/deer/deer_2065/data/deer_2065.economizers.json +248 -6
  306. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.AirLoopHVAC.rb +3 -3
  307. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.FanVariableVolume.rb +1 -1
  308. data/lib/openstudio-standards/standards/deer/deer_2065/deer_2065.Space.rb +3 -3
  309. data/lib/openstudio-standards/standards/deer/deer_2070/data/deer_2070.economizers.json +248 -6
  310. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.AirLoopHVAC.rb +3 -3
  311. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.FanVariableVolume.rb +1 -1
  312. data/lib/openstudio-standards/standards/deer/deer_2070/deer_2070.Space.rb +3 -3
  313. data/lib/openstudio-standards/standards/deer/deer_2075/data/deer_2075.economizers.json +248 -6
  314. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.AirLoopHVAC.rb +3 -3
  315. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.FanVariableVolume.rb +1 -1
  316. data/lib/openstudio-standards/standards/deer/deer_2075/deer_2075.Space.rb +3 -3
  317. data/lib/openstudio-standards/standards/deer/deer_pre_1975/data/deer_pre_1975.economizers.json +246 -4
  318. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/space_types.json +447 -223
  319. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/building_envelope.rb +1 -1
  320. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/space_types.json +447 -223
  321. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/hvac_systems.rb +5 -2
  322. data/lib/openstudio-standards/standards/necb/ECMS/data/chiller_types.json +25 -0
  323. data/lib/openstudio-standards/standards/necb/ECMS/data/chillers.json +44 -0
  324. data/lib/openstudio-standards/standards/necb/ECMS/data/curves.json +225 -0
  325. data/lib/openstudio-standards/standards/necb/ECMS/ecms.rb +2 -2
  326. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +193 -73
  327. data/lib/openstudio-standards/standards/necb/ECMS/pv_ground.rb +1 -1
  328. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +10 -4
  329. data/lib/openstudio-standards/standards/necb/NECB2011/beps_compliance_path.rb +7 -7
  330. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +4 -5
  331. data/lib/openstudio-standards/standards/necb/NECB2011/data/chiller_types.json +32 -0
  332. data/lib/openstudio-standards/standards/necb/NECB2011/data/chillers.json +1 -1
  333. data/lib/openstudio-standards/standards/necb/NECB2011/data/constants.json +36 -0
  334. data/lib/openstudio-standards/standards/necb/NECB2011/data/fuel_type_sets.json +7 -7
  335. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +47587 -0
  336. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +49764 -0
  337. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Warehouse.osm +283 -297
  338. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_type_unit_definitions.txt +2 -1
  339. data/lib/openstudio-standards/standards/necb/NECB2011/data/space_types.json +447 -223
  340. data/lib/openstudio-standards/standards/necb/NECB2011/data/standards_data.rb +3 -3
  341. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_system_1_multi_speed.rb +1 -1
  342. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +49 -27
  343. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +400 -202
  344. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +4 -4
  345. data/lib/openstudio-standards/standards/necb/NECB2015/data/space_types.json +637 -318
  346. data/lib/openstudio-standards/standards/necb/NECB2015/hvac_systems.rb +18 -1
  347. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +3 -3
  348. data/lib/openstudio-standards/standards/necb/NECB2017/data/space_types.json +637 -318
  349. data/lib/openstudio-standards/standards/necb/NECB2017/hvac_systems.rb +1 -1
  350. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +3 -3
  351. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +1 -1
  352. data/lib/openstudio-standards/standards/necb/NECB2020/data/space_types.json +615 -307
  353. data/lib/openstudio-standards/standards/necb/NECB2020/service_water_heating.rb +4 -4
  354. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +10 -5
  355. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +1 -1
  356. data/lib/openstudio-standards/utilities/assertion.rb +128 -0
  357. data/lib/openstudio-standards/utilities/logging.rb +2 -3
  358. data/lib/openstudio-standards/utilities/object_info.rb +39 -18
  359. data/lib/openstudio-standards/utilities/schedule_translator.rb +8 -6
  360. data/lib/openstudio-standards/utilities/simulation.rb +24 -11
  361. data/lib/openstudio-standards/utilities/sqlfile.rb +10 -5
  362. data/lib/openstudio-standards/version.rb +1 -1
  363. data/lib/openstudio-standards/weather/Weather.Model.rb +8 -9
  364. data/lib/openstudio-standards/weather/Weather.stat_file.rb +3 -3
  365. data/lib/openstudio-standards/weather/information.rb +35 -0
  366. data/lib/openstudio-standards.rb +69 -5
  367. metadata +54 -18
  368. data/License.txt +0 -65
  369. data/data/standards/OpenStudio_Standards-deer-ALL-comstock(space_types).xlsx +0 -0
  370. data/lib/openstudio-standards/hvac_sizing/Siz.AirLoopHVAC.rb +0 -59
  371. data/lib/openstudio-standards/hvac_sizing/Siz.CoilCoolingWater.rb +0 -13
  372. data/lib/openstudio-standards/hvac_sizing/Siz.HVACComponent.rb +0 -36
  373. data/lib/openstudio-standards/hvac_sizing/Siz.HeatingCoolingFuels.rb +0 -898
  374. data/lib/openstudio-standards/hvac_sizing/Siz.Model.rb +0 -126
  375. data/lib/openstudio-standards/hvac_sizing/Siz.ThermalZone.rb +0 -356
  376. data/lib/openstudio-standards/prototypes/ashrae_90_1/nrel_nze_ready_2017/nrel_zne_ready_2017.Model.rb +0 -35
  377. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTower.rb +0 -110
  378. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm.CoolingTowerVariableSpeed.rb +0 -5
@@ -0,0 +1,2170 @@
1
+ # Methods to create geometry
2
+ module OpenstudioStandards
3
+ module Geometry
4
+ # @!group CreateBar
5
+
6
+ # Building Form Defaults from Table 4.2 in Achieving the 30% Goal: Energy and Cost Savings Analysis of ASHRAE Standard 90.1-2010
7
+ # aspect ratio for NA replaced with floor area to perimeter ratio from prototype model
8
+ # currently no reason to split apart doe and deer inputs here
9
+ #
10
+ # @param building_type [String] standard building type
11
+ # @return [Hash] Hash of aspect_ratio, wwr, typical_story, and perim_mult
12
+ def self.building_form_defaults(building_type)
13
+ hash = {}
14
+
15
+ # DOE Prototypes
16
+
17
+ # calculate aspect ratios not represented on Table 4.2
18
+ primary_footprint = 73958.0
19
+ primary_p = 619.0 # wrote measure using calculate_perimeter method in os_lib_geometry
20
+ primary_ns_ew_ratio = 2.829268293 # estimated from ratio of ns/ew total wall area
21
+ primary_width = Math.sqrt(primary_footprint / primary_ns_ew_ratio)
22
+ primary_p_min = 2 * (primary_width + primary_width / primary_footprint)
23
+ primary_p_mult = primary_p / primary_p_min
24
+
25
+ secondary_footprint = 210887.0 / 2.0 # floor area divided by area instead of true footprint 128112.0)
26
+ secondary_p = 708.0 # wrote measure using calculate_perimeter method in os_lib_geometry
27
+ secondary_ns_ew_ratio = 2.069230769 # estimated from ratio of ns/ew total wall area
28
+ secondary_width = Math.sqrt(secondary_footprint / secondary_ns_ew_ratio)
29
+ secondary_p_min = 2 * (secondary_width + secondary_width / secondary_footprint)
30
+ secondary_p_mult = secondary_p / secondary_p_min
31
+
32
+ outpatient_footprint = 40946.0 / 3.0 # floor area divided by area instead of true footprint 17872.0)
33
+ outpatient_p = 537.0 # wrote measure using calculate_perimeter method in os_lib_geometry
34
+ outpatient_ns_ew_ratio = 1.56448737 # estimated from ratio of ns/ew total wall area
35
+ outpatient_width = Math.sqrt(outpatient_footprint / outpatient_ns_ew_ratio)
36
+ outpatient_p_min = 2 * (outpatient_width + outpatient_footprint / outpatient_width)
37
+ outpatient_p_mult = outpatient_p / outpatient_p_min
38
+
39
+ # primary_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(73958.0, 2060.0)
40
+ # secondary_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(128112.0, 2447.0)
41
+ # outpatient_aspet_ratio = OpenstudioStandards::Geometry.aspect_ratio(14782.0, 588.0)
42
+ supermarket_a = 45001.0
43
+ supermarket_p = 866.0
44
+ supermarket_wwr = 1880.0 / (supermarket_p * 20.0)
45
+ supermarket_aspect_ratio = OpenstudioStandards::Geometry.aspect_ratio(supermarket_a, supermarket_p)
46
+
47
+ hash['SmallOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
48
+ hash['MediumOffice'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 13.0, perim_mult: 1.0 }
49
+ hash['LargeOffice'] = { aspect_ratio: 1.5, wwr: 0.15, typical_story: 13.0, perim_mult: 1.0 }
50
+ hash['RetailStandalone'] = { aspect_ratio: 1.28, wwr: 0.07, typical_story: 20.0, perim_mult: 1.0 }
51
+ hash['RetailStripmall'] = { aspect_ratio: 4.0, wwr: 0.11, typical_story: 17.0, perim_mult: 1.0 }
52
+ hash['PrimarySchool'] = { aspect_ratio: primary_ns_ew_ratio.round(1), wwr: 0.35, typical_story: 13.0, perim_mult: primary_p_mult.round(3) }
53
+ hash['SecondarySchool'] = { aspect_ratio: secondary_ns_ew_ratio.round(1), wwr: 0.33, typical_story: 13.0, perim_mult: secondary_p_mult.round(3) }
54
+ hash['Outpatient'] = { aspect_ratio: outpatient_ns_ew_ratio.round(1), wwr: 0.20, typical_story: 10.0, perim_mult: outpatient_p_mult.round(3) }
55
+ hash['Hospital'] = { aspect_ratio: 1.33, wwr: 0.16, typical_story: 14.0, perim_mult: 1.0 }
56
+ hash['SmallHotel'] = { aspect_ratio: 3.0, wwr: 0.11, typical_story: 9.0, first_story: 11.0, perim_mult: 1.0 }
57
+ hash['LargeHotel'] = { aspect_ratio: 5.1, wwr: 0.27, typical_story: 10.0, first_story: 13.0, perim_mult: 1.0 }
58
+
59
+ # code in get_space_types_from_building_type is used to override building wwr with space type specific wwr
60
+ hash['Warehouse'] = { aspect_ratio: 2.2, wwr: 0.0, typical_story: 28.0, perim_mult: 1.0 }
61
+
62
+ hash['FullServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
63
+ hash['QuickServiceRestaurant'] = { aspect_ratio: 1.0, wwr: 0.18, typical_story: 10.0, perim_mult: 1.0 }
64
+ hash['MidriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
65
+ hash['HighriseApartment'] = { aspect_ratio: 2.75, wwr: 0.15, typical_story: 10.0, perim_mult: 1.0 }
66
+ # SuperMarket inputs come from prototype model
67
+ hash['SuperMarket'] = { aspect_ratio: supermarket_aspect_ratio.round(1), wwr: supermarket_wwr.round(2), typical_story: 20.0, perim_mult: 1.0 }
68
+
69
+ # Add Laboratory and Data Centers
70
+ hash['Laboratory'] = { aspect_ratio: 1.33, wwr: 0.12, typical_story: 10.0, perim_mult: 1.0 }
71
+ hash['LargeDataCenterLowITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
72
+ hash['LargeDataCenterHighITE'] = { aspect_ratio: 1.67, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
73
+ hash['SmallDataCenterLowITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
74
+ hash['SmallDataCenterHighITE'] = { aspect_ratio: 1.5, wwr: 0.0, typical_story: 14.0, perim_mult: 1.0 }
75
+
76
+ # Add Courthouse and Education
77
+ hash['Courthouse'] = { aspect_ratio: 2.06, wwr: 0.18, typical_story: 16.0, perim_mult: 1.0 }
78
+ hash['College'] = { aspect_ratio: 2.5, wwr: 0.037, typical_story: 13.0, perim_mult: 1.0 }
79
+
80
+ # DEER Prototypes
81
+ hash['Asm'] = { aspect_ratio: 1.0, wwr: 0.19, typical_story: 15.0 }
82
+ hash['ECC'] = { aspect_ratio: 4.0, wwr: 0.25, typical_story: 13.0 }
83
+ hash['EPr'] = { aspect_ratio: 2.0, wwr: 0.16, typical_story: 12.0 }
84
+ hash['ERC'] = { aspect_ratio: 1.7, wwr: 0.03, typical_story: 12.0 }
85
+ hash['ESe'] = { aspect_ratio: 1.0, wwr: 0.15, typical_story: 13.0 }
86
+ hash['EUn'] = { aspect_ratio: 2.5, wwr: 0.3, typical_story: 14.0 }
87
+ hash['Gro'] = { aspect_ratio: 1.0, wwr: 0.07, typical_story: 25.0 }
88
+ hash['Hsp'] = { aspect_ratio: 1.5, wwr: 0.11, typical_story: 13.0 }
89
+ hash['Htl'] = { aspect_ratio: 3.0, wwr: 0.23, typical_story: 9.5, first_story: 12.0 }
90
+ hash['MBT'] = { aspect_ratio: 10.7, wwr: 0.12, typical_story: 15.0 }
91
+ hash['MFm'] = { aspect_ratio: 1.4, wwr: 0.24, typical_story: 9.5 }
92
+ hash['MLI'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 35.0 }
93
+ hash['Mtl'] = { aspect_ratio: 5.1, wwr: 0.41, typical_story: 9.0 }
94
+ hash['Nrs'] = { aspect_ratio: 10.3, wwr: 0.2, typical_story: 13.0 }
95
+ hash['OfL'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
96
+ hash['OfS'] = { aspect_ratio: 1.5, wwr: 0.33, typical_story: 12.0 }
97
+ hash['RFF'] = { aspect_ratio: 1.0, wwr: 0.25, typical_story: 13.0 }
98
+ hash['RSD'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 13.0 }
99
+ hash['Rt3'] = { aspect_ratio: 1.0, wwr: 0.02, typical_story: 20.8 }
100
+ hash['RtL'] = { aspect_ratio: 1.0, wwr: 0.03, typical_story: 20.5 }
101
+ hash['RtS'] = { aspect_ratio: 1.0, wwr: 0.13, typical_story: 12.0 }
102
+ hash['SCn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
103
+ hash['SUn'] = { aspect_ratio: 1.0, wwr: 0.01, typical_story: 48.0 }
104
+ hash['WRf'] = { aspect_ratio: 1.6, wwr: 0.0, typical_story: 32.0 }
105
+
106
+ return hash[building_type]
107
+ end
108
+
109
+ # sort building stories
110
+ #
111
+ # @param model [OpenStudio::Model::Model] OpenStudio model object
112
+ # @return [Hash] An Hash with key OpenStudio BuildingStory objects and their minimum z value
113
+ def self.model_sort_building_stories_and_get_min_multiplier(model)
114
+ sorted_building_stories = {}
115
+ # loop through stories
116
+ model.getBuildingStorys.sort.each do |story|
117
+ story_min_z = nil
118
+ # loop through spaces in story.
119
+ story.spaces.sort.each do |space|
120
+ space_z_min = OpenstudioStandards::Geometry.surfaces_get_z_values(space.surfaces.to_a).min + space.zOrigin
121
+ if story_min_z.nil? || (story_min_z > space_z_min)
122
+ story_min_z = space_z_min
123
+ end
124
+ end
125
+ sorted_building_stories[story] = story_min_z
126
+ end
127
+
128
+ return sorted_building_stories
129
+ end
130
+
131
+ # gather envelope data for envelope simplification
132
+ #
133
+ # @param model [OpenStudio::Model::Model] OpenStudio model object
134
+ # @return [Hash] A hash of envelope data used by other methods
135
+ # @todo full list of hash returns aren't documented yet
136
+ def self.model_envelope_data(model)
137
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Gathering envelope data.')
138
+
139
+ # hash to contain envelope data
140
+ envelope_data_hash = {}
141
+
142
+ # used for overhang and party wall orientation catigorization
143
+ facade_options = {
144
+ 'north_east' => 45.0,
145
+ 'south_east' => 125.0,
146
+ 'south_west' => 225.0,
147
+ 'north_west' => 315.0
148
+ }
149
+
150
+ # get building level inputs
151
+ envelope_data_hash[:north_axis] = model.getBuilding.northAxis
152
+ envelope_data_hash[:building_floor_area] = model.getBuilding.floorArea
153
+ envelope_data_hash[:building_exterior_surface_area] = model.getBuilding.exteriorSurfaceArea
154
+ envelope_data_hash[:building_exterior_wall_area] = model.getBuilding.exteriorWallArea
155
+ envelope_data_hash[:building_exterior_roof_area] = envelope_data_hash[:building_exterior_surface_area] - envelope_data_hash[:building_exterior_wall_area]
156
+ envelope_data_hash[:building_air_volume] = model.getBuilding.airVolume
157
+ envelope_data_hash[:building_perimeter] = nil # will be applied for first story without ground walls
158
+
159
+ # get bounding_box
160
+ bounding_box = OpenStudio::BoundingBox.new
161
+ model.getSpaces.sort.each do |space|
162
+ space.surfaces.sort.each do |space_surface|
163
+ bounding_box.addPoints(space.transformation * space_surface.vertices)
164
+ end
165
+ end
166
+ min_x = bounding_box.minX.get
167
+ min_y = bounding_box.minY.get
168
+ min_z = bounding_box.minZ.get
169
+ max_x = bounding_box.maxX.get
170
+ max_y = bounding_box.maxY.get
171
+ max_z = bounding_box.maxZ.get
172
+ envelope_data_hash[:building_min_xyz] = [min_x, min_y, min_z]
173
+ envelope_data_hash[:building_max_xyz] = [max_x, max_y, max_z]
174
+
175
+ # add orientation specific wwr
176
+ ext_surfaces_hash = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)
177
+ envelope_data_hash[:building_wwr_n] = ext_surfaces_hash['north_window'] / ext_surfaces_hash['north_wall']
178
+ envelope_data_hash[:building_wwr_s] = ext_surfaces_hash['south_window'] / ext_surfaces_hash['south_wall']
179
+ envelope_data_hash[:building_wwr_e] = ext_surfaces_hash['east_window'] / ext_surfaces_hash['east_wall']
180
+ envelope_data_hash[:building_wwr_w] = ext_surfaces_hash['west_window'] / ext_surfaces_hash['west_wall']
181
+ envelope_data_hash[:stories] = {} # each entry will be hash with buildingStory as key and attributes has values
182
+ envelope_data_hash[:space_types] = {} # each entry will be hash with spaceType as key and attributes has values
183
+
184
+ # as rough estimate overhang area / glazing area should be close to projection factor assuming overhang is same width as windows
185
+ # will only add building shading surfaces assoicated with a sub-surface.
186
+ building_overhang_area_n = 0.0
187
+ building_overhang_area_s = 0.0
188
+ building_overhang_area_e = 0.0
189
+ building_overhang_area_w = 0.0
190
+
191
+ # loop through stories based on mine z height of surfaces.
192
+ sorted_stories = sort_building_stories_and_get_min_multiplier(model).sort_by { |k, v| v }
193
+ sorted_stories.each do |story, story_min_z|
194
+ story_min_multiplier = nil
195
+ story_footprint = nil
196
+ story_multiplied_floor_area = OpenstudioStandards::Geometry.spaces_get_floor_area(story.spaces)
197
+ # goal of footprint calc is to count multiplier for hotel room on facade,but not to count what is intended as a story multiplier
198
+ story_multiplied_exterior_surface_area = OpenstudioStandards::Geometry.spaces_get_exterior_area(story.spaces)
199
+ story_multiplied_exterior_wall_area = OpenstudioStandards::Geometry.spaces_get_exterior_wall_area(story.spaces)
200
+ story_multiplied_exterior_roof_area = story_multiplied_exterior_surface_area - story_multiplied_exterior_wall_area
201
+ story_has_ground_walls = []
202
+ story_has_adiabatic_walls = []
203
+ story_included_in_building_area = false # will be true if any spaces on story are inclued in building area
204
+ story_max_z = nil
205
+
206
+ # loop through spaces for story gathering information
207
+ story.spaces.each do |space|
208
+ # get min multiplier value
209
+ multiplier = space.multiplier
210
+ if story_min_multiplier.nil? || (story_min_multiplier > multiplier)
211
+ story_min_multiplier = multiplier
212
+ end
213
+
214
+ # calculate footprint
215
+ story_footprint = story_multiplied_floor_area / story_min_multiplier
216
+
217
+ # see if part of floor area
218
+ if space.partofTotalFloorArea
219
+ story_included_in_building_area = true
220
+
221
+ # add to space type ratio hash when space is included in building floor area
222
+ if space.spaceType.is_initialized
223
+ space_type = space.spaceType.get
224
+ space_floor_area = space.floorArea * space.multiplier
225
+ if envelope_data_hash[:space_types].key?(space_type)
226
+ envelope_data_hash[:space_types][space_type][:floor_area] += space_floor_area
227
+ else
228
+ envelope_data_hash[:space_types][space_type] = {}
229
+ envelope_data_hash[:space_types][space_type][:floor_area] = space_floor_area
230
+
231
+ # make hash for heating and cooling setpoints
232
+ envelope_data_hash[:space_types][space_type][:htg_setpoint] = {}
233
+ envelope_data_hash[:space_types][space_type][:clg_setpoint] = {}
234
+
235
+ end
236
+
237
+ # add heating and cooling setpoints
238
+ if space.thermalZone.is_initialized && space.thermalZone.get.thermostatSetpointDualSetpoint.is_initialized
239
+ thermostat = space.thermalZone.get.thermostatSetpointDualSetpoint.get
240
+
241
+ # log heating schedule
242
+ if thermostat.heatingSetpointTemperatureSchedule.is_initialized
243
+ htg_sch = thermostat.heatingSetpointTemperatureSchedule.get
244
+ if envelope_data_hash[:space_types][space_type][:htg_setpoint].key?(htg_sch)
245
+ envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] += space_floor_area
246
+ else
247
+ envelope_data_hash[:space_types][space_type][:htg_setpoint][htg_sch] = space_floor_area
248
+ end
249
+ else
250
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
251
+ end
252
+
253
+ # log cooling schedule
254
+ if thermostat.coolingSetpointTemperatureSchedule.is_initialized
255
+ clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
256
+ if envelope_data_hash[:space_types][space_type][:clg_setpoint].key?(clg_sch)
257
+ envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] += space_floor_area
258
+ else
259
+ envelope_data_hash[:space_types][space_type][:clg_setpoint][clg_sch] = space_floor_area
260
+ end
261
+ else
262
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.thermalZone.get.name} containing #{space.name} doesn't have a heating setpoint schedule.")
263
+ end
264
+
265
+ else
266
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} either isn't in a thermal zone or doesn't have a thermostat assigned")
267
+ end
268
+
269
+ else
270
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} is included in the building floor area but isn't assigned a space type.")
271
+ end
272
+
273
+ end
274
+
275
+ # check for walls with adiabatic and ground boundary condition
276
+ space.surfaces.each do |surface|
277
+ next if surface.surfaceType != 'Wall'
278
+
279
+ if surface.outsideBoundaryCondition == 'Ground'
280
+ story_has_ground_walls << surface
281
+ elsif surface.outsideBoundaryCondition == 'Adiabatic'
282
+ story_has_adiabatic_walls << surface
283
+ end
284
+ end
285
+
286
+ # populate overhang values
287
+ space.surfaces.each do |surface|
288
+ surface.subSurfaces.each do |sub_surface|
289
+ sub_surface.shadingSurfaceGroups.each do |shading_surface_group|
290
+ shading_surface_group.shadingSurfaces.each do |shading_surface|
291
+ absolute_azimuth = OpenStudio.convert(sub_surface.azimuth, 'rad', 'deg').get + sub_surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
292
+ absolute_azimuth -= 360.0 until absolute_azimuth < 360.0
293
+ # add to hash based on orientation
294
+ if (facade_options['north_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_east']) # East overhang
295
+ building_overhang_area_e += shading_surface.grossArea * space.multiplier
296
+ elsif (facade_options['south_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_west']) # South overhang
297
+ building_overhang_area_s += shading_surface.grossArea * space.multiplier
298
+ elsif (facade_options['south_west'] <= absolute_azimuth) && (absolute_azimuth < facade_options['north_west']) # West overhang
299
+ building_overhang_area_w += shading_surface.grossArea * space.multiplier
300
+ else # North overhang
301
+ building_overhang_area_n += shading_surface.grossArea * space.multiplier
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ # get max z
309
+ space_z_max = OpenstudioStandards::Geometry.surfaces_get_z_values(space.surfaces.to_a).max + space.zOrigin
310
+ if story_max_z.nil? || (story_max_z > space_z_max)
311
+ story_max_z = space_z_max
312
+ end
313
+ end
314
+
315
+ # populate hash for story data
316
+ envelope_data_hash[:stories][story] = {}
317
+ envelope_data_hash[:stories][story][:story_min_height] = story_min_z
318
+ envelope_data_hash[:stories][story][:story_max_height] = story_max_z
319
+ envelope_data_hash[:stories][story][:story_min_multiplier] = story_min_multiplier
320
+ envelope_data_hash[:stories][story][:story_has_ground_walls] = story_has_ground_walls
321
+ envelope_data_hash[:stories][story][:story_has_adiabatic_walls] = story_has_adiabatic_walls
322
+ envelope_data_hash[:stories][story][:story_included_in_building_area] = story_included_in_building_area
323
+ envelope_data_hash[:stories][story][:story_footprint] = story_footprint
324
+ envelope_data_hash[:stories][story][:story_multiplied_floor_area] = story_multiplied_floor_area
325
+ envelope_data_hash[:stories][story][:story_exterior_surface_area] = story_multiplied_exterior_surface_area
326
+ envelope_data_hash[:stories][story][:story_multiplied_exterior_wall_area] = story_multiplied_exterior_wall_area
327
+ envelope_data_hash[:stories][story][:story_multiplied_exterior_roof_area] = story_multiplied_exterior_roof_area
328
+
329
+ # get perimeter and adiabatic walls that appear to be party walls
330
+ perimeter_and_party_walls = OpenstudioStandards::Geometry.story_get_exterior_wall_perimeter(story,
331
+ multiplier_adjustment: story_min_multiplier,
332
+ bounding_box: bounding_box)
333
+ envelope_data_hash[:stories][story][:story_perimeter] = perimeter_and_party_walls[:perimeter]
334
+ envelope_data_hash[:stories][story][:story_party_walls] = []
335
+ east = false
336
+ south = false
337
+ west = false
338
+ north = false
339
+ perimeter_and_party_walls[:party_walls].each do |surface|
340
+ absolute_azimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
341
+ absolute_azimuth -= 360.0 until absolute_azimuth < 360.0
342
+
343
+ # add to hash based on orientation (initially added array of sourfaces, but swtiched to just true/false flag)
344
+ if (facade_options['north_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_east']) # East party walls
345
+ east = true
346
+ elsif (facade_options['south_east'] <= absolute_azimuth) && (absolute_azimuth < facade_options['south_west']) # South party walls
347
+ south = true
348
+ elsif (facade_options['south_west'] <= absolute_azimuth) && (absolute_azimuth < facade_options['north_west']) # West party walls
349
+ west = true
350
+ else # North party walls
351
+ north = true
352
+ end
353
+ end
354
+
355
+ if east then envelope_data_hash[:stories][story][:story_party_walls] << 'east' end
356
+ if south then envelope_data_hash[:stories][story][:story_party_walls] << 'south' end
357
+ if west then envelope_data_hash[:stories][story][:story_party_walls] << 'west' end
358
+ if north then envelope_data_hash[:stories][story][:story_party_walls] << 'north' end
359
+
360
+ # store perimeter from first story that doesn't have ground walls
361
+ if story_has_ground_walls.empty? && envelope_data_hash[:building_perimeter].nil?
362
+ envelope_data_hash[:building_perimeter] = envelope_data_hash[:stories][story][:story_perimeter]
363
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', " * #{story.name} is the first above grade story and will be used for the building perimeter.")
364
+ end
365
+ end
366
+
367
+ envelope_data_hash[:building_overhang_proj_factor_n] = building_overhang_area_n / ext_surfaces_hash['northWindow']
368
+ envelope_data_hash[:building_overhang_proj_factor_s] = building_overhang_area_s / ext_surfaces_hash['southWindow']
369
+ envelope_data_hash[:building_overhang_proj_factor_e] = building_overhang_area_e / ext_surfaces_hash['eastWindow']
370
+ envelope_data_hash[:building_overhang_proj_factor_w] = building_overhang_area_w / ext_surfaces_hash['westWindow']
371
+
372
+ # warn for spaces that are not on a story (in future could infer stories for these)
373
+ model.getSpaces.sort.each do |space|
374
+ if !space.buildingStory.is_initialized
375
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space.name} is not on a building story, may have unexpected results.")
376
+ end
377
+ end
378
+
379
+ return envelope_data_hash
380
+ end
381
+
382
+ # get length and width of rectangle matching bounding box aspect ratio will maintaining proper floor area
383
+ #
384
+ # @param envelope_data_hash [Hash] Hash of envelope data
385
+ # @return [Hash] hash of bar length and width
386
+ def self.bar_reduced_bounding_box(envelope_data_hash)
387
+ bar = {}
388
+
389
+ bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
390
+ bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
391
+ bounding_area = bounding_length * bounding_width
392
+ footprint_area = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective_num_stories].to_f
393
+ area_multiplier = footprint_area / bounding_area
394
+ edge_multiplier = Math.sqrt(area_multiplier)
395
+ bar[:length] = bounding_length * edge_multiplier
396
+ bar[:width] = bounding_width * edge_multiplier
397
+
398
+ return bar
399
+ end
400
+
401
+ # get length and width of rectangle matching longer of two edges, and reducing the other way until floor area matches
402
+ #
403
+ # @param envelope_data_hash [Hash] Hash of envelope data
404
+ # @return [Hash] hash of bar length and width
405
+ def self.bar_reduced_width(envelope_data_hash)
406
+ bar = {}
407
+
408
+ bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
409
+ bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
410
+ footprint_area = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective_num_stories].to_f
411
+
412
+ if bounding_length >= bounding_width
413
+ bar[:length] = bounding_length
414
+ bar[:width] = footprint_area / bounding_length
415
+ else
416
+ bar[:width] = bounding_width
417
+ bar[:length] = footprint_area / bounding_width
418
+ end
419
+
420
+ return bar
421
+ end
422
+
423
+ # get length and width of rectangle by stretching it until both floor area and exterior wall area or perimeter match
424
+ #
425
+ # @param envelope_data_hash [Hash] Hash of envelope data including
426
+ # @return [Hash] hash of bar length and width
427
+ def self.bar_stretched(envelope_data_hash)
428
+ bar = {}
429
+
430
+ bounding_length = envelope_data_hash[:building_max_xyz][0] - envelope_data_hash[:building_min_xyz][0]
431
+ bounding_width = envelope_data_hash[:building_max_xyz][1] - envelope_data_hash[:building_min_xyz][1]
432
+ a = envelope_data_hash[:building_floor_area] / envelope_data_hash[:effective_num_stories].to_f
433
+ p = envelope_data_hash[:building_perimeter]
434
+
435
+ if bounding_length >= bounding_width
436
+ bar[:length] = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
437
+ bar[:width] = 0.25 * (p - Math.sqrt(p**2 - 16 * a))
438
+ else
439
+ bar[:length] = 0.25 * (p - Math.sqrt(p**2 - 16 * a))
440
+ bar[:width] = 0.25 * (p + Math.sqrt(p**2 - 16 * a))
441
+ end
442
+
443
+ return bar
444
+ end
445
+
446
+ # create_bar creates spaces based on a set of geometric characteristics
447
+ #
448
+ # @param model [OpenStudio::Model::Model] OpenStudio model object
449
+ # @param bar_hash [Hash] A hash object of bar characteristics
450
+ # @return [Array<OpenStudio::Model::Space>] An array of OpenStudio Space objects
451
+ def self.create_bar(model, bar_hash)
452
+ # make custom story hash when number of stories below grade > 0
453
+ # @todo update this so have option basements are not below 0? (useful for simplifying existing model and maintaining z position relative to site shading)
454
+ story_hash = {}
455
+ eff_below = bar_hash[:num_stories_below_grade]
456
+ eff_above = bar_hash[:num_stories_above_grade]
457
+ footprint_origin_point = bar_hash[:center_of_footprint]
458
+ typical_story_height = bar_hash[:floor_height]
459
+
460
+ # warn about site shading
461
+ if !model.getSite.shadingSurfaceGroups.empty?
462
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'The model has one or more site shading surfaces. New geometry may not be positioned where expected, it will be centered over the center of the original geometry.')
463
+ end
464
+
465
+ # flatten story_hash out to individual stories included in building area
466
+ stories_flat = []
467
+ stories_flat_counter = 0
468
+ bar_hash[:stories].each_with_index do |(k, v), i|
469
+ # k is invalid in some cases, old story object that has been removed, should be from low to high including basement
470
+ # skip if source story insn't included in building area
471
+ if v[:story_included_in_building_area].nil? || (v[:story_included_in_building_area] == true)
472
+
473
+ # add to counter
474
+ stories_flat_counter += v[:story_min_multiplier]
475
+
476
+ flat_hash = {}
477
+ flat_hash[:story_party_walls] = v[:story_party_walls]
478
+ flat_hash[:below_partial_story] = v[:below_partial_story]
479
+ flat_hash[:bottom_story_ground_exposed_floor] = v[:bottom_story_ground_exposed_floor]
480
+ flat_hash[:top_story_exterior_exposed_roof] = v[:top_story_exterior_exposed_roof]
481
+ if i < eff_below
482
+ flat_hash[:story_type] = 'b'
483
+ flat_hash[:multiplier] = 1
484
+ elsif i == eff_below
485
+ flat_hash[:story_type] = 'ground'
486
+ flat_hash[:multiplier] = 1
487
+ elsif stories_flat_counter == eff_below + eff_above.ceil
488
+ flat_hash[:story_type] = 'top'
489
+ flat_hash[:multiplier] = 1
490
+ else
491
+ flat_hash[:story_type] = 'mid'
492
+ flat_hash[:multiplier] = v[:story_min_multiplier]
493
+ end
494
+
495
+ compare_hash = {}
496
+ if !stories_flat.empty?
497
+ stories_flat.last.each { |k, v| compare_hash[k] = flat_hash[k] if flat_hash[k] != v }
498
+ end
499
+ if (bar_hash[:story_multiplier_method] != 'None' && stories_flat.last == flat_hash) || (bar_hash[:story_multiplier_method] != 'None' && compare_hash.size == 1 && compare_hash.include?(:multiplier))
500
+ stories_flat.last[:multiplier] += v[:story_min_multiplier]
501
+ else
502
+ stories_flat << flat_hash
503
+ end
504
+ end
505
+ end
506
+
507
+ if bar_hash[:num_stories_below_grade] > 0
508
+
509
+ # add in below grade levels (may want to add below grade multipliers at some point if we start running deep basements)
510
+ eff_below.times do |i|
511
+ story_hash["B#{i + 1}"] = { space_origin_z: footprint_origin_point.z - typical_story_height * (i + 1), space_height: typical_story_height, multiplier: 1 }
512
+ end
513
+ end
514
+
515
+ # add in above grade levels
516
+ if eff_above > 2
517
+ story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
518
+
519
+ footprint_counter = 0
520
+ effective_stories_counter = 1
521
+ stories_flat.each do |hash|
522
+ next if hash[:story_type] != 'mid'
523
+
524
+ if footprint_counter == 0
525
+ string = 'mid'
526
+ else
527
+ string = "mid#{footprint_counter + 1}"
528
+ end
529
+ story_hash[string] = { space_origin_z: footprint_origin_point.z + typical_story_height * effective_stories_counter + typical_story_height * (hash[:multiplier] - 1) / 2.0, space_height: typical_story_height, multiplier: hash[:multiplier] }
530
+ footprint_counter += 1
531
+ effective_stories_counter += hash[:multiplier]
532
+ end
533
+
534
+ story_hash['top'] = { space_origin_z: footprint_origin_point.z + typical_story_height * (eff_above.ceil - 1), space_height: typical_story_height, multiplier: 1 }
535
+ elsif eff_above > 1
536
+ story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
537
+ story_hash['top'] = { space_origin_z: footprint_origin_point.z + typical_story_height * (eff_above.ceil - 1), space_height: typical_story_height, multiplier: 1 }
538
+ else # one story only
539
+ story_hash['ground'] = { space_origin_z: footprint_origin_point.z, space_height: typical_story_height, multiplier: 1 }
540
+ end
541
+
542
+ # create footprints
543
+ if bar_hash[:bar_division_method] == 'Multiple Space Types - Simple Sliced'
544
+ footprints = []
545
+ story_hash.size.times do |i|
546
+ # adjust size of bar of top story is not a full story
547
+ if i + 1 == story_hash.size
548
+ area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
549
+ edge_multiplier = Math.sqrt(area_multiplier)
550
+ length = bar_hash[:length] * edge_multiplier
551
+ width = bar_hash[:width] * edge_multiplier
552
+ else
553
+ length = bar_hash[:length]
554
+ width = bar_hash[:width]
555
+ end
556
+ footprints << OpenstudioStandards::Geometry.create_sliced_bar_simple_polygons(bar_hash[:space_types], length, width, bar_hash[:center_of_footprint])
557
+ end
558
+
559
+ elsif bar_hash[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
560
+
561
+ # update story_hash for partial_story_above
562
+ story_hash.each_with_index do |(k, v), i|
563
+ # adjust size of bar of top story is not a full story
564
+ if i + 1 == story_hash.size
565
+ story_hash[k][:partial_story_multiplier] = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
566
+ end
567
+ end
568
+
569
+ footprints = OpenstudioStandards::Geometry.create_sliced_bar_multi_polygons(bar_hash[:space_types], bar_hash[:length], bar_hash[:width], bar_hash[:center_of_footprint], story_hash)
570
+
571
+ else
572
+ footprints = []
573
+ story_hash.size.times do |i|
574
+ # adjust size of bar of top story is not a full story
575
+ if i + 1 == story_hash.size
576
+ area_multiplier = (1.0 - bar_hash[:num_stories_above_grade].ceil + bar_hash[:num_stories_above_grade])
577
+ edge_multiplier = Math.sqrt(area_multiplier)
578
+ length = bar_hash[:length] * edge_multiplier
579
+ width = bar_hash[:width] * edge_multiplier
580
+ else
581
+ length = bar_hash[:length]
582
+ width = bar_hash[:width]
583
+ end
584
+ # perimeter defaults to 15 ft
585
+ footprints << OpenstudioStandards::Geometry.create_core_and_perimeter_polygons(length, width, bar_hash[:center_of_footprint])
586
+ end
587
+
588
+ # set primary space type to building default space type
589
+ space_types = bar_hash[:space_types].sort_by { |k, v| v[:floor_area] }
590
+ if space_types.last.first.class.to_s == 'OpenStudio::Model::SpaceType'
591
+ model.getBuilding.setSpaceType(space_types.last.first)
592
+ end
593
+
594
+ end
595
+
596
+ # make spaces from polygons
597
+ new_spaces = OpenstudioStandards::Geometry.create_spaces_from_polygons(model, footprints, bar_hash[:floor_height], bar_hash[:num_stories], bar_hash[:center_of_footprint], story_hash)
598
+
599
+ # put all of the spaces in the model into a vector for intersection and surface matching
600
+ spaces = OpenStudio::Model::SpaceVector.new
601
+ model.getSpaces.sort.each do |space|
602
+ spaces << space
603
+ end
604
+
605
+ # flag for intersection and matching type
606
+ diagnostic_intersect = true
607
+
608
+ # only intersect if make_mid_story_surfaces_adiabatic false
609
+ if diagnostic_intersect
610
+
611
+ model.getPlanarSurfaces.sort.each do |surface|
612
+ array = []
613
+ vertices = surface.vertices
614
+ fixed = false
615
+ vertices.each do |vertex|
616
+ next if fixed
617
+
618
+ if array.include?(vertex)
619
+ # create a new set of vertices
620
+ new_vertices = OpenStudio::Point3dVector.new
621
+ array_b = []
622
+ surface.vertices.each do |vertex_b|
623
+ next if array_b.include?(vertex_b)
624
+
625
+ new_vertices << vertex_b
626
+ array_b << vertex_b
627
+ end
628
+ surface.setVertices(new_vertices)
629
+ num_removed = vertices.size - surface.vertices.size
630
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface.name} has duplicate vertices. Started with #{vertices.size} vertices, removed #{num_removed}.")
631
+ fixed = true
632
+ else
633
+ array << vertex
634
+ end
635
+ end
636
+ end
637
+
638
+ # remove collinear points in a surface
639
+ model.getPlanarSurfaces.sort.each do |surface|
640
+ new_vertices = OpenStudio.removeCollinear(surface.vertices)
641
+ starting_count = surface.vertices.size
642
+ final_count = new_vertices.size
643
+ if final_count < starting_count
644
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Removing #{starting_count - final_count} collinear vertices from #{surface.name}.")
645
+ surface.setVertices(new_vertices)
646
+ end
647
+ end
648
+
649
+ # remove duplicate surfaces in a space (should be done after remove duplicate and collinear points)
650
+ model.getSpaces.sort.each do |space|
651
+ # secondary array to compare against
652
+ surfaces_b = space.surfaces.sort
653
+
654
+ space.surfaces.sort.each do |surface_a|
655
+ # delete from secondary array
656
+ surfaces_b.delete(surface_a)
657
+
658
+ surfaces_b.each do |surface_b|
659
+ next if surface_a == surface_b # dont' test against same surface
660
+
661
+ if surface_a.equalVertices(surface_b)
662
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface_a.name} and #{surface_b.name} in #{space.name} have duplicate geometry, removing #{surface_b.name}.")
663
+ surface_b.remove
664
+ elsif surface_a.reverseEqualVertices(surface_b)
665
+ # @todo add logic to determine which face naormal is reversed and which is correct
666
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{surface_a.name} and #{surface_b.name} in #{space.name} have reversed geometry, removing #{surface_b.name}.")
667
+ surface_b.remove
668
+ end
669
+ end
670
+ end
671
+ end
672
+
673
+ if !(bar_hash[:make_mid_story_surfaces_adiabatic])
674
+ # intersect and surface match two pair by pair
675
+ spaces_b = model.getSpaces.sort
676
+ # looping through vector of each space
677
+ model.getSpaces.sort.each do |space_a|
678
+ spaces_b.delete(space_a)
679
+ spaces_b.each do |space_b|
680
+ # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces between #{space_a.name} and #{space.name}")
681
+ spaces_temp = OpenStudio::Model::SpaceVector.new
682
+ spaces_temp << space_a
683
+ spaces_temp << space_b
684
+ # intersect and sort
685
+ OpenStudio::Model.intersectSurfaces(spaces_temp)
686
+ OpenStudio::Model.matchSurfaces(spaces_temp)
687
+ end
688
+ end
689
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Intersecting and matching surfaces in model, this will create additional geometry.')
690
+ else
691
+ # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
692
+ model.getBuilding.buildingStories.sort.each do |story|
693
+ # intersect and surface match two pair by pair
694
+ spaces_b = story.spaces.sort
695
+ # looping through vector of each space
696
+ story.spaces.sort.each do |space_a|
697
+ spaces_b.delete(space_a)
698
+ spaces_b.each do |space_b|
699
+ spaces_temp = OpenStudio::Model::SpaceVector.new
700
+ spaces_temp << space_a
701
+ spaces_temp << space_b
702
+
703
+ # intersect and sort
704
+ OpenStudio::Model.intersectSurfaces(spaces_temp)
705
+ OpenStudio::Model.matchSurfaces(spaces_temp)
706
+ end
707
+ end
708
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
709
+ end
710
+ end
711
+ else
712
+ if !(bar_hash[:make_mid_story_surfaces_adiabatic])
713
+ # intersect surfaces
714
+ # (when bottom floor has many space types and one above doesn't will end up with heavily subdivided floor. Maybe use adiabatic and don't intersect floor/ceilings)
715
+ intersect_surfaces = true
716
+ if intersect_surfaces
717
+ OpenStudio::Model.intersectSurfaces(spaces)
718
+ OpenStudio::Model.matchSurfaces(spaces)
719
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Intersecting and matching surfaces in model, this will create additional geometry.')
720
+ end
721
+ else
722
+ # elsif bar_hash[:double_loaded_corridor] # only intersect spaces in each story, not between wtory
723
+ model.getBuilding.buildingStories.sort.each do |story|
724
+ story_spaces = OpenStudio::Model::SpaceVector.new
725
+ story.spaces.sort.each do |space|
726
+ story_spaces << space
727
+ end
728
+
729
+ # intersect and sort
730
+ OpenStudio::Model.intersectSurfaces(story_spaces)
731
+ OpenStudio::Model.matchSurfaces(story_spaces)
732
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Intersecting and matching surfaces in story #{story.name}, this will create additional geometry.")
733
+ end
734
+ end
735
+
736
+ end
737
+
738
+ # set boundary conditions if not already set when geometry was created
739
+ # @todo update this to use space original z value vs. story name
740
+ if bar_hash[:num_stories_below_grade] > 0
741
+ model.getBuildingStorys.sort.each do |story|
742
+ next if !story.name.to_s.include?('Story B')
743
+
744
+ story.spaces.sort.each do |space|
745
+ next if !new_spaces.include?(space)
746
+
747
+ space.surfaces.sort.each do |surface|
748
+ next if surface.surfaceType != 'Wall'
749
+ next if surface.outsideBoundaryCondition != 'Outdoors'
750
+
751
+ surface.setOutsideBoundaryCondition('Ground')
752
+ end
753
+ end
754
+ end
755
+ end
756
+
757
+ # set wall boundary condtions to adiabatic if using make_mid_story_surfaces_adiabatic prior to windows being made
758
+ if bar_hash[:make_mid_story_surfaces_adiabatic]
759
+
760
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Finding non-exterior walls and setting boundary condition to adiabatic')
761
+
762
+ # need to organize by story incase top story is partial story
763
+ # should also be only for a single bar
764
+ story_bounding = {}
765
+ missed_match_count = 0
766
+
767
+ # gather new spaces by story
768
+ new_spaces.each do |space|
769
+ story = space.buildingStory.get
770
+ if story_bounding.key?(story)
771
+ story_bounding[story][:spaces] << space
772
+ else
773
+ story_bounding[story] = { spaces: [space] }
774
+ end
775
+ end
776
+
777
+ # get bounding box for each story
778
+ story_bounding.each do |story, v|
779
+ # get bounding_box
780
+ bounding_box = OpenStudio::BoundingBox.new
781
+ v[:spaces].each do |space|
782
+ space.surfaces.each do |space_surface|
783
+ bounding_box.addPoints(space.transformation * space_surface.vertices)
784
+ end
785
+ end
786
+ min_x = bounding_box.minX.get
787
+ min_y = bounding_box.minY.get
788
+ max_x = bounding_box.maxX.get
789
+ max_y = bounding_box.maxY.get
790
+ ext_wall_toll = 0.01
791
+
792
+ # check surfaces again against min/max and change to adiabatic if not fully on one min or max x or y
793
+ # todo - may need to look at aidiabiatc constructions in downstream measure. Some may be exterior party wall others may be interior walls
794
+ v[:spaces].each do |space|
795
+ space.surfaces.each do |space_surface|
796
+ next if space_surface.surfaceType != 'Wall'
797
+ next if space_surface.outsideBoundaryCondition == 'Surface' # if if found a match leave it alone, don't change to adiabiatc
798
+
799
+ surface_bounding_box = OpenStudio::BoundingBox.new
800
+ surface_bounding_box.addPoints(space.transformation * space_surface.vertices)
801
+ surface_on_outside = false
802
+ # check xmin
803
+ if (surface_bounding_box.minX.get - min_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - min_x).abs < ext_wall_toll then surface_on_outside = true end
804
+ # check xmax
805
+ if (surface_bounding_box.minX.get - max_x).abs < ext_wall_toll && (surface_bounding_box.maxX.get - max_x).abs < ext_wall_toll then surface_on_outside = true end
806
+ # check ymin
807
+ if (surface_bounding_box.minY.get - min_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - min_y).abs < ext_wall_toll then surface_on_outside = true end
808
+ # check ymax
809
+ if (surface_bounding_box.minY.get - max_y).abs < ext_wall_toll && (surface_bounding_box.maxY.get - max_y).abs < ext_wall_toll then surface_on_outside = true end
810
+
811
+ # change if not exterior
812
+ if !surface_on_outside
813
+ space_surface.setOutsideBoundaryCondition('Adiabatic')
814
+ missed_match_count += 1
815
+ end
816
+ end
817
+ end
818
+ end
819
+
820
+ if missed_match_count > 0
821
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "#{missed_match_count} surfaces that were exterior appear to be interior walls and had boundary condition chagned to adiabiatic.")
822
+ end
823
+ end
824
+
825
+ # sort stories (by name for now but need better way)
826
+ sorted_stories = {}
827
+ new_spaces.each do |space|
828
+ next if !space.buildingStory.is_initialized
829
+
830
+ story = space.buildingStory.get
831
+ if !sorted_stories.key?(name.to_s)
832
+ sorted_stories[story.name.to_s] = story
833
+ end
834
+ end
835
+
836
+ # flag space types that have wwr overrides
837
+ space_type_wwr_overrides = {}
838
+
839
+ # loop through building stories, spaces, and surfaces
840
+ sorted_stories.sort.each_with_index do |(key, story), i|
841
+ # flag for adiabatic floor if building doesn't have ground exposed floor
842
+ if stories_flat[i][:bottom_story_ground_exposed_floor] == false
843
+ adiabatic_floor = true
844
+ end
845
+ # flag for adiabatic roof if building doesn't have exterior exposed roof
846
+ if stories_flat[i][:top_story_exterior_exposed_roof] == false
847
+ adiabatic_ceiling = true
848
+ end
849
+
850
+ # make all mid story floor and ceilings adiabatic if requested
851
+ if bar_hash[:make_mid_story_surfaces_adiabatic]
852
+ if i > 0
853
+ adiabatic_floor = true
854
+ end
855
+ if i < sorted_stories.size - 1
856
+ adiabatic_ceiling = true
857
+ end
858
+ end
859
+
860
+ # flag orientations for this story to recieve party walls
861
+ party_wall_facades = stories_flat[i][:story_party_walls]
862
+
863
+ story.spaces.each do |space|
864
+ next if !new_spaces.include?(space)
865
+
866
+ space.surfaces. each do |surface|
867
+ # set floor to adiabatic if requited
868
+ if adiabatic_floor && surface.surfaceType == 'Floor'
869
+ surface.setOutsideBoundaryCondition('Adiabatic')
870
+ elsif adiabatic_ceiling && surface.surfaceType == 'RoofCeiling'
871
+ surface.setOutsideBoundaryCondition('Adiabatic')
872
+ end
873
+
874
+ # skip of not exterior wall
875
+ next if surface.surfaceType != 'Wall'
876
+ next if surface.outsideBoundaryCondition != 'Outdoors'
877
+
878
+ # get the absolute azimuth for the surface so we can categorize it
879
+ absolute_azimuth = OpenStudio.convert(surface.azimuth, 'rad', 'deg').get + surface.space.get.directionofRelativeNorth + model.getBuilding.northAxis
880
+ absolute_azimuth = absolute_azimuth % 360.0 # should result in value between 0 and 360
881
+ absolute_azimuth = absolute_azimuth.round(5) # this was creating issues at 45 deg angles with opposing facades
882
+
883
+ # target wwr values that may be changed for specific space types
884
+ wwr_n = bar_hash[:building_wwr_n]
885
+ wwr_e = bar_hash[:building_wwr_e]
886
+ wwr_s = bar_hash[:building_wwr_s]
887
+ wwr_w = bar_hash[:building_wwr_w]
888
+
889
+ # look for space type specific wwr values
890
+ if surface.space.is_initialized && surface.space.get.spaceType.is_initialized
891
+ space_type = surface.space.get.spaceType.get
892
+
893
+ # see if space type has wwr value
894
+ bar_hash[:space_types].each do |k, v|
895
+ if v.key?(:space_type) && space_type == v[:space_type]
896
+
897
+ # if matching space type specifies a wwr then override the orientation specific recommendations for this surface.
898
+ if v.key?(:wwr)
899
+ wwr_n = v[:wwr]
900
+ wwr_e = v[:wwr]
901
+ wwr_s = v[:wwr]
902
+ wwr_w = v[:wwr]
903
+ space_type_wwr_overrides[space_type] = v[:wwr]
904
+ end
905
+ end
906
+ end
907
+ end
908
+
909
+ # add fenestration (wwr for now, maybe overhang and overhead doors later)
910
+ if (absolute_azimuth >= 315.0) || (absolute_azimuth < 45.0)
911
+ if party_wall_facades.include?('north')
912
+ surface.setOutsideBoundaryCondition('Adiabatic')
913
+ else
914
+ surface.setWindowToWallRatio(wwr_n)
915
+ end
916
+ elsif (absolute_azimuth >= 45.0) && (absolute_azimuth < 135.0)
917
+ if party_wall_facades.include?('east')
918
+ surface.setOutsideBoundaryCondition('Adiabatic')
919
+ else
920
+ surface.setWindowToWallRatio(wwr_e)
921
+ end
922
+ elsif (absolute_azimuth >= 135.0) && (absolute_azimuth < 225.0)
923
+ if party_wall_facades.include?('south')
924
+ surface.setOutsideBoundaryCondition('Adiabatic')
925
+ else
926
+ surface.setWindowToWallRatio(wwr_s)
927
+ end
928
+ elsif (absolute_azimuth >= 225.0) && (absolute_azimuth < 315.0)
929
+ if party_wall_facades.include?('west')
930
+ surface.setOutsideBoundaryCondition('Adiabatic')
931
+ else
932
+ surface.setWindowToWallRatio(wwr_w)
933
+ end
934
+ else
935
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Unexpected value of facade: ' + absolute_azimuth + '.')
936
+ return false
937
+ end
938
+ end
939
+ end
940
+ end
941
+
942
+ # report space types with custom wwr values
943
+ space_type_wwr_overrides.each do |space_type, wwr|
944
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For #{space_type.name} the default building wwr was replaced with a space type specfic value of #{wwr}")
945
+ end
946
+
947
+ new_floor_area_si = 0.0
948
+ new_spaces.each do |space|
949
+ new_floor_area_si += space.floorArea * space.multiplier
950
+ end
951
+ new_floor_area_ip = OpenStudio.convert(new_floor_area_si, 'm^2', 'ft^2').get
952
+
953
+ final_floor_area_ip = OpenStudio.convert(model.getBuilding.floorArea, 'm^2', 'ft^2').get
954
+ if new_floor_area_ip == final_floor_area_ip
955
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2.")
956
+ else
957
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Created bar envelope with floor area of #{OpenStudio.toNeatString(new_floor_area_ip, 0, true)} ft^2. Total building area is #{OpenStudio.toNeatString(final_floor_area_ip, 0, true)} ft^2.")
958
+ end
959
+
960
+ return new_spaces
961
+ end
962
+
963
+ # give info messages bar hash for create_bar method
964
+ #
965
+ # @param model [OpenStudio::Model::Model] OpenStudio model object
966
+ # @param args [Hash] user arguments
967
+ # @param length [Double] length of building in meters
968
+ # @param width [Double] width of building in meters
969
+ # @param floor_height [Double] floor height in meters
970
+ # @param center_of_footprint [OpenStudio::Point3d] center of footprint
971
+ # @param space_types_hash [Hash] space type hash
972
+ # @param num_stories [Double] number of stories
973
+ # @return [Boolean] returns true if successful, false if not
974
+ def self.bar_hash_setup_run(model, args, length, width, floor_height, center_of_footprint, space_types_hash, num_stories)
975
+ # create envelope
976
+ # populate bar_hash and create envelope with data from envelope_data_hash and user arguments
977
+ bar_hash = {}
978
+ bar_hash[:length] = length
979
+ bar_hash[:width] = width
980
+ bar_hash[:num_stories_below_grade] = args[:num_stories_below_grade]
981
+ bar_hash[:num_stories_above_grade] = args[:num_stories_above_grade]
982
+ bar_hash[:floor_height] = floor_height
983
+ bar_hash[:center_of_footprint] = center_of_footprint
984
+ bar_hash[:bar_division_method] = args[:bar_division_method]
985
+ bar_hash[:story_multiplier_method] = args[:story_multiplier_method]
986
+ bar_hash[:make_mid_story_surfaces_adiabatic] = args[:make_mid_story_surfaces_adiabatic]
987
+ bar_hash[:space_types] = space_types_hash
988
+ bar_hash[:building_wwr_n] = args[:wwr]
989
+ bar_hash[:building_wwr_s] = args[:wwr]
990
+ bar_hash[:building_wwr_e] = args[:wwr]
991
+ bar_hash[:building_wwr_w] = args[:wwr]
992
+
993
+ # round up non integer stoires to next integer
994
+ num_stories_round_up = num_stories.ceil
995
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Making bar with length of #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft and width of #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft")
996
+
997
+ # party_walls_array to be used by orientation specific or fractional party wall values
998
+ party_walls_array = [] # this is an array of arrays, where each entry is effective building story with array of directions
999
+
1000
+ if args[:party_wall_stories_north] + args[:party_wall_stories_south] + args[:party_wall_stories_east] + args[:party_wall_stories_west] > 0
1001
+
1002
+ # loop through effective number of stories add orientation specific party walls per user arguments
1003
+ num_stories_round_up.times do |i|
1004
+ test_value = i + 1 - bar_hash[:num_stories_below_grade]
1005
+
1006
+ array = []
1007
+ if args[:party_wall_stories_north] >= test_value
1008
+ array << 'north'
1009
+ end
1010
+ if args[:party_wall_stories_south] >= test_value
1011
+ array << 'south'
1012
+ end
1013
+ if args[:party_wall_stories_east] >= test_value
1014
+ array << 'east'
1015
+ end
1016
+ if args[:party_wall_stories_west] >= test_value
1017
+ array << 'west'
1018
+ end
1019
+
1020
+ # populate party_wall_array for this story
1021
+ party_walls_array << array
1022
+ end
1023
+ end
1024
+
1025
+ # calculate party walls if using party_wall_fraction method
1026
+ if args[:party_wall_fraction] > 0 && !party_walls_array.empty?
1027
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Both orientation and fractional party wall values arguments were populated, will ignore fractional party wall input')
1028
+ elsif args[:party_wall_fraction] > 0
1029
+ # orientation of long and short side of building will vary based on building rotation
1030
+
1031
+ # full story ext wall area
1032
+ typical_length_facade_area = length * floor_height
1033
+ typical_width_facade_area = width * floor_height
1034
+
1035
+ # top story ext wall area, may be partial story
1036
+ partial_story_multiplier = (1.0 - args[:num_stories_above_grade].ceil + args[:num_stories_above_grade])
1037
+ area_multiplier = partial_story_multiplier
1038
+ edge_multiplier = Math.sqrt(area_multiplier)
1039
+ top_story_length = length * edge_multiplier
1040
+ top_story_width = width * edge_multiplier
1041
+ top_story_length_facade_area = top_story_length * floor_height
1042
+ top_story_width_facade_area = top_story_width * floor_height
1043
+
1044
+ total_exterior_wall_area = 2 * (length + width) * (args[:num_stories_above_grade].ceil - 1.0) * floor_height + 2 * (top_story_length + top_story_width) * floor_height
1045
+ target_party_wall_area = total_exterior_wall_area * args[:party_wall_fraction]
1046
+
1047
+ width_counter = 0
1048
+ width_area = 0.0
1049
+ facade_area = typical_width_facade_area
1050
+ until (width_area + facade_area >= target_party_wall_area) || (width_counter == args[:num_stories_above_grade].ceil * 2)
1051
+ # update facade area for top story
1052
+ if width_counter == args[:num_stories_above_grade].ceil - 1 || width_counter == args[:num_stories_above_grade].ceil * 2 - 1
1053
+ facade_area = top_story_width_facade_area
1054
+ else
1055
+ facade_area = typical_width_facade_area
1056
+ end
1057
+
1058
+ width_counter += 1
1059
+ width_area += facade_area
1060
+
1061
+ end
1062
+ width_area_remainder = target_party_wall_area - width_area
1063
+
1064
+ length_counter = 0
1065
+ length_area = 0.0
1066
+ facade_area = typical_length_facade_area
1067
+ until (length_area + facade_area >= target_party_wall_area) || (length_counter == args[:num_stories_above_grade].ceil * 2)
1068
+ # update facade area for top story
1069
+ if length_counter == args[:num_stories_above_grade].ceil - 1 || length_counter == args[:num_stories_above_grade].ceil * 2 - 1
1070
+ facade_area = top_story_length_facade_area
1071
+ else
1072
+ facade_area = typical_length_facade_area
1073
+ end
1074
+
1075
+ length_counter += 1
1076
+ length_area += facade_area
1077
+ end
1078
+ length_area_remainder = target_party_wall_area - length_area
1079
+
1080
+ # get rotation and best fit to adjust orientation for fraction party wall
1081
+ rotation = args[:building_rotation] % 360.0 # should result in value between 0 and 360
1082
+ card_dir_array = [0.0, 90.0, 180.0, 270.0, 360.0]
1083
+ # reverse array to properly handle 45, 135, 225, and 315
1084
+ best_fit = card_dir_array.reverse.min_by { |x| (x.to_f - rotation).abs }
1085
+
1086
+ if ![90.0, 270.0].include? best_fit
1087
+ width_card_dir = ['east', 'west']
1088
+ length_card_dir = ['north', 'south']
1089
+ else # if rotation is closest to 90 or 270 then reverse which orientation is used for length and width
1090
+ width_card_dir = ['north', 'south']
1091
+ length_card_dir = ['east', 'west']
1092
+ end
1093
+
1094
+ # if dont' find enough on short sides
1095
+ if width_area_remainder <= typical_length_facade_area
1096
+
1097
+ num_stories_round_up.times do |i|
1098
+ if i + 1 <= args[:num_stories_below_grade]
1099
+ party_walls_array << []
1100
+ next
1101
+ end
1102
+ if i + 1 - args[:num_stories_below_grade] <= width_counter
1103
+ if i + 1 - args[:num_stories_below_grade] <= width_counter - args[:num_stories_above_grade]
1104
+ party_walls_array << width_card_dir
1105
+ else
1106
+ party_walls_array << [width_card_dir.first]
1107
+ end
1108
+ else
1109
+ party_walls_array << []
1110
+ end
1111
+ end
1112
+
1113
+ else
1114
+ # use long sides instead
1115
+ num_stories_round_up.times do |i|
1116
+ if i + 1 <= args[:num_stories_below_grade]
1117
+ party_walls_array << []
1118
+ next
1119
+ end
1120
+ if i + 1 - args[:num_stories_below_grade] <= length_counter
1121
+ if i + 1 - args[:num_stories_below_grade] <= length_counter - args[:num_stories_above_grade]
1122
+ party_walls_array << length_card_dir
1123
+ else
1124
+ party_walls_array << [length_card_dir.first]
1125
+ end
1126
+ else
1127
+ party_walls_array << []
1128
+ end
1129
+ end
1130
+
1131
+ end
1132
+ # @todo currently won't go past making two opposing sets of walls party walls. Info and registerValue are after create_bar in measure.rb
1133
+ end
1134
+
1135
+ # populate bar hash with story information
1136
+ bar_hash[:stories] = {}
1137
+ num_stories_round_up.times do |i|
1138
+ if party_walls_array.empty?
1139
+ party_walls = []
1140
+ else
1141
+ party_walls = party_walls_array[i]
1142
+ end
1143
+
1144
+ # add below_partial_story
1145
+ if num_stories.ceil > num_stories && i == num_stories_round_up - 2
1146
+ below_partial_story = true
1147
+ else
1148
+ below_partial_story = false
1149
+ end
1150
+
1151
+ # bottom_story_ground_exposed_floor and top_story_exterior_exposed_roof already setup as bool
1152
+ bar_hash[:stories]["key #{i}"] = { story_party_walls: party_walls, story_min_multiplier: 1, story_included_in_building_area: true, below_partial_story: below_partial_story, bottom_story_ground_exposed_floor: args[:bottom_story_ground_exposed_floor], top_story_exterior_exposed_roof: args[:top_story_exterior_exposed_roof] }
1153
+ end
1154
+
1155
+ # create bar
1156
+ new_spaces = create_bar(model, bar_hash)
1157
+
1158
+ # check expect roof and wall area
1159
+ target_footprint = bar_hash[:length] * bar_hash[:width]
1160
+ ground_floor_area = 0.0
1161
+ roof_area = 0.0
1162
+ new_spaces.each do |space|
1163
+ space.surfaces.each do |surface|
1164
+ if surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground'
1165
+ ground_floor_area += surface.netArea
1166
+ elsif surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors'
1167
+ roof_area += surface.netArea
1168
+ end
1169
+ end
1170
+ end
1171
+ # @todo extend to address when top and or bottom story are not exposed via argument
1172
+ if ground_floor_area > target_footprint + 0.001 || roof_area > target_footprint + 0.001
1173
+ # OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.")
1174
+ # return false
1175
+
1176
+ # not providing adiabatic work around when top story is partial story.
1177
+ if args[:num_stories_above_grade].to_f != args[:num_stories_above_grade].ceil
1178
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error.')
1179
+ return false
1180
+ else
1181
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ground exposed floor or Roof area is larger than footprint, likely inter-floor surface matching and intersection error, altering impacted surfaces boundary condition to be adiabatic.')
1182
+ match_error = true
1183
+ end
1184
+ else
1185
+ match_error = false
1186
+ end
1187
+
1188
+ # @todo should be able to remove this fix after OpenStudio intersection issue is fixed. At that time turn the above message into an error with return false after it
1189
+ if match_error
1190
+
1191
+ # identify z value of top and bottom story
1192
+ bottom_story = nil
1193
+ top_story = nil
1194
+ new_spaces.each do |space|
1195
+ story = space.buildingStory.get
1196
+ nom_z = story.nominalZCoordinate.get
1197
+ if bottom_story.nil?
1198
+ bottom_story = nom_z
1199
+ elsif bottom_story > nom_z
1200
+ bottom_story = nom_z
1201
+ end
1202
+ if top_story.nil?
1203
+ top_story = nom_z
1204
+ elsif top_story < nom_z
1205
+ top_story = nom_z
1206
+ end
1207
+ end
1208
+
1209
+ # change boundary condition and intersection as needed.
1210
+ new_spaces.each do |space|
1211
+ if space.buildingStory.get.nominalZCoordinate.get > bottom_story
1212
+ # change floors
1213
+ space.surfaces.each do |surface|
1214
+ next if !(surface.surfaceType == 'Floor' && surface.outsideBoundaryCondition == 'Ground')
1215
+
1216
+ surface.setOutsideBoundaryCondition('Adiabatic')
1217
+ end
1218
+ end
1219
+ if space.buildingStory.get.nominalZCoordinate.get < top_story
1220
+ # change ceilings
1221
+ space.surfaces.each do |surface|
1222
+ next if !(surface.surfaceType == 'RoofCeiling' && surface.outsideBoundaryCondition == 'Outdoors')
1223
+
1224
+ surface.setOutsideBoundaryCondition('Adiabatic')
1225
+ end
1226
+ end
1227
+ end
1228
+ end
1229
+ end
1230
+
1231
+ # create bar from arguments and building type hash
1232
+ #
1233
+ # @param args [Hash] user arguments
1234
+ # @option args [Double] :single_floor_area (0.0) Single floor area in ft^2. Non-zero value will fix the single floor area, overriding a user entry for total_bldg_floor_area
1235
+ # @option args [Double] :total_bldg_floor_area (10000.0) Total building floor area in ft^2
1236
+ # @option args [Double] :floor_height (0.0) Typical floor to floor height. Selecting a typical floor height of 0 will trigger a smart building type default.
1237
+ # @option args [Boolean] :custom_height_bar (true) This is argument value is only relevant when smart default floor to floor height is used for a building type that has spaces with custom heights.
1238
+ # @option args [Integer] :num_stories_above_grade (1) Number of stories above grade
1239
+ # @option args [Integer] :num_stories_below_grade (0) Number of stories below grade
1240
+ # @option args [Double] :building_rotation (0.0) Building rotation. Set Building Rotation off of North (positive value is clockwise). Rotation applied after geometry generation. Values greater than +/- 45 will result in aspect ratio and party wall orientations that do not match cardinal directions of the inputs.
1241
+ # @option args [Double] :ns_to_ew_ratio (0.0) Ratio of North/South facade length relative to east/west facade length. Selecting an aspect ratio of 0 will trigger a smart building type default. Aspect ratios less than one are not recommended for sliced bar geometry, instead rotate building and use a greater than 1 aspect ratio.
1242
+ # @option args [Double] :perim_mult (0.0) Perimeter multiplier. Selecting a value of 0 will trigger a smart building type default. This represents a multiplier for the building perimeter relative to the perimeter of a rectangular building that meets the area and aspect ratio inputs. Other than the smart default of 0.0 this argument should have a value of 1.0 or higher and is only applicable Multiple Space Types - Individual Stories Sliced division method.
1243
+ # @option args [Double] :bar_width (0.0) Bar Width. Non-zero value will fix the building width, overriding user entry for Perimeter Multiplier. NS/EW Aspect Ratio may be limited based on target width.
1244
+ # @option args [Double] :bar_sep_dist_mult (10.0) Bar separation distance multiplier. Multiplier of separation between bar elements relative to building height.
1245
+ # @option args [Double] :wwr (0.0) Window to wall ratio. Selecting a window to wall ratio of 0 will trigger a smart building type default.
1246
+ # @option args [Double] :party_wall_fraction (0.0) fraction of exterior wall area with an adjacent structure
1247
+ # @option args [Integer] :party_wall_stories_north (0) Number of North facing stories with party wall
1248
+ # @option args [Integer] :party_wall_stories_south (0) Number of South facing stories with party wall
1249
+ # @option args [Integer] :party_wall_stories_east (0) Number of East facing stories with party wall
1250
+ # @option args [Integer] :party_wall_stories_west (0) Number of West facing stories with party wall
1251
+ # @option args [Boolean] :bottom_story_ground_exposed_floor (true) Is the bottom story exposed to the ground
1252
+ # @option args [Boolean] :top_story_exterior_exposed_roof (true) Is the top story an exterior roof
1253
+ # @option args [String] :story_multiplier_method ('Basements Ground Mid Top') Calculation method for story multiplier. Options are 'None' and 'Basements Ground Mid Top'
1254
+ # @option args [Boolean] :make_mid_story_surfaces_adiabatic (true) Make mid story floor surfaces adiabatic. If set to true, this will skip surface intersection and make mid story floors and celings adiabatic, not just at multiplied gaps.
1255
+ # @option args [String] :bar_division_method ('Multiple Space Types - Individual Stories Sliced') Division method for bar space types. Options are 'Multiple Space Types - Simple Sliced', 'Multiple Space Types - Individual Stories Sliced', 'Single Space Type - Core and Perimeter'
1256
+ # @option args [String] :double_loaded_corridor ('Primary Space Type') Method for double loaded corridor. Add double loaded corridor for building types that have a defined circulation space type, to the selected space types. Options are 'None' and 'Primary Space Type'
1257
+ # @option args [String] :space_type_sort_logic ('Building Type > Size') Space type sorting method. Options are 'Size' and 'Building Type > Size'
1258
+ # @option args [String] :template ('90.1-2013') target standard
1259
+ # @param building_type_hash [Array<Hash>] array of building type hashes
1260
+ # @option building_type_hash [Double] :frac_bldg_area fraction of building area
1261
+ # @option building_type_hash [Hash] :space_types hash of space types data
1262
+ # @return [Boolean] returns true if successful, false if not
1263
+ def self.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)
1264
+ # set argument defaults if not present
1265
+ args[:single_floor_area] = args.fetch(:single_floor_area, 0.0)
1266
+ args[:total_bldg_floor_area] = args.fetch(:total_bldg_floor_area, 10000.0)
1267
+ args[:floor_height] = args.fetch(:floor_height, 0.0)
1268
+ args[:custom_height_bar] = args.fetch(:custom_height_bar, true)
1269
+ args[:num_stories_above_grade] = args.fetch(:num_stories_above_grade, 1)
1270
+ args[:num_stories_below_grade] = args.fetch(:num_stories_below_grade, 0)
1271
+ args[:building_rotation] = args.fetch(:building_rotation, 0.0)
1272
+ args[:ns_to_ew_ratio] = args.fetch(:ns_to_ew_ratio, 0.0)
1273
+ args[:perim_mult] = args.fetch(:perim_mult, 0.0)
1274
+ args[:bar_width] = args.fetch(:bar_width, 0.0)
1275
+ args[:bar_sep_dist_mult] = args.fetch(:bar_sep_dist_mult, 10.0)
1276
+ args[:wwr] = args.fetch(:wwr, 0.0)
1277
+ args[:party_wall_fraction] = args.fetch(:party_wall_fraction, 0.0)
1278
+ args[:party_wall_stories_north] = args.fetch(:party_wall_stories_north, 0)
1279
+ args[:party_wall_stories_south] = args.fetch(:party_wall_stories_south, 0)
1280
+ args[:party_wall_stories_east] = args.fetch(:party_wall_stories_east, 0)
1281
+ args[:party_wall_stories_west] = args.fetch(:party_wall_stories_west, 0)
1282
+ args[:bottom_story_ground_exposed_floor] = args.fetch(:bottom_story_ground_exposed_floor, true)
1283
+ args[:top_story_exterior_exposed_roof] = args.fetch(:top_story_exterior_exposed_roof, true)
1284
+ args[:story_multiplier_method] = args.fetch(:story_multiplier_method, 'Basements Ground Mid Top')
1285
+ args[:make_mid_story_surfaces_adiabatic] = args.fetch(:make_mid_story_surfaces_adiabatic, true)
1286
+ args[:bar_division_method] = args.fetch(:bar_division_method, 'Multiple Space Types - Individual Stories Sliced')
1287
+ args[:double_loaded_corridor] = args.fetch(:double_loaded_corridor, 'Primary Space Type')
1288
+ args[:space_type_sort_logic] = args.fetch(:space_type_sort_logic, 'Building Type > Size')
1289
+ args[:template] = args.fetch(:template, '90.1-2013')
1290
+
1291
+ # get defaults for the primary building type
1292
+ primary_building_type = args[:primary_building_type]
1293
+ building_form_defaults = OpenstudioStandards::Geometry.building_form_defaults(primary_building_type)
1294
+
1295
+ # if aspect ratio, story height or wwr have argument value of 0 then use smart building type defaults
1296
+ # store list of defaulted items
1297
+ defaulted_args = []
1298
+
1299
+ if args[:ns_to_ew_ratio] == 0.0
1300
+ args[:ns_to_ew_ratio] = building_form_defaults[:aspect_ratio]
1301
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for aspect ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:aspect_ratio]}.")
1302
+ end
1303
+
1304
+ if args[:perim_mult] == 0.0
1305
+ # if this is not defined then use default of 1.0
1306
+ if !building_form_defaults.key?(:perim_mult)
1307
+ args[:perim_mult] = 1.0
1308
+ else
1309
+ args[:perim_mult] = building_form_defaults[:perim_mult]
1310
+ end
1311
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for minimum perimeter multiplier will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:perim_mult]}.")
1312
+ elsif args[:perim_mult] < 1.0
1313
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Other than the smart default value of 0, the minimum perimeter multiplier should be equal to 1.0 or greater.')
1314
+ return false
1315
+ end
1316
+
1317
+ if args[:floor_height] == 0.0
1318
+ args[:floor_height] = building_form_defaults[:typical_story]
1319
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for floor height will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:typical_story]}.")
1320
+ defaulted_args << 'floor_height'
1321
+ end
1322
+ # because of this can't set wwr to 0.0. If that is desired then we can change this to check for 1.0 instead of 0.0
1323
+ if args[:wwr] == 0.0
1324
+ args[:wwr] = building_form_defaults[:wwr]
1325
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "0.0 value for window to wall ratio will be replaced with smart default for #{primary_building_type} of #{building_form_defaults[:wwr]}.")
1326
+ end
1327
+
1328
+ # report initial condition of model
1329
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building started with #{model.getSpaces.size} spaces.")
1330
+
1331
+ # determine of ns_ew needs to be mirrored
1332
+ mirror_ns_ew = false
1333
+ rotation = model.getBuilding.northAxis
1334
+ if rotation > 45.0 && rotation < 135.0
1335
+ mirror_ns_ew = true
1336
+ elsif rotation > 45.0 && rotation < 135.0
1337
+ mirror_ns_ew = true
1338
+ end
1339
+
1340
+ # remove non-resource objects not removed by removing the building
1341
+ # remove_non_resource_objects(model)
1342
+
1343
+ # creating space types for requested building types
1344
+ building_type_hash.each do |building_type, building_type_hash|
1345
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating Space Types for #{building_type}.")
1346
+
1347
+ # mapping building_type name is needed for a few methods
1348
+ temp_standard = Standard.build('90.1-2013')
1349
+ building_type = temp_standard.model_get_lookup_name(building_type)
1350
+
1351
+ # create space_type_map from array
1352
+ sum_of_ratios = 0.0
1353
+ building_type_hash[:space_types] = building_type_hash[:space_types].sort_by { |k, v| v[:ratio] }.to_h
1354
+ building_type_hash[:space_types].each do |space_type_name, hash|
1355
+ next if hash[:space_type_gen] == false # space types like undeveloped and basement are skipped.
1356
+
1357
+ # create space type
1358
+ space_type = OpenStudio::Model::SpaceType.new(model)
1359
+ space_type.setStandardsBuildingType(building_type)
1360
+ space_type.setStandardsSpaceType(space_type_name)
1361
+ space_type.setName("#{building_type} #{space_type_name}")
1362
+
1363
+ # set color
1364
+ test = temp_standard.space_type_apply_rendering_color(space_type)
1365
+ if !test
1366
+ # @todo once fixed in standards un-comment this
1367
+ # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Could not find color for #{space_type.name}")
1368
+ end
1369
+
1370
+ # extend hash to hold new space type object
1371
+ hash[:space_type] = space_type
1372
+
1373
+ # add to sum_of_ratios counter for adjustment multiplier
1374
+ sum_of_ratios += hash[:ratio]
1375
+ end
1376
+
1377
+ # store multiplier needed to adjust sum of ratios to equal 1.0
1378
+ building_type_hash[:ratio_adjustment_multiplier] = 1.0 / sum_of_ratios
1379
+ end
1380
+
1381
+ # calculate length and with of bar
1382
+ total_bldg_floor_area_si = OpenStudio.convert(args[:total_bldg_floor_area], 'ft^2', 'm^2').get
1383
+ single_floor_area_si = OpenStudio.convert(args[:single_floor_area], 'ft^2', 'm^2').get
1384
+
1385
+ # store number of stories
1386
+ num_stories = args[:num_stories_below_grade] + args[:num_stories_above_grade]
1387
+
1388
+ # handle user-assigned single floor plate size condition
1389
+ if args[:single_floor_area] > 0.0
1390
+ footprint_si = single_floor_area_si
1391
+ total_bldg_floor_area_si = footprint_si * num_stories.to_f
1392
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'User-defined single floor area was used for calculation of total building floor area')
1393
+ # add warning if custom_height_bar is true and applicable building type is selected
1394
+ if args[:custom_height_bar]
1395
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Cannot use custom height bar with single floor area method, will not create custom height bar.')
1396
+ args[:custom_height_bar] = false
1397
+ end
1398
+ else
1399
+ footprint_si = nil
1400
+ end
1401
+
1402
+ # populate space_types_hash
1403
+ space_types_hash = {}
1404
+ multi_height_space_types_hash = {}
1405
+ custom_story_heights = []
1406
+ if args[:space_type_sort_logic] == 'Building Type > Size'
1407
+ building_type_hash = building_type_hash.sort_by { |k, v| v[:frac_bldg_area] }
1408
+ end
1409
+ building_type_hash.each do |building_type, building_type_hash|
1410
+ if args[:double_loaded_corridor] == 'Primary Space Type'
1411
+
1412
+ # see if building type has circulation space type, if so then merge that along with default space type into hash key in place of space type
1413
+ default_st = nil
1414
+ circ_st = nil
1415
+ building_type_hash[:space_types].each do |space_type_name, hash|
1416
+ if hash[:default] then default_st = space_type_name end
1417
+ if hash[:circ] then circ_st = space_type_name end
1418
+ end
1419
+
1420
+ # update building hash
1421
+ if !default_st.nil? && !circ_st.nil?
1422
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Combining #{default_st} and #{circ_st} into a group representing a double loaded corridor")
1423
+
1424
+ # add new item
1425
+ building_type_hash[:space_types]['Double Loaded Corridor'] = {}
1426
+ double_loaded_st = building_type_hash[:space_types]['Double Loaded Corridor']
1427
+ double_loaded_st[:ratio] = building_type_hash[:space_types][default_st][:ratio] + building_type_hash[:space_types][circ_st][:ratio]
1428
+ double_loaded_st[:double_loaded_corridor] = true
1429
+ double_loaded_st[:space_type] = model.getBuilding
1430
+ double_loaded_st[:children] = {}
1431
+ building_type_hash[:space_types][default_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][default_st][:ratio]
1432
+ building_type_hash[:space_types][circ_st][:orig_ratio] = building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area] * building_type_hash[:space_types][circ_st][:ratio]
1433
+ building_type_hash[:space_types][default_st][:name] = default_st
1434
+ building_type_hash[:space_types][circ_st][:name] = circ_st
1435
+ double_loaded_st[:children][:default] = building_type_hash[:space_types][default_st]
1436
+ double_loaded_st[:children][:circ] = building_type_hash[:space_types][circ_st]
1437
+ double_loaded_st[:orig_ratio] = 0.0
1438
+
1439
+ # zero out ratios from old item (don't delete because I still want the space types made)
1440
+ building_type_hash[:space_types][default_st][:ratio] = 0.0
1441
+ building_type_hash[:space_types][circ_st][:ratio] = 0.0
1442
+ end
1443
+ end
1444
+
1445
+ building_type_hash[:space_types].each do |space_type_name, hash|
1446
+ next if hash[:space_type_gen] == false
1447
+
1448
+ space_type = hash[:space_type]
1449
+ ratio_of_bldg_total = hash[:ratio] * building_type_hash[:ratio_adjustment_multiplier] * building_type_hash[:frac_bldg_area]
1450
+ final_floor_area = ratio_of_bldg_total * total_bldg_floor_area_si # I think I can just pass ratio but passing in area is cleaner
1451
+
1452
+ # only add custom height space if 0 is used for floor_height
1453
+ if defaulted_args.include?(:floor_height) && hash.key?(:story_height) && args[:custom_height_bar]
1454
+ multi_height_space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type, story_height: hash[:story_height] }
1455
+ if hash.key?(:orig_ratio) then multi_height_space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
1456
+ custom_story_heights << hash[:story_height]
1457
+ if args[:wwr] == 0 && hash.key?(:wwr)
1458
+ multi_height_space_types_hash[space_type][:wwr] = hash[:wwr]
1459
+ end
1460
+ else
1461
+ # only add wwr if 0 used for wwr arg and if space type has wwr as key
1462
+ space_types_hash[space_type] = { floor_area: final_floor_area, space_type: space_type }
1463
+ if hash.key?(:orig_ratio) then space_types_hash[space_type][:orig_ratio] = hash[:orig_ratio] end
1464
+ if args[:wwr] == 0 && hash.key?(:wwr)
1465
+ space_types_hash[space_type][:wwr] = hash[:wwr]
1466
+ end
1467
+ if hash[:double_loaded_corridor]
1468
+ space_types_hash[space_type][:children] = hash[:children]
1469
+ end
1470
+ end
1471
+ end
1472
+ end
1473
+
1474
+ # resort if not sorted by building type
1475
+ if args[:space_type_sort_logic] == 'Size'
1476
+ # added code to convert to hash. I use sort_by 3 other times, but those seem to be working fine as is now.
1477
+ space_types_hash = Hash[space_types_hash.sort_by { |k, v| v[:floor_area] }]
1478
+ end
1479
+
1480
+ # calculate targets for testing
1481
+ target_areas = {} # used for checks
1482
+ target_areas_cust_height = 0.0
1483
+ space_types_hash.each do |k, v|
1484
+ if v.key?(:orig_ratio)
1485
+ target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
1486
+ else
1487
+ target_areas[k] = v[:floor_area]
1488
+ end
1489
+ end
1490
+ multi_height_space_types_hash.each do |k, v|
1491
+ if v.key?(:orig_ratio)
1492
+ target_areas[k] = v[:orig_ratio] * total_bldg_floor_area_si
1493
+ target_areas_cust_height += v[:orig_ratio] * total_bldg_floor_area_si
1494
+ else
1495
+ target_areas[k] = v[:floor_area]
1496
+ target_areas_cust_height += v[:floor_area]
1497
+ end
1498
+ end
1499
+
1500
+ # gather inputs
1501
+ if footprint_si.nil?
1502
+ footprint_si = (total_bldg_floor_area_si - target_areas_cust_height) / num_stories.to_f
1503
+ end
1504
+ floor_height = OpenStudio.convert(args[:floor_height], 'ft', 'm').get
1505
+ min_allow_size = OpenStudio.convert(15.0, 'ft', 'm').get
1506
+ specified_bar_width_si = OpenStudio.convert(args[:bar_width], 'ft', 'm').get
1507
+
1508
+ # set custom width
1509
+ if specified_bar_width_si > 0
1510
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier argument when non zero width argument is used')
1511
+ if footprint_si / specified_bar_width_si >= min_allow_size
1512
+ width = specified_bar_width_si
1513
+ length = footprint_si / width
1514
+ else
1515
+ length = min_allow_size
1516
+ width = footprint_si / length
1517
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'User specified width results in a length that is too short, adjusting width to be narrower than specified.')
1518
+ end
1519
+ width_cust_height = specified_bar_width_si
1520
+ else
1521
+ width = Math.sqrt(footprint_si / args[:ns_to_ew_ratio])
1522
+ length = footprint_si / width
1523
+ width_cust_height = Math.sqrt(target_areas_cust_height / args[:ns_to_ew_ratio])
1524
+ end
1525
+ length_cust_height = target_areas_cust_height / width_cust_height
1526
+ if args[:perim_mult] > 1.0 && target_areas_cust_height > 0.0
1527
+ # @todo update tests that hit this warning
1528
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier for bar that represents custom height spaces.')
1529
+ end
1530
+
1531
+ # check if dual bar is needed
1532
+ dual_bar = false
1533
+ if specified_bar_width_si > 0.0 && args[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
1534
+ if length / width != args[:ns_to_ew_ratio]
1535
+
1536
+ if args[:ns_to_ew_ratio] >= 1.0 && args[:ns_to_ew_ratio] > length / width
1537
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Can't meet target aspect ratio of #{args[:ns_to_ew_ratio]}, Lowering it to #{length / width} ")
1538
+ args[:ns_to_ew_ratio] = length / width
1539
+ elsif args[:ns_to_ew_ratio] < 1.0 && args[:ns_to_ew_ratio] > length / width
1540
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Can't meet target aspect ratio of #{args[:ns_to_ew_ratio]}, Increasing it to #{length / width} ")
1541
+ args[:ns_to_ew_ratio] = length / width
1542
+ else
1543
+ # check if each bar would be longer then 15 feet, then set as dual bar and override perimeter multiplier
1544
+ length_alt1 = ((args[:ns_to_ew_ratio] * footprint_si) / width + 2 * args[:ns_to_ew_ratio] * width - 2 * width) / (1 + args[:ns_to_ew_ratio])
1545
+ length_alt2 = length - length_alt1
1546
+ if [length_alt1, length_alt2].min >= min_allow_size
1547
+ dual_bar = true
1548
+ else
1549
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Second bar would be below minimum length, will model as single bar')
1550
+ # swap length and width if single bar and aspect ratio less than 1
1551
+ if args[:ns_to_ew_ratio] < 1.0
1552
+ width = length
1553
+ length = specified_bar_width_si
1554
+ end
1555
+ end
1556
+ end
1557
+ end
1558
+ elsif args[:perim_mult] > 1.0 && args[:bar_division_method] == 'Multiple Space Types - Individual Stories Sliced'
1559
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'You selected a perimeter multiplier greater than 1.0 for a supported bar division method. This will result in two detached rectangular buildings if secondary bar meets minimum size requirements.')
1560
+ dual_bar = true
1561
+ elsif args[:perim_mult] > 1.0
1562
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "You selected a perimeter multiplier greater than 1.0 but didn't select a bar division method that supports this. The value for this argument will be ignored by the measure")
1563
+ end
1564
+
1565
+ # calculations for dual bar, which later will be setup to run create_bar twice
1566
+ if dual_bar
1567
+ min_perim = 2 * width + 2 * length
1568
+ target_area = footprint_si
1569
+ target_perim = min_perim * args[:perim_mult]
1570
+ tol_testing = 0.00001
1571
+ dual_bar_calc_approach = nil # stretched, adiabatic_ends_bar_b, dual_bar
1572
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Minimum rectangle is #{OpenStudio.toNeatString(OpenStudio.convert(length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(width, 'm', 'ft').get, 0, true)} ft with an area of #{OpenStudio.toNeatString(OpenStudio.convert(length * width, 'm^2', 'ft^2').get, 0, true)} ft^2. Perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(min_perim, 'm', 'ft').get, 0, true)} ft.")
1573
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Target dual bar perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(target_perim, 'm', 'ft').get, 0, true)} ft.")
1574
+
1575
+ # determine which of the three paths to hit target perimeter multiplier are possible
1576
+ # A use dual bar non adiabatic
1577
+ # B use dual bar adiabatic
1578
+ # C use stretched bar (requires model to miss ns/ew ratio)
1579
+
1580
+ # custom quadratic equation to solve two bars with common width 2l^2 - p*l + 4a = 0
1581
+ if target_perim**2 - 32 * footprint_si > 0
1582
+ if specified_bar_width_si > 0
1583
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Ignoring perimeter multiplier argument and using use specified bar width.')
1584
+ dual_double_end_width = specified_bar_width_si
1585
+ dual_double_end_length = footprint_si / dual_double_end_width
1586
+ else
1587
+ dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 32 * footprint_si))
1588
+ dual_double_end_width = footprint_si / dual_double_end_length
1589
+ end
1590
+
1591
+ # now that stretched bar is made, determine where to split it and rotate
1592
+ bar_a_length = (args[:ns_to_ew_ratio] * (dual_double_end_length + dual_double_end_width) - dual_double_end_width) / (1 + args[:ns_to_ew_ratio])
1593
+ bar_b_length = dual_double_end_length - bar_a_length
1594
+ area_a = bar_a_length * dual_double_end_width
1595
+ area_b = bar_b_length * dual_double_end_width
1596
+ else
1597
+ # this will throw it to adiabatic ends test
1598
+ bar_a_length = 0
1599
+ bar_b_length = 0
1600
+ end
1601
+
1602
+ if bar_a_length >= min_allow_size && bar_b_length >= min_allow_size
1603
+ dual_bar_calc_approach = 'dual_bar'
1604
+ else
1605
+ # adiabatic bar input calcs
1606
+ if target_perim**2 - 16 * footprint_si > 0
1607
+ adiabatic_dual_double_end_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
1608
+ adiabatic_dual_double_end_width = footprint_si / adiabatic_dual_double_end_length
1609
+ # test for unexpected
1610
+ unexpected = false
1611
+ if (target_area - adiabatic_dual_double_end_length * adiabatic_dual_double_end_width).abs > tol_testing then unexpected = true end
1612
+ if specified_bar_width_si == 0
1613
+ if (target_perim - (adiabatic_dual_double_end_length * 2 + adiabatic_dual_double_end_width * 2)).abs > tol_testing then unexpected = true end
1614
+ end
1615
+ if unexpected
1616
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for dual rectangle adiabatic ends bar b.')
1617
+ end
1618
+ # now that stretched bar is made, determine where to split it and rotate
1619
+ adiabatic_bar_a_length = (args[:ns_to_ew_ratio] * (adiabatic_dual_double_end_length + adiabatic_dual_double_end_width)) / (1 + args[:ns_to_ew_ratio])
1620
+ adiabatic_bar_b_length = adiabatic_dual_double_end_length - adiabatic_bar_a_length
1621
+ adiabatic_area_a = adiabatic_bar_a_length * adiabatic_dual_double_end_width
1622
+ adiabatic_area_b = adiabatic_bar_b_length * adiabatic_dual_double_end_width
1623
+ else
1624
+ # this will throw it stretched single bar
1625
+ adiabatic_bar_a_length = 0
1626
+ adiabatic_bar_b_length = 0
1627
+ end
1628
+ if adiabatic_bar_a_length >= min_allow_size && adiabatic_bar_b_length >= min_allow_size
1629
+ dual_bar_calc_approach = 'adiabatic_ends_bar_b'
1630
+ else
1631
+ dual_bar_calc_approach = 'stretched'
1632
+ end
1633
+ end
1634
+
1635
+ # apply prescribed approach for stretched or dual bar
1636
+ if dual_bar_calc_approach == 'dual_bar'
1637
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Stretched #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length, 'm', 'ft').get, 0, true)} ft x #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_width, 'm', 'ft').get, 0, true)} ft rectangle has an area of #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * dual_double_end_width, 'm^2', 'ft^2').get, 0, true)} ft^2. When split in two the perimeter will be #{OpenStudio.toNeatString(OpenStudio.convert(dual_double_end_length * 2 + dual_double_end_width * 4, 'm', 'ft').get, 0, true)} ft")
1638
+ if (target_area - dual_double_end_length * dual_double_end_width).abs > tol_testing || (target_perim - (dual_double_end_length * 2 + dual_double_end_width * 4)).abs > tol_testing
1639
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for dual rectangle.')
1640
+ end
1641
+
1642
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For stretched split bar, to match target ns/ew aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be horizontal, with #{OpenStudio.toNeatString(OpenStudio.convert(bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(area_a + area_b, 'm^2', 'ft^2').get, 0, true)} ft^2. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(bar_a_length * 2 + bar_b_length * 2 + dual_double_end_width * 4, 'm', 'ft').get, 0, true)} ft")
1643
+ if (target_area - (area_a + area_b)).abs > tol_testing || (target_perim - (bar_a_length * 2 + bar_b_length * 2 + dual_double_end_width * 4)).abs > tol_testing
1644
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for rotated dual rectangle')
1645
+ end
1646
+ elsif dual_bar_calc_approach == 'adiabatic_ends_bar_b'
1647
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Can't hit target perimeter with two rectangles, need to make two ends adiabatic")
1648
+
1649
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "For dual bar with adiabatic ends on bar b, to reach target aspect ratio #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length, 'm', 'ft').get, 0, true)} ft of bar should be north/south, with #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_b_length, 'm', 'ft').get, 0, true)} ft turned 90 degrees. Combined area is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_area_a + adiabatic_area_b, 'm^2', 'ft^2').get, 0, true)} ft^2}. Combined perimeter is #{OpenStudio.toNeatString(OpenStudio.convert(adiabatic_bar_a_length * 2 + adiabatic_bar_b_length * 2 + adiabatic_dual_double_end_width * 2, 'm', 'ft').get, 0, true)} ft")
1650
+ if (target_area - (adiabatic_area_a + adiabatic_area_b)).abs > tol_testing || (target_perim - (adiabatic_bar_a_length * 2 + adiabatic_bar_b_length * 2 + adiabatic_dual_double_end_width * 2)).abs > tol_testing
1651
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for rotated dual rectangle adiabatic ends bar b')
1652
+ end
1653
+ else
1654
+ # stretched bar
1655
+ dual_bar = false
1656
+
1657
+ stretched_length = 0.25 * (target_perim + Math.sqrt(target_perim**2 - 16 * footprint_si))
1658
+ stretched_width = footprint_si / stretched_length
1659
+ if (target_area - stretched_length * stretched_width).abs > tol_testing || (target_perim - (stretched_length + stretched_width) * 2) > tol_testing
1660
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Unexpected values for single stretched')
1661
+ end
1662
+
1663
+ width = stretched_width
1664
+ length = stretched_length
1665
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating a dual bar to match the target minimum perimeter multiplier at the given aspect ratio would result in a bar with edge shorter than #{OpenStudio.toNeatString(OpenStudio.convert(min_allow_size, 'm', 'ft').get, 0, true)} ft. Will create a single stretched bar instead that hits the target perimeter with a slightly different ns/ew aspect ratio.")
1666
+ end
1667
+ end
1668
+
1669
+ bars = {}
1670
+ bars['primary'] = {}
1671
+ if dual_bar
1672
+ if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
1673
+ bars['primary'][:length] = dual_double_end_width
1674
+ bars['primary'][:width] = bar_a_length
1675
+ elsif dual_bar_calc_approach == 'dual_bar'
1676
+ bars['primary'][:length] = bar_a_length
1677
+ bars['primary'][:width] = dual_double_end_width
1678
+ elsif mirror_ns_ew
1679
+ bars['primary'][:length] = adiabatic_dual_double_end_width
1680
+ bars['primary'][:width] = adiabatic_bar_a_length
1681
+ else
1682
+ bars['primary'][:length] = adiabatic_bar_a_length
1683
+ bars['primary'][:width] = adiabatic_dual_double_end_width
1684
+ end
1685
+ else
1686
+ if mirror_ns_ew
1687
+ bars['primary'][:length] = width
1688
+ bars['primary'][:width] = length
1689
+ else
1690
+ bars['primary'][:length] = length
1691
+ bars['primary'][:width] = width
1692
+ end
1693
+ end
1694
+ bars['primary'][:floor_height] = floor_height # can make use of this when breaking out multi-height spaces
1695
+ bars['primary'][:num_stories] = num_stories
1696
+ bars['primary'][:center_of_footprint] = OpenStudio::Point3d.new(0.0, 0.0, 0.0)
1697
+ space_types_hash_secondary = {}
1698
+ if dual_bar
1699
+ # loop through each story and move portion for other bar to its own hash
1700
+ primary_footprint = bars['primary'][:length] * bars['primary'][:width]
1701
+ secondary_footprint = target_area - primary_footprint
1702
+ footprint_counter = primary_footprint
1703
+ secondary_footprint_counter = secondary_footprint
1704
+ story_counter = 0
1705
+ pri_sec_tol = 0.0001 # m^2
1706
+ pri_sec_min_area = 0.0001 # m^2
1707
+ space_types_hash.each do |k, v|
1708
+ space_type_left = v[:floor_area]
1709
+
1710
+ # do not go to next space type until this one is evaulate, which may span stories
1711
+ until space_type_left == 0.0 || story_counter >= num_stories
1712
+
1713
+ # use secondary footprint if any left
1714
+ if secondary_footprint_counter > 0.0
1715
+ hash_area = [space_type_left, secondary_footprint_counter].min
1716
+
1717
+ # confirm that the part of space type use or what is left is greater than min allowed value
1718
+ projected_space_type_left = space_type_left - hash_area
1719
+ test_a = hash_area >= pri_sec_min_area
1720
+ test_b = projected_space_type_left >= pri_sec_min_area || projected_space_type_left == 0.0 ? true : false
1721
+ test_c = k == space_types_hash.keys.last # if last space type accept sliver, no other space to infil
1722
+ if (test_a && test_b) || test_c
1723
+ if space_types_hash_secondary.key?(k)
1724
+ # add to what was added for previous story
1725
+ space_types_hash_secondary[k][:floor_area] += hash_area
1726
+ else
1727
+ # add new space type to hash
1728
+ if v.key?(:children)
1729
+ space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type], children: v[:children] }
1730
+ else
1731
+ space_types_hash_secondary[k] = { floor_area: hash_area, space_type: v[:space_type] }
1732
+ end
1733
+ end
1734
+ space_types_hash[k][:floor_area] -= hash_area
1735
+ secondary_footprint_counter -= hash_area
1736
+ space_type_left -= hash_area
1737
+ else
1738
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Shifting space types between bars to avoid sliver of #{k.name}.")
1739
+ end
1740
+ end
1741
+
1742
+ # remove space if entirely used up by secondary bar
1743
+ if space_types_hash[k][:floor_area] <= pri_sec_tol
1744
+ space_types_hash.delete(k)
1745
+ space_type_left = 0.0
1746
+ else
1747
+ # then look at primary bar
1748
+ hash_area_pri = [space_type_left, footprint_counter].min
1749
+ footprint_counter -= hash_area_pri
1750
+ space_type_left -= hash_area_pri
1751
+ end
1752
+
1753
+ # reset counter when full
1754
+ if footprint_counter <= pri_sec_tol && secondary_footprint_counter <= pri_sec_tol
1755
+ # check if this is partial top floor
1756
+ story_counter += 1
1757
+ if num_stories < story_counter + 1
1758
+ footprint_counter = primary_footprint * (num_stories - story_counter)
1759
+ secondary_footprint_counter = secondary_footprint * (num_stories - story_counter)
1760
+ else
1761
+ footprint_counter = primary_footprint
1762
+ secondary_footprint_counter = secondary_footprint
1763
+ end
1764
+ end
1765
+ end
1766
+ end
1767
+ end
1768
+
1769
+ # setup bar_hash and run create_bar
1770
+ bars['primary'][:space_types_hash] = space_types_hash
1771
+ bars['primary'][:args] = args
1772
+ v = bars['primary']
1773
+ OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
1774
+
1775
+ # store offset value for multiple bars
1776
+ if args.key?(:bar_sep_dist_mult) && args[:bar_sep_dist_mult] > 0.0
1777
+ offset_val = num_stories.ceil * floor_height * args[:bar_sep_dist_mult]
1778
+ elsif args.key?(:bar_sep_dist_mult)
1779
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Positive value is required for bar_sep_dist_mult, ignoring input and using value of 0.1')
1780
+ offset_val = num_stories.ceil * floor_height * 0.1
1781
+ else
1782
+ offset_val = num_stories.ceil * floor_height * 10.0
1783
+ end
1784
+
1785
+ if dual_bar
1786
+ args2 = args.clone
1787
+ bars['secondary'] = {}
1788
+ if mirror_ns_ew && dual_bar_calc_approach == 'dual_bar'
1789
+ bars['secondary'][:length] = bar_b_length
1790
+ bars['secondary'][:width] = dual_double_end_width
1791
+ elsif dual_bar_calc_approach == 'dual_bar'
1792
+ bars['secondary'][:length] = dual_double_end_width
1793
+ bars['secondary'][:width] = bar_b_length
1794
+ elsif mirror_ns_ew
1795
+ bars['secondary'][:length] = adiabatic_bar_b_length
1796
+ bars['secondary'][:width] = adiabatic_dual_double_end_width
1797
+ args2[:party_wall_stories_east] = num_stories.ceil
1798
+ args2[:party_wall_stories_west] = num_stories.ceil
1799
+ else
1800
+ bars['secondary'][:length] = adiabatic_dual_double_end_width
1801
+ bars['secondary'][:width] = adiabatic_bar_b_length
1802
+ args2[:party_wall_stories_south] = num_stories.ceil
1803
+ args2[:party_wall_stories_north] = num_stories.ceil
1804
+ end
1805
+ bars['secondary'][:floor_height] = floor_height # can make use of this when breaking out multi-height spaces
1806
+ bars['secondary'][:num_stories] = num_stories
1807
+ bars['secondary'][:space_types_hash] = space_types_hash_secondary
1808
+ if dual_bar_calc_approach == 'adiabatic_ends_bar_b'
1809
+ # warn that combination of dual bar with low perimeter multiplier and use of party wall may result in discrepency between target and actual adiabatic walls
1810
+ if args[:party_wall_fraction] > 0 || args[:party_wall_stories_north] > 0 || args[:party_wall_stories_south] > 0 || args[:party_wall_stories_east] > 0 || args[:party_wall_stories_west] > 0
1811
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'The combination of low perimeter multiplier and use of non zero party wall inputs may result in discrepency between target and actual adiabatic walls. This is due to the need to create adiabatic walls on secondary bar to maintian target building perimeter.')
1812
+ else
1813
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Adiabatic ends added to secondary bar because target perimeter multiplier could not be met with two full rectangular footprints.')
1814
+ end
1815
+ bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(adiabatic_bar_a_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val, adiabatic_bar_b_length * 0.5 + adiabatic_dual_double_end_width * 0.5 + offset_val, 0.0)
1816
+ else
1817
+ bars['secondary'][:center_of_footprint] = OpenStudio::Point3d.new(bar_a_length * 0.5 + dual_double_end_width * 0.5 + offset_val, bar_b_length * 0.5 + dual_double_end_width * 0.5 + offset_val, 0.0)
1818
+ end
1819
+ bars['secondary'][:args] = args2
1820
+
1821
+ # setup bar_hash and run create_bar
1822
+ v = bars['secondary']
1823
+ OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
1824
+ end
1825
+
1826
+ # future development (up against primary bar run intersection and surface matching after add all bars, avoid interior windows)
1827
+ # I could loop through each space type and give them unique height but for now will just take largest height and make bar of that height, which is fine for prototypes
1828
+ if !multi_height_space_types_hash.empty?
1829
+ args3 = args.clone
1830
+ bars['custom_height'] = {}
1831
+ if mirror_ns_ew
1832
+ bars['custom_height'][:length] = width_cust_height
1833
+ bars['custom_height'][:width] = length_cust_height
1834
+ else
1835
+ bars['custom_height'][:length] = length_cust_height
1836
+ bars['custom_height'][:width] = width_cust_height
1837
+ end
1838
+ if args[:party_wall_stories_east] + args[:party_wall_stories_west] + args[:party_wall_stories_south] + args[:party_wall_stories_north] > 0.0
1839
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'Ignorning party wall inputs for custom height bar')
1840
+ end
1841
+
1842
+ # disable party walls
1843
+ args3['party_wall_stories_east'] = 0
1844
+ args3['party_wall_stories_west'] = 0
1845
+ args3['party_wall_stories_south'] = 0
1846
+ args3['party_wall_stories_north'] = 0
1847
+
1848
+ # setup stories
1849
+ args3['num_stories_below_grade'] = 0
1850
+ args3['num_stories_above_grade'] = 1
1851
+
1852
+ # can make use of this when breaking out multi-height spaces
1853
+ bars['custom_height'][:floor_height] = floor_height
1854
+ bars['custom_height'][:num_stories] = num_stories
1855
+ bars['custom_height'][:center_of_footprint] = OpenStudio::Point3d.new(bars['primary'][:length] * -0.5 - length_cust_height * 0.5 - offset_val, 0.0, 0.0)
1856
+ bars['custom_height'][:floor_height] = OpenStudio.convert(custom_story_heights.max, 'ft', 'm').get
1857
+ bars['custom_height'][:num_stories] = 1
1858
+ bars['custom_height'][:space_types_hash] = multi_height_space_types_hash
1859
+ bars['custom_height'][:args] = args3
1860
+
1861
+ v = bars['custom_height']
1862
+ OpenstudioStandards::Geometry.bar_hash_setup_run(model, v[:args], v[:length], v[:width], v[:floor_height], v[:center_of_footprint], v[:space_types_hash], v[:num_stories])
1863
+ end
1864
+
1865
+ # diagnostic log
1866
+ sum_actual = 0.0
1867
+ sum_target = 0.0
1868
+ throw_error = false
1869
+
1870
+ # check expected floor areas against actual
1871
+ model.getSpaceTypes.sort.each do |space_type|
1872
+ next if !target_areas.key? space_type # space type in model not part of building type(s), maybe issue warning
1873
+
1874
+ # convert to IP
1875
+ actual_ip = OpenStudio.convert(space_type.floorArea, 'm^2', 'ft^2').get
1876
+ target_ip = OpenStudio.convert(target_areas[space_type], 'm^2', 'ft^2').get
1877
+ sum_actual += actual_ip
1878
+ sum_target += target_ip
1879
+
1880
+ if (space_type.floorArea - target_areas[space_type]).abs >= 1.0
1881
+
1882
+ if !args[:bar_division_method].include? 'Single Space Type'
1883
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
1884
+ throw_error = true
1885
+ else
1886
+ # will see this if use Single Space type division method on multi-use building or single building type without whole building space type
1887
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space_type.name} doesn't have the expected floor area (actual #{OpenStudio.toNeatString(actual_ip, 0, true)} ft^2, target #{OpenStudio.toNeatString(target_ip, 0, true)} ft^2)")
1888
+ end
1889
+
1890
+ end
1891
+ end
1892
+
1893
+ # report summary then throw error
1894
+ if throw_error
1895
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "Sum of actual floor area is #{sum_actual} ft^2, sum of target floor area is #{sum_target}.")
1896
+ return false
1897
+ end
1898
+
1899
+ # check party wall fraction by looping through surfaces
1900
+ if args[:party_wall_fraction] > 0
1901
+ actual_ext_wall_area = model.getBuilding.exteriorWallArea
1902
+ actual_party_wall_area = 0.0
1903
+ model.getSurfaces.sort.each do |surface|
1904
+ next if surface.outsideBoundaryCondition != 'Adiabatic'
1905
+ next if surface.surfaceType != 'Wall'
1906
+
1907
+ actual_party_wall_area += surface.grossArea * surface.space.get.multiplier
1908
+ end
1909
+ actual_party_wall_fraction = actual_party_wall_area / (actual_party_wall_area + actual_ext_wall_area)
1910
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Target party wall fraction is #{args[:party_wall_fraction]}. Realized fraction is #{actual_party_wall_fraction.round(2)}")
1911
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "party_wall_fraction_actual: #{actual_party_wall_fraction}")
1912
+ end
1913
+
1914
+ # check ns/ew aspect ratio (harder to check when party walls are added)
1915
+ wall_and_window_by_orientation = OpenstudioStandards::Geometry.model_get_exterior_window_and_wall_area_by_orientation(model)
1916
+ wall_ns = (wall_and_window_by_orientation['north_wall'] + wall_and_window_by_orientation['south_wall'])
1917
+ wall_ew = wall_and_window_by_orientation['east_wall'] + wall_and_window_by_orientation['west_wall']
1918
+ wall_ns_ip = OpenStudio.convert(wall_ns, 'm^2', 'ft^2').get
1919
+ wall_ew_ip = OpenStudio.convert(wall_ew, 'm^2', 'ft^2').get
1920
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "wall_area_ip: #{wall_ns_ip + wall_ew_ip} ft^2")
1921
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "ns_wall_area_ip: #{wall_ns_ip} ft^2")
1922
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "ew_wall_area_ip: #{wall_ew_ip} ft^2")
1923
+ # for now using perimeter of ground floor and average story area (building area / num_stories)
1924
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "floor_area_to_perim_ratio: #{model.getBuilding.floorArea / (OpenstudioStandards::Geometry.model_get_perimeter_length(model) * num_stories)}")
1925
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "bar_width: #{OpenStudio.convert(bars['primary'][:width], 'm', 'ft').get} ft")
1926
+
1927
+ if args[:party_wall_fraction] > 0 || args[:party_wall_stories_north] > 0 || args[:party_wall_stories_south] > 0 || args[:party_wall_stories_east] > 0 || args[:party_wall_stories_west] > 0
1928
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when party walls are applied')
1929
+ elsif args[:num_stories_above_grade] != args[:num_stories_above_grade].ceil
1930
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when partial top story is used')
1931
+ elsif dual_bar_calc_approach == 'stretched'
1932
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when single stretched bar has to be used to meet target minimum perimeter multiplier')
1933
+ elsif defaulted_args.include?(:floor_height) && args[:custom_height_bar] && !multi_height_space_types_hash.empty?
1934
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when a dedicated bar is added for space types with custom heights')
1935
+ elsif args[:bar_width] > 0
1936
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', 'Target facade area by orientation not validated when a dedicated custom bar width is defined')
1937
+ else
1938
+
1939
+ # adjust length versus width based on building rotation
1940
+ if mirror_ns_ew
1941
+ wall_target_ns_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
1942
+ wall_target_ew_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
1943
+ else
1944
+ wall_target_ns_ip = 2 * OpenStudio.convert(length, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
1945
+ wall_target_ew_ip = 2 * OpenStudio.convert(width, 'm', 'ft').get * args[:perim_mult] * args[:num_stories_above_grade] * args[:floor_height]
1946
+ end
1947
+ flag_error = false
1948
+ if (wall_target_ns_ip - wall_ns_ip).abs > 0.1
1949
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "North/South walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ns_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ns_ip, 4, true)} ft^2)")
1950
+ flag_error = true
1951
+ end
1952
+ if (wall_target_ew_ip - wall_ew_ip).abs > 0.1
1953
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', "East/West walls don't have the expected area (actual #{OpenStudio.toNeatString(wall_ew_ip, 4, true)} ft^2, target #{OpenStudio.toNeatString(wall_target_ew_ip, 4, true)} ft^2)")
1954
+ flag_error = true
1955
+ end
1956
+ if flag_error
1957
+ return false
1958
+ end
1959
+ end
1960
+
1961
+ # test for excessive exterior roof area (indication of problem with intersection and or surface matching)
1962
+ ext_roof_area = model.getBuilding.exteriorSurfaceArea - model.getBuilding.exteriorWallArea
1963
+ expected_roof_area = args[:total_bldg_floor_area] / (args[:num_stories_above_grade] + args[:num_stories_below_grade]).to_f
1964
+ if ext_roof_area > expected_roof_area && single_floor_area_si == 0.0 # only test if using whole-building area input
1965
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Roof area larger than expected, may indicate problem with inter-floor surface intersection or matching.')
1966
+ return false
1967
+ end
1968
+
1969
+ # set building rotation
1970
+ initial_rotation = model.getBuilding.northAxis
1971
+ if args[:building_rotation] != initial_rotation
1972
+ model.getBuilding.setNorthAxis(args[:building_rotation])
1973
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Set Building Rotation to #{model.getBuilding.northAxis}. Rotation altered after geometry generation is completed, as a result party wall orientation and aspect ratio may not reflect input values.")
1974
+ end
1975
+
1976
+ # report final condition of model
1977
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building finished with #{model.getSpaces.size} spaces.")
1978
+
1979
+ return true
1980
+ end
1981
+
1982
+ # create bar from building type ratios
1983
+ # arguments are passed through to lower level methods.
1984
+ # See create_bar_from_args_and_building_type_hash for additional argument options.
1985
+ #
1986
+ # @param model [OpenStudio::Model::Model] OpenStudio model object
1987
+ # @param args [Hash] user arguments
1988
+ # @option args [String] :bldg_type_a ('SmallOffice') primary building type
1989
+ # @option args [String] :bldg_type_b (nil) building type b
1990
+ # @option args [String] :bldg_type_c (nil) building type c
1991
+ # @option args [String] :bldg_type_d (nil) building type d
1992
+ # @option args [String] :bldg_subtype_a ('NA') primary building subtype
1993
+ # @option args [String] :bldg_subtype_b ('NA') building type b subtype
1994
+ # @option args [String] :bldg_subtype_c ('NA') building type c subtype
1995
+ # @option args [String] :bldg_subtype_d ('NA') building type d subtype
1996
+ # @option args [String] :bldg_type_a_fract_bldg_area (1.0) building type a area fraction of total floor area
1997
+ # @option args [String] :bldg_type_b_fract_bldg_area (0.0) building type b area fraction of total floor area
1998
+ # @option args [String] :bldg_type_c_fract_bldg_area (0.0) building type c area fraction of total floor area
1999
+ # @option args [String] :bldg_type_d_fract_bldg_area (0.0) building type d area fraction of total floor area
2000
+ # @option args [String] :template ('90.1-2013') target standard
2001
+ # @return [Boolean] returns true if successful, false if not
2002
+ def self.create_bar_from_building_type_ratios(model, args)
2003
+ bldg_type_a = args.fetch(:bldg_type_a, 'SmallOffice')
2004
+ bldg_type_b = args.fetch(:bldg_type_b, nil)
2005
+ bldg_type_c = args.fetch(:bldg_type_c, nil)
2006
+ bldg_type_d = args.fetch(:bldg_type_d, nil)
2007
+ bldg_subtype_a = args.fetch(:bldg_subtype_a, 'NA')
2008
+ bldg_subtype_b = args.fetch(:bldg_subtype_b, 'NA')
2009
+ bldg_subtype_c = args.fetch(:bldg_subtype_c, 'NA')
2010
+ bldg_subtype_d = args.fetch(:bldg_subtype_d, 'NA')
2011
+ bldg_type_a_fract_bldg_area = args.fetch(:bldg_type_a_fract_bldg_area, 1.0)
2012
+ bldg_type_b_fract_bldg_area = args.fetch(:bldg_type_b_fract_bldg_area, 0.0)
2013
+ bldg_type_c_fract_bldg_area = args.fetch(:bldg_type_c_fract_bldg_area, 0.0)
2014
+ bldg_type_d_fract_bldg_area = args.fetch(:bldg_type_d_fract_bldg_area, 0.0)
2015
+ template = args.fetch(:template, '90.1-2013')
2016
+
2017
+ # check that sum of fractions for b,c, and d is less than 1.0 (so something is left for primary building type)
2018
+ bldg_type_a_fract_bldg_area = 1.0 - bldg_type_b_fract_bldg_area - bldg_type_c_fract_bldg_area - bldg_type_d_fract_bldg_area
2019
+ if bldg_type_a_fract_bldg_area <= 0.0
2020
+ OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Geometry.Create', 'Primary Building Type fraction of floor area must be greater than 0. Please lower one or more of the fractions for Building Type B-D.')
2021
+ return false
2022
+ end
2023
+
2024
+ # report initial condition of model
2025
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "The building started with #{model.getSpaces.size} spaces.")
2026
+
2027
+ # determine of ns_ew needs to be mirrored
2028
+ mirror_ns_ew = false
2029
+ rotation = model.getBuilding.northAxis
2030
+ if rotation > 45.0 && rotation < 135.0
2031
+ mirror_ns_ew = true
2032
+ elsif rotation > 45.0 && rotation < 135.0
2033
+ mirror_ns_ew = true
2034
+ end
2035
+
2036
+ # remove non-resource objects not removed by removing the building
2037
+ # remove_non_resource_objects(model)
2038
+
2039
+ # rename building to infer template in downstream measure
2040
+ name_array = [template, bldg_type_a]
2041
+ if bldg_type_b_fract_bldg_area > 0 then name_array << bldg_type_b end
2042
+ if bldg_type_c_fract_bldg_area > 0 then name_array << bldg_type_c end
2043
+ if bldg_type_d_fract_bldg_area > 0 then name_array << bldg_type_d end
2044
+ model.getBuilding.setName(name_array.join('|').to_s)
2045
+
2046
+ # hash to whole building type data
2047
+ building_type_hash = {}
2048
+
2049
+ # gather data for bldg_type_a
2050
+ building_type_hash[bldg_type_a] = {}
2051
+ building_type_hash[bldg_type_a][:frac_bldg_area] = bldg_type_a_fract_bldg_area
2052
+ building_type_hash[bldg_type_a][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_a, building_subtype: bldg_subtype_a, template: template, whole_building: true)
2053
+
2054
+ # gather data for bldg_type_b
2055
+ if bldg_type_b_fract_bldg_area > 0
2056
+ building_type_hash[bldg_type_b] = {}
2057
+ building_type_hash[bldg_type_b][:frac_bldg_area] = bldg_type_b_fract_bldg_area
2058
+ building_type_hash[bldg_type_b][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_b, building_subtype: bldg_subtype_b, template: template, whole_building: true)
2059
+ end
2060
+
2061
+ # gather data for bldg_type_c
2062
+ if bldg_type_c_fract_bldg_area > 0
2063
+ building_type_hash[bldg_type_c] = {}
2064
+ building_type_hash[bldg_type_c][:frac_bldg_area] = bldg_type_c_fract_bldg_area
2065
+ building_type_hash[bldg_type_c][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_c, building_subtype: bldg_subtype_c, template: template, whole_building: true)
2066
+ end
2067
+
2068
+ # gather data for bldg_type_d
2069
+ if bldg_type_d_fract_bldg_area > 0
2070
+ building_type_hash[bldg_type_d] = {}
2071
+ building_type_hash[bldg_type_d][:frac_bldg_area] = bldg_type_d_fract_bldg_area
2072
+ building_type_hash[bldg_type_d][:space_types] = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(bldg_type_d, building_subtype: bldg_subtype_d, template: template, whole_building: true)
2073
+ end
2074
+
2075
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating bar based on space ratios from #{bldg_type_a} for building form defaults.")
2076
+
2077
+ # call create_bar_from_args_and_building_type_hash to generate bar
2078
+ args[:primary_building_type] = bldg_type_a
2079
+ OpenstudioStandards::Geometry.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)
2080
+
2081
+ return true
2082
+ end
2083
+
2084
+ # create bar from space type ratios
2085
+ # arguments are passed through to lower level methods.
2086
+ # See create_bar_from_args_and_building_type_hash for additional argument options.
2087
+ #
2088
+ # @param args [Hash] user arguments
2089
+ # @option args [String] :space_type_hash_string Space types ratio string in the form 'BuildingType | SpaceType => 0.75, BuildingType | SpaceType => 0.25'. Fractions should add up to 1. All space types should come from the selected OpenStudio Standards template.
2090
+ # @option args [String] :template ('90.1-2013') target standard
2091
+ # @return [Boolean] returns true if successful, false if not
2092
+ def self.create_bar_from_space_type_ratios(model, args)
2093
+ if args[:space_type_hash_string].empty?
2094
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', 'args hash passed to create_bar_from_space_type_ratios must include a non-empty :space_type_hash_string')
2095
+ return false
2096
+ end
2097
+ template = args.fetch(:template, '90.1-2013')
2098
+
2099
+ # process arg into hash
2100
+ space_type_hash_name = {}
2101
+ args[:space_type_hash_string][0..-1].split(/, /) do |entry|
2102
+ entry_map = entry.split(/=>/)
2103
+ value_str = entry_map[1]
2104
+ space_type_hash_name[entry_map[0].strip[0..-1].to_s] = value_str.nil? ? '' : value_str.strip[0..-1].to_f
2105
+ end
2106
+
2107
+ # create building type hash from space type ratios
2108
+ building_type_hash = {}
2109
+ building_type_fraction_of_building = 0.0
2110
+ space_type_hash_name.each do |building_space_type, ratio|
2111
+ building_type = building_space_type.split('|')[0].strip
2112
+ space_type = building_space_type.split('|')[1].strip
2113
+
2114
+ # harvest height and circ info from get_space_types_from_building_type(building_type, template, whole_building = true)
2115
+ building_type_lookup_info = OpenstudioStandards::CreateTypical.get_space_types_from_building_type(building_type, template: template)
2116
+ if building_type_lookup_info.empty?
2117
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{building_type} looks like an invalid building type for #{template}")
2118
+ end
2119
+ space_type_info_hash = {}
2120
+ if building_type_lookup_info.key?(space_type)
2121
+ if building_type_lookup_info[space_type].key?(:story_height)
2122
+ space_type_info_hash[:story_height] = building_type_lookup_info[space_type][:story_height]
2123
+ end
2124
+ if building_type_lookup_info[space_type].key?(:default)
2125
+ space_type_info_hash[:default] = building_type_lookup_info[space_type][:default]
2126
+ end
2127
+ if building_type_lookup_info[space_type].key?(:circ)
2128
+ space_type_info_hash[:circ] = building_type_lookup_info[space_type][:circ]
2129
+ end
2130
+ else
2131
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "#{space_type} looks like an invalid space type for #{building_type}")
2132
+ end
2133
+
2134
+ # extend harvested data with custom ratios from space type ratio string argument.
2135
+ if building_type_hash.key?(building_type)
2136
+ building_type_hash[building_type][:frac_bldg_area] += ratio
2137
+ space_type_info_hash[:ratio] = ratio
2138
+ building_type_hash[building_type][:space_types][space_type] = space_type_info_hash
2139
+ else
2140
+ building_type_hash[building_type] = {}
2141
+ building_type_hash[building_type][:frac_bldg_area] = ratio
2142
+ space_type_info_hash[:ratio] = ratio
2143
+ space_types = {}
2144
+ space_types[space_type] = space_type_info_hash
2145
+ building_type_hash[building_type][:space_types] = space_types
2146
+ end
2147
+ building_type_fraction_of_building += ratio
2148
+ end
2149
+
2150
+ # @todo confirm if this will get normalized up/down later of if I should fix or stop here instead of just a warning
2151
+ if building_type_fraction_of_building > 1.0
2152
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Sum of Space Type Ratio of #{building_type_fraction_of_building} is greater than the expected value of 1.0")
2153
+ elsif building_type_fraction_of_building < 1.0
2154
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Geometry.Create', "Sum of Space Type Ratio of #{building_type_fraction_of_building} is less than the expected value of 1.0")
2155
+ end
2156
+
2157
+ # identify primary building type for building form defaults
2158
+ # update to choose building with highest ratio
2159
+ primary_building_type = building_type_hash.keys.first
2160
+
2161
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Geometry.Create', "Creating bar based space type ratios provided. Using building type #{primary_building_type} from the first ratio as the primary building type. This determines the building form defaults.")
2162
+
2163
+ # call create_bar_from_args_and_building_type_hash to generate bar
2164
+ args[:primary_building_type] = primary_building_type
2165
+ OpenstudioStandards::Geometry.create_bar_from_args_and_building_type_hash(model, args, building_type_hash)
2166
+
2167
+ return true
2168
+ end
2169
+ end
2170
+ end