openstudio-standards 0.4.0 → 0.5.0.rc1

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