# ******************************************************************************* # OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC. # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # (1) Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # (2) Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # (3) Neither the name of the copyright holder nor the names of any contributors # may be used to endorse or promote products derived from this software without # specific prior written permission from the respective party. # # (4) Other than as required in clauses (1) and (2), distributions in any form # of modifications or other derivative works may not use the "OpenStudio" # trademark, "OS", "os", or any other confusingly similar designation without # specific prior written permission from Alliance for Sustainable Energy, LLC. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ******************************************************************************* # see the URL below for information on how to write OpenStudio measures # http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/ begin # load OpenStudio measure libraries from common location require 'measure_resources/os_lib_helper_methods' rescue LoadError # common location unavailable, load from local resources require_relative 'resources/os_lib_helper_methods' end require_relative 'resources/ScheduleTranslator' # start the measure class MergeSpacesFromExternalFile < OpenStudio::Measure::ModelMeasure # human readable name def name return 'Merge Spaces from External File' end # human readable description def description return 'The measure lets you merge the contents from spaces in an external file into spaces in your current model. Spaces are identifed by the space name being the same in the two models. If a space is in the current model but not the external model they will be deleted. If a space is in both models the selecd elments willl be udpated based on the external model. If a space is not in the current model but is in the external model it will be cloned into the current model.' end # human readable description of modeling approach def modeler_description return "A string argument is used to identify the external model that is being merged into the current model. user agrument determine which kind of objets are brought over from the external model. Some characteristics that can be merged are surfaces, shading surface groups, interior partition groups, daylight controls, and internal loads. Additionally thermal zone, space space type, building story, construction set, and schedule set assignments names will can taken from the space, but objets they represent won't be cloned if objects by that name already exist in the current model." end # define the arguments that the user will input def arguments(model) args = OpenStudio::Measure::OSArgumentVector.new # make an argument for external_model_name external_model_name = OpenStudio::Measure::OSArgument.makeStringArgument('external_model_name', true) external_model_name.setDisplayName('External OSM File Name') external_model_name.setDescription('Name of the model to merge into current model. This is the filename with the extension (e.g. MyModel.osm). Optionally this can inclucde the full file path, but for most use cases should just be file name.') args << external_model_name # merge geometry merge_geometry = OpenStudio::Measure::OSArgument.makeBoolArgument('merge_geometry', true) merge_geometry.setDisplayName('Merge Geometry from External Model') merge_geometry.setDescription('Replace geometry in current model with geometry from external model.') merge_geometry.setDefaultValue(true) args << merge_geometry # merge internal loads merge_loads = OpenStudio::Measure::OSArgument.makeBoolArgument('merge_loads', true) merge_loads.setDisplayName('Merge Internal Loads from External Model') merge_loads.setDescription('Replace internal loads directly assigned so spaces in current model with internal loads directly assigned to spaces frp, external model. If a schedule is hard assigned to a load instance, it will be brought over as well.') merge_loads.setDefaultValue(true) args << merge_loads # merge space attributes merge_attribute_names = OpenStudio::Measure::OSArgument.makeBoolArgument('merge_attribute_names', true) merge_attribute_names.setDisplayName('Merge Space Attribute names from External Model') merge_attribute_names.setDescription('Replace space attribute names in current model with space attribute names from external models. When external model has unkown attribute name that object will be cloned into the current model.') merge_attribute_names.setDefaultValue(true) args << merge_attribute_names # add_spaces add_spaces = OpenStudio::Measure::OSArgument.makeBoolArgument('add_spaces', true) add_spaces.setDisplayName('Add Spaces to Current Model') add_spaces.setDescription('Add spaces to current model that exist in external model but do not exist in current model.') add_spaces.setDefaultValue(true) args << add_spaces # remove_spaces remove_spaces = OpenStudio::Measure::OSArgument.makeBoolArgument('remove_spaces', true) remove_spaces.setDisplayName('Remove Spaces from Current Model') remove_spaces.setDescription('Remove spaces from current model that do not exist in externa model.') remove_spaces.setDefaultValue(true) args << remove_spaces # merge schedules # doesn't bring in schedules from external model that are not used in current model, this measures isn't mean to load in resources that are not used merge_schedules = OpenStudio::Measure::OSArgument.makeBoolArgument('merge_schedules', true) merge_schedules.setDisplayName('Merge Schedules from External Model') merge_schedules.setDescription("This isn't limited to spaces, this will replace any scheules in the current model with schedules of the same name in the external model. It will not replace schedule named 'a' from an internal load in th emodel with a schedule named 'b' from an internal load by that same name in the external model, to perform that task currently, you must merge loads.") merge_schedules.setDefaultValue(true) args << merge_schedules # convert compact to ruleset compact_to_ruleset = OpenStudio::Measure::OSArgument.makeBoolArgument('compact_to_ruleset', true) compact_to_ruleset.setDisplayName('Convert Merged Schedule Compact objects to Schedule Ruleset.') compact_to_ruleset.setDescription('Will convert any imported schedules to Schedule Ruleset instead of Schedule Compact and will connect them to objects that had previously refered to the Schedule Compact object.') compact_to_ruleset.setDefaultValue(true) args << compact_to_ruleset # TODO: - in future have arg for logic when resource objects exist in both models # (constructions, materials for geometry, and schedule and load defs for internal loads) return args end def remove_space_loads(space) # remove loads from target space space.internalMass.each(&:remove) space.people.each(&:remove) space.lights.each(&:remove) space.luminaires.each(&:remove) space.electricEquipment.each(&:remove) space.gasEquipment.each(&:remove) space.hotWaterEquipment.each(&:remove) space.steamEquipment.each(&:remove) space.otherEquipment.each(&:remove) space.spaceInfiltrationDesignFlowRates.each(&:remove) space.spaceInfiltrationEffectiveLeakageAreas.each(&:remove) space.resetDesignSpecificationOutdoorAir end def reassign_loads(target_space, source_space) # re-assign loads from source space to target space source_space.internalMass.each { |instance| instance.setSpace(target_space) } source_space.people.each { |instance| instance.setSpace(target_space) } source_space.lights.each { |instance| instance.setSpace(target_space) } source_space.luminaires.each { |instance| instance.setSpace(target_space) } source_space.electricEquipment.each { |instance| instance.setSpace(target_space) } source_space.gasEquipment.each { |instance| instance.setSpace(target_space) } source_space.hotWaterEquipment.each { |instance| instance.setSpace(target_space) } source_space.steamEquipment.each { |instance| instance.setSpace(target_space) } source_space.otherEquipment.each { |instance| instance.setSpace(target_space) } source_space.spaceInfiltrationDesignFlowRates.each { |instance| instance.setSpace(target_space) } source_space.spaceInfiltrationEffectiveLeakageAreas.each { |instance| instance.setSpace(target_space) } target_space.setDesignSpecificationOutdoorAir(source_space.designSpecificationOutdoorAir.get) end def remove_space_attributes(space) space.resetSpaceType space.resetThermalZone space.resetBuildingStory space.resetDefaultConstructionSet space.resetDefaultScheduleSet end # don't clone object if it already exists in the model # todo see if space type of that name already exist in the model, if then clone in the requested on def reassign_space_attributes(target_space, source_space_hash, model) # re-assign space types if source_space_hash[:space_type].is_initialized target_space_type = source_space_hash[:space_type].get if !(model.getModelObjectByName(target_space_type.name.get).is_initialized && model.getModelObjectByName(target_space_type.name.get).get.to_SpaceType.is_initialized) # clone object target_space_type = source_space_hash[:space_type].get.clone(model).to_SpaceType.get end target_space.setSpaceType(target_space_type) else target_space.resetSpaceType end # re-assign thermal zones if source_space_hash[:thermal_zone].is_initialized target_zone = source_space_hash[:thermal_zone].get if !(model.getModelObjectByName(target_zone.name.get).is_initialized && model.getModelObjectByName(target_zone.name.get).get.to_ThermalZone.is_initialized) # clone object target_zone = source_space_hash[:thermal_zone].get.clone(model).to_ThermalZone.get end target_space.setThermalZone(target_zone) else target_space.resetThermalZone end # re-assign building story if source_space_hash[:building_story].is_initialized target_building_story = source_space_hash[:building_story].get if !(model.getModelObjectByName(target_building_story.name.get).is_initialized && model.getModelObjectByName(target_building_story.name.get).get.to_BuildingStory.is_initialized) # clone object target_building_story = source_space_hash[:building_story].get.clone(model).to_BuildingStory.get end target_space.setBuildingStory(target_building_story) else target_space.resetBuildingStory end # re-assign construction set if source_space_hash[:const_set].is_initialized target_const_set = source_space_hash[:const_set].get if !(model.getModelObjectByName(target_const_set.name.get).is_initialized && model.getModelObjectByName(target_const_set.name.get).get.to_DefaultConstructionSet.is_initialized) # clone object target_const_set = source_space_hash[:const_set].get.clone(model).to_DefaultConstructionSet.get end target_space.setDefaultConstructionSet(target_const_set) else target_space.resetDefaultConstructionSet end # re-assign schedule set if source_space_hash[:sch_set].is_initialized target_schedule_set = source_space_hash[:sch_set].get if !(model.getModelObjectByName(target_schedule_set.name.get).is_initialized && model.getModelObjectByName(target_schedule_set.name.get).get.to_DefaultScheduleSet.is_initialized) # clone object target_schedule_set = source_space_hash[:sch_set].get.clone(model).to_DefaultScheduleSet.get end target_space.setDefaultConstructionSet(target_schedule_set) else target_space.resetDefaultConstructionSet end end # define what happens when the measure is run def run(model, runner, user_arguments) super(model, runner, user_arguments) if !runner.validateUserArguments(arguments(model), user_arguments) return false end # assign the user inputs to variables args = OsLib_HelperMethods.createRunVariables(runner, model, user_arguments, arguments(model)) if !args then return false end # initial condition runner.registerInitialCondition("The model started with #{model.getSpaces.size} spaces.") if args['merge_geometry'] == false && args['merge_loads'] == false && args['merge_attribute_names'] == false && args['merge_schedules'] == false runner.registerAsNotApplicable('No change made in model') return true end # create hash of spaces in current model current_spaces_hash = {} model.getSpaces.sort.each do |space| hash = {} # populate space attributes hash[:space_type] = space.spaceType hash[:thermal_zone] = space.thermalZone hash[:building_story] = space.buildingStory hash[:const_set] = space.defaultConstructionSet hash[:sch_set] = space.defaultScheduleSet # populate internal laods hash[:int_mass] = space.internalMass hash[:people] = space.people hash[:lights] = space.lights hash[:luminaires] = space.luminaires hash[:elec_equip] = space.electricEquipment hash[:hot_water_equip] = space.hotWaterEquipment hash[:steam_equip] = space.steamEquipment hash[:other_equip] = space.otherEquipment hash[:infil] = space.spaceInfiltrationDesignFlowRates hash[:infil_leakage] = space.spaceInfiltrationEffectiveLeakageAreas hash[:oa] = space.designSpecificationOutdoorAir # store space object itself hash[:space] = space # add to main hash current_spaces_hash[space.name.get.to_s] = hash end # find external model osw_file = runner.workflow.findFile(args['external_model_name']) if osw_file.is_initialized osmPath_2 = osw_file.get.to_s else runner.registerError("Did not find #{args['external_model_name']} in paths described in OSW file.") return false end # Open OSM file model_2 = OpenStudio::Model::Model.load(OpenStudio::Path.new(osmPath_2)).get runner.registerInfo("#{args['osm_file_name']} has #{model_2.getSpaces.size} spaces") # create hash of spaces in external model external_spaces_hash = {} model_2.getSpaces.sort.each do |space| hash = {} # populate space attributes hash[:space_type] = space.spaceType hash[:thermal_zone] = space.thermalZone hash[:building_story] = space.buildingStory hash[:const_set] = space.defaultConstructionSet hash[:sch_set] = space.defaultScheduleSet # populate internal laods hash[:int_mass] = space.internalMass hash[:people] = space.people hash[:lights] = space.lights hash[:luminaires] = space.luminaires hash[:elec_equip] = space.electricEquipment hash[:hot_water_equip] = space.hotWaterEquipment hash[:steam_equip] = space.steamEquipment hash[:other_equip] = space.otherEquipment hash[:infil] = space.spaceInfiltrationDesignFlowRates hash[:infil_leakage] = space.spaceInfiltrationEffectiveLeakageAreas hash[:oa] = space.designSpecificationOutdoorAir # store space object itself hash[:space] = space # add to main hash external_spaces_hash[space.name.get.to_s] = hash end # look for matching space names external_spaces_hash.each do |space_name, hash| if current_spaces_hash.key?(space_name) runner.registerInfo("Merging #{space_name} from external model to current model") if args['merge_geometry'] # rename current space current_spaces_hash[space_name][:space].setName('to be deleted') # remove loads before cloning if they will not be used if !(args['merge_loads']) remove_space_loads(hash[:space]) end # remove space attributes before cloning if they will not be used if !(args['merge_attribute_names']) remove_space_attributes(hash[:space]) end final_space = hash[:space].clone(model).to_Space.get else final_space = current_spaces_hash[space_name][:space] # remove loads before if they will not be used if args['merge_loads'] remove_space_loads(final_space) end # remove space attributes if they will not be used if args['merge_attribute_names'] remove_space_attributes(final_space) end end # merge internal loads if requested if args['merge_loads'] && args['merge_geometry'] # nothing to do, correct loads are already with space cloned from external model elsif args['merge_loads'] == false && args['merge_geometry'] == false # nothing to do, correct loads are already with space from current model, nothing brought in from external model elsif args['merge_loads'] && args['merge_geometry'] == false # clone in and reassign load instances from external model temp_space = hash[:space].clone(model).to_Space.get reassign_loads(final_space, temp_space) temp_space.remove elsif args['merge_loads'] == false && args['merge_geometry'] # reassign load instances from external model reassign_loads(final_space, current_spaces_hash[space_name][:space]) end # merge space attribute names if requested if args['merge_attribute_names'] && args['merge_geometry'] # remap attributes reassign_space_attributes(final_space, hash, model) elsif args['merge_attribute_names'] == false && args['merge_geometry'] == false # nothing to do, correct attributes are already with space from current model, nothing brought in from external model elsif args['merge_attribute_names'] && args['merge_geometry'] == false # re-assign attributes based on names in external space hash. If object of correct name is not found in current model then clone object from external model reassign_space_attributes(final_space, hash, model) elsif args['merge_attribute_names'] == false && args['merge_geometry'] # re-assign attribute names back to what was used in current model space reassign_space_attributes(final_space, current_spaces_hash[space_name], model) end # remove current space if replacement geometry was cloned from external model if args['merge_geometry'] current_spaces_hash[space_name][:space].remove end elsif args['add_spaces'] # clone space into model new_space_from_ext = hash[:space].clone(model).to_Space.get reassign_space_attributes(new_space_from_ext, hash, model) runner.registerInfo("Adding #{space_name} from external model to current model. Since it doesn't exist in current model bring in all characteristics reguardless of user argument values.") end end # remove spaces in current model that are not in external model current_spaces_hash.each do |space_name, hash| if !external_spaces_hash.key?(space_name) && args['remove_spaces'] hash[:space].remove runner.registerInfo("Removing #{space_name} from current model, since it doesn't exist in external model.") end end if args['merge_geometry'] # put all of the spaces in the model into a vector spaces = OpenStudio::Model::SpaceVector.new model.getSpaces.each do |space| spaces << space end # match surfaces for each space in the vector OpenStudio::Model.matchSurfaces(spaces) runner.registerInfo('Matching surfaces..') end if args['merge_schedules'] model_2.getSchedules.each do |schedule| # swap schedule if it is already in the model if model.getScheduleByName(schedule.name.get).is_initialized # store name and get schedule orig_name = schedule.name.get schedule_old = model.getScheduleByName(schedule.name.get).get # if schedule is compact convert it to ruleset and clone if args['compact_to_ruleset'] && schedule.to_ScheduleCompact.is_initialized sch_translator = ScheduleTranslator.new(model_2, schedule) os_sch = sch_translator.translate cloned_schedule = os_sch.clone(model).to_Schedule.get else cloned_schedule = schedule.clone(model).to_Schedule.get end # replace uses of schedule, model.swap(compact,os_sch) doesn't work schedule_old.sources.each do |source| source_index = source.getSourceIndices(schedule_old.handle) source_index.each do |field| source.setPointer(field, cloned_schedule.handle) end end schedule_old.remove cloned_schedule.setName(orig_name) end end end # register final condition runner.registerFinalCondition("The model finished with #{model.getSpaces.size} spaces.") return true end end # register the measure to be used by the application MergeSpacesFromExternalFile.new.registerWithApplication