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

Surface matching and intersecting adds unnecessary surfaces

asked 2020-06-30 12:20:41 -0500

jugonzal07's avatar

updated 2020-06-30 16:07:15 -0500

Hi all,

We've hit a bug in our code base that will randomly add unnecessary edges/surfaces when doing surface matching. It's surprisingly inconsistent and I haven't been able to pin down why it happens.

Below is a screenshot before and after performing intersect surfaces and match surfaces. I have a ceiling plenum space touching a set zones for a perimeter and core layout. For some reason, two extra lines are added:

image description

image description

I created a minimum working example with the old legacy code we use for surface matching and intersecting. The original author has long since left the project but it seems fairly simple to grasp. The code and example for regenerating this problem can be found on github here. The script you'd want to run is titled surface_matching.rb. The one edit you'd need to make is on line 48 for the path to the before_matching.osm file.

require 'openstudio'

# Helper to load a model in one line
def osload(path)
    translator =
    ospath =
    model = translator.loadModel(ospath)
    if model.empty?
        raise "Path '#{path}' is not a valid path to an OpenStudio Model"
        model = model.get
    return model

def match_blocks(model)
    spaces = model.getSpaces
    outside_spaces = []
    spaces.each do |space|
        space.surfaces.each do |surface|
            if (surface.outsideBoundaryCondition == "Outdoors" ||
                    surface.outsideBoundaryCondition == "Ground")
                outside_spaces << space

    n = outside_spaces.size

    boundingBoxes = []
    (0...n).each do |i|
        boundingBoxes[i] = outside_spaces[i].transformation * outside_spaces[i].boundingBox

    (0...n).each do |i|
        (i+1...n).each do |j|
            next if not boundingBoxes[i].intersects(boundingBoxes[j])
        end #j
    end #i

#----- Main testing starts here

osm_path = 'C:/git/stack_overflow_questions/unmet_hours/surface_matching/before_matching.osm'

model = osload(osm_path)

# Match and intersect surfaces
match_blocks(model)"after_matching.osm", 'w') {|f| f.write(model)}

Any thoughts on what's causing this?


edit retag flag offensive close merge delete



Hey Juan, these bugs are very hard to track down. One suggestion would be to sort surfaces by area or spaces by some criteria to try to intersect larger surfaces before smaller ones. At least sorting should make the result repeatable.

macumber's avatar macumber  ( 2020-06-30 20:26:33 -0500 )edit

@macumber, I could be wrong, but just looking at it, if the first surfaces to intersect were the third story core floor with the single surface whole story zone ceiling of the second story, it would have created the long diagonals to avoid the new surface from being a sub-surface.

@jugonzal07 as a test if you first try to manually split the second story roof at new footprint boundary it may go away. I have written code in past to create footprint polygon from collection of floors.

David Goldwasser's avatar David Goldwasser  ( 2020-07-01 11:58:22 -0500 )edit

Maybe we can come up with a way to pre-intersect all surfaces against these footprints, it wouldn't catch all cases but might catch a lot. I've also had code that looks for floor surfaces that share an edge with an exterior wall. Could group those together and intersect them before surfaces that don't share an edge with an exterior surface.

David Goldwasser's avatar David Goldwasser  ( 2020-07-01 12:06:57 -0500 )edit

Another thought I had was pre-sorting surfaces to be intersected, not by name, but by min or max z values, then y, then x.Working way across in this way might avoid some of these issues.

David Goldwasser's avatar David Goldwasser  ( 2020-07-02 10:13:12 -0500 )edit

Thanks all for the responses. I'll give some of these a try! I agree that sorting the surfaces, at the very least, will give consistent results that I can learn from. I will report back with my findings. I think it makes sense to work in some post processing checks/tests to flag buildings with odd surfaces. While I'm not positive it would catch all cases, if I check for triangular surfaces I bet I could catch most of them-- at least for this project.

jugonzal07's avatar jugonzal07  ( 2020-07-02 10:36:47 -0500 )edit

2 Answers

Sort by » oldest newest most voted

answered 2020-07-05 20:25:37 -0500

jugonzal07's avatar

Big thanks to all the folks who helped here (@macumber and @david-goldwasser).

I pushed out a solution on github here

My solution proved very similar to David's final proposal. I'm not sure it's the most elegant or "Ruby" way of doing this, but I created a class which was Comparable (I called it SpaceSort). The whole purpose of this class was to sort an array of OpenStudio::Model::Spaces by their bounding box limits. The order of heirarchy:

  • min_x > max_x >min_y > max_y >min_z > max_z

The code here might explain it better.

I then use that class to sort my spaces so that they are always consistent.

def sort_spaces(spaces)

# Array of SpaceSort objects, used to sort spaces by their bounding boxes
space_sort_list = []

# Construct SurfaceSort objects for sorting surfaces
spaces.each do |space|
    space_sort_list <<

# sort SurfaceSort objects by their bounding box boundaries
sorted_list = space_sort_list.sort()

sorted_spaces = []

# Extract spaces from sorted SurfaceSort objects
sorted_list.each do |space_sort|
    sorted_spaces <<



Once sorted, this behavior became consistent and, at least in this model, fixed my issue. I'll have to do more extensive testing to make sure this is a generalized solution to this problem.

edit flag offensive delete link more



Glad that worked. We could add this or something similar to the surface matching measure, but would probably add it as a bool option so it could be turned on or off. May have performance hit for large models that everyone will not want.

David Goldwasser's avatar David Goldwasser  ( 2020-07-08 12:38:16 -0500 )edit

answered 2020-10-22 02:10:14 -0500

@jugonzal07 @David Goldwasser @macumber what about using the clipper gem? I've used it for computing shadows in this repository but I cannot "require" it in an OpenStudio measure. I've followed this post but that was not successful.

edit flag offensive delete link more

Your Answer

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

Add Answer

Training Workshops

Question Tools



Asked: 2020-06-30 12:20:41 -0500

Seen: 354 times

Last updated: Oct 22 '20