def test_add_polygon_to_polygon(): # 2D poly1 = Polygon([(1, 0), (0, 0), (0, 1)]) poly2 = Polygon([(1, 0), (1, 0), (1, 0)]) expected = Polygon([(2, 0), (1, 0), (1, 1)]) result = poly1 + poly2 assert almostequal(result, expected) vector = Vector2D(1, 0) result = poly1 + vector assert almostequal(result, expected) vector = Vector3D(1, 0, 0) try: result = poly1 + vector # should fail assert False except ValueError: pass # 3D poly1 = Polygon([(1, 0, 1), (0, 0, 1), (0, 1, 1)]) poly2 = Polygon([(1, 0, 1), (1, 0, 1), (1, 0, 1)]) expected = Polygon([(2, 0, 2), (1, 0, 2), (1, 1, 2)]) result = poly1 + poly2 assert almostequal(result, expected) vector = Vector3D(1, 0, 1) result = poly1 + vector assert almostequal(result, expected) vector = Vector2D(1, 0) try: result = poly1 + vector # should fail assert False except ValueError: pass
def is_coplanar(self, other): """Check if polygon is in the same plane as another polygon. This includes the same plane but opposite orientation. Parameters ---------- other : Polygon3D Another polygon. Returns ------- bool """ n1 = self.normal_vector n2 = other.normal_vector d1 = self.distance d2 = other.distance if (almostequal(n1, n2) and almostequal(d1, d2)): return True elif (almostequal(n1, -n2) and almostequal(d1, -d2)): return True else: return False
def populate_adjacencies(adjacencies, s1, s2): # type: (defaultdict, EpBunch, EpBunch) -> defaultdict """Update the adjacencies dict with any intersections between two surfaces. :param adjacencies: Dict to contain lists of adjacent surfaces. :param s1: Object representing an EnergyPlus surface. :param s2: Object representing an EnergyPlus surface. :returns: An updated dict of adjacencies. """ poly1 = Polygon3D(s1.coords) poly2 = Polygon3D(s2.coords) if not almostequal(abs(poly1.distance), abs(poly2.distance), 4): return adjacencies if not almostequal(poly1.normal_vector, poly2.normal_vector, 4): if not almostequal(poly1.normal_vector, -poly2.normal_vector, 4): return adjacencies intersection = poly1.intersect(poly2) if intersection: new_surfaces = intersect(poly1, poly2) new_s1 = [ s for s in new_surfaces if almostequal(s.normal_vector, poly1.normal_vector, 4) ] new_s2 = [ s for s in new_surfaces if almostequal(s.normal_vector, poly2.normal_vector, 4) ] adjacencies[(s1.key, s1.Name)] += new_s1 adjacencies[(s2.key, s2.Name)] += new_s2 return adjacencies
def populate_adjacencies(s1, s2): poly1 = Polygon3D(s1.coords) poly2 = Polygon3D(s2.coords) if almostequal(abs(poly1.distance), abs(poly2.distance), 3): if almostequal(poly1.normal_vector, poly2.normal_vector, 3) or almostequal( poly1.normal_vector, -poly2.normal_vector, 3): return True
def __eq__(self, other): # check they're in the same plane if not almostequal(self.normal_vector, other.normal_vector): return False if not almostequal(self.distance, other.distance): return False # if they are in the same plane, check they completely overlap in 2D return (self.project_to_2D() == other.project_to_2D())
def test_rotate_idf_360(self, base_idf): # type: (IDF) -> None idf1 = base_idf idf2 = IDF() idf2.initreadtxt(idf1.idfstr()) idf1.rotate(360) floor1 = Polygon3D(idf1.getsurfaces("floor")[0].coords).normalize_coords(None) floor2 = Polygon3D(idf2.getsurfaces("floor")[0].coords).normalize_coords(None) assert almostequal(floor1, floor2) shade1 = Polygon3D(idf1.getshadingsurfaces()[0].coords).normalize_coords(None) shade2 = Polygon3D(idf1.getshadingsurfaces()[0].coords).normalize_coords(None) assert almostequal(shade1, shade2)
def is_collinear(self, other): if almostequal(other, self) or almostequal(other, -self): return True a = self.p1 - other.p1 b = self.p1 - other.p2 angle_between = a.cross(b) if almostequal(angle_between, Vector3D(0, 0, 0)): return True a = self.p2 - other.p1 b = self.p2 - other.p2 angle_between = a.cross(b) if almostequal(angle_between, Vector3D(0, 0, 0)): return True return False
def test_real_scale(): # type: () -> None """Test building, intersecting and matching from a real building footprint. """ iddfhandle = StringIO(iddcurrent.iddtxt) if IDF.getiddname() == None: IDF.setiddname(iddfhandle) idf = IDF(StringIO("Version, 8.5;")) poly1 = [ (526492.65, 185910.65), (526489.05, 185916.45), (526479.15, 185910.3), (526482.65, 185904.6), (526492.65, 185910.65), ] poly2 = [ (526483.3, 185903.15), (526483.5, 185903.25), (526482.65, 185904.6), (526479.15, 185910.3), (526489.05, 185916.45), (526492.65, 185910.65), (526493.4, 185909.4), (526500, 185913.95), (526500.45, 185914.3), (526500, 185914.85), (526497.4, 185918.95), (526499.45, 185920.2), (526494.4, 185928.35), (526466.05, 185910.95), (526471.1, 185902.75), (526473.05, 185903.9), (526476.2, 185898.8), (526479.95, 185901.1), (526483.3, 185903.15), ] idf.add_block("small", poly1, 6.0, 2) idf.add_block("large", poly2, 5.0, 2) idf.translate_to_origin() idf.intersect_match() idf.set_wwr(0.25) walls = idf.getsurfaces("wall") # look for a wall which should have been split assert "Block large Storey 1 Wall 0003" not in [w.Name for w in walls] # look for another wall which should have been split assert "Block large Storey 1 Wall 0005" not in [w.Name for w in walls] # look for a wall which should be an internal wall wall = idf.getobject("BUILDINGSURFACE:DETAILED", "Block small Storey 1 Wall 0002_1") assert wall.Outside_Boundary_Condition != "outdoors" # look for another wall which should be an internal wall wall = idf.getobject("BUILDINGSURFACE:DETAILED", "Block large Storey 1 Wall 0003_2") assert wall.Outside_Boundary_Condition != "outdoors" walls = idf.getsurfaces("wall") # look for walls which are being incorrectly duplicated for s1, s2 in itertools.combinations(walls, 2): assert not almostequal(s1.coords, s2.coords), "Dupes: '{}' and '{}'".format( s1.Name, s2.Name )
def difference_3D_polys(poly1, poly2): """Difference between two 3D polygons. Parameters ---------- poly1 : Polygon3D The subject polygon. poly2 : Polygon3D The clip polygon. Returns ------- list or False False if no difference, otherwise a list of lists of Polygon3D objects representing each difference. """ clipper = prep_3D_polys(poly1, poly2) if not clipper: return [] differences = clipper.Execute( pc.CT_DIFFERENCE, pc.PFT_NONZERO, pc.PFT_NONZERO) polys = process_clipped_3D_polys(differences, poly1) # orient to match poly1 results = [] for poly in polys: if almostequal(poly.normal_vector, poly1.normal_vector): results.append(poly) else: results.append(poly.invert_orientation()) return results
def intersect_3D_polys(poly1, poly2): """Intersection of two 3D polygons. Parameters ---------- poly1 : Polygon3D The subject polygon. poly2 : Polygon3D The clip polygon. Returns ------- list or False False if no intersection, otherwise a list of lists of Polygon3D objects representing each intersection. """ clipper = prep_3D_polys(poly1, poly2) if not clipper: return [] intersections = clipper.Execute( pc.CT_INTERSECTION, pc.PFT_NONZERO, pc.PFT_NONZERO) polys = process_clipped_3D_polys(intersections, poly1) # orient to match poly1 results = [] for poly in polys: if almostequal(poly.normal_vector, poly1.normal_vector): results.append(poly) else: results.append(poly.invert_orientation()) return results
def set_unmatched_surface(s, vector): """Set boundary conditions for a surface which does not adjoin another one. Parameters ---------- s : EpBunch The surface. vector : Vector3D The surface normal vector """ s.View_Factor_to_Ground = 'autocalculate' poly = Polygon3D(s.coords) if min(poly.zs) < 0 or all(z == 0 for z in poly.zs): # below ground or ground-adjacent surfaces s.Outside_Boundary_Condition_Object = '' s.Outside_Boundary_Condition = 'ground' s.Sun_Exposure = 'NoSun' s.Wind_Exposure = 'NoWind' else: s.Outside_Boundary_Condition = 'outdoors' s.Outside_Boundary_Condition_Object = '' s.Wind_Exposure = 'WindExposed' if almostequal(vector, (0, 0, -1)): # downward facing surfaces s.Sun_Exposure = 'NoSun' else: s.Sun_Exposure = 'SunExposed' # other external surfaces
def test_polygon3d_attributes(): # type: () -> None poly3d = Polygon3D([(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 0, 0)]) assert len(poly3d) == 4 assert poly3d.xs == [0, 0, 1, 1] assert poly3d.ys == [0, 1, 1, 0] assert poly3d.zs == [0, 1, 1, 0] assert poly3d.vertices_list == [(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 0, 0)] assert poly3d.vertices == [Vector3D(*v) for v in poly3d] assert poly3d.distance == 0 assert poly3d.is_horizontal is False assert almostequal(poly3d.normal_vector, [0.0, 0.70710678, -0.70710678]) poly3d_2 = Polygon3D([(0, 1, 1), (0, 2, 2), (1, 2, 2), (1, 1, 1)]) assert almostequal(poly3d_2.normal_vector, [0.0, 0.70710678, -0.70710678]) assert poly3d_2.projection_axis == 1 result = poly3d.is_coplanar(poly3d_2) assert result
def test_bounding_box(): poly = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) poly3d = Polygon3D([(0, 0, 0), (0, 1, 1), (1, 1, 1), (1, 0, 0)]) expected = Polygon([(1, 1, 1), (1, 0, 0), (0, 0, 0), (0, 1, 1)]) result = poly3d.bounding_box assert almostequal(result, expected)
def test_rotate_360(self, base_idf): # type: (IDF) -> None idf = base_idf surface = idf.getsurfaces()[0] coords = [Vector3D(*v) for v in [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)]] surface.setcoords(coords) expected = surface.coords rotate([surface], 360) result = surface.coords assert almostequal(result, expected)
def test_align_face_transformations_trapezoid_floor(self): tol = 12 # places testVertices = Polygon3D([(27.69, 0, 0), (0, 0, 0), (5, 5, 0), (22.69, 5, 0)]) t = Transformation().align_face(testVertices) tempVertices = t.inverse() * testVertices expectedVertices = Polygon3D([(0, 0, 0), (27.69, 0, 0), (22.69, 5, 0), (5, 5, 0)]) assert almostequal(tempVertices, expectedVertices, tol)
def test_scale_idf(self, base_idf): # type: (IDF) -> None idf1 = base_idf idf2 = IDF() idf2.initreadtxt(idf1.idfstr()) idf1.scale(10) idf1.scale(0.1) floor1 = Polygon3D(idf1.getsurfaces("floor")[0].coords).normalize_coords(None) floor2 = Polygon3D(idf2.getsurfaces("floor")[0].coords).normalize_coords(None) assert almostequal(floor1, floor2)
def test_set_wwr(self): """Check that the correct WWR is set for all walls. """ idf = self.idf wwr = 0.25 set_wwr(idf, wwr) windows = idf.idfobjects['FENESTRATIONSURFACE:DETAILED'] assert len(windows) == 8 for window in windows: wall = idf.getobject('BUILDINGSURFACE:DETAILED', window.Building_Surface_Name) assert almostequal(window.area, wall.area * wwr, 3)
def populate_adjacencies(adjacencies, s1, s2): """Update the adjacencies dict with any intersections between two surfaces. Parameters ---------- adjacencies : dict Dict to contain lists of adjacent surfaces. s1 : EPBunch Object representing an EnergyPlus surface. s2 : EPBunch Object representing an EnergyPlus surface. Returns ------- dict """ poly1 = Polygon3D(s1.coords) poly2 = Polygon3D(s2.coords) if not almostequal(abs(poly1.distance), abs(poly2.distance), 4): return adjacencies if not almostequal(poly1.normal_vector, poly2.normal_vector, 4): if not almostequal(poly1.normal_vector, -poly2.normal_vector, 4): return adjacencies intersection = poly1.intersect(poly2) if intersection: new_surfaces = intersect(poly1, poly2) new_s1 = [ s for s in new_surfaces if almostequal(s.normal_vector, poly1.normal_vector, 4) ] new_s2 = [ s for s in new_surfaces if almostequal(s.normal_vector, poly2.normal_vector, 4) ] adjacencies[(s1.key, s1.Name)] += new_s1 adjacencies[(s2.key, s2.Name)] += new_s2 return adjacencies
def test_set_wwr(self, base_idf): # type: () -> None idf = base_idf intersect_idf_surfaces(idf) match_idf_surfaces(idf) wwr = 0.25 set_wwr(idf, wwr) windows = idf.idfobjects['FENESTRATIONSURFACE:DETAILED'] assert len(windows) == 8 for window in windows: wall = idf.getobject('BUILDINGSURFACE:DETAILED', window.Building_Surface_Name) assert almostequal(window.area, wall.area * wwr, 3)
def match_idf_surfaces(idf): """Match all surfaces in an IDF. Parameters ---------- idf : IDF object The IDF. """ surfaces = getidfsurfaces(idf) planes = getidfplanes(surfaces) for distance in planes: for vector in planes[distance]: surfaces = planes[distance][vector] matches = planes.get(-distance, {}).get(-vector, []) if not matches: # default set all the surfaces boundary conditions for s in surfaces: set_unmatched_surface(s, vector) else: # check which are matches for s in surfaces: for m in matches: matched = False poly1 = Polygon3D(s.coords) poly2 = Polygon3D(m.coords) if almostequal(poly1, poly2.invert_orientation()): matched = True # matched surfaces s.Outside_Boundary_Condition = 'surface' s.Outside_Boundary_Condition_Object = m.Name s.Sun_Exposure = 'NoSun' s.Wind_Exposure = 'NoWind' # matched surfaces m.Outside_Boundary_Condition = 'surface' m.Outside_Boundary_Condition_Object = s.Name m.Sun_Exposure = 'NoSun' m.Wind_Exposure = 'NoWind' break if not matched: # unmatched surfaces set_unmatched_surface(s, vector) set_unmatched_surface(m, vector)
def match_idf_surfaces(idf): # type: (IDF) -> None """Match all surfaces in an IDF. :param idf: The IDF. """ surfaces = idf.getsurfaces() + idf.getshadingsurfaces() planes = getidfplanes(surfaces) matched = {} for distance in planes: for vector in planes[distance]: surfaces = planes[distance][vector] for surface in surfaces: set_unmatched_surface(surface, vector) matches = planes.get(-distance, {}).get(-vector, []) for s, m in product(surfaces, matches): if almostequal(s.coords, reversed(m.coords)): matched[sorted_tuple(m, s)] = (m, s) for key in matched: set_matched_surfaces(*matched[key])
def test_real_scale(): # type: () -> None """Test building, intersecting and matching from a real building footprint. """ iddfhandle = StringIO(iddcurrent.iddtxt) if IDF.getiddname() == None: IDF.setiddname(iddfhandle) idf = IDF(StringIO('Version, 8.5;')) poly1 = [(526492.65, 185910.65), (526489.05, 185916.45), (526479.15, 185910.3), (526482.65, 185904.6), (526492.65, 185910.65)] poly2 = [ (526483.3, 185903.15), (526483.5, 185903.25), (526482.65, 185904.6), (526479.15, 185910.3), (526489.05, 185916.45), (526492.65, 185910.65), (526493.4, 185909.4), (526500, 185913.95), (526500.45, 185914.3), (526500, 185914.85), (526497.4, 185918.95), (526499.45, 185920.2), (526494.4, 185928.35), (526466.05, 185910.95), (526471.1, 185902.75), (526473.05, 185903.9), (526476.2, 185898.8), (526479.95, 185901.1), (526483.3, 185903.15) ] idf.add_block('small', poly1, 6.0, 2) idf.add_block('large', poly2, 5.0, 2) idf.translate_to_origin() idf.intersect_match() idf.set_wwr(0.25) walls = idf.getsurfaces('wall') # look for a wall which should have been split assert 'Block large Storey 1 Wall 0003' not in [w.Name for w in walls] # look for another wall which should have been split assert 'Block large Storey 1 Wall 0005' not in [w.Name for w in walls] # look for a wall which should be an internal wall wall = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block small Storey 1 Wall 0002_1') assert wall.Outside_Boundary_Condition != 'outdoors' # look for another wall which should be an internal wall wall = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block large Storey 1 Wall 0003_2') assert wall.Outside_Boundary_Condition != 'outdoors' # look for two walls which are being incorrectly duplicated wall_1 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block small Storey 0 Wall 0001_1') wall_2 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block small Storey 0 Wall 0001_4') if wall_1 and wall_2: assert not almostequal(wall_1.coords, wall_2.coords) # look for two walls which are being incorrectly duplicated wall_1 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block large Storey 1 Wall 0005_3') wall_2 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block large Storey 1 Wall 0005_2') if wall_1 and wall_2: assert not almostequal(wall_1.coords, wall_2.coords) # look for two walls which are being incorrectly duplicated wall_1 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block large Storey 1 Wall 0004_3') wall_2 = idf.getobject('BUILDINGSURFACE:DETAILED', 'Block large Storey 1 Wall 0004_1') if wall_1 and wall_2: assert not almostequal(wall_1.coords, wall_2.coords)
def test_align_face_transformations(self): # type: () -> None tol = 12 # places vertices = Polygon3D([(1, 0, 1), (1, 0, 0), (2, 0, 0), (2, 0, 1)]) t = Transformation() # rotate 0 degrees about z testVertices = t._rotation(Vector3D(0, 0, 1), 0) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate 30 degrees about z testVertices = t._rotation(Vector3D(0, 0, 1), np.deg2rad(30)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate -30 degrees about z testVertices = t._rotation(Vector3D(0, 0, 1), -np.deg2rad(30)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate -30 degrees about x testVertices = t._rotation(Vector3D(1, 0, 0), -np.deg2rad(30)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate -90 degrees about x testVertices = t._rotation(Vector3D(1, 0, 0), -np.deg2rad(90)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate 30 degrees about x testVertices = t._rotation(Vector3D(1, 0, 0), np.deg2rad(30)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(0, 1, 0), (0, 0, 0), (1, 0, 0), (1, 1, 0)]) assert almostequal(tempVertices, expectedVertices, tol) # rotate 90 degrees about x testVertices = t._rotation(Vector3D(1, 0, 0), np.deg2rad(90)) * vertices t = Transformation()._align_face(testVertices) tempVertices = t._inverse() * testVertices expectedVertices = Polygon3D([(1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 0, 0)]) assert almostequal(tempVertices, expectedVertices, tol)
def test_translation_transformations(self): # type: () -> None tol = 12 # places trans = Vector3D(1, 1, 1) point1 = Vector3D(1, 0, 0) t = Transformation() # identity transformation temp = t * point1 assert almostequal(1.0, temp.x, tol) assert almostequal(0.0, temp.y, tol) assert almostequal(0.0, temp.z, tol) # move by 1, 1, 1 t = Transformation(translation_matrix(trans)) temp = t * point1 assert almostequal(2.0, temp.x, tol) assert almostequal(1.0, temp.y, tol) assert almostequal(1.0, temp.z, tol) # move by -1, -1, -1 temp = t._inverse() * point1 assert almostequal(0.0, temp.x, tol) assert almostequal(-1.0, temp.y, tol) assert almostequal(-1.0, temp.z, tol) # identity transformation temp = t._inverse() * t * point1 assert almostequal(1.0, temp.x, tol) assert almostequal(0.0, temp.y, tol) assert almostequal(0.0, temp.z, tol) # identity transformation temp = t * t._inverse() * point1 assert almostequal(1.0, temp.x, tol) assert almostequal(0.0, temp.y, tol) assert almostequal(0.0, temp.z, tol)
def test_normalize(): # type: () -> None v = Vector3D(1, 1, 1) v.normalize() for i in v: assert almostequal(i, 0.57735026)
def test_set_length(): # type: () -> None v = Vector3D(1, 1, 1) v.set_length(1) for i in v: assert almostequal(i, 0.57735026)
def is_expected_wwr(self, idf, wwr): windows_area = sum(w.area for w in idf.getsubsurfaces("window")) walls_area = sum(w.area for w in idf.getsurfaces("wall")) expected_area = walls_area * wwr return almostequal(windows_area, expected_area, 3)
def is_expected_wwr(self, idf, wwr): windows_area = sum(w.area for w in idf.getsubsurfaces('window')) walls_area = sum(w.area for w in idf.getsurfaces('wall')) return almostequal(windows_area, walls_area * wwr, 3)
def break_polygons(poly, hole): """Break up a surface with a hole in it. This produces two surfaces, neither of which have a hole in them. Parameters ---------- poly : Polygon3D The surface with a hole in. hole : Polygon3D The hole. Returns ------- list Two Polygon3D objects. """ # take the two closest points on the surface perimeter links = product(poly, hole) links = sorted(links, key=lambda x: distance(x[0], x[1])) first_on_poly = links[0][0] last_on_poly = links[1][0] first_on_hole = links[1][1] last_on_hole = links[0][1] coords = poly[:] + poly[:] # a double loop section_on_poly = [] for item in coords: if item == first_on_poly: section_on_poly.append(item) elif section_on_poly: section_on_poly.append(item) if item == last_on_poly: break coords = reversed(hole[:] + hole[:]) # a double loop section_on_hole = [] for item in coords: if item == first_on_hole: section_on_hole.append(item) elif section_on_hole: section_on_hole.append(item) if item == last_on_hole: break new_poly = section_on_poly + section_on_hole new_poly = Polygon3D(new_poly) union = hole.union(new_poly) union = union[0] new_poly2 = poly.difference(union)[0] if not almostequal(new_poly.normal_vector, poly.normal_vector): print("inverting 1") new_poly = new_poly.invert_orientation() if not almostequal(new_poly2.normal_vector, poly.normal_vector): print("inverting 2") new_poly2 = new_poly2.invert_orientation() # view_polygons({'blue': [new_poly], 'red': [hole]}) # view_polygons({'blue': [new_poly, new_poly2], 'red': [hole]}) return [new_poly, new_poly2]