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 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 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 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 ------- :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_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 ------- :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_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 -------- >>> from compas.geometry import Box >>> box = Box.from_corner_corner_height([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 1.0) """ 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) 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): """Vector: The (average) normal of the polygon.""" o = self.center 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 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 normal(self, vector): self._normal = Vector(*vector) self._normal.unitize()
plane = self.copy() plane.transform(transformation) return plane # ============================================================================== # Main # ============================================================================== if __name__ == '__main__': from compas.geometry import Frame from compas.geometry import Transformation base = Point(0.0, 0.0, 0.0) normal = Vector(1.0, 0.0, 0.0) plane = Plane(base, normal) print(plane) print(plane.d) a, b, c = normal p = [1.0, 0.0, 1.0] d = a * p[0] + b * p[1] + c * p[2] print(d) print(d <= plane.d)
class Plane(Primitive): """A plane is defined by a base point and a normal vector. Parameters ---------- point : point The base point of the plane. normal : vector The normal vector of the plane. Attributes ---------- data : dict The data representation of the plane. point : :class:`compas.geometry.Point` The base point of the plane. normal : :class:`compas.geometry.Vector` The normal of the plane. d : float, read-only The *d* parameter of the equation describing the plane. Examples -------- >>> plane = Plane([0, 0, 0], [0, 0, 1]) >>> plane.point Point(0.000, 0.000, 0.000) >>> plane.normal Vector(0.000, 0.000, 1.000) """ __slots__ = ['_point', '_normal'] def __init__(self, point, normal): self._point = None self._normal = None self.point = point self.normal = normal @property def data(self): """dict : The data dictionary that represents the plane.""" return {'point': list(self.point), 'normal': list(self.normal)} @data.setter def data(self, data): self.point = data['point'] self.normal = data['normal'] @property def point(self): """:class:`compas.geometry.Plane` : The base point of the plane.""" return self._point @point.setter def point(self, point): self._point = Point(*point) @property def normal(self): """:class:`compas.geometry.Vector` : The normal vector of the plane.""" return self._normal @normal.setter def normal(self, vector): self._normal = Vector(*vector) self._normal.unitize() @property def d(self): """float: The *d* parameter of the linear equation describing the plane.""" a, b, c = self.normal x, y, z = self.point return -a * x - b * y - c * z # ========================================================================== # customization # ========================================================================== def __repr__(self): return 'Plane({0}, {1})'.format(self.point, self.normal) def __len__(self): return 2 def __getitem__(self, key): if key == 0: return self.point if key == 1: return self.normal raise KeyError def __setitem__(self, key, value): if key == 0: self.point = value return if key == 1: self.normal = value return raise KeyError def __iter__(self): return iter([self.point, self.normal]) def __eq__(self, other): return self.point == other[0] and self.normal == other[1] # ========================================================================== # constructors # ========================================================================== @classmethod 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(data['point'], data['normal']) @classmethod 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 ------- :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) @classmethod 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 ------- :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) @classmethod def worldXY(cls): """Construct the world XY plane. Returns ------- :class:`compas.geometry.Plane` The world XY plane. """ return cls([0, 0, 0], [0, 0, 1]) # ========================================================================== # methods # ========================================================================== def transform(self, T): """Transform this plane. Parameters ---------- T : :class:`compas.geometry.Transformation` or list of list The transformation. Examples -------- >>> from compas.geometry import Frame >>> from compas.geometry import Transformation >>> from compas.geometry import Plane >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = Transformation.from_frame(f) >>> plane = Plane.worldXY() >>> plane.transform(T) """ self.point.transform(T) self.normal.transform(T)
def xaxis(self, vector): xaxis = Vector(*vector) xaxis.unitize() self._xaxis = xaxis
class Plane(object): """A plane is defined by a base point and a normal vector. Parameters ---------- point : point The base point of the plane. normal : vector The normal vector of the plane. Examples -------- >>> from compas.geometry import Plane plane = Plane([0,0,0], [0,0,1]) Notes ----- For more info on lines and linear equations, see [1]_. References ---------- .. [1] Wikipedia. *Plane (geometry)*. Available at: https://en.wikipedia.org/wiki/Plane_(geometry). """ __slots__ = ['_point', '_normal'] def __init__(self, point, normal): self._point = None self._normal = None self.point = point self.normal = normal # ========================================================================== # factory # ========================================================================== @classmethod 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) @classmethod 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) @classmethod def from_points(cls, points): """Construct the *best-fit* plane through more than three (non-coplanar) points. Parameters ---------- points : list of point List of points. Returns ------- Plane A plane that minimizes the distance to each point in the list. """ raise NotImplementedError @classmethod def worldXY(cls): """Construct the world XY plane. Returns ------- Plane The world XY plane. """ return cls([0, 0, 0], [0, 0, 1]) @classmethod def from_data(cls, data): """Construct a plane from its data representation. Parameters ---------- data : :obj:`dict` The data dictionary. Returns ------- Plane The constructed plane. Examples -------- >>> """ plane = cls.worldXY() plane.data = data return plane # ========================================================================== # descriptors # ========================================================================== @property def point(self): """Point: The base point of the plane.""" return self._point @point.setter def point(self, point): self._point = Point(*point) @property def normal(self): """Vector: The normal vector of the plane.""" return self._normal @normal.setter def normal(self, vector): self._normal = Vector(*vector) self._normal.unitize() @property def d(self): """:obj:`float`: The *d* parameter of the linear equation describing the plane.""" a, b, c = self.normal x, y, z = self.point return -a * x - b * y - c * z # @property # def frame(self): # """Frame: The frame that forms a basis for the local coordinates of all # points in the half-spaces defined by the plane. # """ # a, b, c = self.normal # u = 1.0, 0.0, - a / c # v = 0.0, 1.0, - b / c # u, v = orthonormalize_vectors([u, v]) # u = Vector(*u) # v = Vector(*v) # u.unitize() # v.unitize() # return self.point, u, v # ========================================================================== # representation # ========================================================================== def __repr__(self): return 'Plane({0}, {1})'.format(self.point, self.normal) def __len__(self): return 2 @property def data(self): """Returns the data dictionary that represents the plane. Returns ------- dict The plane data. """ return {'point': list(self.point), 'normal': list(self.normal)} @data.setter def data(self, data): self.point = data['point'] self.normal = data['normal'] def to_data(self): """Returns the data dictionary that represents the plane. Returns ------- dict The plane data. """ return self.data # ========================================================================== # access # ========================================================================== def __getitem__(self, key): if key == 0: return self.point if key == 1: return self.normal raise KeyError def __setitem__(self, key, value): if key == 0: self.point = value return if key == 1: self.normal = value return raise KeyError def __iter__(self): return iter([self.point, self.normal]) # ========================================================================== # comparison # ========================================================================== def __eq__(self, other): raise NotImplementedError # ========================================================================== # operators # ========================================================================== # ========================================================================== # inplace operators # ========================================================================== # ========================================================================== # helpers # ========================================================================== def copy(self): """Make a copy of this ``Plane``. Returns ------- Plane The copy. """ cls = type(self) return cls(self.point.copy(), self.normal.copy()) # ========================================================================== # methods # ========================================================================== # ========================================================================== # transformations # ========================================================================== def transform(self, transformation): """Transform this ``Plane`` using a given ``Transformation``. Parameters ---------- transformation : :class:`Transformation` The transformation used to transform the plane. Examples -------- >>> from compas.geometry import Frame >>> from compas.geometry import Transformation >>> from compas.geometry import Plane >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = Transformation.from_frame(f) >>> plane = Plane.worldXY() >>> plane.transform(T) """ self.point.transform(transformation) self.normal.transform(transformation) def transformed(self, transformation): """Returns a transformed copy of the current plane. Parameters ---------- transformation : :class:`Transformation` The transformation used to transform the plane. Returns ------- :class:`Plane` The transformed plane. Examples -------- >>> from compas.geometry import Frame >>> from compas.geometry import Transformation >>> from compas.geometry import Plane >>> f = Frame([1, 1, 1], [0.68, 0.68, 0.27], [-0.67, 0.73, -0.15]) >>> T = Transformation.from_frame(f) >>> plane = Plane.worldXY() >>> plane_transformed = plane.transformed(T) """ plane = self.copy() plane.transform(transformation) return plane
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)
if __name__ == '__main__': from math import pi from compas.geometry import Point from compas.geometry import Vector from compas.geometry import Plane from compas.geometry import Line from compas.geometry import Polygon from compas.geometry import matrix_from_axis_and_angle M = matrix_from_axis_and_angle([0, 0, 1], pi / 2) point = Point(0.0, 0.0, 0.0) normal = Vector(0.0, 0.0, 1.0) plane = Plane(point, normal) line = Line([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]) triangle = Polygon([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]]) polygon = Polygon([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]]) p = Point(1.0, 1.0, 1.0) p.transform(M) print(*p) print(repr(p)) print(p.distance_to_point(point))
def normal(self): """:class:`Vector` : The frame's normal (z-axis).""" return Vector(*cross_vectors(self.xaxis, self.yaxis))