def test_polysplit_hashing(): """Test that tolerance sets hashing correctly from parent functions.""" poly = Polygon2D.from_array([[1.0123456789, 3.0123456789], [3.0123456789, 5.0123456789], [1.0123456789, 5.0123456789]]) # 7 digit tolerance w/ rounding tol = 1e-7 g = polysplit._skeleton_as_directed_graph(poly, holes=None, tol=tol) k = g.ordered_nodes[0].key assert k == '(1.0123457, 3.0123457)', k # 10 digit tolerance w/o rounding tol = 1e-10 g = polysplit._skeleton_as_directed_graph(poly, holes=None, tol=tol) k = g.ordered_nodes[0].key assert k == '(1.0123456789, 3.0123456789)', k # Test with number x 100 poly = Polygon2D.from_array([[100.0123456789, 300.0123456789], [300.0123456789, 500.0123456789], [100.0123456789, 500.0123456789]]) # 10 digit tolerance tol = 1e-7 g = polysplit._skeleton_as_directed_graph(poly, holes=None, tol=tol) k = g.ordered_nodes[0].key assert k == '(100.0123457, 300.0123457)', k # 10 digit tolerance w/o rounding tol = 1e-10 g = polysplit._skeleton_as_directed_graph(poly, holes=None, tol=tol) k = g.ordered_nodes[0].key assert k == '(100.0123456789, 300.0123456789)', k
def test_perimeter_core_subpolygons_hole_error(): """Test throwing an exception if hole doesn't get computed in straight skeleton.""" # Construct a simple rectangle poly = Polygon2D.from_array([[0, 0], [6, 0], [6, 8], [0, 8]]) holes = [Polygon2D.from_array([[2, 2], [4, 2], [4, 6], [2, 6]])] # Run method with pytest.raises(RuntimeError): _ = polysplit.perimeter_core_subpolygons(poly, 1, holes)
def test_polygon_offset_outward(): """Test the offset method""" # Construct a simple rectangle poly = [[1, 1], [3, 1], [3, 5], [1, 5]] poly = Polygon2D.from_array(poly) # Make solution polygon (list of polygons) chk_off = Polygon2D.from_array([[0, 0], [4, 0], [4, 6], [0, 6]]) # Run method offset = polyskel.offset(poly, -1, [])
def test_polygon_offset_inward(): """Test the offset method""" # Construct a simple rectangle poly = [[0, 0], [4, 0], [4, 6], [0, 6]] poly = Polygon2D.from_array(poly) # Make solution polygon (list of polygons) chk_off = Polygon2D.from_array([[1, 1], [3, 1], [3, 5], [1, 5]]) # Run method offset = polyskel.offset(poly, 1, []) assert offset[0].is_equivalent(chk_off, 1e-2)
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 frequency_lines(self): """Get the frequency lines for windrose as Polygon2D lists.""" # Reset computed graphics to account for changes to cached viz properties self._compass = None self._container = None ytick_array = [] ytick_num = self.frequency_intervals_compass zero_dist = self._zero_mesh_radius ytick_dist = self.frequency_spacing_hypot_distance # Add frequency polygon for calmrose, if exists if self.show_zeros and self.zero_count > 0: _ytick_array = [(vec1[0] * zero_dist, vec1[1] * zero_dist) for (vec1, _) in self.bin_vectors] ytick_array.append(_ytick_array) # Add the regular frequency polygons for i in range(1, ytick_num + 1): _ytick_array = [] for (vec1, _) in self.bin_vectors: _x = (vec1[0] * i * ytick_dist) + (vec1[0] * zero_dist) _y = (vec1[1] * i * ytick_dist) + (vec1[1] * zero_dist) _ytick_array.append((_x, _y)) ytick_array.append(_ytick_array) return [ self._transform(Polygon2D.from_array(vecs)) for vecs in ytick_array ]
def test_dg_skel_concave(): """Test the dg with skeleton in concave geom""" pt_array = [[0, 0], [6, 0], [6, 6], [3, 4], [0, 6]] # Make the polygon polygon = Polygon2D.from_array(pt_array) # Adj matrix to test against chk_amtx = [ [0, 1, 0, 0, 0, 1, 0, 0], # 0 [0, 0, 1, 0, 0, 0, 1, 0], # 1 [0, 0, 0, 1, 0, 0, 1, 0], # 2 [0, 0, 0, 0, 1, 0, 0, 1], # 3 [1, 0, 0, 0, 0, 1, 0, 0], # 4 [1, 0, 0, 0, 1, 0, 0, 1], # 5 [0, 1, 1, 0, 0, 0, 0, 1], # 6 [0, 0, 0, 1, 0, 1, 1, 0] ] # 7 # 0, 1, 2, 3, 4, 5, 6, 7 chk_lbls = { 0: '(0.0, 0.0)', 1: '(6.0, 0.0)', 2: '(6.0, 6.0)', 3: '(3.0, 4.0)', 4: '(0.0, 6.0)', 5: '(2.09, 2.09)', 6: '(3.91, 2.09)', 7: '(3.0, 1.82)' } dg = polysplit._skeleton_as_directed_graph(polygon, [], 1e-2) amtx = dg.adj_matrix() lbls = dg.adj_matrix_labels() # Test the size assert len(amtx) == len(chk_amtx), _cmpstr(len(amtx), len(chk_amtx)) assert len(amtx[0]) == len(chk_amtx[0]), _cmpstr(len(amtx[0]), len(chk_amtx[0])) assert len(lbls) == len(chk_lbls) # Test the labels for key in range(len(lbls)): assert lbls[key] == chk_lbls[key], 'key: {} result in '.format(key) + \ _cmpstr(lbls[key], chk_lbls[key]) # Test if the adj matrix is correct # Flip dict for chk, this ensures if points are deleted or unordered in # adj mtx test will still pass chk_row_dict = {val: key for key, val in chk_lbls.items()} for i in range(len(amtx)): key = lbls[i] ci = chk_row_dict[key] for j in range(len(amtx[0])): assert amtx[i][j] == chk_amtx[ci][j], 'at index {},{}: '.format(i, j) + \ _cmpstr(amtx[i][j], chk_amtx[i][j])
def test_complex_perimeter_core_subpolygons(): """Test splitting perimeter and core subpolygons from polygon.""" poly = Polygon2D.from_array([[0.7, 0.2], [2, 0], [2, 2], [1, 1], [0, 2], [0, 0]]) holes = [ Polygon2D.from_array([[0.6, 0.6], [1.5, 0.6], [1, 0.8], [0.6, 1.2]]), Polygon2D.from_array([[1.1, 0.25], [1.5, 0.25], [1.3, 0.5]]) ] p, c = polysplit.perimeter_core_subpolygons(poly, .1, holes=holes, tol=1e-10) # Some very simple tests assert len(p) == 13 assert len(c) == 1
def test_dg_noskel(): """Test the dg with no skeleton""" # Points pt_array = [[0, 0], [6, 0], [6, 6], [3, 9], [0, 6]] # Make the polygon polygon = Polygon2D.from_array(pt_array) # Make the check cases chk_pt_lst = [Point2D.from_array(p) for p in pt_array] # Inititalize a dg object d = PolygonDirectedGraph(1e-10) vertices = polygon.vertices # Add edges to dg for i in range(len(vertices) - 1): k = d.add_node(vertices[i], [vertices[i + 1]]) if i == 0: d.outer_root_key = k d.add_node(vertices[-1], [vertices[0]]) # Test number assert len(chk_pt_lst) == d.num_nodes, _cmpstr(len(chk_pt_lst), d.num_nodes) # Test root assert d.node(d.outer_root_key)._order == 0 # Test adjacencies are correct curr_node = d.node(d.outer_root_key) for chk_pt in chk_pt_lst: assert chk_pt.is_equivalent(curr_node.pt, TOL), _cmpstr(chk_pt, curr_node.pt) # Increment curr_node = curr_node.adj_lst[0] # Test the adj matrix amtx = d.adj_matrix() # Adj matrix to test against chk_amtx = [ [0, 1, 0, 0, 0], # 0 [0, 0, 1, 0, 0], # 1 [0, 0, 0, 1, 0], # 2 [0, 0, 0, 0, 1], # 3 [1, 0, 0, 0, 0] ] # 4 # 0, 1, 2, 3, 4 # Test if the adj matrix is correct for i in range(len(chk_amtx)): for j in range(len(chk_amtx[0])): assert amtx[i][j] == chk_amtx[i][j], _cmpstr( amtx[i][j], chk_amtx[i][j])
def test_exterior_cycle(): """ Tests the exterior cycles method """ # Make the polygon polygon = Polygon2D.from_array([[0, 0], [6, 0], [6, 4], [0, 4]]) dg = polysplit._skeleton_as_directed_graph(polygon, [], 1e-10) root = dg.node(dg.outer_root_key) exterior = dg.exterior_cycle(root) for pt, node in zip(polygon.vertices, exterior): assert node.pt.is_equivalent(pt, 1e-10)
def test_min_ccw_cycle(): """ Find a closed loop from a PolygonDirectedGraph """ # Make the polygon poly = Polygon2D.from_array([[0, 0], [6, 0], [6, 6], [3, 4], [0, 6]]) # Make the test cases chk_poly = [[0, 0], [6, 0], [3.91, 2.09], [3, 1.82], [2.09, 2.09]] chk_poly = Polygon2D.from_array(chk_poly) # Skeletonize dg = polysplit._skeleton_as_directed_graph(poly, [], 1e-10) ref_node = dg.node(dg.outer_root_key) next_node = dg.next_unidirect_node(ref_node) cycle = dg.min_ccw_cycle(ref_node, next_node) cycle_poly = Polygon2D.from_array([n.pt for n in cycle]) assert cycle_poly.is_equivalent(chk_poly, 1e-2)
def test_polygon_is_equivalent(): """ Test if polygons are equivalent based on point equivalence""" tol = 1e-10 p1 = Polygon2D.from_array([[0, 0], [6, 0], [7, 3], [0, 4]]) # Test no points are the same p2 = Polygon2D.from_array([[0, 1], [6, 1], [7, 4]]) assert not p1.is_equivalent(p2, tol) # Test when length is not same p2 = Polygon2D.from_array([[0, 0], [6, 0], [7, 3]]) assert not p1.is_equivalent(p2, tol) # Test equal condition same order p2 = Polygon2D.from_array([[0, 0], [6, 0], [7, 3], [0, 4]]) assert p1.is_equivalent(p2, tol) # Test equal condition different order 1 p2 = Polygon2D.from_array([[7, 3], [0, 4], [0, 0], [6, 0]]) assert p1.is_equivalent(p2, tol) # Test equal condition different order 2 p2 = Polygon2D.from_array([[0, 4], [0, 0], [6, 0], [7, 3]]) assert p1.is_equivalent(p2, tol)
def test_concave_angles(): """ Test simple concave angles""" poly = Polygon2D.from_array( [[0, 0], [4, 0], [4, 6], [2, 4], [0, 6]]) conv_theta = math.acos(2 / math.sqrt(8)) / math.pi * 180 conc_theta = 180. + (2 * conv_theta) chk_angles = [90, conv_theta, conc_theta, conv_theta, 90] angles = polyskel.interior_angles(poly, radian=False) angles = list(angles) for i in range(len(angles)): assert abs(chk_angles[i] - angles[i]) < 1e-10
def test_convex_angles(): """ Test simple rectangle angles""" poly = Polygon2D.from_array([[0, 0], [4, 0], [4, 6], [0, 6]]) chk_angles = [90, 90, 90, 90] angles = polyskel.interior_angles(poly, radian=False) angles = list(angles) for i in range(len(angles)): assert abs(chk_angles[i] - angles[i]) < 1e-10 # Test pentagon poly = Polygon2D.from_array( [[0, 0], [4, 0], [4, 6], [2, 8], [0, 6]]) chk_angles = [90, 135, 90, 135, 90] angles = polyskel.interior_angles(poly, radian=False) angles = list(angles) pp(angles) for i in range(len(angles)): assert abs(chk_angles[i] - angles[i]) < 1e-10
def _ytick_radial_lines(bin_vecs, ytick_num): """Y-axis lines for radial histogram as List of lineSegment2Ds. Args: bin_vecs: Array of histogram bin edge vectors. ytick_num: Number of Y-axis intervals. Returns: List of LineSegment2Ds. """ max_bar_radius = 1.0 # Compute y-axis in radial coordinates, vector multiplication with max_bar_radius bar_edge_loop = bin_vecs + [bin_vecs[0]] ytick_dist = max_bar_radius / ytick_num # Length of 1 y-tick ytick_array = (((vec1[0] * i * ytick_dist, vec1[1] * i * ytick_dist) for (vec1, _) in bar_edge_loop) for i in range(1, ytick_num + 1)) return [Polygon2D.from_array((v for v in vecs)) for vecs in ytick_array]
def test_sub_polygon_traversal(): # Graph methods to retrieve subpolygons tol = 1e-10 pts = [[0, 0], [6, 0], [6, 8], [0, 8]] poly = Polygon2D.from_array(pts) # Test retrieval of exterior cycle form root node g = polysplit._skeleton_as_directed_graph(poly, None, tol) chk_nodes = [[0, 0], [6, 0], [6, 8], [0, 8], [3, 5], [3, 3]] assert len(chk_nodes) == len(g.ordered_nodes) for i, n in enumerate(g.ordered_nodes): assert n.pt.is_equivalent(Point2D.from_array(chk_nodes[i]), tol) # Check root assert g.outer_root_key == _vector2hash(Vector2D(0, 0), 1e-5) # Get exterior cycle exterior = g.exterior_cycle(g.node(g.outer_root_key)) chk_nodes = [[0, 0], [6, 0], [6, 8], [0, 8]] assert len(chk_nodes) == len(exterior) for i, n in enumerate(exterior): assert n.pt.is_equivalent(Point2D.from_array(chk_nodes[i]), tol)
def test_perimeter_core_subpolygons(): """Test splitting perimeter and core subpolygons from polygon.""" # Construct a simple rectangle poly = Polygon2D.from_array([[0, 0], [6, 0], [6, 8], [0, 8]]) # Make solution polygons (list of polygons) test_perims = [ Polygon2D.from_array(((0.0, 0.0), (6.0, 0.0), (5.0, 1.0), (1.0, 1.0))), Polygon2D.from_array(((6.0, 0.0), (6.0, 8.0), (5.0, 7.0), (5.0, 1.0))), Polygon2D.from_array(((6.0, 8.0), (0.0, 8.0), (1.0, 7.0), (5.0, 7.0))), Polygon2D.from_array(((0.0, 8.0), (0.0, 0.0), (1.0, 1.0), (1.0, 7.0))) ] test_core = Polygon2D.from_array([[1, 1], [5, 1], [5, 7], [1, 7]]) # Run method perims, cores = polysplit.perimeter_core_subpolygons(poly, 1) # Check equality for perim, test_perim in zip(perims, test_perims): assert perim.is_equivalent(test_perim, 1e-10) assert test_core.is_equivalent(cores[0], 1e-10)
def test_skeleton_subpolygons(): """Test splitting polygon into skeleton polygons""" # Make polygon poly = Polygon2D.from_array([[2, 0], [2, 2], [1, 1], [0, 2], [0, 0]]) # Tested Polygons test_subpolys = [ Polygon2D.from_array([[2., 0.], [2., 2.], [1.41421356, 0.58578644]]), Polygon2D.from_array([[2., 2.], [1., 1.], [1., 0.41421356], [1.41421356, 0.58578644]]), Polygon2D.from_array([[1., 1.], [0., 2.], [0.58578644, 0.58578644], [1., 0.41421356]]), Polygon2D.from_array([[0., 2.], [0., 0.], [0.58578644, 0.58578644]]), Polygon2D.from_array([[0., 0.], [2., 0.], [1.41421356, 0.58578644], [1., 0.41421356], [0.58578644, 0.58578644]]) ] # Split subpolys = polysplit.skeleton_subpolygons(poly) # Assert for subpoly, test_subpoly in zip(subpolys, test_subpolys): assert subpoly.is_equivalent(test_subpoly, 1e-7)
def _skeleton_as_directed_graph(_polygon, holes, tol): """Compute the straight skeleton of a polygon as a PolygonDirectedGraph. Args: polygon: polygon as Polygon2D. holes: holes as list of Polygon2Ds. tol: Tolerance for point equivalence. Returns: A PolygonDirectedGraph object. """ if _polygon.is_clockwise: # Exterior polygon must be in counter-clockwise order. _polygon = _polygon.reverse() if holes is not None: # Interior polygon must be in counter-clockwise order. for i, hole in enumerate(holes): if hole.is_clockwise: holes[i] = hole.reverse() # Make directed graph dg = PolygonDirectedGraph(tol=tol) # Reverse order to ensure cw order for input holes_array = [] if holes is None else [hole.to_array() for hole in holes] polygon = _polygon.reverse() # flip to cw order slav = polyskel._SLAV(polygon.to_array(), holes_array, tol) # Get the exterior polygon coordinates making sure to flip back to ccw for lav in slav._lavs: vertices = list(reversed(list(lav))) # Get order, account for the fact we flip outer polygons back to ccw order is_lav_cw_order = not Polygon2D.from_array([v.point for v in vertices ]).is_clockwise # Start with last point to be consistent with order of point input, and then # add rest of vertices in order. for j in range(len(vertices) - 1): curr_v = vertices[j] next_v = vertices[j + 1] k = dg.add_node(curr_v.point, [next_v.point], exterior=True) # Add roots if j == 0: if is_lav_cw_order: # Outer polygon if dg.outer_root_key is None: dg.outer_root_key = k else: raise RuntimeError( 'Outer root key is already assigned. ' 'Cannot reassign outer root key.') else: dg.hole_root_keys.append(k) # Close loop dg.add_node(vertices[-1].point, [vertices[0].point], exterior=True) # Compute the skeleton subtree_list = polyskel._skeletonize(slav) for subtree in subtree_list: event_pt = subtree.source for sink_pt in subtree.sinks: # Add a bidirectional edge to represent skeleton edges dg.add_node(sink_pt, [event_pt]) dg.add_node(event_pt, [sink_pt], exterior=False) return dg
if isinstance(polygon, list): # face with holes verts = [] for poly in polygon: verts.append([Point3D(pt.x, pt.y, z_height) for pt in poly]) return from_face3d(Face3D(verts[0], holes=verts[1:])) else: verts = [Point3D(pt.x, pt.y, z_height) for pt in polygon] return from_face3d(Face3D(verts)) if all_required_inputs(ghenv.Component): # first extract the straight skeleton from the geometry polyskel, boundaries, hole_polygons = [], [], [] for face in to_face3d(_floor_geo): # convert the input geometry into Polygon2D for straight skeleton analysis boundary = Polygon2D.from_array([(pt.x, pt.y) for pt in face.boundary]) if boundary.is_clockwise: boundary = boundary.reverse() holes, z_height = None, face[0].z if face.has_holes: holes = [] for hole in face.holes: h_poly = Polygon2D.from_array([(pt.x, pt.y) for pt in hole]) if not h_poly.is_clockwise: h_poly = h_poly.reverse() holes.append(h_poly) boundaries.append(boundary) hole_polygons.append(holes) # compute the skeleton and convert to line segments skel_lines = skeleton_as_edge_list(boundary, holes, tolerance) skel_lines_rh = [