buildingsync 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|