Question-and-Answer Resource for the Building Energy Modeling Community
Get started with the Help page
Ask Your Question
5

Measure writing: import dependent Objects

asked 2015-02-07 13:26:14 -0500

LSurf's avatar

updated 2017-08-05 13:32:28 -0500

Since I was unable to produce the HVAC system in OpenStudio, I'm writing a measure to import my system from an .idf file.

Problem is, when loading an object, or adding it to a workspace, all fields checked if referencing objects exist. References to objects that have not been inserted yet, are therefore deleted. Before I tried fixing this by adjusting the order of my .idf file. However, this does not work when two objects reference each other. Every broken connection would have to be fixed manually in Ruby by finding the offending object and resetting its reference. In my case this would be 100s lines of code.

I've tried looking into the workspace.insertObjects function to see if that would not have this issue, but couldn't get it to work.

Is there another way of disabling this error checking, or inserting multiple objects at the same time into a workspace?

edit retag flag offensive close merge delete

2 Answers

Sort by ยป oldest newest most voted
1

answered 2015-02-08 05:17:42 -0500

LSurf's avatar

updated 2015-02-11 14:08:37 -0500

Credits go to macumber and David. Solution was in using the .toIdfFile and adding all objects simulataneously using the .addObjects.

I created an importer for a YAML-file, which holds data for fixing the remaining missing references. This way the connection between the original .osm-file and imported .idf-file can be fixed.

I don't belong to any group in the BCL-library, so for ppl looking for a way to import an .idf file and fixing remaining links, below the full code, including example .yml file.

# YAML style file containing params to be adjusted
---
- class: BuildingSurface:Detailed
  name: Surface Name
  field: 4
  value: OtherSideConditionsModel
- class: BuildingSurface:Detailed
  name: Surface Name
  field: 5
  value: ICS Collector 1 OSCM
- class: SurfaceProperty:ExteriorNaturalVentedCavity
  name: ICSRoofPaverExtVentCav1
  field: 11
  value: Zolder voor
- class: SolarCollector:IntegralCollectorStorage
  name: Collector 1
  field: 2
  value: Surface Name

.

class ImportIDFFile < OpenStudio::Ruleset::WorkspaceUserScript

  # human readable name
  def name
    return " Import IDF-file"
  end

  # human readable description
  def description
    return "This measure can be used to import functionality from EnergyPlus, which is not exposed (yet) to OpenStudio. E.g. HVAC-components can be imported."
  end

  # human readable description of modeling approach
  def modeler_description
    return "Imports an .idf-file from the /resources folder. Connections from .idf-file which are not satisfied internally (e.g. references to objects in the .osm-file) will be broken. Use the .yml-file to restore these references, example file provided.

Note that this measure is run after the preprocessor. This means you cannot use HVACTemplate objects, instead expand these first and copy from the .expidf-file.

"
  end

  # define the arguments that the user will input
  def arguments(workspace)
    args = OpenStudio::Ruleset::OSArgumentVector.new

    idf_file = OpenStudio::Ruleset::OSArgument::makeStringArgument('idf_file', false)
    idf_file.setDisplayName("IDF-file")
    idf_file.setDescription("The name of the IDF-file residing in the resources directory of this measure")
    idf_file.setDefaultValue('import.idf')
    args << idf_file

    yml_file = OpenStudio::Ruleset::OSArgument::makeStringArgument('yml_file', false)
    yml_file.setDisplayName("YML-file")
    yml_file.setDescription("The name of the YML-file residing in the resources directory of this measure")
    yml_file.setDefaultValue('import.yml')
    args << yml_file

    return args
  end 

  # define what happens when the measure is run
  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
    idf_file = runner.getStringArgumentValue("idf_file", user_arguments)
    yml_file = runner.getStringArgumentValue("yml_file", user_arguments)

    # Parameters
    idf_file = "#{File.dirname(__FILE__)}/resources/#{idf_file}"
    yml_file = "#{File.dirname(__FILE__)}/resources/#{yml_file}"

    # Process .idf file
    if !idf_file.empty?
      # Load workspace to be imported
      idf = OpenStudio::Workspace::load(idf_file)
      if idf.empty?
        runner.registerError("Cannot load #{idf_file}")
        return false
      end
      idf = idf.get.toIdfFile()
      workspace.addObjects(idf.objects)
    end

    # Process .yml file
    if !yml_file.empty?
      require 'yaml'
      fixfields = YAML::load_file(yml_file)

      fixfields.each do |f|
        o = workspace.getObjectByTypeAndName(f['class'].to_IddObjectType,f['name'])
        if o.empty?
          runner.registerError("No #{f['class']} found with name #{f['name']}")
          return false
        end
        o = o.get
        if !o.setString(f['field'],f['value'])
          runner.registerError("Unable to set #{f['value']} to field #{f['field']} in #{f['name']}")
          return false
        end
      end
    end

    return true

  end ...
(more)
edit flag offensive delete link more

Comments

Actually, it appears that I'm not quite there yet. It seems that the calling openstudio/Ruby script passes the workspace by reference. In my last line of code the local workspace variable is reassigned, but not in the caller method.

Calling load on the workspace itself appears to be a private method. Any other way to load the idf file without creating a new workspace?

For now I'm saving the workspace in my script after making the modifications I need, and then run the .idf file manually.

LSurf's avatar LSurf  ( 2015-02-08 07:10:45 -0500 )edit

Thought I could be smart and remove all objects from the current workspace, and insert them all at once. Turns out this breaks the dependence again...

    # Reload workspace
ws = OpenStudio::Workspace::load("#{File.dirname(__FILE__)}/resources/tmp.idf").get

# Remove existing objects
workspace.removeObjects(workspace.handles)

# Add objects from ws, breaks the objects
workspace.addObjects(ws.objects)
LSurf's avatar LSurf  ( 2015-02-08 08:15:48 -0500 )edit

Hey @LSurf, you are on the right track with this but unfortunately using those methods can be a bit tricky. I'll reply with an answer when I get some time to work something up.

macumber's avatar macumber  ( 2015-02-08 10:52:00 -0500 )edit

That would be great, let me know if you need some sort of input file.

I'm also thinking about setting every field of every object using .setString. I couldn't get that to work because I couldn't find a method of finding the object type (needed for finding the correct original object). But that might be worth a second shot

LSurf's avatar LSurf  ( 2015-02-08 11:44:48 -0500 )edit

@LSurf, I can't reproduce your issue in a unit test, this test should be doing the same thing you are. The one thing to look at is to make sure that the Workspaces you are working with are using the correct IDD for validation. You can use the command: w.iddFileType.valueName, to print the name of the IddFile your Workspace is using. They should all return 'EnergyPlus' in your case.

macumber's avatar macumber  ( 2015-02-09 16:02:57 -0500 )edit
1

answered 2015-02-09 00:22:30 -0500

updated 2015-02-09 08:37:24 -0500

I'll let @macumber give longer answer but what I think you want is something like this.

idf = workspace.toIdfFile()

I use that method on the Inject OSM Geometry into an External IDF measure. This measure takes the IDF generated from the OSM and merges it with a user specified IDF file. The geometry comes from the OSM generated IDF, and everything else comes from the user specified IDF. I hit the same issue you did.

Here is some of the code from the linked measure.

#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

#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.")
edit flag offensive delete link more

Comments

1

@LSurf, I'll just expand a little on @david-goldwasser's answer. OpenStudio WorkspaceObjects always exist within a Workspace, all references between objects are tracked at all times. If object1 refers to object2 and object2 is removed, the reference in object1 will be cleared. This is nice for working with entire files but make some operations tricky.

macumber's avatar macumber  ( 2015-02-09 09:43:50 -0500 )edit
1

IdfObjects are basically just snippets of text, references between them are not tracked. If object1 refers to object2 and object2 is removed, object1 will remain unchanged. This makes working with files difficult as you have to update multiple references when an object changes, but it is more robust when doing object bulk add operations.

macumber's avatar macumber  ( 2015-02-09 09:44:20 -0500 )edit

I'll add a unit test around your code snippet dealing with Workspace objects, in the meantime you should convert your WorkspaceObjects to IdfObjects to do the add (as in David's example). You can also just load the IdfFile directly using IdfFile::load.

macumber's avatar macumber  ( 2015-02-09 09:45:41 -0500 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Careers

Question Tools

1 follower

Stats

Asked: 2015-02-07 13:26:14 -0500

Seen: 321 times

Last updated: Jul 11 '15