# ******************************************************************************* # OpenStudio(R), Copyright (c) 2008-2020, 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. # ******************************************************************************* module OsLib_HelperMethods # populate choice argument from model objects def self.populateChoiceArgFromModelObjects(model, modelObject_args_hash, includeBuilding = nil) # populate choice argument for constructions that are applied to surfaces in the model modelObject_handles = OpenStudio::StringVector.new modelObject_display_names = OpenStudio::StringVector.new # looping through sorted hash of constructions modelObject_args_hash.sort.map do |key, value| modelObject_handles << value.handle.to_s modelObject_display_names << key end unless includeBuilding.nil? # add building to string vector with space type building = model.getBuilding modelObject_handles << building.handle.to_s modelObject_display_names << includeBuilding end result = { 'modelObject_handles' => modelObject_handles, 'modelObject_display_names' => modelObject_display_names } return result end # create variables in run from user arguments def self.createRunVariables(runner, model, user_arguments, arguments) result = {} error = false # use the built-in error checking unless runner.validateUserArguments(arguments, user_arguments) error = true runner.registerError('Invalid argument values.') end user_arguments.each do |argument| # get argument info arg = user_arguments[argument] arg_type = arg.print.lines($/)[1] # create argument variable if arg_type.include? 'Double, Required' eval("result[\"#{arg.name}\"] = runner.getDoubleArgumentValue(\"#{arg.name}\", user_arguments)") elsif arg_type.include? 'Integer, Required' eval("result[\"#{arg.name}\"] = runner.getIntegerArgumentValue(\"#{arg.name}\", user_arguments)") elsif arg_type.include? 'String, Required' eval("result[\"#{arg.name}\"] = runner.getStringArgumentValue(\"#{arg.name}\", user_arguments)") elsif arg_type.include? 'Boolean, Required' eval("result[\"#{arg.name}\"] = runner.getBoolArgumentValue(\"#{arg.name}\", user_arguments)") elsif arg_type.include? 'Choice, Required' eval("result[\"#{arg.name}\"] = runner.getStringArgumentValue(\"#{arg.name}\", user_arguments)") else puts 'not setup to handle all argument types yet, or any optional arguments' end end if error return false else return result end end # check choice argument made from model objects def self.checkChoiceArgFromModelObjects(object, variableName, to_ObjectType, runner, user_arguments) apply_to_building = false modelObject = nil if object.empty? handle = runner.getStringArgumentValue(variableName, user_arguments) if handle.empty? runner.registerError("No #{variableName} was chosen.") # this logic makes this not work on an optional model object argument else runner.registerError("The selected #{variableName} with handle '#{handle}' was not found in the model. It may have been removed by another measure.") end return false else if !eval("object.get.#{to_ObjectType}").empty? modelObject = eval("object.get.#{to_ObjectType}").get elsif !object.get.to_Building.empty? apply_to_building = true else runner.registerError("Script Error - argument not showing up as #{variableName}.") return false end end result = { 'modelObject' => modelObject, 'apply_to_building' => apply_to_building } end # check choice argument made from model objects def self.checkOptionalChoiceArgFromModelObjects(object, variableName, to_ObjectType, runner, user_arguments) apply_to_building = false modelObject = nil if object.empty? handle = runner.getOptionalStringArgumentValue(variableName, user_arguments) if handle.empty? # do nothing, this is a valid option puts 'hello' modelObject = nil apply_to_building = false else runner.registerError("The selected #{variableName} with handle '#{handle}' was not found in the model. It may have been removed by another measure.") return false end else if !eval("object.get.#{to_ObjectType}").empty? modelObject = eval("object.get.#{to_ObjectType}").get elsif !object.get.to_Building.empty? apply_to_building = true else runner.registerError("Script Error - argument not showing up as #{variableName}.") return false end end result = { 'modelObject' => modelObject, 'apply_to_building' => apply_to_building } end # check value of double arguments def self.checkDoubleAndIntegerArguments(runner, user_arguments, arg_check_hash) error = false # get hash values min = arg_check_hash['min'] max = arg_check_hash['max'] min_eq_bool = arg_check_hash['min_eq_bool'] max_eq_bool = arg_check_hash['max_eq_bool'] arg_check_hash['arg_array'].each do |argument| argument = user_arguments[argument] # get arg values arg_value = nil if argument.hasValue arg_value = argument.valueDisplayName.to_f # instead of valueAsDouble so it allows integer arguments as well elsif argument.hasDefaultValue arg_value = argument.defaultValueDisplayName.to_f end arg_display = argument.displayName unless min.nil? if min_eq_bool if arg_value < min runner.registerError("Please enter value greater than or equal to #{min} for #{arg_display}.") # add in argument display name error = true end else if arg_value <= min runner.registerError("Please enter value greater than #{min} for #{arg_display}.") # add in argument display name error = true end end end unless max.nil? if max_eq_bool if arg_value > max runner.registerError("Please enter value less than or equal to #{max} for #{arg_display}.") # add in argument display name error = true end else if arg_value >= max runner.registerError("Please enter value less than #{max} for #{arg_display}.") # add in argument display name error = true end end end end # check for any errors if error return false else return true end end # open channel to log info/warning/error messages def self.setup_log_msgs(runner, debug = false) # Open a channel to log info/warning/error messages @msg_log = OpenStudio::StringStreamLogSink.new if debug @msg_log.setLogLevel(OpenStudio::Debug) else @msg_log.setLogLevel(OpenStudio::Info) end @start_time = Time.new @runner = runner end # Get all the log messages and put into output # for users to see. def self.log_msgs @msg_log.logMessages.each do |msg| # DLM: you can filter on log channel here for now if /openstudio.*/.match(msg.logChannel) # /openstudio\.model\..*/ # Skip certain messages that are irrelevant/misleading next if msg.logMessage.include?('Skipping layer') || # Annoying/bogus "Skipping layer" warnings msg.logChannel.include?('runmanager') || # RunManager messages msg.logChannel.include?('setFileExtension') || # .ddy extension unexpected msg.logChannel.include?('Translator') || # Forward translator and geometry translator msg.logMessage.include?('UseWeatherFile') # 'UseWeatherFile' is not yet a supported option for YearDescription # Report the message in the correct way if msg.logLevel == OpenStudio::Info @runner.registerInfo(msg.logMessage) elsif msg.logLevel == OpenStudio::Warn @runner.registerWarning("[#{msg.logChannel}] #{msg.logMessage}") elsif msg.logLevel == OpenStudio::Error @runner.registerError("[#{msg.logChannel}] #{msg.logMessage}") elsif msg.logLevel == OpenStudio::Debug && @debug @runner.registerInfo("DEBUG - #{msg.logMessage}") end end end @runner.registerInfo("Total Time = #{(Time.new - @start_time).round}sec.") end def self.check_upstream_measure_for_arg(runner, arg_name) # 2.x methods (currently setup for measure display name but snake_case arg names) arg_name_value = {} runner.workflow.workflowSteps.each do |step| if step.to_MeasureStep.is_initialized measure_step = step.to_MeasureStep.get measure_name = measure_step.measureDirName if measure_step.name.is_initialized measure_name = measure_step.name.get # this is instance name in PAT end if measure_step.result.is_initialized result = measure_step.result.get result.stepValues.each do |arg| name = arg.name value = arg.valueAsVariant.to_s if name == arg_name arg_name_value[:value] = value arg_name_value[:measure_name] = measure_name return arg_name_value # stop after find first one end end else # puts "No result for #{measure_name}" end else # puts "This step is not a measure" end end return arg_name_value end # populate choice argument from model objects. areaType should be string like "floorArea" or "exteriorArea" # note: it seems like spaceType.floorArea does account for multiplier, so I don't have to call this method unless I have a custom collection of spaces. def self.getAreaOfSpacesInArray(model, spaceArray, areaType = 'floorArea') # find selected floor spaces, make array and get floor area. totalArea = 0 spaceAreaHash = {} spaceArray.each do |space| spaceArea = eval("space.#{areaType}*space.multiplier") spaceAreaHash[space] = spaceArea totalArea += spaceArea end result = { 'totalArea' => totalArea, 'spaceAreaHash' => spaceAreaHash } return result end # runs conversion and neat string, and returns value with units in string, optionally before or after the value def self.neatConvertWithUnitDisplay(double, fromString, toString, digits, unitBefore = false, unitAfter = true, space = true, parentheses = true) # convert units doubleConverted = OpenStudio.convert(double, fromString, toString) if !doubleConverted.nil? doubleConverted = doubleConverted.get else puts "Couldn't convert values, check string choices passed in. From: #{fromString}, To: #{toString}" end # get neat version of converted neatConverted = OpenStudio.toNeatString(doubleConverted, digits, true) # add prefix if unitBefore if space == true && parentheses == true prefix = "(#{toString}) " elsif space == true && parentheses == false prefix = "(#{toString})" elsif space == false && parentheses == true prefix = "#{toString} " else prefix = toString.to_s end else prefix = '' end # add suffix if unitAfter if space == true && parentheses == true suffix = " (#{toString})" elsif space == true && parentheses == false suffix = "(#{toString})" elsif space == false && parentheses == true suffix = " #{toString}" else suffix = toString.to_s end else suffix = '' end finalString = "#{prefix}#{neatConverted}#{suffix}" return finalString end # helper that loops through lifecycle costs getting total costs under "Construction" and add to counter if occurs during year 0 def self.getTotalCostForObjects(objectArray, category = 'Construction', onlyYearFromStartZero = true) counter = 0 objectArray.each do |object| object_LCCs = object.lifeCycleCosts object_LCCs.each do |object_LCC| if object_LCC.category == category if onlyYearFromStartZero == false || object_LCC.yearsFromStart == 0 counter += object_LCC.totalCost end end end end return counter end # helper that loops through lifecycle costs getting total costs under "Construction" and add to counter if occurs during year 0 def self.getSpaceTypeStandardsInformation(spaceTypeArray) # hash of space types spaceTypeStandardsInfoHash = {} spaceTypeArray.each do |spaceType| # get standards building if !spaceType.standardsBuildingType.empty? standardsBuilding = spaceType.standardsBuildingType.get else standardsBuilding = nil end # get standards space type if !spaceType.standardsSpaceType.empty? standardsSpaceType = spaceType.standardsSpaceType.get else standardsSpaceType = nil end # populate hash spaceTypeStandardsInfoHash[spaceType] = [standardsBuilding, standardsSpaceType] end return spaceTypeStandardsInfoHash end # OpenStudio has built in toNeatString method # OpenStudio::toNeatString(double,2,true)# double,decimals, show commas # OpenStudio has built in helper for unit conversion. That can be done using OpenStudio::convert() as shown below. # OpenStudio::convert(double,"from unit string","to unit string").get end