Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 4361 #4424

Merged
merged 19 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
220 changes: 220 additions & 0 deletions src/energyplus/Test/SubSurface_GTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,223 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_SubSurface) {
ASSERT_TRUE(subSurfaceObject.getTarget(FenestrationSurface_DetailedFields::FrameandDividerName));
EXPECT_EQ(frameObject.handle(), subSurfaceObject.getTarget(FenestrationSurface_DetailedFields::FrameandDividerName)->handle());
}

// https://github.com/NREL/OpenStudio/issues/4361
TEST_F(EnergyPlusFixture, Issue_4361) {
Model model;
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

ThermalZone thermalZone(model);

Space space(model);
space.setThermalZone(thermalZone);

// Create a surface and subsurface
// get the surface net and gross area & subsurface gross area
// surface gross area == surface net area + subsurface gross area
// That is before the subsurface is assigned a frame and divider

std::vector<Point3d> vertices;
vertices.push_back(Point3d(0, 0, 2));
vertices.push_back(Point3d(0, 0, 0));
vertices.push_back(Point3d(2, 0, 0));
vertices.push_back(Point3d(2, 0, 2));
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
Surface surface(vertices, model);
surface.setSpace(space);
surface.setSurfaceType("Wall");

vertices.clear();
vertices.push_back(Point3d(0.5, 0, 1.5));
vertices.push_back(Point3d(0.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 1.5));
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

SubSurface subSurface(vertices, model);
subSurface.setSurface(surface);
subSurface.setSubSurfaceType("FixedWindow");

double surfaceGrossArea = surface.grossArea();
double surfaceNetArea = surface.netArea();
double subSurfaceGrossArea = subSurface.grossArea();
double subSurfaceTotalArea = subSurface.roughOpeningArea();

EXPECT_EQ(surfaceGrossArea, surfaceNetArea + subSurfaceGrossArea);
double windowWallRatio = surface.windowToWallRatio();
EXPECT_EQ(windowWallRatio, 0.25);

// Then assign a frame and divider to the subsurface
// then do the same thing with the areas

WindowPropertyFrameAndDivider frame(model);
frame.setFrameWidth(0.030);
subSurface.setWindowPropertyFrameAndDivider(frame);

surfaceNetArea = surface.netArea();
subSurfaceGrossArea = subSurface.grossArea();
subSurfaceTotalArea = subSurface.roughOpeningArea();
EXPECT_EQ(surfaceGrossArea, surfaceNetArea + subSurfaceTotalArea);

windowWallRatio = surface.windowToWallRatio();
EXPECT_NEAR(windowWallRatio, 0.28, 0.01);
}
TEST_F(EnergyPlusFixture, Issue_4361_Subsurface_Outside_Parent) {
Model model;

ThermalZone thermalZone(model);

Space space(model);
space.setThermalZone(thermalZone);

// Create a surface and subsurface
// get the surface net and gross area & subsurface gross area
// surface gross area == surface net area + subsurface gross area
// That is before the subsurface is assigned a frame and divider

std::vector<Point3d> vertices;
vertices.push_back(Point3d(0, 0, 2));
vertices.push_back(Point3d(0, 0, 0));
vertices.push_back(Point3d(2, 0, 0));
vertices.push_back(Point3d(2, 0, 2));
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
Surface surface(vertices, model);
surface.setSpace(space);
surface.setSurfaceType("Wall");

vertices.clear();
vertices.push_back(Point3d(0.5, 0, 1.99));
vertices.push_back(Point3d(0.5, 0, 0.99));
vertices.push_back(Point3d(1.5, 0, 0.99));
vertices.push_back(Point3d(1.5, 0, 1.99));
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

SubSurface subSurface(vertices, model);
subSurface.setSurface(surface);
subSurface.setSubSurfaceType("FixedWindow");

double surfaceGrossArea = surface.grossArea();
double surfaceNetArea = surface.netArea();
double subSurfaceGrossArea = subSurface.grossArea();
double subSurfaceTotalArea = subSurface.roughOpeningArea();

EXPECT_EQ(surfaceGrossArea, surfaceNetArea + subSurfaceGrossArea);
double windowWallRatio = surface.windowToWallRatio();
EXPECT_EQ(windowWallRatio, 0.25);

// Then assign a frame and divider to the subsurface
// then do the same thing with the areas
// The area should be unchanged because the subsurface is not enclosed
// by the parent serface after including the frame
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

WindowPropertyFrameAndDivider frame(model);
frame.setFrameWidth(0.030);
subSurface.setWindowPropertyFrameAndDivider(frame);

surfaceNetArea = surface.netArea();
subSurfaceGrossArea = subSurface.grossArea();
subSurfaceTotalArea = subSurface.roughOpeningArea();
EXPECT_EQ(surfaceGrossArea, surfaceNetArea + subSurfaceTotalArea);

windowWallRatio = surface.windowToWallRatio();
EXPECT_NEAR(windowWallRatio, 0.25, 0.01);
}

TEST_F(EnergyPlusFixture, Issue_4361_Multi_Subsurfaces_Non_Overlapping) {
Model model;

ThermalZone thermalZone(model);

Space space(model);
space.setThermalZone(thermalZone);

std::vector<Point3d> vertices;
vertices.push_back(Point3d(0, 0, 2));
vertices.push_back(Point3d(0, 0, 0));
vertices.push_back(Point3d(4, 0, 0));
vertices.push_back(Point3d(4, 0, 2));
Surface surface(vertices, model);
surface.setSpace(space);
surface.setSurfaceType("Wall");

vertices.clear();
vertices.push_back(Point3d(0.5, 0, 1.5));
vertices.push_back(Point3d(0.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 1.5));

SubSurface subSurface1(vertices, model);
subSurface1.setSurface(surface);
subSurface1.setSubSurfaceType("FixedWindow");

vertices.clear();
vertices.push_back(Point3d(2.5, 0, 1.5));
vertices.push_back(Point3d(2.5, 0, 0.5));
vertices.push_back(Point3d(3.5, 0, 0.5));
vertices.push_back(Point3d(3.5, 0, 1.5));

SubSurface subSurface2(vertices, model);
subSurface2.setSurface(surface);
subSurface2.setSubSurfaceType("FixedWindow");

double windowWallRatio = surface.windowToWallRatio();
EXPECT_NEAR(windowWallRatio, 0.25, 0.01);

WindowPropertyFrameAndDivider frame1(model);
frame1.setFrameWidth(0.030);
subSurface1.setWindowPropertyFrameAndDivider(frame1);

WindowPropertyFrameAndDivider frame2(model);
frame2.setFrameWidth(0.030);
subSurface2.setWindowPropertyFrameAndDivider(frame2);

windowWallRatio = surface.windowToWallRatio();
EXPECT_NEAR(windowWallRatio, 0.281, 0.01);
}

TEST_F(EnergyPlusFixture, Issue_4361_Multi_Subsurfaces_Overlapping) {
Model model;

ThermalZone thermalZone(model);

Space space(model);
space.setThermalZone(thermalZone);

std::vector<Point3d> vertices;
vertices.push_back(Point3d(0, 0, 2));
vertices.push_back(Point3d(0, 0, 0));
vertices.push_back(Point3d(4, 0, 0));
vertices.push_back(Point3d(4, 0, 2));
Surface surface(vertices, model);
surface.setSpace(space);
surface.setSurfaceType("Wall");

vertices.clear();
vertices.push_back(Point3d(0.5, 0, 1.5));
vertices.push_back(Point3d(0.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 0.5));
vertices.push_back(Point3d(1.5, 0, 1.5));

SubSurface subSurface1(vertices, model);
subSurface1.setSurface(surface);
subSurface1.setSubSurfaceType("FixedWindow");

vertices.clear();
vertices.push_back(Point3d(1.51, 0, 1.5));
vertices.push_back(Point3d(1.51, 0, 0.5));
vertices.push_back(Point3d(2.51, 0, 0.5));
vertices.push_back(Point3d(2.51, 0, 1.5));

SubSurface subSurface2(vertices, model);
subSurface2.setSurface(surface);
subSurface2.setSubSurfaceType("FixedWindow");

double windowWallRatio = surface.windowToWallRatio();
EXPECT_NEAR(windowWallRatio, 0.25, 0.01);

WindowPropertyFrameAndDivider frame1(model);
frame1.setFrameWidth(0.030);
subSurface1.setWindowPropertyFrameAndDivider(frame1);

WindowPropertyFrameAndDivider frame2(model);
frame2.setFrameWidth(0.030);
subSurface2.setWindowPropertyFrameAndDivider(frame2);

windowWallRatio = surface.windowToWallRatio();
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_NEAR(windowWallRatio, 0.2654, 0.01);
}
2 changes: 1 addition & 1 deletion src/model/PlanarSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ namespace model {
if (subSurface) {
multiplier = subSurface->multiplier();
}
result -= multiplier * surface->grossArea();
result -= multiplier * subSurface->roughOpeningArea();
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions src/model/SubSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@

#include "../utilities/geometry/Geometry.hpp"
#include "../utilities/geometry/Transformation.hpp"
#include "../utilities/geometry/Intersection.hpp"
#include "../utilities/core/Assert.hpp"

using boost::to_upper_copy;
Expand Down Expand Up @@ -1595,6 +1596,37 @@ namespace model {
void SubSurface::resetShadingControl() {
removeAllShadingControls();
}

std::vector<Point3d> SubSurface::roughOpeningVertices() const {
auto frameAndDivider = windowPropertyFrameAndDivider();
if (frameAndDivider) {
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
double fw = frameAndDivider->frameWidth();
// Get a transform to change the points to x/y
Transformation faceTransform = Transformation::alignFace(this->vertices());
std::vector<Point3d> faceVertices = faceTransform.inverse() * this->vertices();
// Offset the points by the framewidth
boost::optional<std::vector<Point3d>> offset = openstudio::buffer(faceVertices, fw, 0.01);
ggartside marked this conversation as resolved.
Show resolved Hide resolved
if (!offset) {
// If offset failed it is because the points are in the wrong order
// If it fails again then something went awry with boost::buffer
faceVertices = openstudio::reverse(faceVertices);
offset = openstudio::buffer(faceVertices, fw, 0.01);
std::vector<Point3d> roughOpeningVertices = faceTransform * offset.get();
return roughOpeningVertices;
}
}
ggartside marked this conversation as resolved.
Show resolved Hide resolved

return this->vertices();
}

double SubSurface::roughOpeningArea() const {

boost::optional<double> area = openstudio::getArea(roughOpeningVertices());
if (area)
return *area;
else
return grossArea();
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
}
/// @endcond

std::vector<SubSurface> applySkylightPattern(const std::vector<std::vector<Point3d>>& pattern, const std::vector<Space>& spaces,
Expand Down
6 changes: 6 additions & 0 deletions src/model/SubSurface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ namespace model {

boost::optional<AirflowNetworkSurface> airflowNetworkSurface() const;
ggartside marked this conversation as resolved.
Show resolved Hide resolved

/* Get the total area of the sub surface rough area which includes the frame */
double roughOpeningArea() const;

/* Get the rough opening vertices for the subsurface */
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
std::vector<Point3d> roughOpeningVertices() const;

// DLM: todo add methods to create light shelves by projection factor

protected:
Expand Down
93 changes: 89 additions & 4 deletions src/model/Surface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1440,16 +1440,101 @@ namespace model {
return result;
}

double windowArea = 0.0;
//double windowArea = 0.0;
//for (const SubSurface& subSurface : this->subSurfaces()) {
// if (istringEqual(subSurface.subSurfaceType(), "FixedWindow") || istringEqual(subSurface.subSurfaceType(), "OperableWindow")) {
// windowArea += subSurface.multiplier() * subSurface.roughOpeningArea();
// }
//}
Copy link
Collaborator

@jmarrec jmarrec Oct 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • TODO: Delete?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - delete


double roughOpeningArea = totalAreaOfSubSurfaces();
double wwr = roughOpeningArea / grossArea;

return wwr;
}

// Calcuklates and returns the toital area of the subsurfaces
// If any subsrface extends outside the parent surface or overlaps an adjacent subsurface
// then that subsurface's vertices rather than rough opening vertioces are used to calculate the area
// NOTE: I'm starting to think a better way to do this would be to use polygon booleans
// 1 - Intersect the subsurface rough opening with the parent surface to get the area inside the parent surface
// 1 - Add all the subsurface rough openings together (so overlap areas dont count twice)
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
double Surface_Impl::totalAreaOfSubSurfaces() const {
double tol = 0.01;
// iterate over all sub surfaces
// make a map of sub surface/roughOpening vertices (flattened)
// iterate over map
// check each subsurface for overlap with parent and not overlap with sibling
// if either fails, replace roughOpening vertices with original vertices
// Add up all the openign areas

// Records the rough opening of each sub surface
std::map<SubSurface, Point3dVector> roughOpenings;
// Get the flattened parent surface vertices
Transformation parentToXY = Transformation::alignFace(this->vertices());
std::vector<Point3d> parentVertices = parentToXY.inverse() * this->vertices();
// Make sure the parent surface is oriented clockwise
auto norm = openstudio::getOutwardNormal(parentVertices);
if (norm && norm->z() > 0) {
std::reverse(parentVertices.begin(), parentVertices.end());
}

// Get the flattened sub surface vertices for windows
for (const SubSurface& subSurface : this->subSurfaces()) {
if (istringEqual(subSurface.subSurfaceType(), "FixedWindow") || istringEqual(subSurface.subSurfaceType(), "OperableWindow")) {
windowArea += subSurface.multiplier() * subSurface.netArea();
auto opening = parentToXY.inverse() * subSurface.roughOpeningVertices();
auto norm = openstudio::getOutwardNormal(opening);
if (norm && norm->z() > 0) {
std::reverse(opening.begin(), opening.end());
}
roughOpenings[subSurface] = opening;
}
}

double wwr = windowArea / grossArea;
// Check each rough opening against the parent surface, if any vertices are outside
// the parent surfave then revert back to the original vertices
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
for (const auto& opening : roughOpenings) {
const SubSurface& subSurface = opening.first;
std::vector<Point3d> vertices = opening.second;

return wwr;
if (!openstudio::polygonInPolygon(vertices, parentVertices, tol)) {
// One or more vertices not within the parent surface so replace with the original vertices
auto vertices = parentToXY * subSurface.vertices();
auto norm = openstudio::getOutwardNormal(vertices);
if (norm && norm->z() > 0) {
std::reverse(vertices.begin(), vertices.end());
}
roughOpenings[subSurface] = vertices;
break;
Copy link
Collaborator

@jmarrec jmarrec Oct 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • TODO: Is the break intended? I don't understand everything that's going on, but don't you want to fix all of them?
    Also, why not make this check directly in the loop above when populating roughOpenins?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are correct an original iteration had a loop that checked each vertex and the break was to hop out of that loop - good find! I also found I'd re-used the name vertices in the second loop and had missed a place where I needed to get the inverse of the transform

I probably could have combined the first two loops, but I think separating them makes the code easier to follow

  1. First loop sets up the sub surface rough opening vertices in the coordinate space of the parent surface
  2. Second loop checks these vertices to see if they extend outside the parent surface
  3. Third loop checks each subsurface for overlaps with adjacent sub surfaces

}
}

// Check subsurfaces for overlaps
// (if I knew how I'd order themn left to right in x then we could test adjacent ones only)
for (const auto& opening1 : roughOpenings) {
for (const auto& opening2 : roughOpenings) {
if (opening1 == opening2) continue;
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

auto result = openstudio::intersect(opening1.second, opening2.second, tol);
if (result) {
jmarrec marked this conversation as resolved.
Show resolved Hide resolved
//There's an overlap so swap out the overlapped vertices for the original
auto vertices = parentToXY * opening1.first.vertices();
auto norm = openstudio::getOutwardNormal(vertices);
if (norm && norm->z() > 0) {
std::reverse(vertices.begin(), vertices.end());
}
roughOpenings[opening1.first] = vertices;
}
}
}

// Accumulate the areas
double area = 0;
for (const auto& roughOpening : roughOpenings) {
area += openstudio::getArea(roughOpening.second).value() * roughOpening.first.multiplier();
}
jmarrec marked this conversation as resolved.
Show resolved Hide resolved

return area;
}

double Surface_Impl::skylightToRoofRatio() const {
Expand Down