buildingsync 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/continuous_integration.yml +146 -0
- data/.gitignore +33 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +50 -0
- data/Gemfile +31 -0
- data/Jenkinsfile +10 -0
- data/LICENSE.md +29 -0
- data/README.md +105 -0
- data/Rakefile +77 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/buildingsync.gemspec +37 -0
- data/config.rb.in +26 -0
- data/doc_templates/LICENSE.md +29 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +38 -0
- data/doc_templates/copyright_js.txt +5 -0
- data/doc_templates/copyright_ruby.txt +36 -0
- data/lib/buildingsync.rb +43 -0
- data/lib/buildingsync/all_resource_total.rb +54 -0
- data/lib/buildingsync/audit_date.rb +54 -0
- data/lib/buildingsync/constants.rb +49 -0
- data/lib/buildingsync/contact.rb +54 -0
- data/lib/buildingsync/extension.rb +57 -0
- data/lib/buildingsync/generator.rb +584 -0
- data/lib/buildingsync/get_bcl_weather_file.rb +326 -0
- data/lib/buildingsync/helpers/Model.hvac.rb +216 -0
- data/lib/buildingsync/helpers/helper.rb +494 -0
- data/lib/buildingsync/helpers/xml_get_set.rb +215 -0
- data/lib/buildingsync/makers/phase_zero_base.osw +178 -0
- data/lib/buildingsync/makers/workflow_maker.json +811 -0
- data/lib/buildingsync/makers/workflow_maker.rb +581 -0
- data/lib/buildingsync/makers/workflow_maker_base.rb +167 -0
- data/lib/buildingsync/model_articulation/building.rb +1119 -0
- data/lib/buildingsync/model_articulation/building_and_system_types.json +121 -0
- data/lib/buildingsync/model_articulation/building_section.rb +190 -0
- data/lib/buildingsync/model_articulation/building_system.rb +49 -0
- data/lib/buildingsync/model_articulation/envelope_system.rb +102 -0
- data/lib/buildingsync/model_articulation/exterior_floor_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/facility.rb +439 -0
- data/lib/buildingsync/model_articulation/foundation_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/hvac_system.rb +395 -0
- data/lib/buildingsync/model_articulation/lighting_system.rb +102 -0
- data/lib/buildingsync/model_articulation/loads_system.rb +287 -0
- data/lib/buildingsync/model_articulation/location_element.rb +129 -0
- data/lib/buildingsync/model_articulation/measure.rb +57 -0
- data/lib/buildingsync/model_articulation/roof_system_type.rb +64 -0
- data/lib/buildingsync/model_articulation/service_hot_water_system.rb +87 -0
- data/lib/buildingsync/model_articulation/site.rb +242 -0
- data/lib/buildingsync/model_articulation/spatial_element.rb +343 -0
- data/lib/buildingsync/model_articulation/wall_system_type.rb +64 -0
- data/lib/buildingsync/report.rb +217 -0
- data/lib/buildingsync/resource_use.rb +55 -0
- data/lib/buildingsync/scenario.rb +622 -0
- data/lib/buildingsync/selection_tool.rb +98 -0
- data/lib/buildingsync/time_series.rb +85 -0
- data/lib/buildingsync/translator.rb +167 -0
- data/lib/buildingsync/utility.rb +67 -0
- data/lib/buildingsync/version.rb +45 -0
- metadata +223 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# *******************************************************************************
|
4
|
+
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
|
5
|
+
# BuildingSync(R), Copyright (c) 2015-2020, Alliance for Sustainable Energy, LLC.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
19
|
+
# may be used to endorse or promote products derived from this software without
|
20
|
+
# specific prior written permission from the respective party.
|
21
|
+
#
|
22
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
23
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
24
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
25
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
28
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
29
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
30
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
31
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
32
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
33
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
34
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
35
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
36
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
37
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
38
|
+
# *******************************************************************************
|
39
|
+
|
40
|
+
require 'fileutils'
|
41
|
+
require 'json'
|
42
|
+
|
43
|
+
module BuildingSync
|
44
|
+
# base class for objects that will configure workflows based on building sync files
|
45
|
+
class WorkflowMakerBase
|
46
|
+
# initialize
|
47
|
+
# @param doc [REXML::Document]
|
48
|
+
# @param ns [String]
|
49
|
+
def initialize(doc, ns)
|
50
|
+
if !doc.is_a?(REXML::Document)
|
51
|
+
raise StandardError, "doc must be an REXML::Document. Passed object of class: #{doc.class}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if !ns.is_a?(String)
|
55
|
+
raise StandardError, "ns must be String. Passed object of class: #{ns.class}"
|
56
|
+
end
|
57
|
+
|
58
|
+
@doc = doc
|
59
|
+
@ns = ns
|
60
|
+
@workflow = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_prefix
|
64
|
+
if @ns == ''
|
65
|
+
return ''
|
66
|
+
else
|
67
|
+
return "#{@ns}:"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# TODO: add a schema validation and re-ordering mechanism for XML elements
|
72
|
+
# Format, add declaration, and write xml to disk
|
73
|
+
# @param filename [String] full path including filename, i.e. output/path/results.xml
|
74
|
+
def save_xml(filename)
|
75
|
+
# first we make sure all directories exist
|
76
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
77
|
+
|
78
|
+
# Setup formatting
|
79
|
+
formatter = REXML::Formatters::Pretty.new
|
80
|
+
formatter.compact = true
|
81
|
+
|
82
|
+
# Setup document declaration
|
83
|
+
decl = REXML::XMLDecl.new
|
84
|
+
decl.encoding = REXML::XMLDecl::DEFAULT_ENCODING # UTF-8
|
85
|
+
@doc << decl
|
86
|
+
|
87
|
+
# Write file
|
88
|
+
File.open(filename, 'w') do |file|
|
89
|
+
formatter.write(@doc, file)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# set only one measure path
|
94
|
+
# @param workflow [Hash] a hash of the openstudio workflow
|
95
|
+
# @param measures_dir [String]
|
96
|
+
def set_measure_path(workflow, measures_dir)
|
97
|
+
workflow['measure_paths'] = [measures_dir]
|
98
|
+
end
|
99
|
+
|
100
|
+
# set multiple measure paths
|
101
|
+
# @param measures_dir_array [Array]
|
102
|
+
def set_measure_paths(measures_dir_array)
|
103
|
+
@workflow['measure_paths'] = measures_dir_array
|
104
|
+
end
|
105
|
+
|
106
|
+
# clear all measures from the list in the workflow
|
107
|
+
def clear_all_measures
|
108
|
+
@workflow.delete('steps')
|
109
|
+
@workflow['steps'] = []
|
110
|
+
end
|
111
|
+
|
112
|
+
# add measure path
|
113
|
+
# @param measures_dir [String]
|
114
|
+
# @return [Boolean]
|
115
|
+
def add_measure_path(measures_dir)
|
116
|
+
@workflow['measure_paths'].each do |dir|
|
117
|
+
if dir == measures_dir
|
118
|
+
return false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
@workflow['measure_paths'] << measures_dir
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
|
125
|
+
# set measure argument
|
126
|
+
# @param workflow [Hash] a hash of the openstudio workflow
|
127
|
+
# @param measure_dir_name [String] the directory name for the measure, as it appears
|
128
|
+
# in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
|
129
|
+
# @param argument_name [String]
|
130
|
+
# @param argument_value [String]
|
131
|
+
# @return [Boolean]
|
132
|
+
def set_measure_argument(workflow, measure_dir_name, argument_name, argument_value)
|
133
|
+
result = false
|
134
|
+
workflow['steps'].each do |step|
|
135
|
+
if step['measure_dir_name'] == measure_dir_name
|
136
|
+
step['arguments'][argument_name] = argument_value
|
137
|
+
result = true
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
if !result
|
142
|
+
raise "Could not set '#{argument_name}' to '#{argument_value}' for measure '#{measure_dir_name}'"
|
143
|
+
end
|
144
|
+
|
145
|
+
return result
|
146
|
+
end
|
147
|
+
|
148
|
+
# Adds a new measure to the workflow ONLY if it doesn't already exist
|
149
|
+
# @param workflow [Hash] a hash of the openstudio workflow
|
150
|
+
# @param measure_dir_name [String] the directory name for the measure, as it appears
|
151
|
+
# in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
|
152
|
+
# @return [Boolean] whether or not a new measure was added
|
153
|
+
def add_new_measure(workflow, measure_dir_name)
|
154
|
+
# first we check if the measure already exists
|
155
|
+
workflow['steps'].each do |step|
|
156
|
+
if step['measure_dir_name'] == measure_dir_name
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
# if it does not exist we add it
|
161
|
+
new_step = {}
|
162
|
+
new_step['measure_dir_name'] = measure_dir_name
|
163
|
+
workflow['steps'].unshift(new_step)
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,1119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# *******************************************************************************
|
4
|
+
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
|
5
|
+
# BuildingSync(R), Copyright (c) 2015-2020, Alliance for Sustainable Energy, LLC.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# Redistribution and use in source and binary forms, with or without
|
9
|
+
# modification, are permitted provided that the following conditions are met:
|
10
|
+
#
|
11
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer.
|
13
|
+
#
|
14
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
15
|
+
# this list of conditions and the following disclaimer in the documentation
|
16
|
+
# and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
19
|
+
# may be used to endorse or promote products derived from this software without
|
20
|
+
# specific prior written permission from the respective party.
|
21
|
+
#
|
22
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
23
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
24
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
25
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
28
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO
|
29
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
30
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
31
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
32
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
33
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
34
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
35
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
36
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
37
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
38
|
+
# *******************************************************************************
|
39
|
+
require 'date'
|
40
|
+
|
41
|
+
require 'openstudio/extension/core/os_lib_helper_methods'
|
42
|
+
require 'openstudio/extension/core/os_lib_model_generation'
|
43
|
+
|
44
|
+
require 'buildingsync/model_articulation/building_section'
|
45
|
+
require 'buildingsync/model_articulation/location_element'
|
46
|
+
require 'buildingsync/get_bcl_weather_file'
|
47
|
+
|
48
|
+
module BuildingSync
|
49
|
+
# Building class
|
50
|
+
class Building < LocationElement
|
51
|
+
include OsLib_HelperMethods
|
52
|
+
include EnergyPlus
|
53
|
+
include OsLib_ModelGeneration
|
54
|
+
|
55
|
+
# initialize
|
56
|
+
# @param building_element [REXML::Element] an element corresponding to a single auc:Building
|
57
|
+
# @param site_occupancy_classification [String]
|
58
|
+
# @param site_total_floor_area [String]
|
59
|
+
# @param ns [String] namespace, likely 'auc'
|
60
|
+
def initialize(base_xml, site_occupancy_classification, site_total_floor_area, ns)
|
61
|
+
super(base_xml, ns)
|
62
|
+
@base_xml = base_xml
|
63
|
+
@ns = ns
|
64
|
+
|
65
|
+
help_element_class_type_check(base_xml, 'Building')
|
66
|
+
@building_sections = []
|
67
|
+
@building_sections_whole_building = []
|
68
|
+
@model = nil
|
69
|
+
@all_set = false
|
70
|
+
|
71
|
+
# parameter to read and write.
|
72
|
+
@epw_file_path = nil
|
73
|
+
@standard_template = nil
|
74
|
+
@building_rotation = 0.0
|
75
|
+
@floor_height = 0.0
|
76
|
+
@width = 0.0
|
77
|
+
@length = 0.0
|
78
|
+
@wwr = 0.0
|
79
|
+
@name = nil
|
80
|
+
# variables not used during read xml for now
|
81
|
+
@party_wall_stories_north = 0
|
82
|
+
@party_wall_stories_south = 0
|
83
|
+
@party_wall_stories_west = 0
|
84
|
+
@party_wall_stories_east = 0
|
85
|
+
@party_wall_fraction = 0
|
86
|
+
@built_year = 0
|
87
|
+
@open_studio_standard = nil
|
88
|
+
@occupant_quantity = nil
|
89
|
+
@number_of_units = nil
|
90
|
+
@fraction_area = 1.0
|
91
|
+
# code to initialize
|
92
|
+
read_xml(site_occupancy_classification, site_total_floor_area)
|
93
|
+
end
|
94
|
+
|
95
|
+
# returns number of stories
|
96
|
+
# @return [Integer]
|
97
|
+
def num_stories
|
98
|
+
return @num_stories_above_grade + @num_stories_below_grade
|
99
|
+
end
|
100
|
+
|
101
|
+
# read xml
|
102
|
+
# @param site_occupancy_classification [String]
|
103
|
+
# @param site_total_floor_area [String]
|
104
|
+
def read_xml(site_occupancy_classification, site_total_floor_area)
|
105
|
+
# floor areas
|
106
|
+
@total_floor_area = read_floor_areas(site_total_floor_area)
|
107
|
+
# read location specific values
|
108
|
+
read_location_values
|
109
|
+
check_occupancy_classification(site_occupancy_classification)
|
110
|
+
set_built_year
|
111
|
+
|
112
|
+
# deal with stories above and below grade
|
113
|
+
read_stories_above_and_below_grade
|
114
|
+
# aspect ratio
|
115
|
+
set_ns_to_ew_ratio
|
116
|
+
|
117
|
+
# Create the BuildingSections
|
118
|
+
@base_xml.elements.each("#{@ns}:Sections/#{@ns}:Section") do |section_element|
|
119
|
+
section = BuildingSection.new(section_element, xget_text('OccupancyClassification'), @total_floor_area, num_stories, @ns)
|
120
|
+
if section.section_type == 'Whole building'
|
121
|
+
@building_sections_whole_building.push(section)
|
122
|
+
elsif section.section_type == 'Space function' || section.section_type.nil?
|
123
|
+
@building_sections.push(section)
|
124
|
+
else
|
125
|
+
puts "Unknown section type found:#{section.section_type}:"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# generate building name
|
130
|
+
read_other_building_details
|
131
|
+
end
|
132
|
+
|
133
|
+
# set all function to set all parameters for this building
|
134
|
+
def set_all
|
135
|
+
if !@all_set
|
136
|
+
@all_set = true
|
137
|
+
set_bldg_and_system_type_for_building_and_section
|
138
|
+
set_building_form_defaults
|
139
|
+
set_width_and_length
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# set width and length of the building footprint
|
144
|
+
def set_width_and_length
|
145
|
+
footprint = @total_floor_area / num_stories.to_f
|
146
|
+
@width = Math.sqrt(footprint / @ns_to_ew_ratio)
|
147
|
+
@length = footprint / @width
|
148
|
+
end
|
149
|
+
|
150
|
+
def check_occupancy_classification(site_occupancy_classification)
|
151
|
+
# Set the OccupancyClassification text as that defined by the Site
|
152
|
+
# ONLY if it is not already defined
|
153
|
+
if !site_occupancy_classification.nil?
|
154
|
+
xset_or_create('OccupancyClassification', site_occupancy_classification, false)
|
155
|
+
end
|
156
|
+
if xget_text('OccupancyClassification').nil?
|
157
|
+
raise StandardError, "Building ID: #{xget_id}. OccupancyClassification must be defined at either the Site or Building level."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Set the @built_year based on YearOfConstruction / YearOfLastMajorRemodel
|
162
|
+
def set_built_year
|
163
|
+
if !@base_xml.elements["#{@ns}:YearOfConstruction"]
|
164
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.read_standard_template_based_on_year', 'Year of Construction is blank in your BuildingSync file.')
|
165
|
+
raise StandardError, "Building ID: #{xget_id}. Year of Construction is blank in your BuildingSync file, but is required."
|
166
|
+
end
|
167
|
+
|
168
|
+
@built_year = xget_text_as_integer('YearOfConstruction')
|
169
|
+
remodel_year = xget_text_as_integer('YearOfLastMajorRemodel')
|
170
|
+
if !remodel_year.nil? && remodel_year > @built_year
|
171
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_built_year', "built_year for Standards reset from #{@built_year} (YearOfConstruction) to #{remodel_year} (YearOfLastMajorRemodel).")
|
172
|
+
@built_year = remodel_year
|
173
|
+
else
|
174
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_built_year', "built_year for Standards set to #{@built_year} (YearOfConstruction).")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# read stories above and below grade
|
179
|
+
def read_stories_above_and_below_grade
|
180
|
+
if @base_xml.elements["#{@ns}:FloorsAboveGrade"]
|
181
|
+
@num_stories_above_grade = @base_xml.elements["#{@ns}:FloorsAboveGrade"].text.to_f
|
182
|
+
elsif @base_xml.elements["#{@ns}:ConditionedFloorsAboveGrade"]
|
183
|
+
@num_stories_above_grade = @base_xml.elements["#{@ns}:ConditionedFloorsAboveGrade"].text.to_f
|
184
|
+
else
|
185
|
+
@num_stories_above_grade = 1.0 # setDefaultValue
|
186
|
+
end
|
187
|
+
|
188
|
+
if @base_xml.elements["#{@ns}:FloorsBelowGrade"]
|
189
|
+
@num_stories_below_grade = @base_xml.elements["#{@ns}:FloorsBelowGrade"].text.to_f
|
190
|
+
elsif @base_xml.elements["#{@ns}:ConditionedFloorsBelowGrade"]
|
191
|
+
@num_stories_below_grade = @base_xml.elements["#{@ns}:ConditionedFloorsBelowGrade"].text.to_f
|
192
|
+
else
|
193
|
+
@num_stories_below_grade = 0.0 # setDefaultValue
|
194
|
+
end
|
195
|
+
|
196
|
+
if @num_stories_below_grade > 1.0
|
197
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.read_stories_above_and_below_grade', "Number of stories below grade is larger than 1: #{@num_stories_below_grade}, currently only one basement story is supported.")
|
198
|
+
raise StandardError, "Building ID: #{xget_id}. Number of stories below grade is > 1 (#{num_stories_below_grade}). Currently, only one story below grade is supported."
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Set the @ns_to_ew_ratio parameter using the AspectRatio element if present
|
203
|
+
def set_ns_to_ew_ratio
|
204
|
+
if @base_xml.elements["#{@ns}:AspectRatio"]
|
205
|
+
@ns_to_ew_ratio = xget_text_as_float('AspectRatio')
|
206
|
+
else
|
207
|
+
@ns_to_ew_ratio = 0.0 # setDefaultValue
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# get building type
|
212
|
+
# @return [String]
|
213
|
+
def get_building_type
|
214
|
+
set_all
|
215
|
+
# try to get the bldg type at the building level, if it is nil then look at the first section
|
216
|
+
if !@standards_building_type.nil?
|
217
|
+
return @standards_building_type
|
218
|
+
else
|
219
|
+
if @building_sections.count == 0
|
220
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.get_building_type', 'There is no occupancy type attached to this building in your BuildingSync file.')
|
221
|
+
raise 'Error: There is no occupancy type attached to this building in your BuildingSync file.'
|
222
|
+
else
|
223
|
+
return @building_sections[0].standards_building_type
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# get full path to epw file
|
229
|
+
# return [String]
|
230
|
+
def get_epw_file_path
|
231
|
+
return @epw_file_path
|
232
|
+
end
|
233
|
+
|
234
|
+
# set aspect ratio, floor height, and WWR
|
235
|
+
def set_building_form_defaults
|
236
|
+
# if aspect ratio, story height or wwr have argument value of 0 then use smart building type defaults
|
237
|
+
building_form_defaults = building_form_defaults(get_building_type)
|
238
|
+
if @ns_to_ew_ratio == 0.0 && !building_form_defaults.nil?
|
239
|
+
@ns_to_ew_ratio = building_form_defaults[:aspect_ratio]
|
240
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_building_form_defaults', "0.0 value for aspect ratio will be replaced with smart default for #{get_building_type} of #{building_form_defaults[:aspect_ratio]}.")
|
241
|
+
end
|
242
|
+
if @floor_height == 0.0 && !building_form_defaults.nil?
|
243
|
+
@floor_height = OpenStudio.convert(building_form_defaults[:typical_story], 'ft', 'm').get
|
244
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_building_form_defaults', "0.0 value for floor height will be replaced with smart default for #{get_building_type} of #{building_form_defaults[:typical_story]}.")
|
245
|
+
end
|
246
|
+
# 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
|
247
|
+
if @wwr == 0.0 && !building_form_defaults.nil?
|
248
|
+
@wwr = building_form_defaults[:wwr]
|
249
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_building_form_defaults', "0.0 value for window to wall ratio will be replaced with smart default for #{get_building_type} of #{building_form_defaults[:wwr]}.")
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# check building fraction
|
254
|
+
def check_building_fraction
|
255
|
+
# check that sum of fractions for b,c, and d is less than 1.0 (so something is left for primary building type)
|
256
|
+
building_fraction = 1.0
|
257
|
+
if @building_sections.count > 0
|
258
|
+
# first we check if the building sections do have a fraction
|
259
|
+
if @building_sections.count > 1
|
260
|
+
areas = []
|
261
|
+
floor_area = 0
|
262
|
+
@building_sections.each do |section|
|
263
|
+
if section.fraction_area.nil?
|
264
|
+
areas.push(section.total_floor_area)
|
265
|
+
floor_area += section.total_floor_area
|
266
|
+
end
|
267
|
+
end
|
268
|
+
i = 0
|
269
|
+
@building_sections.each do |section|
|
270
|
+
section.fraction_area = areas[i] / @total_floor_area
|
271
|
+
i += 1
|
272
|
+
end
|
273
|
+
elsif @building_sections.count == 1
|
274
|
+
# only if we have just one section the section fraction is set to the building fraction (1)
|
275
|
+
@building_sections[0].fraction_area = building_fraction
|
276
|
+
end
|
277
|
+
@building_sections.each do |section|
|
278
|
+
puts "section with ID: #{section.xget_id} and type: '#{section.xget_text('SectionType')}' has fraction: #{section.fraction_area}"
|
279
|
+
next if section.fraction_area.nil?
|
280
|
+
building_fraction -= section.fraction_area
|
281
|
+
end
|
282
|
+
if building_fraction.round(3) < 0.0
|
283
|
+
puts "building fraction is #{building_fraction}"
|
284
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.check_building_fraction', '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.')
|
285
|
+
raise 'ERROR: 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.'
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# read other building details
|
291
|
+
def read_other_building_details
|
292
|
+
if @base_xml.elements["#{@ns}:OccupancyLevels/#{@ns}:OccupancyLevel/#{@ns}:OccupantQuantity"]
|
293
|
+
@occupant_quantity = @base_xml.elements["#{@ns}:OccupancyLevels/#{@ns}:OccupancyLevel/#{@ns}:OccupantQuantity"].text
|
294
|
+
else
|
295
|
+
@occupant_quantity = nil
|
296
|
+
end
|
297
|
+
|
298
|
+
if @base_xml.elements["#{@ns}:SpatialUnits/#{@ns}:SpatialUnit/#{@ns}:NumberOfUnits"]
|
299
|
+
@number_of_units = @base_xml.elements["#{@ns}:SpatialUnits/#{@ns}:SpatialUnit/#{@ns}:NumberOfUnits"].text
|
300
|
+
else
|
301
|
+
@number_of_units = nil
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# create building space types
|
306
|
+
# @param model [OpenStudio::Model]
|
307
|
+
def create_bldg_space_types(model)
|
308
|
+
@building_sections.each do |bldg_subsec|
|
309
|
+
bldg_subsec.create_space_types(model, @total_floor_area, num_stories, @standard_template, @open_studio_standard)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# build zone hash that stores zone lists for buildings and building sections
|
314
|
+
# @return [[hash<string, array<Zone>>]]
|
315
|
+
def build_zone_hash
|
316
|
+
zone_hash = {}
|
317
|
+
if @space_types
|
318
|
+
zone_list = []
|
319
|
+
@space_types.each do |space_name, space_type|
|
320
|
+
zone_list.concat(get_zones_per_space_type(space_type[:space_type]))
|
321
|
+
end
|
322
|
+
zone_hash[xget_id] = zone_list
|
323
|
+
end
|
324
|
+
@building_sections.each do |bldg_subsec|
|
325
|
+
zone_list = []
|
326
|
+
bldg_subsec.space_types_floor_area.each do |space_type, hash|
|
327
|
+
zone_list.concat(get_zones_per_space_type(space_type))
|
328
|
+
end
|
329
|
+
zone_hash[bldg_subsec.xget_id] = zone_list
|
330
|
+
end
|
331
|
+
return zone_hash
|
332
|
+
end
|
333
|
+
|
334
|
+
# build space types hash
|
335
|
+
# @return [hash<string, array<hash<string, string>>]
|
336
|
+
def build_space_type_hash
|
337
|
+
space_type_hash = {}
|
338
|
+
if @space_types
|
339
|
+
space_type_list = []
|
340
|
+
@space_types.each do |space_name, space_type|
|
341
|
+
space_type_list << space_type[:space_type]
|
342
|
+
end
|
343
|
+
space_type_hash[xget_id] = space_type_list
|
344
|
+
end
|
345
|
+
@building_sections.each do |bldg_subsec|
|
346
|
+
space_type_list = []
|
347
|
+
bldg_subsec.space_types_floor_area.each do |space_type, hash|
|
348
|
+
space_type_list << space_type
|
349
|
+
end
|
350
|
+
space_type_hash[bldg_subsec.xget_id] = space_type_list
|
351
|
+
end
|
352
|
+
return space_type_hash
|
353
|
+
end
|
354
|
+
|
355
|
+
# generate building space types floor area hash
|
356
|
+
# @return [Hash]
|
357
|
+
def bldg_space_types_floor_area_hash
|
358
|
+
new_hash = {}
|
359
|
+
if @building_sections.count > 0
|
360
|
+
@building_sections.each do |bldg_subsec|
|
361
|
+
bldg_subsec.space_types_floor_area.each do |space_type, hash|
|
362
|
+
new_hash[space_type] = hash
|
363
|
+
end
|
364
|
+
end
|
365
|
+
# if we have no sections we need to do the same just for the building
|
366
|
+
elsif @building_sections.count == 0
|
367
|
+
@space_types = get_space_types_from_building_type(@standards_building_type, @standard_template, true)
|
368
|
+
puts " Space types: #{@space_types} selected for building type: #{@standards_building_type} and standard template: #{@standard_template}"
|
369
|
+
space_types_floor_area = create_space_types(@model, @total_floor_area, num_stories, @standard_template, @open_studio_standard)
|
370
|
+
space_types_floor_area.each do |space_type, hash|
|
371
|
+
new_hash[space_type] = hash
|
372
|
+
end
|
373
|
+
end
|
374
|
+
return new_hash
|
375
|
+
end
|
376
|
+
|
377
|
+
# in initialize an empty model
|
378
|
+
def initialize_model
|
379
|
+
# let's create our new empty model
|
380
|
+
@model = OpenStudio::Model::Model.new if @model.nil?
|
381
|
+
end
|
382
|
+
|
383
|
+
# set building and system type for building and sections
|
384
|
+
def set_bldg_and_system_type_for_building_and_section
|
385
|
+
@building_sections.each(&:set_bldg_and_system_type)
|
386
|
+
|
387
|
+
set_bldg_and_system_type(xget_text('OccupancyClassification'), @total_floor_area, num_stories, true)
|
388
|
+
end
|
389
|
+
|
390
|
+
# determine the open studio standard and call the set_all function
|
391
|
+
# @param standard_to_be_used [String]
|
392
|
+
# @return [Standard]
|
393
|
+
def determine_open_studio_standard(standard_to_be_used)
|
394
|
+
set_all
|
395
|
+
begin
|
396
|
+
set_standard_template(standard_to_be_used, get_built_year)
|
397
|
+
building_type = get_building_type
|
398
|
+
@open_studio_standard = Standard.build("#{@standard_template}_#{building_type}")
|
399
|
+
update_name
|
400
|
+
rescue StandardError => e
|
401
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.determine_open_studio_standard', e.message[0..20])
|
402
|
+
raise StandardError, "BuildingSync.Building.determine_open_studio_standard: #{e.message[0..20]}"
|
403
|
+
end
|
404
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.determine_open_studio_standard', "Building Standard with template: #{@standard_template}_#{building_type}") if !@open_studio_standard.nil?
|
405
|
+
return @open_studio_standard
|
406
|
+
end
|
407
|
+
|
408
|
+
# update the name of the building
|
409
|
+
def update_name
|
410
|
+
# update the name so it includes the standard_template string
|
411
|
+
name_array = [@standard_template]
|
412
|
+
name_array << get_building_type
|
413
|
+
@building_sections.each do |bld_tp|
|
414
|
+
name_array << bld_tp.standards_building_type
|
415
|
+
end
|
416
|
+
name_array << @name if !@name.nil? && !@name == ''
|
417
|
+
@name = name_array.join('|').to_s
|
418
|
+
end
|
419
|
+
|
420
|
+
# set standard template
|
421
|
+
# @param standard_to_be_used [String]
|
422
|
+
# @param built_year [Integer]
|
423
|
+
def set_standard_template(standard_to_be_used, built_year)
|
424
|
+
if standard_to_be_used == CA_TITLE24
|
425
|
+
if built_year < 1978
|
426
|
+
@standard_template = 'CBES Pre-1978'
|
427
|
+
elsif built_year >= 1978 && built_year < 1992
|
428
|
+
@standard_template = 'CBES T24 1978'
|
429
|
+
elsif built_year >= 1992 && built_year < 2001
|
430
|
+
@standard_template = 'CBES T24 1992'
|
431
|
+
elsif built_year >= 2001 && built_year < 2005
|
432
|
+
@standard_template = 'CBES T24 2001'
|
433
|
+
elsif built_year >= 2005 && built_year < 2008
|
434
|
+
@standard_template = 'CBES T24 2005'
|
435
|
+
else
|
436
|
+
@standard_template = 'CBES T24 2008'
|
437
|
+
end
|
438
|
+
elsif standard_to_be_used == ASHRAE90_1
|
439
|
+
if built_year < 1980
|
440
|
+
@standard_template = 'DOE Ref Pre-1980'
|
441
|
+
elsif built_year >= 1980 && built_year < 2004
|
442
|
+
@standard_template = 'DOE Ref 1980-2004'
|
443
|
+
elsif built_year >= 2004 && built_year < 2007
|
444
|
+
@standard_template = '90.1-2004'
|
445
|
+
elsif built_year >= 2007 && built_year < 2010
|
446
|
+
@standard_template = '90.1-2007'
|
447
|
+
elsif built_year >= 2010 && built_year < 2013
|
448
|
+
@standard_template = '90.1-2010'
|
449
|
+
elsif built_year >= 2013
|
450
|
+
@standard_template = '90.1-2013'
|
451
|
+
end
|
452
|
+
# TODO: add ASHRAE 2016 once it is available
|
453
|
+
else
|
454
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.get_standard_template', "Unknown standard_to_be_used #{standard_to_be_used}.")
|
455
|
+
raise StandardError, "BuildingSync.Building.get_standard_template: Unknown standard_to_be_used #{standard_to_be_used}."
|
456
|
+
end
|
457
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.get_standard_template', "Using the following standard for default values #{@standard_template}.")
|
458
|
+
end
|
459
|
+
|
460
|
+
# get zones per space type
|
461
|
+
# @param space_type [OpenStudio::Model::SpaceType]
|
462
|
+
# @return [array<OpenStudio::Model::ThermalZone>]
|
463
|
+
def get_zones_per_space_type(space_type)
|
464
|
+
list_of_zones = []
|
465
|
+
model_space_type = @model.getSpaceTypeByName(space_type.name.get).get
|
466
|
+
model_space_type.spaces.each do |space|
|
467
|
+
list_of_zones << space.thermalZone.get
|
468
|
+
end
|
469
|
+
return list_of_zones
|
470
|
+
end
|
471
|
+
|
472
|
+
# get model
|
473
|
+
# @return [OpenStudio::Model]
|
474
|
+
def get_model
|
475
|
+
# in case the model was not initialized before we create a new model if it is nil
|
476
|
+
initialize_model
|
477
|
+
return @model
|
478
|
+
end
|
479
|
+
|
480
|
+
# get year building was built
|
481
|
+
# @return [Integer]
|
482
|
+
def get_built_year
|
483
|
+
return @built_year
|
484
|
+
end
|
485
|
+
|
486
|
+
# get @standard_template
|
487
|
+
# @return [String]
|
488
|
+
def get_standard_template
|
489
|
+
return @standard_template
|
490
|
+
end
|
491
|
+
|
492
|
+
# get system type
|
493
|
+
# @return [String]
|
494
|
+
def get_system_type
|
495
|
+
set_all
|
496
|
+
if !@system_type.nil?
|
497
|
+
return @system_type
|
498
|
+
else
|
499
|
+
return @building_sections[0].system_type
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# get stat file path
|
504
|
+
# @param epw_file [String]
|
505
|
+
# @return [String]
|
506
|
+
def get_stat_file(epw_file)
|
507
|
+
# Add SiteWaterMainsTemperature -- via parsing of STAT file.
|
508
|
+
stat_file = "#{File.join(File.dirname(epw_file.path.to_s), File.basename(epw_file.path.to_s, '.*'))}.stat"
|
509
|
+
unless File.exist? stat_file
|
510
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.get_stat_file', 'Could not find STAT file by filename, looking in the directory')
|
511
|
+
stat_files = Dir["#{File.dirname(epw_file.path.to_s)}/*.stat"]
|
512
|
+
if stat_files.size > 1
|
513
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.get_stat_file', 'More than one stat file in the EPW directory')
|
514
|
+
return nil
|
515
|
+
end
|
516
|
+
if stat_files.empty?
|
517
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.get_stat_file', 'Cound not find the stat file in the EPW directory')
|
518
|
+
return nil
|
519
|
+
end
|
520
|
+
|
521
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.get_stat_file', "Using STAT file: #{stat_files.first}")
|
522
|
+
stat_file = stat_files.first
|
523
|
+
end
|
524
|
+
unless stat_file
|
525
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.get_stat_file', 'Could not find stat file')
|
526
|
+
return nil
|
527
|
+
end
|
528
|
+
return stat_file
|
529
|
+
end
|
530
|
+
|
531
|
+
# set weather file and climate zone
|
532
|
+
# @param climate_zone [String]
|
533
|
+
# @param epw_file_path [String]
|
534
|
+
# @param standard_to_be_used [String]
|
535
|
+
# @param latitude [String]
|
536
|
+
# @param longitude [String]
|
537
|
+
# @param ddy_file [String]
|
538
|
+
# @param weather_argb [array]
|
539
|
+
def set_weather_and_climate_zone(climate_zone, epw_file_path, standard_to_be_used, latitude, longitude, ddy_file, *weather_argb)
|
540
|
+
initialize_model
|
541
|
+
|
542
|
+
determine_climate_zone(standard_to_be_used) if climate_zone.nil?
|
543
|
+
|
544
|
+
# here we check if there is an valid EPW file, if there is we use that file otherwise everything will be generated from climate zone
|
545
|
+
if !epw_file_path.nil? && File.exist?(epw_file_path)
|
546
|
+
@epw_file_path = epw_file_path
|
547
|
+
puts "case 1: epw file exists #{epw_file_path} and climate_zone is: #{climate_zone}"
|
548
|
+
set_weather_and_climate_zone_from_epw(climate_zone, standard_to_be_used, latitude, longitude, ddy_file)
|
549
|
+
elsif climate_zone.nil? && @climate_zone.nil?
|
550
|
+
weather_station_id = weather_argb[1]
|
551
|
+
state_name = weather_argb[2]
|
552
|
+
city_name = weather_argb[3]
|
553
|
+
puts 'case 2: climate_zone is nil at the Site and Building level'
|
554
|
+
if !weather_station_id.nil?
|
555
|
+
puts "case 2.1: weather_station_id is not nil #{weather_station_id}"
|
556
|
+
@epw_file_path = BuildingSync::GetBCLWeatherFile.new.download_weather_file_from_weather_id(weather_station_id)
|
557
|
+
elsif !city_name.nil? && !state_name.nil?
|
558
|
+
puts "case 2.2: SITE LEVEL city_name and state_name is not nil #{city_name} #{state_name}"
|
559
|
+
@epw_file_path = BuildingSync::GetBCLWeatherFile.new.download_weather_file_from_city_name(state_name, city_name)
|
560
|
+
elsif !@city_name.nil? && !@state_name.nil?
|
561
|
+
puts "case 2.3: BUILDING LEVEL city_name and state_name is not nil #{@city_name} #{@state_name}"
|
562
|
+
@epw_file_path = BuildingSync::GetBCLWeatherFile.new.download_weather_file_from_city_name(@state_name, @city_name)
|
563
|
+
end
|
564
|
+
|
565
|
+
else
|
566
|
+
puts "case 3: SITE LEVEL climate zone #{climate_zone} BUILDING LEVEL climate zone #{@climate_zone}."
|
567
|
+
puts "lat #{latitude} long #{longitude}"
|
568
|
+
if climate_zone.nil?
|
569
|
+
climate_zone = @climate_zone
|
570
|
+
puts "Climate Zone set at the Building level: #{climate_zone}"
|
571
|
+
else
|
572
|
+
puts "Climate Zone set at the Site level: #{climate_zone}"
|
573
|
+
end
|
574
|
+
@epw_file_path = set_weather_and_climate_zone_from_climate_zone(climate_zone, standard_to_be_used, latitude, longitude)
|
575
|
+
@epw_file_path = @epw_file_path.to_s
|
576
|
+
end
|
577
|
+
|
578
|
+
# Ensure a file path gets set, else raise error
|
579
|
+
if @epw_file_path.nil?
|
580
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone', 'epw_file_path is nil and no way to set from Site or Building parameters.')
|
581
|
+
raise StandardError, 'BuildingSync.Building.set_weather_and_climate_zone: epw_file_path is nil and no way to set from Site or Building parameters.'
|
582
|
+
elsif !@epw_file_path
|
583
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone', "epw_file_path is false: #{@epw_file_path}")
|
584
|
+
raise StandardError, "BuildingSync.Building.set_weather_and_climate_zone: epw_file_path is false: #{@epw_file_path}"
|
585
|
+
elsif !File.exist?(@epw_file_path)
|
586
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone', "epw_file_path does not exist: #{@epw_file_path}")
|
587
|
+
raise StandardError, "BuildingSync.Building.set_weather_and_climate_zone: epw_file_path does not exist: #{@epw_file_path}"
|
588
|
+
end
|
589
|
+
|
590
|
+
# setting the current year, so we do not get these annoying log messages:
|
591
|
+
# [openstudio.model.YearDescription] <1> 'UseWeatherFile' is not yet a supported option for YearDescription
|
592
|
+
year_description = @model.getYearDescription
|
593
|
+
year_description.setCalendarYear(::Date.today.year)
|
594
|
+
|
595
|
+
# add final condition
|
596
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone', "The final weather file is #{@model.getWeatherFile.city} and the model has #{@model.getDesignDays.size} design day objects.")
|
597
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone', "The path to the epw file is: #{@epw_file_path}")
|
598
|
+
end
|
599
|
+
|
600
|
+
# set weather file and climate zone from climate zone
|
601
|
+
# @param climate_zone [String]
|
602
|
+
# @param standard_to_be_used [String]
|
603
|
+
# @param latitude [String]
|
604
|
+
# @param longitude [String]
|
605
|
+
def set_weather_and_climate_zone_from_climate_zone(climate_zone, standard_to_be_used, latitude, longitude)
|
606
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_weather_and_climate_zone_from_climate_zone', "Cannot add design days and weather file for climate zone: #{climate_zone}, no epw file provided")
|
607
|
+
climate_zone_standard_string = climate_zone
|
608
|
+
puts climate_zone
|
609
|
+
puts standard_to_be_used
|
610
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone_from_climate_zone', "climate zone: #{climate_zone}")
|
611
|
+
if standard_to_be_used == CA_TITLE24 && !climate_zone.nil?
|
612
|
+
climate_zone_standard_string = "CEC T24-CEC#{climate_zone.gsub('Climate Zone', '').strip}"
|
613
|
+
elsif standard_to_be_used == ASHRAE90_1 && !climate_zone.nil?
|
614
|
+
climate_zone_standard_string = "ASHRAE 169-2006-#{climate_zone.gsub('Climate Zone', '').strip}"
|
615
|
+
elsif climate_zone.nil?
|
616
|
+
climate_zone_standard_string = ''
|
617
|
+
end
|
618
|
+
|
619
|
+
puts @open_studio_standard
|
620
|
+
puts @open_studio_standard.class
|
621
|
+
puts climate_zone_standard_string
|
622
|
+
if !@open_studio_standard.nil? && !@open_studio_standard.model_add_design_days_and_weather_file(@model, climate_zone_standard_string, nil)
|
623
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone_from_climate_zone', "Cannot add design days and weather file for climate zone: #{climate_zone}, no epw file provided")
|
624
|
+
end
|
625
|
+
|
626
|
+
# overwrite latitude and longitude if available
|
627
|
+
if !latitude.nil? || !longitude.nil?
|
628
|
+
site = @model.getSite
|
629
|
+
if !latitude.nil?
|
630
|
+
site.setLatitude(latitude.to_f)
|
631
|
+
end
|
632
|
+
if !longitude.nil?
|
633
|
+
site.setLongitude(longitude.to_f)
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
weather_file = @model.getWeatherFile
|
638
|
+
|
639
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone_from_climate_zone', "city is #{weather_file.city}. State is #{weather_file.stateProvinceRegion}")
|
640
|
+
|
641
|
+
set_climate_zone(climate_zone, standard_to_be_used)
|
642
|
+
return weather_file.path.get
|
643
|
+
end
|
644
|
+
|
645
|
+
# set climate zone
|
646
|
+
# @param climate_zone [String]
|
647
|
+
# @param standard_to_be_used [String]
|
648
|
+
# @param stat_file [String]
|
649
|
+
# @return [Boolean]
|
650
|
+
def set_climate_zone(climate_zone, standard_to_be_used, stat_file = nil)
|
651
|
+
# Set climate zone
|
652
|
+
if climate_zone.nil?
|
653
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_climate_zone', 'Climate Zone is nil, trying to get it from stat file')
|
654
|
+
# get climate zone from stat file
|
655
|
+
text = nil
|
656
|
+
File.open(stat_file) do |f|
|
657
|
+
text = f.read.force_encoding('iso-8859-1')
|
658
|
+
end
|
659
|
+
|
660
|
+
# Get Climate zone.
|
661
|
+
# - Climate type "3B" (ASHRAE Standard 196-2006 Climate Zone)**
|
662
|
+
# - Climate type "6A" (ASHRAE Standards 90.1-2004 and 90.2-2004 Climate Zone)**
|
663
|
+
regex = /Climate type \"(.*?)\" \(ASHRAE Standards?(.*)\)\*\*/
|
664
|
+
match_data = text.match(regex)
|
665
|
+
if match_data.nil?
|
666
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_climate_zone', "Can't find ASHRAE climate zone in stat file.")
|
667
|
+
else
|
668
|
+
climate_zone = match_data[1].to_s.strip
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
climate_zones = @model.getClimateZones
|
673
|
+
# set climate zone
|
674
|
+
climate_zones.clear
|
675
|
+
if standard_to_be_used == ASHRAE90_1 && !climate_zone.nil?
|
676
|
+
climate_zones.setClimateZone('ASHRAE', climate_zone)
|
677
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_climate_zone', "Setting Climate Zone to #{climate_zones.getClimateZones('ASHRAE').first.value}")
|
678
|
+
puts "setting ASHRAE climate zone to: #{climate_zone}"
|
679
|
+
return true
|
680
|
+
elsif standard_to_be_used == CA_TITLE24 && !climate_zone.nil?
|
681
|
+
climate_zone = climate_zone.gsub('CEC', '').strip
|
682
|
+
climate_zone = climate_zone.gsub('Climate Zone', '').strip
|
683
|
+
climate_zone = climate_zone.delete('A').strip
|
684
|
+
climate_zone = climate_zone.delete('B').strip
|
685
|
+
climate_zone = climate_zone.delete('C').strip
|
686
|
+
climate_zones.setClimateZone('CEC', climate_zone)
|
687
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_climate_zone', "Setting Climate Zone to #{climate_zone}")
|
688
|
+
puts "setting CA_TITLE24 climate zone to: #{climate_zone}"
|
689
|
+
return true
|
690
|
+
end
|
691
|
+
puts "could not set climate_zone #{climate_zone}"
|
692
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.set_climate_zone', "Cannot set the #{climate_zone} in context of this standard #{standard_to_be_used}")
|
693
|
+
return false
|
694
|
+
end
|
695
|
+
|
696
|
+
# set weather file and climate zone from EPW file
|
697
|
+
# @param climate_zone [String]
|
698
|
+
# @param standard_to_be_used [String]
|
699
|
+
# @param latitude [String]
|
700
|
+
# @param longitude [String]
|
701
|
+
# @param ddy_file [String]
|
702
|
+
def set_weather_and_climate_zone_from_epw(climate_zone, standard_to_be_used, latitude, longitude, ddy_file = nil)
|
703
|
+
epw_file = OpenStudio::EpwFile.new(@epw_file_path)
|
704
|
+
|
705
|
+
weather_lat = epw_file.latitude
|
706
|
+
if !latitude.nil?
|
707
|
+
weather_lat = latitude.to_f
|
708
|
+
end
|
709
|
+
weather_lon = epw_file.longitude
|
710
|
+
if !longitude.nil?
|
711
|
+
weather_lon = longitude.to_f
|
712
|
+
end
|
713
|
+
|
714
|
+
weather_file = @model.getWeatherFile
|
715
|
+
weather_file.setCity(epw_file.city)
|
716
|
+
weather_file.setStateProvinceRegion(epw_file.stateProvinceRegion)
|
717
|
+
weather_file.setCountry(epw_file.country)
|
718
|
+
weather_file.setDataSource(epw_file.dataSource)
|
719
|
+
weather_file.setWMONumber(epw_file.wmoNumber.to_s)
|
720
|
+
weather_file.setLatitude(weather_lat)
|
721
|
+
weather_file.setLongitude(weather_lon)
|
722
|
+
weather_file.setTimeZone(epw_file.timeZone)
|
723
|
+
weather_file.setElevation(epw_file.elevation)
|
724
|
+
weather_file.setString(10, epw_file.path.to_s)
|
725
|
+
|
726
|
+
weather_name = "#{epw_file.city}_#{epw_file.stateProvinceRegion}_#{epw_file.country}"
|
727
|
+
weather_time = epw_file.timeZone
|
728
|
+
weather_elev = epw_file.elevation
|
729
|
+
|
730
|
+
# Add or update site data
|
731
|
+
site = @model.getSite
|
732
|
+
site.setName(weather_name)
|
733
|
+
site.setLatitude(weather_lat)
|
734
|
+
site.setLongitude(weather_lon)
|
735
|
+
site.setTimeZone(weather_time)
|
736
|
+
site.setElevation(weather_elev)
|
737
|
+
|
738
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone_from_epw', "city is #{epw_file.city}. State is #{epw_file.stateProvinceRegion}")
|
739
|
+
|
740
|
+
stat_file = get_stat_file(epw_file)
|
741
|
+
add_site_water_mains_temperature(stat_file) if !stat_file.nil?
|
742
|
+
|
743
|
+
set_climate_zone(climate_zone, standard_to_be_used, stat_file)
|
744
|
+
|
745
|
+
# Remove all the Design Day objects that are in the file
|
746
|
+
@model.getObjectsByType('OS:SizingPeriod:DesignDay'.to_IddObjectType).each(&:remove)
|
747
|
+
|
748
|
+
# find the ddy files
|
749
|
+
ddy_file = "#{File.join(File.dirname(epw_file.path.to_s), File.basename(epw_file.path.to_s, '.*'))}.ddy" if ddy_file.nil?
|
750
|
+
unless File.exist? ddy_file
|
751
|
+
ddy_files = Dir["#{File.dirname(epw_file.path.to_s)}/*.ddy"]
|
752
|
+
if ddy_files.size > 1
|
753
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone_from_epw', 'More than one ddy file in the EPW directory')
|
754
|
+
return false
|
755
|
+
end
|
756
|
+
if ddy_files.empty?
|
757
|
+
OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Building.set_weather_and_climate_zone_from_epw', 'could not find the ddy file in the EPW directory')
|
758
|
+
return false
|
759
|
+
end
|
760
|
+
|
761
|
+
ddy_file = ddy_files.first
|
762
|
+
end
|
763
|
+
|
764
|
+
unless ddy_file
|
765
|
+
runner.registerError "Could not find DDY file for #{ddy_file}"
|
766
|
+
return error
|
767
|
+
end
|
768
|
+
|
769
|
+
ddy_model = OpenStudio::EnergyPlus.loadAndTranslateIdf(ddy_file).get
|
770
|
+
ddy_model.getObjectsByType('OS:SizingPeriod:DesignDay'.to_IddObjectType).each do |d|
|
771
|
+
# grab only the ones that matter
|
772
|
+
ddy_list = /(Htg 99.6. Condns DB)|(Clg .4. Condns WB=>MDB)|(Clg .4% Condns DB=>MWB)/
|
773
|
+
if d.name.get.match?(ddy_list)
|
774
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.set_weather_and_climate_zone_from_epw', "Adding object #{d.name}")
|
775
|
+
|
776
|
+
# add the object to the existing model
|
777
|
+
@model.addObject(d.clone)
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
# add site water mains temperature -- via parsing of STAT file.
|
783
|
+
# @param stat_file [String]
|
784
|
+
# @return [Boolean]
|
785
|
+
def add_site_water_mains_temperature(stat_file)
|
786
|
+
stat_model = ::EnergyPlus::StatFile.new(stat_file)
|
787
|
+
water_temp = @model.getSiteWaterMainsTemperature
|
788
|
+
water_temp.setAnnualAverageOutdoorAirTemperature(stat_model.mean_dry_bulb)
|
789
|
+
water_temp.setMaximumDifferenceInMonthlyAverageOutdoorAirTemperatures(stat_model.delta_dry_bulb)
|
790
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.add_site_water_mains_temperature', "mean dry bulb is #{stat_model.mean_dry_bulb}")
|
791
|
+
return true
|
792
|
+
end
|
793
|
+
|
794
|
+
# generate baseline model in osm file format
|
795
|
+
def generate_baseline_osm
|
796
|
+
# checking that the fractions add up
|
797
|
+
check_building_fraction
|
798
|
+
|
799
|
+
# set building rotation
|
800
|
+
initial_rotation = @model.getBuilding.northAxis
|
801
|
+
if @building_rotation != initial_rotation
|
802
|
+
@model.getBuilding.setNorthAxis(building_rotation)
|
803
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.generate_baseline_osm', "Set Building Rotation to #{@model.getBuilding.northAxis}")
|
804
|
+
end
|
805
|
+
if !@name.nil?
|
806
|
+
@model.getBuilding.setName(@name)
|
807
|
+
end
|
808
|
+
|
809
|
+
create_bldg_space_types(@model)
|
810
|
+
|
811
|
+
# create envelope
|
812
|
+
# populate bar_hash and create envelope with data from envelope_data_hash and user arguments
|
813
|
+
bar_hash = {}
|
814
|
+
bar_hash[:length] = @length
|
815
|
+
bar_hash[:width] = @width
|
816
|
+
bar_hash[:num_stories_below_grade] = num_stories_below_grade.to_i
|
817
|
+
bar_hash[:num_stories_above_grade] = num_stories_above_grade.to_i
|
818
|
+
bar_hash[:floor_height] = floor_height
|
819
|
+
bar_hash[:center_of_footprint] = OpenStudio::Point3d.new(0, 0, 0)
|
820
|
+
bar_hash[:bar_division_method] = 'Multiple Space Types - Individual Stories Sliced'
|
821
|
+
# default for now 'Multiple Space Types - Individual Stories Sliced', 'Multiple Space Types - Simple Sliced', 'Single Space Type - Core and Perimeter'
|
822
|
+
bar_hash[:make_mid_story_surfaces_adiabatic] = false
|
823
|
+
bar_hash[:space_types] = bldg_space_types_floor_area_hash
|
824
|
+
bar_hash[:building_wwr_n] = wwr
|
825
|
+
bar_hash[:building_wwr_s] = wwr
|
826
|
+
bar_hash[:building_wwr_e] = wwr
|
827
|
+
bar_hash[:building_wwr_w] = wwr
|
828
|
+
|
829
|
+
runner = OpenStudio::Measure::OSRunner.new(OpenStudio::WorkflowJSON.new)
|
830
|
+
# remove non-resource objects not removed by removing the building
|
831
|
+
remove_non_resource_objects(runner, @model)
|
832
|
+
|
833
|
+
# party_walls_array to be used by orientation specific or fractional party wall values
|
834
|
+
party_walls_array = generate_party_walls # this is an array of arrays, where each entry is effective building story with array of directions
|
835
|
+
|
836
|
+
# populate bar hash with story information
|
837
|
+
bar_hash[:stories] = {}
|
838
|
+
num_stories.ceil.times do |i|
|
839
|
+
if party_walls_array.empty?
|
840
|
+
party_walls = []
|
841
|
+
else
|
842
|
+
party_walls = party_walls_array[i]
|
843
|
+
end
|
844
|
+
|
845
|
+
# add below_partial_story
|
846
|
+
if num_stories.ceil > num_stories && i == num_stories_round_up - 2
|
847
|
+
below_partial_story = true
|
848
|
+
else
|
849
|
+
below_partial_story = false
|
850
|
+
end
|
851
|
+
|
852
|
+
# bottom_story_ground_exposed_floor and top_story_exterior_exposed_roof already setup as bool
|
853
|
+
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: true, top_story_exterior_exposed_roof: true }
|
854
|
+
end
|
855
|
+
|
856
|
+
# store expected floor areas to check after bar made
|
857
|
+
target_areas = {}
|
858
|
+
bar_hash[:space_types].each do |k, v|
|
859
|
+
target_areas[k] = v[:floor_area]
|
860
|
+
end
|
861
|
+
|
862
|
+
# create bar
|
863
|
+
create_bar(runner, @model, bar_hash, 'Basements Ground Mid Top')
|
864
|
+
# using the default value for story multiplier for now 'Basements Ground Mid Top'
|
865
|
+
|
866
|
+
# check expected floor areas against actual
|
867
|
+
@model.getSpaceTypes.sort.each do |space_type|
|
868
|
+
next if !target_areas.key? space_type
|
869
|
+
|
870
|
+
# convert to IP
|
871
|
+
actual_ip = OpenStudio.convert(space_type.floorArea, 'm^2', 'ft^2').get
|
872
|
+
target_ip = OpenStudio.convert(target_areas[space_type], 'm^2', 'ft^2').get
|
873
|
+
|
874
|
+
if (space_type.floorArea - target_areas[space_type]).abs >= 1.0
|
875
|
+
if !bar_hash[:bar_division_method].include? 'Single Space Type'
|
876
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.generate_baseline_osm', "#{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)")
|
877
|
+
return false
|
878
|
+
else
|
879
|
+
# will see this if use Single Space type division method on multi-use building or single building type without whole building space type
|
880
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.generate_baseline_osm', "WARNING: #{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)")
|
881
|
+
end
|
882
|
+
end
|
883
|
+
end
|
884
|
+
|
885
|
+
# test for excessive exterior roof area (indication of problem with intersection and or surface matching)
|
886
|
+
ext_roof_area = @model.getBuilding.exteriorSurfaceArea - @model.getBuilding.exteriorWallArea
|
887
|
+
expected_roof_area = total_floor_area / num_stories.to_f
|
888
|
+
if ext_roof_area > expected_roof_area # only test if using whole-building area input
|
889
|
+
OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Building.generate_baseline_osm', 'Roof area larger than expected, may indicate problem with inter-floor surface intersection or matching.')
|
890
|
+
return false
|
891
|
+
end
|
892
|
+
|
893
|
+
# report final condition of model
|
894
|
+
OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Building.generate_baseline_osm', "The building finished with #{@model.getSpaces.size} spaces.")
|
895
|
+
|
896
|
+
return true
|
897
|
+
end
|
898
|
+
|
899
|
+
# generate party walls
|
900
|
+
def generate_party_walls
|
901
|
+
party_walls_array = []
|
902
|
+
if @party_wall_stories_north + @party_wall_stories_south + @party_wall_stories_east + @party_wall_stories_west > 0
|
903
|
+
|
904
|
+
# loop through effective number of stories add orientation specific party walls per user arguments
|
905
|
+
num_stories.ceil.times do |i|
|
906
|
+
test_value = i + 1 - bar_hash[:num_stories_below_grade]
|
907
|
+
|
908
|
+
array = []
|
909
|
+
if @party_wall_stories_north >= test_value
|
910
|
+
array << 'north'
|
911
|
+
end
|
912
|
+
if @party_wall_stories_south >= test_value
|
913
|
+
array << 'south'
|
914
|
+
end
|
915
|
+
if @party_wall_stories_east >= test_value
|
916
|
+
array << 'east'
|
917
|
+
end
|
918
|
+
if @party_wall_stories_west >= test_value
|
919
|
+
array << 'west'
|
920
|
+
end
|
921
|
+
|
922
|
+
# populate party_wall_array for this story
|
923
|
+
party_walls_array << array
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
# calculate party walls if using party_wall_fraction method
|
928
|
+
if @party_wall_fraction > 0 && !party_walls_array.empty?
|
929
|
+
runner.registerWarning('Both orientaiton and fractional party wall values arguments were populated, will ignore fractional party wall input')
|
930
|
+
elsif @party_wall_fraction > 0
|
931
|
+
|
932
|
+
# orientation of long and short side of building will vary based on building rotation
|
933
|
+
|
934
|
+
# full story ext wall area
|
935
|
+
typical_length_facade_area = @length * floor_height
|
936
|
+
typical_width_facade_area = @width * floor_height
|
937
|
+
|
938
|
+
# top story ext wall area, may be partial story
|
939
|
+
partial_story_multiplier = (1.0 - @num_stories_above_grade.ceil + @num_stories_above_grade)
|
940
|
+
area_multiplier = partial_story_multiplier
|
941
|
+
edge_multiplier = Math.sqrt(area_multiplier)
|
942
|
+
top_story_length = @length * edge_multiplier
|
943
|
+
top_story_width = @width * edge_multiplier
|
944
|
+
top_story_length_facade_area = top_story_length * floor_height
|
945
|
+
top_story_width_facade_area = top_story_width * floor_height
|
946
|
+
|
947
|
+
total_exterior_wall_area = 2 * (@length + @width) * (@num_stories_above_grade.ceil - 1.0) * floor_height + 2 * (top_story_length + top_story_width) * floor_height
|
948
|
+
target_party_wall_area = total_exterior_wall_area * @party_wall_fraction
|
949
|
+
|
950
|
+
width_counter = 0
|
951
|
+
width_area = 0.0
|
952
|
+
facade_area = typical_width_facade_area
|
953
|
+
until (width_area + facade_area >= target_party_wall_area) || (width_counter == @num_stories_above_grade.ceil * 2)
|
954
|
+
# update facade area for top story
|
955
|
+
if width_counter == @num_stories_above_grade.ceil - 1 || width_counter == @num_stories_above_grade.ceil * 2 - 1
|
956
|
+
facade_area = top_story_width_facade_area
|
957
|
+
else
|
958
|
+
facade_area = typical_width_facade_area
|
959
|
+
end
|
960
|
+
|
961
|
+
width_counter += 1
|
962
|
+
width_area += facade_area
|
963
|
+
|
964
|
+
end
|
965
|
+
width_area_remainder = target_party_wall_area - width_area
|
966
|
+
|
967
|
+
length_counter = 0
|
968
|
+
length_area = 0.0
|
969
|
+
facade_area = typical_length_facade_area
|
970
|
+
until (length_area + facade_area >= target_party_wall_area) || (length_counter == @num_stories_above_grade.ceil * 2)
|
971
|
+
# update facade area for top story
|
972
|
+
if length_counter == @num_stories_above_grade.ceil - 1 || length_counter == @num_stories_above_grade.ceil * 2 - 1
|
973
|
+
facade_area = top_story_length_facade_area
|
974
|
+
else
|
975
|
+
facade_area = typical_length_facade_area
|
976
|
+
end
|
977
|
+
|
978
|
+
length_counter += 1
|
979
|
+
length_area += facade_area
|
980
|
+
end
|
981
|
+
length_area_remainder = target_party_wall_area - length_area
|
982
|
+
|
983
|
+
# get rotation and best fit to adjust orientation for fraction party wall
|
984
|
+
rotation = @building_rotation % 360.0 # should result in value between 0 and 360
|
985
|
+
card_dir_array = [0.0, 90.0, 180.0, 270.0, 360.0]
|
986
|
+
# reverse array to properly handle 45, 135, 225, and 315
|
987
|
+
best_fit = card_dir_array.reverse.min_by { |x| (x.to_f - rotation).abs }
|
988
|
+
|
989
|
+
if ![90.0, 270.0].include? best_fit
|
990
|
+
width_card_dir = ['east', 'west']
|
991
|
+
length_card_dir = ['north', 'south']
|
992
|
+
else
|
993
|
+
# if rotation is closest to 90 or 270 then reverse which orientation is used for length and width
|
994
|
+
width_card_dir = ['north', 'south']
|
995
|
+
length_card_dir = ['east', 'west']
|
996
|
+
end
|
997
|
+
|
998
|
+
# if dont' find enough on short sides
|
999
|
+
if width_area_remainder <= typical_length_facade_area
|
1000
|
+
|
1001
|
+
num_stories.ceil.times do |i|
|
1002
|
+
if i + 1 <= @num_stories_below_grade
|
1003
|
+
party_walls_array << []
|
1004
|
+
next
|
1005
|
+
end
|
1006
|
+
if i + 1 - @num_stories_below_grade <= width_counter
|
1007
|
+
if i + 1 - @num_stories_below_grade <= width_counter - @num_stories_above_grade
|
1008
|
+
party_walls_array << width_card_dir
|
1009
|
+
else
|
1010
|
+
party_walls_array << [width_card_dir.first]
|
1011
|
+
end
|
1012
|
+
else
|
1013
|
+
party_walls_array << []
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
else
|
1018
|
+
# use long sides instead
|
1019
|
+
num_stories.ceil.times do |i|
|
1020
|
+
if i + 1 <= @num_stories_below_grade
|
1021
|
+
party_walls_array << []
|
1022
|
+
next
|
1023
|
+
end
|
1024
|
+
if i + 1 - @num_stories_below_grade <= length_counter
|
1025
|
+
if i + 1 - @num_stories_below_grade <= length_counter - @num_stories_above_grade
|
1026
|
+
party_walls_array << length_card_dir
|
1027
|
+
else
|
1028
|
+
party_walls_array << [length_card_dir.first]
|
1029
|
+
end
|
1030
|
+
else
|
1031
|
+
party_walls_array << []
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
# TODO: - currently won't go past making two opposing sets of walls party walls. Info and registerValue are after create_bar in measure.rb
|
1036
|
+
end
|
1037
|
+
party_walls_array
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
# write baseline model to osm file
|
1041
|
+
# @param dir [String]
|
1042
|
+
def write_osm(dir)
|
1043
|
+
@model.save("#{dir}/in.osm", true)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
# write parameters to xml file
|
1047
|
+
def prepare_final_xml
|
1048
|
+
@base_xml.elements["#{@ns}:OccupancyLevels/#{@ns}:OccupancyLevel/#{@ns}:OccupantQuantity"].text = @occupant_quantity if !@occupant_quantity.nil?
|
1049
|
+
@base_xml.elements["#{@ns}:SpatialUnits/#{@ns}:SpatialUnit/#{@ns}:NumberOfUnits"].text = @number_of_units if !@number_of_units.nil?
|
1050
|
+
|
1051
|
+
# Add new element in the XML file
|
1052
|
+
add_user_defined_field_to_xml_file('OpenStudioModelName', @name)
|
1053
|
+
add_user_defined_field_to_xml_file('StandardTemplateYearOfConstruction', @built_year)
|
1054
|
+
add_user_defined_field_to_xml_file('StandardTemplate', @standard_template)
|
1055
|
+
add_user_defined_field_to_xml_file('BuildingRotation', @building_rotation)
|
1056
|
+
add_user_defined_field_to_xml_file('FloorHeight', @floor_height)
|
1057
|
+
add_user_defined_field_to_xml_file('WindowWallRatio', @wwr)
|
1058
|
+
add_user_defined_field_to_xml_file('PartyWallStoriesNorth', @party_wall_stories_north)
|
1059
|
+
add_user_defined_field_to_xml_file('PartyWallStoriesSouth', @party_wall_stories_south)
|
1060
|
+
add_user_defined_field_to_xml_file('PartyWallStoriesEast', @party_wall_stories_east)
|
1061
|
+
add_user_defined_field_to_xml_file('PartyWallStoriesWest', @party_wall_stories_west)
|
1062
|
+
add_user_defined_field_to_xml_file('Width', @width)
|
1063
|
+
add_user_defined_field_to_xml_file('Length', @length)
|
1064
|
+
add_user_defined_field_to_xml_file('PartyWallFraction', @party_wall_fraction)
|
1065
|
+
add_user_defined_field_to_xml_file('ModelNumberThermalZones', @model.getThermalZones.size)
|
1066
|
+
add_user_defined_field_to_xml_file('ModelNumberSpaces', @model.getSpaces.size)
|
1067
|
+
add_user_defined_field_to_xml_file('ModelNumberStories', @model.getBuildingStorys.size)
|
1068
|
+
add_user_defined_field_to_xml_file('ModelNumberPeople', @model.getBuilding.numberOfPeople)
|
1069
|
+
add_user_defined_field_to_xml_file('ModelFloorArea(m2)', @model.getBuilding.floorArea)
|
1070
|
+
|
1071
|
+
wf = @model.weatherFile.get
|
1072
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileName', wf.nameString)
|
1073
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileDataSource', wf.dataSource)
|
1074
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileCity', wf.city)
|
1075
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileStateProvinceRegion', wf.stateProvinceRegion)
|
1076
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileLatitude', wf.latitude)
|
1077
|
+
add_user_defined_field_to_xml_file('ModelWeatherFileLongitude', wf.longitude)
|
1078
|
+
prepare_final_xml_for_spatial_element
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
# get space types
|
1082
|
+
# @return [array<OpenStudio::Model::SpaceType>]
|
1083
|
+
def get_space_types
|
1084
|
+
return @model.getSpaceTypes
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
# get peak occupancy
|
1088
|
+
# @return [hash<string, float>]
|
1089
|
+
def get_peak_occupancy
|
1090
|
+
peak_occupancy = {}
|
1091
|
+
if @occupant_quantity
|
1092
|
+
peak_occupancy[xget_id] = @occupant_quantity.to_f
|
1093
|
+
return peak_occupancy
|
1094
|
+
end
|
1095
|
+
@building_sections.each do |section|
|
1096
|
+
peak_occupancy[section.xget_id] = section.get_peak_occupancy.to_f if section.get_peak_occupancy
|
1097
|
+
end
|
1098
|
+
return peak_occupancy
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# get floor area
|
1102
|
+
# @return [hash<string, float>]
|
1103
|
+
def get_floor_area
|
1104
|
+
floor_area = {}
|
1105
|
+
if @total_floor_area
|
1106
|
+
floor_area[xget_id] = @total_floor_area.to_f
|
1107
|
+
end
|
1108
|
+
@building_sections.each do |section|
|
1109
|
+
if section.get_floor_area
|
1110
|
+
floor_area[section.xget_id] = section.get_floor_area
|
1111
|
+
end
|
1112
|
+
end
|
1113
|
+
return floor_area
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
attr_reader :building_rotation, :name, :length, :width, :num_stories_above_grade, :num_stories_below_grade, :floor_height, :space, :wwr,
|
1117
|
+
:occupant_quantity, :number_of_units, :built_year, :year_major_remodel, :building_sections
|
1118
|
+
end
|
1119
|
+
end
|