def test_polyline2d_init(): """Test the initialization of Polyline2D objects and basic properties.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) pline = Polyline2D(pts) str(pline) # test the string representation of the polyline assert isinstance(pline.vertices, tuple) assert len(pline.vertices) == 4 assert len(pline) == 4 for point in pline: assert isinstance(point, Point2D) assert isinstance(pline.segments, tuple) assert len(pline.segments) == 3 for seg in pline.segments: assert isinstance(seg, LineSegment2D) assert seg.length == 2 assert pline.p1 == pts[0] assert pline.p2 == pts[-1] assert pline.length == 6 assert pline.is_self_intersecting is False assert pline.vertices[0] == pline[0] p_array = pline.to_array() assert isinstance(p_array, tuple) assert len(p_array) == 4 for arr in p_array: assert isinstance(p_array, tuple) assert len(arr) == 2 pline_2 = Polyline2D.from_array(p_array) assert pline == pline_2
def test_arc2_intersect_line_ray(): """Test the Arc2D intersect_line_ray method.""" pt = Point2D(2, 0) arc = Arc2D(pt, 1, 0, math.pi) circle = Arc2D(pt, 1) seg1 = LineSegment2D(pt, Vector2D(2, 2)) seg2 = LineSegment2D(Point2D(0, -2), Vector2D(6, 6)) seg3 = LineSegment2D(pt, Vector2D(0.5, 0.5)) int1 = arc.intersect_line_ray(seg1) assert len(int1) == 1 assert int1[0].x == pytest.approx(2.71, rel=1e-2) assert int1[0].y == pytest.approx(0.71, rel=1e-2) int2 = circle.intersect_line_ray(seg1) assert len(int2) == 1 assert int2[0].x == pytest.approx(2.71, rel=1e-2) assert int2[0].y == pytest.approx(0.71, rel=1e-2) int3 = arc.intersect_line_ray(seg2) assert len(int3) == 1 assert int3[0].x == pytest.approx(2.71, rel=1e-2) assert int3[0].y == pytest.approx(0.71, rel=1e-2) int4 = circle.intersect_line_ray(seg2) assert len(int4) == 2 assert int4[0].x == pytest.approx(2.71, rel=1e-2) assert int4[0].y == pytest.approx(0.71, rel=1e-2) assert int4[1].x == pytest.approx(1.29, rel=1e-2) assert int4[1].y == pytest.approx(-0.71, rel=1e-2) assert arc.intersect_line_ray(seg3) is None
def test_edge_direction(): """ Tests the bidirection method """ # Make the polygon poly = Polygon2D.from_array([[0, 0], [6, 0], [6, 6], [3, 4], [0, 6]]) pt_array = poly.vertices # Make unidirect graph dg = PolygonDirectedGraph(1e-10) for i in range(len(pt_array) - 1): k = dg.add_node(pt_array[i], [pt_array[i + 1]]) if i == 0: dg.outer_root_key = k dg.add_node(pt_array[-1], [pt_array[0]]) root = dg.node(dg.outer_root_key) # Check nodes = dg.ordered_nodes for i in range(dg.num_nodes - 1): assert not dg.is_edge_bidirect(nodes[i], nodes[i + 1]) # Check unidirectionality next_node = dg.next_unidirect_node(root) assert not dg.is_edge_bidirect(root, next_node) # Add bidirectional edge dg.add_node(Point2D(0, 0), [Point2D(1, 1)]) bidir_key = dg.add_node(Point2D(1, 1), [Point2D(0, 0)]) # Check bidirectionality assert dg.is_edge_bidirect(dg.node(bidir_key), root)
def bounding_rectangle(geometries, axis_angle=0): """Get the min and max of an oriented bounding rectangle around 2D or 3D geometry. Args: geometries: An array of 2D or 3D geometry objects. Note that all objects must have a min and max property. axis_angle: The counter-clockwise rotation angle in radians in the XY plane to represent the orientation of the bounding rectangle extents. (Default: 0). Returns: A tuple with two Point2D objects representing the min point and max point of the bounding rectangle respectively. """ if axis_angle != 0: # rotate geometry to the bounding box cpt = geometries[0].vertices[0] geometries = _orient_geometry(geometries, axis_angle, cpt) xx = bounding_domain_x(geometries) yy = bounding_domain_y(geometries) min_pt = Point2D(xx[0], yy[0]) max_pt = Point2D(xx[1], yy[1]) if axis_angle != 0: # rotate the points back cpt = Point2D(cpt.x, cpt.y) # cast Point3D to Point2D min_pt = min_pt.rotate(axis_angle, cpt) max_pt = max_pt.rotate(axis_angle, cpt) return min_pt, max_pt
def flip(self, seg_length): """Flip the direction of the windows along a segment. This is needed since windows can exist asymmetrically across the wall segment and operations like reflecting the Room2D across a plane will require the window parameters to be flipped to remain in the same place. Args: seg_length: The length of the segment along which the parameters are being flipped. """ new_origins = [] new_widths = [] new_heights = [] for o, width, height in zip(self.origins, self.widths, self.heights): new_x = seg_length - o.x - width if new_x > 0: # fully within the wall boundary new_origins.append(Point2D(new_x, o.y)) new_widths.append(width) new_heights.append(height) elif new_x + width > seg_length * 0.001: # partially within the boundary new_widths.append(width + new_x - (seg_length * 0.001)) new_x = seg_length * 0.001 new_origins.append(Point2D(new_x, o.y)) new_heights.append(height) return RectangularWindows(new_origins, new_widths, new_heights)
def test_reflect(): """Test the Arc2D reflect method.""" pt = Point2D(2, 2) arc = Arc2D(pt, 2, 0, math.pi) origin_1 = Point2D(0, 1) origin_2 = Point2D(1, 1) normal_1 = Vector2D(0, 1) normal_2 = Vector2D(-1, 1).normalize() assert arc.reflect(normal_1, origin_1).c.x == pytest.approx(2, rel=1e-3) assert arc.reflect(normal_1, origin_1).c.y == pytest.approx(0, rel=1e-3) assert arc.reflect(normal_1, origin_1).midpoint.x == pytest.approx(2, rel=1e-3) assert arc.reflect(normal_1, origin_1).midpoint.y == pytest.approx(-2, rel=1e-3) assert arc.reflect(normal_1, origin_2).c == Point2D(2, 0) assert arc.reflect(normal_1, origin_2).midpoint.x == pytest.approx(2, rel=1e-3) assert arc.reflect(normal_1, origin_2).midpoint.y == pytest.approx(-2, rel=1e-3) test_1 = arc.reflect(normal_2, origin_2) assert test_1.c.x == pytest.approx(2, rel=1e-3) assert test_1.c.y == pytest.approx(2, rel=1e-3) assert test_1.midpoint.x == pytest.approx(4, rel=1e-3) assert test_1.midpoint.y == pytest.approx(2, rel=1e-3)
def _segment_mesh_2d(self, base_pt=Point2D(0, 0)): """Mesh2D for the segments in the 2D space of the legend.""" # get general properties _l_par = self.legend_parameters n_seg = self.segment_length # create the 2D mesh of the legend if _l_par.vertical: mesh2d = Mesh2D.from_grid(base_pt, 1, n_seg, _l_par.segment_width, _l_par.segment_height) else: _base_pt = Point2D(base_pt.x - _l_par.segment_width * n_seg, base_pt.y) mesh2d = Mesh2D.from_grid(_base_pt, n_seg, 1, _l_par.segment_width, _l_par.segment_height) # add colors to the mesh _seg_colors = self.segment_colors if not _l_par.continuous_legend: mesh2d.colors = _seg_colors else: if _l_par.vertical: mesh2d.colors = _seg_colors + _seg_colors else: mesh2d.colors = tuple(col for col in _seg_colors for i in (0, 1)) return mesh2d
def test_intersect_polygon_segments_with_3_angled_rectangles(): """Tests that a vertex shared by 2 polygons is added only once to a 3rd polygon Make sure the addedvertex is which is colinear within tolerance. The polygons are rotated 45 degrees counter-clockwise to introduce floating-point closeness considerations. """ r2 = math.sqrt(2.0) tolerance = 0.02 expected_point = Point2D(r2, 0) pts0 = (Point2D(0, 0), Point2D(0.5 * r2 * 0.99, -0.5 * r2 * 0.99), Point2D(1.5 * r2 * 0.99, 0.5 * r2 * 0.99), Point2D(r2, r2)) polygon0 = Polygon2D(pts0) pts1 = (Point2D(0.5 * r2, -0.5 * r2), Point2D(r2, -r2), Point2D(1.5 * r2, -0.5 * r2), expected_point) polygon1 = Polygon2D(pts1) pts2 = (expected_point, Point2D(1.5 * r2, -0.5 * r2), Point2D(2 * r2, 0), Point2D(1.5 * r2, 0.5 * r2)) polygon2 = Polygon2D(pts2) polygons = Polygon2D.intersect_polygon_segments([polygon0, polygon1, polygon2], tolerance) # Extra vertex added to largest polygon, as expected assert len(polygons[0].segments) == 5 assert polygons[0].segments[1].p2 == polygons[0].vertices[2] assert polygons[0].segments[2].p1 == polygons[0].vertices[2] assert len(polygon1.segments) == 4 # No extra vertex added assert len(polygon2.segments) == 4 # No extra vertex added
def test_dict_to_object_win_par(): """Test the dict_to_object method with window parameters.""" simple_window = SingleWindow(5, 2, 0.8) ashrae_base1 = SimpleWindowRatio(0.4) ashrae_base2 = RepeatingWindowRatio(0.4, 2, 0.8, 3) bod_windows = RepeatingWindowWidthHeight(2, 1.5, 0.8, 3) origins = (Point2D(2, 1), Point2D(5, 0.5)) widths = (1, 3) heights = (1, 2) detailed_window1 = RectangularWindows(origins, widths, heights) pts_1 = (Point2D(2, 1), Point2D(3, 1), Point2D(3, 2), Point2D(2, 2)) pts_2 = (Point2D(5, 0.5), Point2D(8, 0.5), Point2D(8, 2.5), Point2D(5, 2.5)) detailed_window2 = DetailedWindows((Polygon2D(pts_1), Polygon2D(pts_2))) assert isinstance(dict_to_object(simple_window.to_dict()), SingleWindow) assert isinstance(dict_to_object(ashrae_base1.to_dict()), SimpleWindowRatio) assert isinstance(dict_to_object(ashrae_base2.to_dict()), RepeatingWindowRatio) assert isinstance(dict_to_object(bod_windows.to_dict()), RepeatingWindowWidthHeight) assert isinstance(dict_to_object(detailed_window1.to_dict()), RectangularWindows) assert isinstance(dict_to_object(detailed_window2.to_dict()), DetailedWindows)
def _hour_mesh_components(self, low_vals, up_vals, x_hr_dist): """Get the vertices and faces of a mesh from upper/lower lists of values. Args: low_vals: A list of lists with each sublist having lower y values. up_vals: A list of lists with each sublist having upper y values. x_hr_dist: The x distance moved by each cell of the mesh. Returns: verts: A list of vertices for the mesh. faces: A list of faces for the mesh. """ verts = [] faces = [] vert_count = 0 for month in range(len(low_vals)): # create the two starting vertices for the month start_x = self._base_point.x + month * self._x_dim v1 = Point2D(start_x, low_vals[month][0]) v2 = Point2D(start_x, up_vals[month][0]) verts.extend((v1, v2)) # loop through the hourly data and add vertices for hour in range(1, len(low_vals[0])): x_val = start_x + x_hr_dist * hour v3 = Point2D(x_val, low_vals[month][hour]) v4 = Point2D(x_val, up_vals[month][hour]) verts.extend((v3, v4)) faces.append(tuple(x + vert_count for x in (0, 2, 3, 1))) vert_count += 2 vert_count += 2 return verts, faces
def test_room2d_init_from_vertices(): """Test the initialization of Room2D objects from 2D vertices.""" pts = (Point2D(0, 0), Point2D(10, 0), Point2D(10, 10), Point2D(0, 10)) ashrae_base = SimpleWindowRatio(0.4) overhang = Overhang(1) boundarycs = (bcs.outdoors, bcs.ground, bcs.outdoors, bcs.ground) window = (ashrae_base, None, ashrae_base, None) shading = (overhang, None, None, None) room2d = Room2D.from_vertices('SquareShoebox', pts, 3, 3, boundarycs, window, shading) assert len(room2d.floor_geometry.vertices) == 4 assert len(room2d) == 4 assert room2d.floor_to_ceiling_height == 3 assert isinstance(room2d.boundary_conditions[0], Outdoors) assert isinstance(room2d.boundary_conditions[1], Ground) assert room2d.window_parameters[0] == ashrae_base assert room2d.window_parameters[1] is None assert room2d.shading_parameters[0] == overhang assert room2d.shading_parameters[1] is None assert room2d.floor_height == 3 assert room2d.ceiling_height == 6 assert room2d.volume == 300 assert room2d.floor_area == 100 assert room2d.exterior_wall_area == 60 assert room2d.exterior_aperture_area == 60 * 0.4
def test_init_compass(): """Test the initialization of Compass and basic properties.""" compass = Compass() str(compass) # test the string representation hash(compass) # test to be sure the compass is hash-able assert compass.radius == 100 assert compass.center == Point2D() assert compass.north_angle == 0 assert compass.north_vector == Vector2D(0, 1) assert compass.spacing_factor == 0.15 assert compass.min_point.is_equivalent(Point2D(-115.0, -115.0), 0.01) assert compass.max_point.is_equivalent(Point2D(115.0, 115.0), 0.01) assert isinstance(compass.inner_boundary_circle, Arc2D) assert len(compass.all_boundary_circles) == 3 for circ in compass.all_boundary_circles: assert isinstance(circ, Arc2D) assert len(compass.major_azimuth_points) == len(compass.MAJOR_AZIMUTHS) for pt in compass.major_azimuth_points: assert isinstance(pt, Point2D) assert len(compass.major_azimuth_ticks) == len(compass.MAJOR_AZIMUTHS) for lin in compass.major_azimuth_ticks: assert isinstance(lin, LineSegment2D) assert len(compass.minor_azimuth_points) == len(compass.MINOR_AZIMUTHS) for pt in compass.minor_azimuth_points: assert isinstance(pt, Point2D) assert len(compass.minor_azimuth_ticks) == len(compass.MINOR_AZIMUTHS) for lin in compass.minor_azimuth_ticks: assert isinstance(lin, LineSegment2D) assert isinstance(compass.min_point3d(), Point3D) assert isinstance(compass.max_point3d(), Point3D)
def test_polygon2d_init(): """Test the initialization of Polygon2D objects and basic properties.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) polygon = Polygon2D(pts) str(polygon) # test the string representation of the polygon assert isinstance(polygon.vertices, tuple) assert len(polygon.vertices) == 4 assert len(polygon) == 4 for point in polygon: assert isinstance(point, Point2D) assert isinstance(polygon.segments, tuple) assert len(polygon.segments) == 4 for seg in polygon.segments: assert isinstance(seg, LineSegment2D) assert seg.length == 2 assert polygon.area == 4 assert polygon.perimeter == 8 assert not polygon.is_clockwise assert polygon.is_convex assert not polygon.is_self_intersecting assert polygon.vertices[0] == polygon[0] p_array = polygon.to_array() assert isinstance(p_array, tuple) assert len(p_array) == 4 for arr in p_array: assert isinstance(p_array, tuple) assert len(arr) == 2
def test_scale(): """Test the Point2D scale method.""" pt_1 = Point2D(2, 2) origin_1 = Point2D(0, 2) origin_2 = Point2D(1, 1) assert pt_1.scale(2, origin_1) == Point2D(4, 2) assert pt_1.scale(2, origin_2) == Point2D(3, 3)
def test_join_segments(): """Test the join_segments method.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[2], pts[3]), LineSegment2D.from_end_points(pts[3], pts[0])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[2], pts[3]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[3], pts[0])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[0], pts[3]), LineSegment2D.from_end_points(pts[3], pts[2])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01)
def offset(polygon, distance, tol=1e-10): """Offset the polygon boundary by defined distance. Args: polygon: A Polygon2D object to offset. distance: Distance to offset. Only positive distance works in current implementation. tol: Tolerance for point equivalence. Returns: A list of offset contours as Polygon2D objects. """ _buffer_angle_tol = math.pi / 4 # 45 degrees # If positive do inward offset if distance > 0: _, offsets = polyskel.sub_polygons(polygon, distance, [], tol) return offsets # Init distance distance = abs(distance) sqrdist = distance * distance # Calculate buffer offset segments = list(polygon.segments) segments = segments + [segments[0]] buffer = 0.0 for i in range(len(segments) - 1): seg1, seg2 = segments[i], segments[i + 1] bisector = _offset_bisector(seg1, seg2, distance) if bisector.length > buffer: buffer = bisector.length buffer += (distance / 2.) # Make buffer buffer_frame = Polygon2D([ Point2D(polygon.max.x + buffer, polygon.max.y + buffer), Point2D(polygon.min.x - buffer, polygon.max.y + buffer), Point2D(polygon.min.x - buffer, polygon.min.y - buffer), Point2D(polygon.max.x + buffer, polygon.min.y - buffer) ]) holes = [list(reversed(polygon.vertices))] perimeter_sub_polygon = polyskel.sub_polygons(buffer_frame, distance, holes) mispqll = "" # Make a graph from the perimeter (offset) polygons so we infer the core polygons g = PolygonDirectedGraph(tol) for poly in perimeter_sub_polygon: pts = poly.vertices for i in range(len(pts) - 1): g.add_node(pts[i], [pts[i + 1]]) g.add_node(pts[-1], [pts[0]]) offset = Polygon2D([node.pt for node in g.exterior_cycles[-1]]) return offset, perimeter_sub_polygon
def test_polyline2d_to_from_dict(): """Test the to/from dict of Polyline2D objects.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) pline = Polyline2D(pts) pline_dict = pline.to_dict() new_pline = Polyline2D.from_dict(pline_dict) assert isinstance(new_pline, Polyline2D) assert new_pline.to_dict() == pline_dict
def to_point2d(point): """Ladybug Point2D from Rhino Point3d.""" if isinstance(point, Point3D): return Point2D(point.x, point.y) elif isinstance(point, Point2D): return point elif isinstance(point, (list, tuple)): return Point2D(point[0], point[1])
def test_polygon2d_to_from_dict(): """Test the to/from dict of Polygon2D objects.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) polygon = Polygon2D(pts) polygon_dict = polygon.to_dict() new_polygon = Polygon2D.from_dict(polygon_dict) assert isinstance(new_polygon, Polygon2D) assert new_polygon.to_dict() == polygon_dict
def test_distance_to_point(): """Test the test_distance_to_point method.""" pt_1 = Point2D(0, 2) assert pt_1.x == 0 assert pt_1.y == 2 pt_2 = Point2D(2, 2) assert pt_1.distance_to_point(pt_2) == 2
def test_from_polygon(): """Test the from_polygon method.""" pts_1 = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) pgon = Polygon2D(pts_1) pline = Polyline2D.from_polygon(pgon) assert isinstance(pline, Polyline2D) assert len(pline) == 5
def _bar_pts(start_x, base_y, bar_width, end_y): """Get a list of bar vertices from input bar properties.""" # create each of the 4 vertices v1 = Point2D(start_x, base_y) v2 = Point2D(start_x + bar_width, base_y) v3 = Point2D(start_x + bar_width, end_y) v4 = Point2D(start_x, end_y) return (v1, v2, v3, v4)
def test_mesh2d_to_from_dict(): """Test the to/from dict of Mesh2D objects.""" pts = (Point2D(0, 0), Point2D(0, 2), Point2D(2, 2), Point2D(2, 0)) mesh = Mesh2D(pts, [(0, 1, 2, 3)]) mesh_dict = mesh.to_dict() new_mesh = Mesh2D.from_dict(mesh_dict) assert isinstance(new_mesh, Mesh2D) assert new_mesh.to_dict() == mesh_dict
def test_context_shade_min_max(): """Test the min and max properties of ContextShade objects.""" awning_geo1 = Face3D.from_rectangle(6, 6, Plane(o=Point3D(5, -10, 6))) awning_geo2 = Face3D.from_rectangle(2, 2, Plane(o=Point3D(-5, -10, 3))) awning_canopy = ContextShade('Awning_Canopy', [awning_geo1, awning_geo2]) assert awning_canopy.area == 40 assert awning_canopy.min == Point2D(-5, -10) assert awning_canopy.max == Point2D(11, -4)
def test_connector_dict_methods(): """Test the ElectricalConnector to/from dict methods.""" geo = LineSegment2D.from_end_points(Point2D(0, 0), Point2D(100, 0)) wire = Wire('OH AL 2/0 A') connector = ElectricalConnector('Connector_1', geo, [wire]) connector_dict = connector.to_dict() new_connector = ElectricalConnector.from_dict(connector_dict) assert connector_dict == new_connector.to_dict()
def test_is_equivalent(): """Test the is_equivalent method.""" pt_1 = Point2D(0, 2) pt_2 = Point2D(0.00001, 2) pt_3 = Point2D(0, 1) assert pt_1.is_equivalent(pt_2, 0.0001) is True assert pt_1.is_equivalent(pt_2, 0.0000001) is False assert pt_1.is_equivalent(pt_3, 0.0001) is False
def test_substation_init(): """Test the initialization of Substation and basic properties.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) polygon = Polygon2D(pts) substation = Substation('Substation_1', polygon) str(substation) # test the string representation assert substation.identifier == 'Substation_1' assert substation.geometry == polygon
def test_substation_dict_methods(): """Test the Substation to/from dict methods.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) polygon = Polygon2D(pts) substation = Substation('Substation_1', polygon) substation_dict = substation.to_dict() new_substation = Substation.from_dict(substation_dict) assert substation_dict == new_substation.to_dict()
def test_reverse(): """Test the reverse method.""" pts_1 = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) pline = Polyline2D(pts_1) new_pline = pline.reverse() assert pline.length == new_pline.length assert pline.vertices == tuple(reversed(new_pline.vertices)) assert pline.is_self_intersecting == new_pline.is_self_intersecting
def _title_point_2d(self): """Point2D for the title in the 2D space of the legend.""" _l_par = self.legend_parameters if _l_par.vertical: offset = 0.5 if self.legend_parameters.continuous_legend is True else 0.25 return Point2D( 0, _l_par.segment_height * (self.segment_length + offset)) else: return Point2D(-_l_par.segment_width * self.segment_length, _l_par.segment_height * 1.25)