Class: InjectOsmGeometryIntoAnExternalIdf

Inherits:
OpenStudio::Measure::EnergyPlusMeasure
  • Object
show all
Defined in:
lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb

Overview

start the measure

Instance Method Summary collapse

Instance Method Details

#arguments(workspace) ⇒ Object

define the arguments that the user will input



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb', line 27

def arguments(workspace)
  args = OpenStudio::Measure::OSArgumentVector.new

  # make an argument for external idf
  source_idf_path = OpenStudio::Measure::OSArgument.makeStringArgument('source_idf_path', true)
  source_idf_path.setDisplayName('External IDF File Name')
  source_idf_path.setDescription('Name of the IDF file to inject OSM geometry into. This is the filename with the extension (e.g. MyModel.idf). Optionally this can inclucde the full file path, but for most use cases should just be file name.')
  args << source_idf_path

  # make an argument to add new zone true/false
  merge_geometry_from_osm = OpenStudio::Measure::OSArgument.makeBoolArgument('merge_geometry_from_osm', true)
  merge_geometry_from_osm.setDisplayName('Merge Geometry From OpenStudio Model into Source IDF File?')
  merge_geometry_from_osm.setDescription('If set to false the entire external IDF will replace the initial IDF generated from the OSM file.')
  merge_geometry_from_osm.setDefaultValue(true)
  args << merge_geometry_from_osm

  return args
end

#insert_object(object, map) ⇒ Object

this is used to clone objects from generated idf to source idf



66
67
68
69
70
# File 'lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb', line 66

def insert_object(object, map)
  if map[object.handle.to_s].nil?
    map[object.handle.to_s] = object
  end
end

#nameObject

define the name that a user will see, this method may be deprecated as the display name in PAT comes from the name field in measure.xml



22
23
24
# File 'lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb', line 22

def name
  return 'InjectOsmGeometryIntoAnExternalIdf'
end

#run(workspace, runner, user_arguments) ⇒ Object

define what happens when the measure is run



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/measures/InjectOsmGeometryIntoAnExternalIdf/measure.rb', line 47

def run(workspace, runner, user_arguments)
  super(workspace, runner, user_arguments)

  # use the built-in error checking
  if !runner.validateUserArguments(arguments(workspace), user_arguments)
    return false
  end

  # assign the user inputs to variables
  source_idf_path = runner.getStringArgumentValue('source_idf_path', user_arguments)
  merge_geometry_from_osm = runner.getBoolArgumentValue('merge_geometry_from_osm', user_arguments)

  # check the source_idf_path for reasonableness
  if source_idf_path == ''
    runner.registerError('No Source IDF File Path was Entered.')
    return false
  end

  # this is used to clone objects from generated idf to source idf
  def insert_object(object, map)
    if map[object.handle.to_s].nil?
      map[object.handle.to_s] = object
    end
  end

  # find source_idf_path
  osw_file = runner.workflow.findFile(source_idf_path)
  if osw_file.is_initialized
    source_idf_path = osw_file.get.to_s
  else
    runner.registerError("Did not find #{source_idf_path} in paths described in OSW file.")
    return false
  end

  # get model from path and error if empty
  source_idf = OpenStudio::Workspace.load(OpenStudio::Path.new(source_idf_path))
  if source_idf.empty?
    runner.registerError("Cannot load #{source_idf_path}")
    return false
  end
  source_idf = source_idf.get

  # TODO: - use this to preserve links of source idf objects to zones and surfaces.
  # to preserve links I need to get an IdfFile from Workspace
  source_idfFile = source_idf.toIdfFile

  # get source_idf surfaces
  source_idf_BuildingSurfaces = source_idfFile.getObjectsByType('BuildingSurface_Detailed'.to_IddObjectType)

  # reporting initial condition of model
  runner.registerInitialCondition("The source IDF has #{source_idf_BuildingSurfaces.size} BuildingSurface_Detail objects.")

  if merge_geometry_from_osm == true

    # remove geometry objects supported by OpenStudio

    # TODO: - see if there is a way to preserve links in objects that refer to surfaces of same name that end up getting added back later
    source_idf_BuildingSurfaces.each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('FenestrationSurface_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Zone_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Building_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Site_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end

    # remove geometry not supported by OpenStudio that will have been converted to a supported type on forward
    source_idfFile.getObjectsByType('Wall_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('RoofCeiling_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Floor_Detailed'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Wall_Exterior'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Wall_Adiabatic'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Wall_Underground'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Wall_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Roof'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Ceiling_Adiabatic'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Ceiling_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Floor_GroundContact'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Floor_Adiabatic'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Floor_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Window'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Door'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('GlazedDoor'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Window_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Door_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('GlazedDoor_Interzone'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Site'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Building'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Overhang'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Overhang_Projection'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Fin'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end
    source_idfFile.getObjectsByType('Shading_Fin_Projection'.to_IddObjectType).each do |object|
      source_idfFile.removeObject(object)
    end

    # not removing internal mass objects because the user may want to keep these independent of the geometry.

    # rename thermal zones without " ThermalZone"
    # eventually it would be nice of reverse translation didn't rename the thermal zone. Then we can remove this
    workspace.getObjectsByType('Zone'.to_IddObjectType).each do |object|
      thermalZoneName = object.getString(0).get
      thermalZoneName = thermalZoneName.gsub(' Thermal Zone', '')
      object.setString(0, thermalZoneName)
    end

    # map of handle to idf object that we want to add
    objectsToAdd = {}

    # array of thermal zones in source_idf
    thermalZonesInSourceIdf = []
    source_idfFile.getObjectsByType('Zone'.to_IddObjectType).each do |object|
      thermalZonesInSourceIdf << object.getString(0).get
    end

    # array of thermal zones in generated idf
    thermalZonesInGeneratedIdf = []
    workspace.getObjectsByType('Zone'.to_IddObjectType).each do |object|
      thermalZonesInGeneratedIdf << object.getString(0).get
    end

    # remove all zones. Will be added from workspace with updated origin and rotation
    # using source_idfFile instead of source_idf (workspace)so objects don't loose link to zones
    source_idfFile.getObjectsByType('Zone'.to_IddObjectType).each do |object|
      if !thermalZonesInGeneratedIdf.include? object.getString(0).get
        runner.registerInfo("Removing #{object.getString(0).get} from the idf. This zone was in the source idf but not the generated idf.")
        source_idfFile.removeObject(object)
      else
        source_idfFile.removeObject(object)
      end
    end

    # clone all zones geometry from generated idf into source idf
    # add any zones from generated idf into source idf
    workspace.getObjectsByType('Zone'.to_IddObjectType).each do |object|
      if !thermalZonesInSourceIdf.include? object.getString(0).get
        insert_object(object, objectsToAdd)
        runner.registerInfo("Adding #{object.getString(0).get} to the idf. This zone was not in the source idf but was in the generated idf.")
      else
        insert_object(object, objectsToAdd)
      end
    end
    workspace.getObjectsByType('BuildingSurface_Detailed'.to_IddObjectType).each do |object|
      insert_object(object, objectsToAdd)
    end
    workspace.getObjectsByType('FenestrationSurface_Detailed'.to_IddObjectType).each do |object|
      insert_object(object, objectsToAdd)
    end
    workspace.getObjectsByType('Shading_Zone_Detailed'.to_IddObjectType).each do |object|
      insert_object(object, objectsToAdd)
    end
    workspace.getObjectsByType('Shading_Building_Detailed'.to_IddObjectType).each do |object|
      insert_object(object, objectsToAdd)
    end
    workspace.getObjectsByType('Shading_Site_Detailed'.to_IddObjectType).each do |object|
      insert_object(object, objectsToAdd)
    end

    # not adding internal mass objects for now, but this could be added later.
    # If we do add this need to think about round trip workflow since we don't strip these objects out of the source_idf

    # create a list of objects to add
    objects = OpenStudio::IdfObjectVector.new
    objectsToAdd.each_value do |object|
      idfObject = object.idfObject()
      objects << idfObject
    end

    # add the objects to the source_idf
    source_idfFile.addObjects(objects)

    # store building rotation from generated model
    building = workspace.getObjectsByType('Building'.to_IddObjectType)
    building_rotation = building[0].getString(1).get

    # apply rotation to source idf
    building = source_idfFile.getObjectsByType('Building'.to_IddObjectType)
    building_rotation = building[0].setString(1, building_rotation)

  end

  # removing everything from workspace
  workspace.objects.each(&:remove)

  # map of handle to idf object that we want to add
  objectsToAddFromSourceIDF = {}

  # make new workspace objects from source_idf
  source_idfFile.objects.each do |object|
    insert_object(object, objectsToAddFromSourceIDF)
  end

  # create a list of objects to add
  objects = OpenStudio::IdfObjectVector.new
  objectsToAddFromSourceIDF.each_value do |idfObject|
    # idfObject = object.idfObject()
    objects << idfObject
  end

  # add the objects to the final idf
  workspace.addObjects(objects)

  # get source_idf surfaces
  final_idf_BuildingSurfaces = workspace.getObjectsByType('BuildingSurface_Detailed'.to_IddObjectType)

  # reporting final condition of model
  runner.registerFinalCondition("The resulting IDF has #{final_idf_BuildingSurfaces.size} BuildingSurface_Detail objects.")

  return true
end