def tangent(self, t): """Compute the tangent vector at a point on the curve. Parameters ---------- t : float The value of the curve parameter. Must be between 0 and 1. Returns ------- Vector The corresponding tangent vector. Examples -------- >>> curve = Bezier([[0.0, 0.0, 0.0], [0.5, 1.0, 0.0], [1.0, 0.0, 0.0]]) >>> curve.tangent(0.5) Vector(1.000, 0.000, 0.000) """ n = self.degree v = Vector(0, 0, 0) for i, p in enumerate(self.points): a = bernstein(n - 1, i - 1, t) b = bernstein(n - 1, i, t) c = n * (a - b) v += p * c v.unitize() return v
def from_data(cls, data): """Construct a frame from its data representation. Parameters ---------- data : dict The data dictionary. Returns ------- :class:`compas.geometry.Frame` The constructed frame. Examples -------- >>> data = {'point': [0.0, 0.0, 0.0], 'xaxis': [1.0, 0.0, 0.0], 'yaxis': [0.0, 1.0, 0.0]} >>> frame = Frame.from_data(data) >>> frame.point Point(0.000, 0.000, 0.000) >>> frame.xaxis Vector(1.000, 0.000, 0.000) >>> frame.yaxis Vector(0.000, 1.000, 0.000) """ frame = cls(Point.from_data(data['point']), Vector.from_data(data['xaxis']), Vector.from_data(data['yaxis'])) return frame
def represent_vector_in_global_coordinates(self, vector): """Represents a vector in local coordinates in the world coordinate system. Parameters ---------- vector: :obj:`list` of :obj:`float` or :class:`Vector` A vector in local coordinates. Returns ------- :class:`Vector` A vector in the world coordinate system. Examples -------- >>> from compas.geometry import Frame >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> pw1 = [2, 2, 2] >>> pf = f.represent_vector_in_local_coordinates(pw1) >>> pw2 = f.represent_vector_in_global_coordinates(pf) >>> allclose(pw1, pw2) True """ T = matrix_from_frame(self) vec = Vector(*vector) vec.transform(T) return vec
def basis_vectors(self): """Returns the basis vectors from the ``Rotation`` component of the ``Transformation``. """ sc, sh, a, t, p = decompose_matrix(self.matrix) R = matrix_from_euler_angles(a, static=True, axes='xyz') xv, yv = basis_vectors_from_matrix(R) return Vector(*xv), Vector(*yv)
def compute_tangent(self, t): n = self.degree v = Vector(0, 0, 0) for i, p in enumerate(self.points): a = bernstein(n - 1, i - 1, t) b = bernstein(n - 1, i, t) c = n * (a - b) v += p * c v.unitize() return v
def basis_vectors(self): """Returns the basis vectors from the ``Rotation`` component of the ``Transformation``. """ # this has to be here to avoid circular import from compas.geometry.primitives import Vector sc, sh, a, t, p = decompose_matrix(self.matrix) R = matrix_from_euler_angles(a, static=True, axes='xyz') xv, yv = basis_vectors_from_matrix(R) return Vector(*xv), Vector(*yv)
def basis_vectors(self): """Returns the basis vectors of the ``Rotation``. Returns ------- tuple: (:class:`Vector`, :class:`Vector`) """ from compas.geometry.primitives import Vector xaxis, yaxis = basis_vectors_from_matrix(self.matrix) return Vector(*xaxis), Vector(*yaxis)
def from_bounding_box(cls, bbox): a = bbox[0] b = bbox[1] d = bbox[3] e = bbox[4] xaxis = Vector(*subtract_vectors(d, a)) yaxis = Vector(*subtract_vectors(b, a)) zaxis = Vector(*subtract_vectors(e, a)) xsize = xaxis.length ysize = yaxis.length zsize = zaxis.length frame = Frame(a, xaxis, yaxis) return cls(frame, xsize, ysize, zsize)
def test_basis_vectors(): trans1 = [1, 2, 3] angle1 = [-2.142, 1.141, -0.142] scale1 = [0.123, 2, 0.5] T1 = Translation(trans1) R1 = Rotation.from_euler_angles(angle1) S1 = Scale(scale1) M = (T1 * R1) * S1 x, y = M.basis_vectors assert np.allclose( x, Vector(0.41249169135312663, -0.05897071585157175, -0.9090506362335324)) assert np.allclose( y, Vector(-0.8335562904208867, -0.4269749553355485, -0.35053715668381935))
def from_bounding_box(cls, bbox): # this should put the frame at the centroid of the box # not at the bottom left corner a = bbox[0] b = bbox[1] d = bbox[3] e = bbox[4] xaxis = Vector(*subtract_vectors(d, a)) yaxis = Vector(*subtract_vectors(b, a)) zaxis = Vector(*subtract_vectors(e, a)) xsize = xaxis.length ysize = yaxis.length zsize = zaxis.length frame = Frame(a, xaxis, yaxis) frame.point += frame.xaxis * 0.5 * xsize + frame.yaxis * 0.5 * ysize + frame.zaxis * 0.5 * zsize return cls(frame, xsize, ysize, zsize)
def from_point_and_two_vectors(cls, point, u, v): """Construct a plane from a base point and two vectors. Parameters ---------- point : [float, float, float] | :class:`compas.geometry.Point` The base point. u : [float, float, float] | :class:`compas.geometry.Vector` The first vector. v : [float, float, float] | :class:`compas.geometry.Vector` The second vector. Returns ------- :class:`compas.geometry.Plane` A plane with base point `point` and normal vector defined as the unitized cross product of vectors `u` and `v`. Examples -------- >>> plane = Plane.from_three_points([0.0, 0.0, 0.0], [2.0, 1.0, 0.0], [0.0, 3.0, 0.0]) >>> plane.point Point(0.000, 0.000, 0.000) >>> plane.normal Vector(0.000, 0.000, 1.000) """ normal = Vector.cross(u, v) return cls(point, normal)
def from_three_points(cls, a, b, c): """Construct a plane from three points in three-dimensional space. Parameters ---------- a : [float, float, float] | :class:`compas.geometry.Point` The first point. b : [float, float, float] | :class:`compas.geometry.Point` The second point. c : [float, float, float] | :class:`compas.geometry.Point` The second point. Returns ------- :class:`compas.geometry.Plane` A plane with base point `a` and normal vector defined as the unitized cross product of the vectors `ab` and `ac`. Examples -------- >>> plane = Plane.from_three_points([0.0, 0.0, 0.0], [2.0, 1.0, 0.0], [0.0, 3.0, 0.0]) >>> plane.point Point(0.000, 0.000, 0.000) >>> plane.normal Vector(0.000, 0.000, 1.000) """ a = Point(*a) b = Point(*b) c = Point(*c) normal = Vector.cross(b - a, c - a) return cls(a, normal)
def from_corner_corner_height(cls, corner1, corner2, height): """Construct a box from the opposite corners of its base and its height. Parameters ---------- corner1 : point The XYZ coordinates of the bottom left corner of the base of the box. corner2 : point The XYZ coordinates of the top right corner of the base of the box. height : float The height of the box. Returns ------- Box The resulting box. Examples -------- >>> box = Box.from_corner_corner_height([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 1.0) """ # this should put the frame at the centroid of the box # not at the bottom left corner if height == 0: raise Exception('The box should have a height.') x1, y1, z1 = corner1 x2, y2, z2 = corner2 xaxis = Vector(x2 - x1, 0, 0) yaxis = Vector(0, y2 - y1, 0) width = xaxis.length depth = yaxis.length if z1 != z2: raise Exception('Corners should be in the same horizontal plane.') frame = Frame(corner1, xaxis, yaxis) frame.point += frame.xaxis * 0.5 * width + frame.yaxis * 0.5 * depth + frame.zaxis * 0.5 * height return cls(frame, width, depth, height)
def from_diagonal(cls, diagonal): """Construct a box from its main diagonal. Parameters ---------- diagonal : segment The diagonal of the box, represented by a pair of points in space. Returns ------- Box The resulting box. Examples -------- >>> diagonal = [0.0, 0.0, 0.0], [1.0, 1.0, 1.0] >>> box = Box.from_diagonal(diagonal) """ # this should put the frame at the centroid of the box # not at the bottom left corner d1, d2 = diagonal x1, y1, z1 = d1 x2, y2, z2 = d2 if z1 == z2: raise Exception('The box has no height.') xaxis = Vector(x2 - x1, 0, 0) yaxis = Vector(0, y2 - y1, 0) zaxis = Vector(0, 0, z2 - z1) width = xaxis.length depth = yaxis.length height = zaxis.length frame = Frame(d1, xaxis, yaxis) frame.point += frame.xaxis * 0.5 * width + frame.yaxis * 0.5 * depth + frame.zaxis * 0.5 * height return cls(frame, width, depth, height)
def from_plane(cls, plane): """Constructs a frame from a plane. Xaxis and yaxis are arbitrarily selected based on the plane's normal. Parameters ---------- plane : :class:`compas.geometry.Plane` A plane. Returns ------- :class:`compas.geometry.Frame` The constructed frame. Examples -------- >>> plane = Plane([0,0,0], [0,0,1]) >>> frame = Frame.from_plane(plane) >>> allclose(frame.normal, plane.normal) True """ # plane equation: a*x + b*y + c*z = d d = Vector(*plane.point).dot(plane.normal) # select 2 arbitrary points in the plane from which we create the xaxis coeffs = list(plane.normal) # a, b, c # select a coeff with a value != 0 coeffs_abs = [math.fabs(x) for x in coeffs] idx = coeffs_abs.index(max(coeffs_abs)) # first point coords = [0, 0, 0] # x, y, z # z = (d - a*0 + b*0)/c, if idx == 2 v = d / coeffs[idx] coords[idx] = v pt1_in_plane = Point(*coords) # second point coords = [1, 1, 1] # x, y, z coords[idx] = 0 # z = (d - a*1 + b*1)/c, if idx == 2 v = (d - sum([a * x for a, x in zip(coeffs, coords)])) / coeffs[idx] coords[idx] = v pt2_in_plane = Point(*coords) xaxis = pt2_in_plane - pt1_in_plane yaxis = plane.normal.cross(xaxis) return cls(plane.point, xaxis, yaxis)
def from_plane(cls, plane): """Constructs a frame from a plane. Xaxis and yaxis are arbitrarily selected based on the plane's normal. Parameters ---------- plane : :class:`compas.geometry.Plane` A plane. Returns ------- :class:`compas.geometry.Frame` The constructed frame. Examples -------- >>> from compas.geometry import Plane >>> plane = Plane([0,0,0], [0,0,1]) >>> frame = Frame.from_plane(plane) >>> allclose(frame.normal, plane.normal) True """ point, normal = plane # To construct a frame we need to find a vector v that is perpendicular # to the plane's normal. This means that the dot-product of v with the # normal must be equal to 0, which is true for the following vectors: vectors = [ Vector(-normal[1], normal[0], 0), Vector(0, -normal[2], normal[1]), Vector(normal[2], 0, -normal[0]) ] # But if we are unlucky, one of these vectors is (0, 0, 0), so we # choose the vector with the longest length as xaxis. idx = argmax([v.length for v in vectors]) xaxis = vectors[idx] yaxis = cross_vectors(normal, xaxis) return cls(point, xaxis, yaxis)
def from_diagonal(cls, diagonal): """Construct a box from its main diagonal. Parameters ---------- diagonal : segment The diagonal of the box, represented by a pair of points in space. Returns ------- Box The resulting box. Examples -------- >>> from compas.geometry import Box >>> diagonal = [0.0, 0.0, 0.0], [1.0, 1.0, 1.0] >>> box = Box.from_diagonal(diagonal) """ d1, d2 = diagonal x1, y1, z1 = d1 x2, y2, z2 = d2 if z1 == z2: raise Exception('The box has no height.') xaxis = Vector(x2 - x1, 0, 0) yaxis = Vector(0, y2 - y1, 0) zaxis = Vector(0, 0, z2 - z1) width = xaxis.length depth = yaxis.length height = zaxis.length frame = Frame(d1, xaxis, yaxis) return cls(frame, width, depth, height)
def normal(self): o = self.centroid points = self.points a2 = 0 normals = [] for i in range(-1, len(points) - 1): p1 = points[i] p2 = points[i + 1] u = [p1[_] - o[_] for _ in range(3)] v = [p2[_] - o[_] for _ in range(3)] w = cross_vectors(u, v) a2 += sum(w[_]**2 for _ in range(3))**0.5 normals.append(w) n = [sum(axis) / a2 for axis in zip(*normals)] n = Vector(*n) return n
def normal(self): """:class:`compas.geometry.Vector` : The (average) normal of the polygon.""" o = self.centroid points = self.points a2 = 0 normals = [] for i in range(-1, len(points) - 1): p1 = points[i] p2 = points[i + 1] u = [p1[_] - o[_] for _ in range(3)] v = [p2[_] - o[_] for _ in range(3)] w = cross_vectors(u, v) a2 += sum(w[_] ** 2 for _ in range(3)) ** 0.5 normals.append(w) n = [sum(axis) / a2 for axis in zip(*normals)] n = Vector(* n) return n
def __sub__(self, other): """Return a vector` that is the the difference between this point and another point. Parameters ---------- other : :class:`compas.geometry.Point` or list The point to subtract. Returns ------- :class:`compas.geometry.Vector` A vector from other to self. """ x = self.x - other[0] y = self.y - other[1] z = self.z - other[2] return Vector(x, y, z)
def __sub__(self, other): """Return a ``Vector`` that is the the difference between this ``Point`` and another point. Parameters ---------- other : point The point to subtract. Returns ------- Vector A vector from other to self. """ x = self.x - other[0] y = self.y - other[1] z = self.z - other[2] return Vector(x, y, z)
def from_point_and_two_vectors(cls, point, u, v): """Construct a plane from a base point and two vectors. Parameters ---------- point : point The base point. u : vector The first vector. v : vector The second vector. Returns ------- Plane A plane with base point ``point`` and normal vector defined as the unitized cross product of vectors ``u`` and ``v``. """ normal = Vector.cross(u, v) return cls(point, normal)
def axis_and_angle(self): """Returns the axis and the angle of the ``Rotation``. Returns ------- tuple: (:class:`Vector`, float) Examples -------- >>> axis1 = normalize_vector([-0.043, -0.254, 0.617]) >>> angle1 = 0.1 >>> R = Rotation.from_axis_and_angle(axis1, angle1) >>> axis2, angle2 = R.axis_and_angle >>> allclose(axis1, axis2) True >>> allclose([angle1], [angle2]) True """ from compas.geometry.primitives import Vector axis, angle = axis_and_angle_from_matrix(self.matrix) return Vector(*axis), angle
def from_data(cls, data): """Construct a plane from its data representation. Parameters ---------- data : dict The data dictionary. Returns ------- :class:`compas.geometry.Plane` The constructed plane. Examples -------- >>> plane = Plane.from_data({'point': [0.0, 0.0, 0.0], 'normal': [0.0, 0.0, 1.0]}) >>> plane.point Point(0.000, 0.000, 0.000) >>> plane.normal Vector(0.000, 0.000, 1.000) """ return cls(Point.from_data(data['point']), Vector.from_data(data['normal']))
def from_three_points(cls, a, b, c): """Construct a plane from three points in three-dimensional space. Parameters ---------- a : point The first point. b : point The second point. c : point The second point. Returns ------- Plane A plane with base point ``a`` and normal vector defined as the unitized cross product of the vectors ``ab`` and ``ac``. """ a = Point(*a) b = Point(*b) c = Point(*c) normal = Vector.cross(b - a, c - a) return cls(a, normal)
def axis_angle_vector(self): """:class:`compas.geometry.Vector` : The axis-angle vector representing the rotation of the frame.""" R = matrix_from_basis_vectors(self.xaxis, self.yaxis) return Vector(*axis_angle_vector_from_matrix(R))
def normal(self): """:class:`compas.geometry.Vector` : The normal of the base plane of the frame.""" return Vector(*cross_vectors(self.xaxis, self.yaxis))
def yaxis(self, vector): yaxis = Vector(*vector) yaxis.unitize() zaxis = Vector.cross(self.xaxis, yaxis) zaxis.unitize() self._yaxis = Vector.cross(zaxis, self.xaxis)
def xaxis(self, vector): xaxis = Vector(*vector) xaxis.unitize() self._xaxis = xaxis
def data(self, data): self.point = Point.from_data(data['point']) self.normal = Vector.from_data(data['normal'])