def label_face(face, _attribute_, _font_, label_text, base_pts, labels, wire_frame): """Generate labels for a face or sub-face and add it to a list.""" face_prop = get_attr_nested(face, _attribute_) # get a base plane and text height for the text label cent_pt = face.geometry.center # base point for the text base_plane = Plane(face.normal, cent_pt) if base_plane.y.z < 0: # base plane pointing downwards; rotate it base_plane = base_plane.rotate(base_plane.n, math.pi, base_plane.o) if _txt_height_ is None: # auto-calculate default text height txt_len = len(face_prop) if len(face_prop) > 10 else 10 largest_dim = max((face.geometry.max.x - face.geometry.min.x), (face.geometry.max.y - face.geometry.min.y)) txt_h = largest_dim / (txt_len * 2) else: txt_h = _txt_height_ # move base plane origin a little to avoid overlaps of adjacent labels if base_plane.n.x != 0: m_vec = base_plane.y if base_plane.n.x < 0 else -base_plane.y else: m_vec = base_plane.y if base_plane.n.z < 0 else -base_plane.y base_plane = base_plane.move(m_vec * txt_h) # create the text label label = text_objects(face_prop, base_plane, txt_h, font=_font_, horizontal_alignment=1, vertical_alignment=3) # append everything to the lists label_text.append(face_prop) base_pts.append(from_plane(base_plane)) labels.append(label) wire_frame.extend(from_face3d_to_wireframe(face.geometry))
def aperture_by_width_height(self, width, height, sill_height=1, aperture_name=None): """Add a single rectangular aperture to the center of this face. While the resulting aperture will always be in the plane of this Face, this method will not check to ensure that the aperture has all of its vertices completely within the boundary of this Face. The are_sub_faces_valid() method can be used afterwards to check this. Args: width: A number for the Aperture width. height: A number for the Aperture height. sill_height: A number for the sill height. Default: 1. aperture_name: Optional name for the aperture. If None, the default name will follow the convention "[face_name]_Glz[count]" where [count] is one more than the current numer of apertures in the face. Returns: The new Aperture object that has been generated. Usage: room = Room.from_box(3.0, 6.0, 3.2, 180) room[1].aperture_by_width_height(2, 2, .7) # aperture in front room[2].aperture_by_width_height(4, 1.5, .5) # aperture on right room[2].aperture_by_width_height(4, 0.5, 2.2) # aperture on right """ # Perform checks self._acceptable_sub_face_check(Aperture) # Generate the aperture geometry face_plane = Plane(self._geometry.plane.n, self._geometry.min) if face_plane.y.z < 0: face_plane = face_plane.rotate(face_plane.n, math.pi, face_plane.o) center2d = face_plane.xyz_to_xy(self._geometry.center) x_dist = width / 2 lower_left = Point2D(center2d.x - x_dist, sill_height) lower_right = Point2D(center2d.x + x_dist, sill_height) upper_right = Point2D(center2d.x + x_dist, sill_height + height) upper_left = Point2D(center2d.x - x_dist, sill_height + height) ap_verts2d = (lower_left, lower_right, upper_right, upper_left) ap_verts3d = tuple(face_plane.xy_to_xyz(pt) for pt in ap_verts2d) ap_face = Face3D(ap_verts3d, self._geometry.plane) if self.normal.angle(ap_face.normal) > math.pi / 2: # reversed normal ap_face = ap_face.flip() # Create the aperture and add it to this Face name = aperture_name or '{}_Glz{}'.format(self.display_name, len(self.apertures)) aperture = Aperture(name, ap_face) aperture._parent = self self._apertures.append(aperture) return aperture
def test_rotate(): """Test the Plane rotate method.""" pt = Point3D(2, 2, 2) vec = Vector3D(0, 2, 0) plane = Plane(vec, pt) origin_1 = Point3D(0, 0, 0) axis_1 = Vector3D(1, 0, 0) test_1 = plane.rotate(axis_1, math.pi, origin_1) assert test_1.o.x == pytest.approx(2, rel=1e-3) assert test_1.o.y == pytest.approx(-2, rel=1e-3) assert test_1.o.z == pytest.approx(-2, rel=1e-3) assert test_1.n.x == pytest.approx(0, rel=1e-3) assert test_1.n.y == pytest.approx(-1, rel=1e-3) assert test_1.n.z == pytest.approx(0, rel=1e-3) assert test_1.x.x == pytest.approx(1, rel=1e-3) assert test_1.x.y == pytest.approx(0, rel=1e-3) assert test_1.x.z == pytest.approx(0, rel=1e-3) assert test_1.y.x == pytest.approx(0, rel=1e-3) assert test_1.y.y == pytest.approx(0, rel=1e-3) assert test_1.y.z == pytest.approx(1, rel=1e-3) assert test_1.k == pytest.approx(2, rel=1e-3) test_2 = plane.rotate(axis_1, math.pi/2, origin_1) assert test_2.o.x == pytest.approx(2, rel=1e-3) assert test_2.o.y == pytest.approx(-2, rel=1e-3) assert test_2.o.z == pytest.approx(2, rel=1e-3) assert test_2.n.x == pytest.approx(0, rel=1e-3) assert test_2.n.y == pytest.approx(0, rel=1e-3) assert test_2.n.z == pytest.approx(1, rel=1e-3) assert test_2.x.x == pytest.approx(1, rel=1e-3) assert test_2.x.y == pytest.approx(0, rel=1e-3) assert test_2.x.z == pytest.approx(0, rel=1e-3) assert test_2.y.x == pytest.approx(0, rel=1e-3) assert test_2.y.y == pytest.approx(1, rel=1e-3) assert test_2.y.z == pytest.approx(0, rel=1e-3) assert test_2.k == pytest.approx(2, rel=1e-3)
def rotate(self, angle, axis=None, origin=None): """Rotate this view by a certain angle around an axis and origin. Args: angle: An angle for rotation in degrees. axis: Rotation axis as a Vector3D. If None, self.up_vector will be used. (Default: None). origin: A ladybug_geometry Point3D for the origin around which the object will be rotated. If None, self.position is used. (Default: None). """ view_up_vector = pv.Vector3D(*self.up_vector) view_position = pv.Point3D(*self.position) view_direction = pv.Vector3D(*self.direction) view_plane = Plane(n=view_up_vector, o=view_position, x=view_direction) axis = axis if axis is not None else view_up_vector position = origin if origin is not None else view_position rotated_plane = view_plane.rotate(axis, math.radians(angle), position) self._apply_plane_properties(rotated_plane, view_direction, view_up_vector)