def test_mesh3d_incorrect(): """Test the initialization of Mesh3D objects with incorrect values.""" pts = (Point3D(0, 0), Point3D(0, 2), Point3D(2, 2), Point3D(2, 0), Point3D(4, 0)) with pytest.raises(AssertionError): Mesh3D(pts, [(0, 1, 2, 3, 5)]) # too many vertices in a face with pytest.raises(AssertionError): Mesh3D(pts, []) # we need at least one face with pytest.raises(AssertionError): Mesh3D(pts, (0, 1, 2, 3)) # incorrect input type for face with pytest.raises(IndexError): Mesh3D(pts, [(0, 1, 2, 6)]) # incorrect index used by face with pytest.raises(TypeError): Mesh3D(pts, [(0.0, 1, 2, 6)]) # incorrect use of floats for face index
def test_equality(): """Test the equality of Mesh3D objects.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) pts_2 = (Point3D(0.1, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) mesh_dup = mesh.duplicate() mesh_alt = Mesh3D(pts_2, [(0, 1, 2, 3)]) assert mesh is mesh assert mesh is not mesh_dup assert mesh == mesh_dup assert hash(mesh) == hash(mesh_dup) assert mesh != mesh_alt assert hash(mesh) != hash(mesh_alt)
def test_offset_mesh(): """Test the offset_mesh method.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) pts_rev = tuple(reversed(pts)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) mesh_rev = Mesh3D(pts_rev, [(0, 1, 2, 3)]) new_mesh = mesh.offset_mesh(2) for v in new_mesh.vertices: assert v.z == 0 new_mesh_rev = mesh_rev.offset_mesh(2) for v in new_mesh_rev.vertices: assert v.z == 4
def test_mesh3d_init_two_faces(): """Test the initialization of Mesh3D objects with two faces.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) assert len(mesh.vertices) == 5 assert len(mesh.faces) == 2 assert mesh[0] == Point3D(0, 0, 2) assert mesh[1] == Point3D(0, 2, 2) assert mesh[2] == Point3D(2, 2, 2) assert mesh[3] == Point3D(2, 0, 2) assert mesh[4] == Point3D(4, 0, 2) assert mesh.area == 6 assert mesh.min == Point3D(0, 0, 2) assert mesh.max == Point3D(4, 2, 2) assert mesh.center == Point3D(2, 1, 2) assert len(mesh.face_areas) == 2 assert mesh.face_areas[0] == 4 assert mesh.face_areas[1] == 2 assert len(mesh.face_centroids) == 2 assert mesh.face_centroids[0] == Point3D(1, 1, 2) assert mesh.face_centroids[1].x == pytest.approx(2.67, rel=1e-2) assert mesh.face_centroids[1].y == pytest.approx(0.67, rel=1e-2) assert mesh.face_centroids[1].z == pytest.approx(2, rel=1e-2) assert mesh._is_color_by_face is False assert mesh.colors is None
def test_mesh3d_init(): """Test the initialization of Mesh3D objects and basic properties.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) str(mesh) # test the string representation of the object assert len(mesh.vertices) == 4 assert len(mesh.faces) == 1 assert mesh[0] == Point3D(0, 0, 2) assert mesh[1] == Point3D(0, 2, 2) assert mesh[2] == Point3D(2, 2, 2) assert mesh[3] == Point3D(2, 0, 2) assert mesh.area == 4 assert mesh.min == Point3D(0, 0, 2) assert mesh.max == Point3D(2, 2, 2) assert mesh.center == Point3D(1, 1, 2) assert len(mesh.face_areas) == 1 assert mesh.face_areas[0] == 4 assert len(mesh.face_centroids) == 1 assert mesh.face_centroids[0] == Point3D(1, 1, 2) assert mesh._is_color_by_face is False assert mesh.colors is None assert len(mesh.vertex_connected_faces) == 4 for vf in mesh.vertex_connected_faces: assert len(vf) == 1 mesh.colors = [] assert mesh.colors is None
def test_rotate(): """Test the Mesh3D rotate method.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) origin = Point3D(0, 0, 0) axis = Vector3D(1, 0, 0) test_1 = mesh.rotate(axis, math.pi, origin) assert test_1[0].x == pytest.approx(0, rel=1e-3) assert test_1[0].y == pytest.approx(0, rel=1e-3) assert test_1[0].z == pytest.approx(-2, rel=1e-3) assert test_1[2].x == pytest.approx(2, rel=1e-3) assert test_1[2].y == pytest.approx(-2, rel=1e-3) assert test_1[2].z == pytest.approx(-2, rel=1e-3) assert mesh.area == test_1.area assert len(mesh.vertices) == len(test_1.vertices) assert len(mesh.faces) == len(test_1.faces) test_2 = mesh.rotate(axis, math.pi / 2, origin) assert test_2[0].x == pytest.approx(0, rel=1e-3) assert test_2[0].y == pytest.approx(-2, rel=1e-3) assert test_2[0].z == pytest.approx(0, rel=1e-3) assert test_2[2].x == pytest.approx(2, rel=1e-3) assert test_2[2].y == pytest.approx(-2, rel=1e-3) assert test_2[2].z == pytest.approx(2, rel=1e-3) assert mesh.area == test_2.area assert len(mesh.vertices) == len(test_2.vertices) assert len(mesh.faces) == len(test_2.faces)
def test_reflect(): """Test the Mesh3D reflect method.""" pts = (Point3D(1, 1, 2), Point3D(2, 1, 2), Point3D(2, 2, 2), Point3D(1, 2, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) origin_1 = Point3D(1, 0, 2) normal_1 = Vector3D(1, 0, 0) normal_2 = Vector3D(-1, -1, 0).normalize() test_1 = mesh.reflect(normal_1, origin_1) assert test_1[0].x == pytest.approx(1, rel=1e-3) assert test_1[0].y == pytest.approx(1, rel=1e-3) assert test_1[0].z == pytest.approx(2, rel=1e-3) assert test_1[2].x == pytest.approx(0, rel=1e-3) assert test_1[2].y == pytest.approx(2, rel=1e-3) assert test_1[2].z == pytest.approx(2, rel=1e-3) test_1 = mesh.reflect(normal_2, Point3D(0, 0, 0)) assert test_1[0].x == pytest.approx(-1, rel=1e-3) assert test_1[0].y == pytest.approx(-1, rel=1e-3) assert test_1[0].z == pytest.approx(2, rel=1e-3) assert test_1[2].x == pytest.approx(-2, rel=1e-3) assert test_1[2].y == pytest.approx(-2, rel=1e-3) assert test_1[2].z == pytest.approx(2, rel=1e-3) test_2 = mesh.reflect(normal_2, origin_1) assert test_2[0].x == pytest.approx(0, rel=1e-3) assert test_2[0].y == pytest.approx(0, rel=1e-3) assert test_2[0].z == pytest.approx(2, rel=1e-3) assert test_2[2].x == pytest.approx(-1, rel=1e-3) assert test_2[2].y == pytest.approx(-1, rel=1e-3) assert test_2[2].z == pytest.approx(2, rel=1e-3)
def radial_positions_mesh(positions, dir_count=8, start_vector=Vector3D(0, -1, 0), mesh_radius=1): """Generate a Mesh3D resembling a circle around each position. Args: positions: A list of (x, y ,z) tuples for position of sensors. dir_count: A positive integer for the number of radial directions to be generated around each position. (Default: 8). start_vector: A Vector3D to set the start direction of the generated directions. (Default: (0, -1, 0)). mesh_radius: A number for the radius of the radial mesh to be generated around each sensor. (Default: 1). """ # set up the start vector and rotation angles st_vec = Vector3D(start_vector.x, start_vector.y, 0).normalize() st_vec = st_vec * mesh_radius inc_ang = (math.pi * 2) / dir_count st_vec = st_vec.rotate_xy(-inc_ang / 2) # loop through the positions and angles to create the mesh verts, faces = [], [] v_count = 0 for pt in positions: st_pt = Point3D(*pt) nxt_pt = st_pt.move(st_vec) verts.extend([st_pt, nxt_pt]) for i in range(dir_count - 1): new_pt = verts[-1].rotate_xy(inc_ang, st_pt) new_f = (v_count, v_count + i + 1, v_count + i + 2) verts.append(new_pt) faces.append(new_f) faces.append((v_count, v_count + dir_count, v_count + 1)) v_count += (dir_count + 1) return Mesh3D(verts, faces)
def test_join_meshes(): """Test the join_meshes method.""" pts1 = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) pts2 = (Point3D(2, 2, 2), Point3D(2, 4, 2), Point3D(4, 4, 2), Point3D(4, 2, 2)) mesh1 = Mesh3D(pts1, [(0, 1, 2, 3)]) mesh2 = Mesh3D(pts2, [(0, 1, 2, 3)]) mesh1.face_centroids mesh2.face_centroids mesh1.face_normals mesh2.face_normals joined_mesh = Mesh3D.join_meshes([mesh1, mesh2]) assert isinstance(joined_mesh, Mesh3D) assert len(joined_mesh.faces) == 2 assert len(joined_mesh.vertices) == 8
def test_mesh3d_to_from_dict(): """Test the to/from dict of Mesh3D objects.""" pts = (Point3D(0, 0), Point3D(0, 2), Point3D(2, 2), Point3D(2, 0)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) mesh_dict = mesh.to_dict() new_mesh = Mesh3D.from_dict(mesh_dict) assert isinstance(new_mesh, Mesh3D) assert new_mesh.to_dict() == mesh_dict
def to_mesh3d(mesh, color_by_face=True): """Ladybug Mesh3D from Rhino Mesh.""" if isinstance(mesh, Mesh3D): return mesh elif isinstance(mesh, bpy.types.Object): lb_verts = tuple( to_point3d(mesh.matrix_world @ pt.co) for pt in mesh.data.vertices) lb_faces, colors = _extract_mesh_faces_colors(mesh, mesh.data, color_by_face) return Mesh3D(lb_verts, lb_faces, colors)
def test_face_normals(): """Test the Mesh3D face_normals property.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) assert len(mesh.face_normals) == 1 assert mesh.face_normals[0] == Vector3D(0, 0, -1) assert len(mesh.vertex_normals) == 4 for vert_norm in mesh.vertex_normals: assert vert_norm == Vector3D(0, 0, -1)
def test_height_field_mesh(): """Test the height_field_mesh method.""" pts = (Point3D(0, 0, 0), Point3D(2, 0, 0), Point3D(2, 2, 0), Point3D(0, 2, 0)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) values = [-1, 0, 1, 2] new_mesh = mesh.height_field_mesh(values, (0, 3)) assert new_mesh[0].z == 0 assert new_mesh[1].z == 1 assert new_mesh[2].z == 2 assert new_mesh[3].z == 3
def _generate_bottom_from_top(m_top, v_top): """Get a joined mesh and vectors for top and bottom from only top vectors.""" # reverse the vectors and negate all the z values of the sky patch mesh verts = tuple(Point3D(pt.x, pt.y, -pt.z) for pt in m_top.vertices) faces = tuple(face[::-1] for face in m_top.faces) m_bottom = Mesh3D(verts, faces) v_bottom = tuple(Vector3D(v.x, v.y, -v.z) for v in v_top) # join everything together patch_mesh = Mesh3D.join_meshes([m_top, m_bottom]) patch_vectors = v_top + v_bottom return patch_mesh, patch_vectors
def dome_radial_patches(self, azimuth_count=72, altitude_count=18): """Get Vector3Ds and a correcponding Mesh3D for a a radial dome. Args: azimuth_count: A positive integer for the number of times that the horizontal circle will be subdivided into azimuth patches. (Default: 72). altitude_count: A positive integer for the number of times that the dome quarter-circle will be subdivided into altitude patches. (Default: 18). Returns: A tuple with two elements - patch_mesh: A ladybug_geometry Mesh3D that represents the patches at the input azimuth_count and altitude_count. - patch_vectors: A list of ladybug_geometry Vector3D with one vector per mesh face. These will align with the faces of the patch_mesh. All vectors are unit vectors. """ # set up starting vectors and points base_vec, rotate_axis = Vector3D(0, 1, 0), Vector3D(1, 0, 0) horiz_angle = -2 * math.pi / azimuth_count vertical_angle = math.pi / (2 * altitude_count) # loop through the patch values and generate points for each vertex vertices, faces = [], [] pt_i = -2 # track the number of vertices in the mesh for row_i in range(altitude_count - 1): pt_i += 2 # advance the number of vertices by two vec1 = base_vec.rotate(rotate_axis, vertical_angle * row_i) vec2 = vec1.rotate(rotate_axis, vertical_angle) vertices.extend((Point3D(v.x, v.y, v.z) for v in (vec1, vec2))) for _ in range(azimuth_count): # generate the row of patches vec3 = vec1.rotate_xy(horiz_angle) vec4 = vec2.rotate_xy(horiz_angle) vertices.extend((Point3D(v.x, v.y, v.z) for v in (vec3, vec4))) faces.append((pt_i, pt_i + 1, pt_i + 3, pt_i + 2)) pt_i += 2 # advance the number of vertices by two vec1, vec2 = vec3, vec4 # reset vec1 and vec2 for the next patch # add triangular faces to represent the last circular patch end_vert_i = len(vertices) start_vert_i = len(vertices) - azimuth_count * 2 - 1 vertices.append(Point3D(0, 0, 1)) for tr_i in range(0, azimuth_count * 2, 2): faces.append((start_vert_i + tr_i, end_vert_i, start_vert_i + tr_i + 2)) # create the Mesh3D object and derive the patch vectors from the mesh patch_mesh = Mesh3D(vertices, faces) patch_vectors = patch_mesh.face_normals return patch_mesh, patch_vectors
def test_height_field_mesh_faces(): """Test the height_field_mesh method with values for faces.""" pts = (Point3D(0, 0, 0), Point3D(2, 0, 0), Point3D(2, 2, 0), Point3D(0, 2, 0), Point3D(4, 0, 0)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) values = [-1, 1] new_mesh = mesh.height_field_mesh(values, (1, 2)) assert new_mesh[0].z == 1 assert new_mesh[1].z == 1 assert new_mesh[2].z == 1.5 assert new_mesh[3].z == 1.5 assert new_mesh[4].z == 2
def test_mesh3d_to_from_json(): """Test the to/from dict with JSON serialization of Mesh3D objects.""" pts = (Point3D(0, 0), Point3D(0, 2), Point3D(2, 2), Point3D(2, 0)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) mesh_dict = mesh.to_dict() geo_file = './tests/json/json_mesh.json' with open(geo_file, 'w') as fp: json.dump(mesh_dict, fp) with open(geo_file, 'r') as fp: new_mesh_dict = json.load(fp) new_mesh = Mesh3D.from_dict(new_mesh_dict) assert isinstance(new_mesh, Mesh3D) assert new_mesh.to_dict() == mesh_dict os.remove(geo_file)
def test_scale_world_origin(): """Test the Mesh2D scale method with None origin.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) new_mesh_1 = mesh.scale(2) assert new_mesh_1[0] == Point3D(0, 0, 4) assert new_mesh_1[1] == Point3D(0, 4, 4) assert new_mesh_1[2] == Point3D(4, 4, 4) assert new_mesh_1[3] == Point3D(4, 0, 4) assert new_mesh_1[4] == Point3D(8, 0, 4) assert new_mesh_1.area == 24 assert len(mesh.vertices) == len(new_mesh_1.vertices) assert len(mesh.faces) == len(new_mesh_1.faces)
def test_scale(): """Test the Mesh3D scale method.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) origin_1 = Point3D(2, 0, 2) new_mesh_1 = mesh.scale(2, origin_1) assert new_mesh_1[0] == Point3D(-2, 0, 2) assert new_mesh_1[1] == Point3D(-2, 4, 2) assert new_mesh_1[2] == Point3D(2, 4, 2) assert new_mesh_1[3] == Point3D(2, 0, 2) assert new_mesh_1[4] == Point3D(6, 0, 2) assert new_mesh_1.area == 24 assert len(mesh.vertices) == len(new_mesh_1.vertices) assert len(mesh.faces) == len(new_mesh_1.faces)
def test_move(): """Test the Mesh3D move method.""" pts = (Point3D(0, 0, 2), Point3D(0, 2, 2), Point3D(2, 2, 2), Point3D(2, 0, 2), Point3D(4, 0, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3), (2, 3, 4)]) vec_1 = Vector3D(2, 2, -1) new_mesh = mesh.move(vec_1) assert new_mesh[0] == Point3D(2, 2, 1) assert new_mesh[1] == Point3D(2, 4, 1) assert new_mesh[2] == Point3D(4, 4, 1) assert new_mesh[3] == Point3D(4, 2, 1) assert new_mesh[4] == Point3D(6, 2, 1) assert mesh.area == new_mesh.area assert len(mesh.vertices) == len(new_mesh.vertices) assert len(mesh.faces) == len(new_mesh.faces)
def envelope_mesh(self): """Compute a Mesh3D representing the solar envelope boundary.""" # extract the relevant proprties from the input geometry pt2ds, poly2ds = self.geometry_point2ds, self.obstacle_polygon2ds vec2ds = self.sun_vector2ds if self.solar_rights else self.sun_vector2ds_reversed altitudes = self._sun_altitudes() obs_heights = [face[0].z for face in self._obstacle_faces] base_height = self.base_height # loop through the points to get the height of each one pt_heights = self._compute_point_heights(pt2ds, poly2ds, obs_heights, vec2ds, altitudes, base_height) # turn the mesh point heights into a full Mesh3D new_vertices = [ Point3D(pt.x, pt.y, h) for pt, h in zip(pt2ds, pt_heights) ] return Mesh3D(new_vertices, self._geometry_mesh.faces)
def test_rotate_xy(): """Test the Mesh3D rotate_xy method.""" pts = (Point3D(1, 1, 2), Point3D(2, 1, 2), Point3D(2, 2, 2), Point3D(1, 2, 2)) mesh = Mesh3D(pts, [(0, 1, 2, 3)]) origin_1 = Point3D(1, 1, 0) test_1 = mesh.rotate_xy(math.pi, origin_1) assert test_1[0].x == pytest.approx(1, rel=1e-3) assert test_1[0].y == pytest.approx(1, rel=1e-3) assert test_1[0].z == pytest.approx(2, rel=1e-3) assert test_1[2].x == pytest.approx(0, rel=1e-3) assert test_1[2].y == pytest.approx(0, rel=1e-3) assert test_1[2].z == pytest.approx(2, rel=1e-3) test_2 = mesh.rotate_xy(math.pi / 2, origin_1) assert test_2[0].x == pytest.approx(1, rel=1e-3) assert test_2[0].y == pytest.approx(1, rel=1e-3) assert test_1[0].z == pytest.approx(2, rel=1e-3) assert test_2[2].x == pytest.approx(0, rel=1e-3) assert test_2[2].y == pytest.approx(2, rel=1e-3) assert test_1[2].z == pytest.approx(2, rel=1e-3)
sky_mask, view_vecs = view_sphere.dome_radial_patches(az_count, alt_count) sky_mask = sky_mask.scale(radius) if center_pt3d != Point3D(): m_vec = Vector3D(center_pt3d.x, center_pt3d.y, center_pt3d.z) sky_mask = sky_mask.move(m_vec) if projection_ is not None: if projection_.title() == 'Orthographic': pts = (Compass.point3d_to_orthographic(pt) for pt in sky_mask.vertices) elif projection_.title() == 'Stereographic': pts = (Compass.point3d_to_stereographic(pt, radius, center_pt3d) for pt in sky_mask.vertices) else: raise ValueError( 'Projection type "{}" is not recognized.'.format(projection_)) pts3d = tuple(Point3D(pt.x, pt.y, center_pt3d.z) for pt in pts) sky_mask = Mesh3D(pts3d, sky_mask.faces) sky_pattern = [True] * len( view_vecs) # pattern to be adjusted by the various masks # account for the orientation and any of the projection strategies orient_pattern, strategy_pattern = None, None if direction is not None: orient_pattern, dir_angles = view_sphere.orientation_pattern( direction, view_vecs) apply_mask_to_sky(sky_pattern, orient_pattern) if overhang_proj_ or left_fin_proj_ or right_fin_proj_: strategy_pattern = [False] * len(view_vecs) if overhang_proj_: over_pattern = view_sphere.overhang_pattern( direction, overhang_proj_, view_vecs) apply_mask_to_base_mask(strategy_pattern, over_pattern,
def to_mesh3d(mesh, color_by_face=True): """Ladybug Mesh3D from Rhino Mesh.""" lb_verts = tuple(to_point3d(pt) for pt in mesh.Vertices) lb_faces, colors = _extract_mesh_faces_colors(mesh, color_by_face) return Mesh3D(lb_verts, lb_faces, colors)
def draw_dome(dome_data, center, dome_name, legend_par): """Draw the dome mesh, compass, legend, and title for a sky dome. Args: dome_data: List of radiation values for the dome data center: Point3D for the center of the sun path. dome_name: Text for the dome name, which will appear in the title. legend_par: Legend parameters to be used for the dome Returns: dome_mesh: A colored mesh for the dome based on dome_data. dome_compass: A compass for the dome. dome_legend: A leend for the colored dome mesh. dome_title: A title for the dome. values: A list of radiation values that align with the dome_mesh faces. """ # create the dome mesh and ensure patch values align with mesh faces if len(dome_data) == 145: # tregenza sky lb_mesh = view_sphere.tregenza_dome_mesh_high_res.scale(radius) values = [] # high res dome has 3 x 3 faces per patch; we must convert tot_i = 0 # track the total number of patches converted for patch_i in view_sphere.TREGENZA_PATCHES_PER_ROW: row_vals = [] for val in dome_data[tot_i:tot_i + patch_i]: row_vals.extend([val] * 3) for i in range(3): values.extend(row_vals) tot_i += patch_i values = values + [dome_data[-1] ] * 18 # last patch has triangular faces else: #reinhart sky lb_mesh = view_sphere.reinhart_dome_mesh.scale(radius) values = dome_data + [dome_data[-1] ] * 11 # last patch has triangular faces # move and/or rotate the mesh as needed if north != 0: lb_mesh = lb_mesh.rotate_xy(math.radians(north), Point3D()) if center != Point3D(): lb_mesh = lb_mesh.move(Vector3D(center.x, center.y, center.z)) # project the mesh if requested if projection_ is not None: if projection_.title() == 'Orthographic': pts = (Compass.point3d_to_orthographic(pt) for pt in lb_mesh.vertices) elif projection_.title() == 'Stereographic': pts = (Compass.point3d_to_stereographic(pt, radius, center) for pt in lb_mesh.vertices) else: raise ValueError( 'Projection type "{}" is not recognized.'.format(projection_)) pts3d = tuple(Point3D(pt.x, pt.y, z) for pt in pts) lb_mesh = Mesh3D(pts3d, lb_mesh.faces) # output the dome visualization, including legend and compass move_fac = radius * 0.15 min_pt = lb_mesh.min.move(Vector3D(-move_fac, -move_fac, 0)) max_pt = lb_mesh.max.move(Vector3D(move_fac, move_fac, 0)) graphic = GraphicContainer(values, min_pt, max_pt, legend_par) graphic.legend_parameters.title = 'kWh/m2' lb_mesh.colors = graphic.value_colors dome_mesh = from_mesh3d(lb_mesh) dome_legend = legend_objects(graphic.legend) dome_compass = compass_objects( Compass(radius, Point2D(center.x, center.y), north), z, None, projection_, graphic.legend_parameters.font) # construct a title from the metadata st, end = metadata[2], metadata[3] time_str = '{} - {}'.format(st, end) if st != end else st title_txt = '{} Radiation\n{}\n{}'.format( dome_name, time_str, '\n'.join([dat for dat in metadata[4:]])) dome_title = text_objects(title_txt, graphic.lower_title_location, graphic.legend_parameters.text_height, graphic.legend_parameters.font) return dome_mesh, dome_compass, dome_legend, dome_title, values
def dome_patches(self, division_count=1, subdivide_in_place=False): """Get Vector3Ds and a correcponding Mesh3D for a dome. Args: division_count: A positive integer for the number of times that the original Tregenza patches are subdivided. 1 indicates that the original Tregenza patches will be used, 2 indicates the Reinhart patches will be used, and so on. (Default: 1). subdivide_in_place: A boolean to note whether patches should be subdivided according to the extension of Tregenza's original logic through the Reinhart method (False) or they should be simply divided into 4 in place (True). The latter is useful for making higher resolution Mesh visualizations of an inherently low-resolution dome. Returns: A tuple with two elements - patch_mesh: A ladybug_geometry Mesh3D that represents the dome at the input division_count. There is one quad face per patch except for the last circular patch, which is represented by a number of triangles equal to division_count * 6. - patch_vectors: A list of ladybug_geometry Vector3D with one vector per patch. These will align with the faces of the patch_mesh up until the last circular patch, which will have a single vector for the several triangular faces. All vectors are unit vectors. """ # compute constants to be used in the generation of patch points patch_row_count = self._patch_row_count_array(division_count) base_vec = Vector3D(0, 1, 0) rotate_axis = Vector3D(1, 0, 0) vertical_angle = math.pi / (2 * len(patch_row_count) + division_count) if \ subdivide_in_place else math.pi / (2 * len(patch_row_count) + 1) # loop through the patch values and generate points for each vertex vertices, faces = [], [] pt_i = -2 # track the number of vertices in the mesh for row_i, row_count in enumerate(patch_row_count): pt_i += 2 # advance the number of vertices by two horiz_angle = -2 * math.pi / row_count # horizontal angle of each patch vec01 = base_vec.rotate(rotate_axis, vertical_angle * row_i) vec02 = vec01.rotate(rotate_axis, vertical_angle) correction_angle = -horiz_angle / 2 if subdivide_in_place: correction_angle * division_count vec1 = vec01.rotate_xy(correction_angle) vec2 = vec02.rotate_xy(correction_angle) vertices.extend((Point3D(v.x, v.y, v.z) for v in (vec1, vec2))) for _ in range(row_count): # generate the row of patches vec3 = vec1.rotate_xy(horiz_angle) vec4 = vec2.rotate_xy(horiz_angle) vertices.extend((Point3D(v.x, v.y, v.z) for v in (vec3, vec4))) faces.append((pt_i, pt_i + 1, pt_i + 3, pt_i + 2)) pt_i += 2 # advance the number of vertices by two vec1, vec2 = vec3, vec4 # reset vec1 and vec2 for the next patch # add triangular faces to represent the last circular patch end_vert_i = len(vertices) start_vert_i = len(vertices) - patch_row_count[-1] * 2 - 1 vertices.append(Point3D(0, 0, 1)) for tr_i in range(0, patch_row_count[-1] * 2, 2): faces.append((start_vert_i + tr_i, end_vert_i, start_vert_i + tr_i + 2)) # create the Mesh3D object and derive the patch vectors from the mesh patch_mesh = Mesh3D(vertices, faces) patch_vectors = patch_mesh.face_normals[:-patch_row_count[-1]] + \ (Vector3D(0, 0, 1),) return patch_mesh, patch_vectors