def from_basis_vectors(cls, xaxis, yaxis): """Creates a ``Rotation`` from basis vectors (= orthonormal vectors). Parameters ---------- xaxis : :class:`Vector` The x-axis of the frame. yaxis : :class:`Vector` The y-axis of the frame. Examples -------- >>> xaxis = [0.68, 0.68, 0.27] >>> yaxis = [-0.67, 0.73, -0.15] >>> R = Rotation.from_basis_vectors(xaxis, yaxis) """ xaxis = normalize_vector(list(xaxis)) yaxis = normalize_vector(list(yaxis)) zaxis = cross_vectors(xaxis, yaxis) yaxis = cross_vectors(zaxis, xaxis) # correction R = cls() R.matrix[0][0], R.matrix[1][0], R.matrix[2][0] = xaxis R.matrix[0][1], R.matrix[1][1], R.matrix[2][1] = yaxis R.matrix[0][2], R.matrix[1][2], R.matrix[2][2] = zaxis return R
def matrix_from_basis_vectors(xaxis, yaxis): """Creates a rotation matrix from basis vectors (= orthonormal vectors). Parameters ---------- xaxis : list of float The x-axis of the frame. yaxis : list of float The y-axis of the frame. Examples -------- >>> xaxis = [0.68, 0.68, 0.27] >>> yaxis = [-0.67, 0.73, -0.15] >>> R = matrix_from_basis_vectors(xaxis, yaxis) """ xaxis = normalize_vector(list(xaxis)) yaxis = normalize_vector(list(yaxis)) zaxis = cross_vectors(xaxis, yaxis) yaxis = cross_vectors(zaxis, xaxis) # correction R = identity_matrix(4) R[0][0], R[1][0], R[2][0] = xaxis R[0][1], R[1][1], R[2][1] = yaxis R[0][2], R[1][2], R[2][2] = zaxis return R
def orthonormalize_axes(xaxis, yaxis): """Corrects xaxis and yaxis to be unit vectors and orthonormal. Parameters ---------- xaxis: :class:`Vector` or list of float yaxis: :class:`Vector` or list of float Returns ------- tuple: (xaxis, yaxis) The corrected axes. Raises ------ ValueError: If xaxis and yaxis cannot span a plane. Examples -------- >>> xaxis = [1, 4, 5] >>> yaxis = [1, 0, -2] >>> xaxis, yaxis = orthonormalize_axes(xaxis, yaxis) >>> allclose(xaxis, [0.1543, 0.6172, 0.7715], tol=0.001) True >>> allclose(yaxis, [0.6929, 0.4891, -0.5298], tol=0.001) True """ xaxis = normalize_vector(xaxis) yaxis = normalize_vector(yaxis) zaxis = cross_vectors(xaxis, yaxis) if not norm_vector(zaxis): raise ValueError("Xaxis and yaxis cannot span a plane.") yaxis = cross_vectors(normalize_vector(zaxis), xaxis) return xaxis, yaxis
def is_intersection_line_line(l1, l2, tol=1e-6): """Verifies if two lines intersect. Parameters ---------- l1 : tuple A sequence of XYZ coordinates of two 3D points representing two points on the line. l2 : tuple A sequence of XYZ coordinates of two 3D points representing two points on the line. tol : float, optional A tolerance for intersection verification. Default is ``1e-6``. Returns -------- bool ``True``if the lines intersect in one point. ``False`` if the lines are skew, parallel or lie on top of each other. """ a, b = l1 c, d = l2 e1 = normalize_vector(subtract_vectors(b, a)) e2 = normalize_vector(subtract_vectors(d, c)) # check for parallel lines if abs(dot_vectors(e1, e2)) > 1.0 - tol: return False # check for intersection d_vector = cross_vectors(e1, e2) if dot_vectors(d_vector, subtract_vectors(c, a)) == 0: return True return False
def is_intersection_line_line(l1, l2, epsilon=1e-6): """Verifies if two lines intersection in one point. Parameters: ab: (tuple): A sequence of XYZ coordinates of two 3D points representing two points on the line. cd: (tuple): A sequence of XYZ coordinates of two 3D points representing two points on the line. Returns: True (bool): if the lines intersect in one point, False is the lines are skew, parallel or lie on top of each other. """ a, b = l1 c, d = l2 e1 = normalize_vector(subtract_vectors(b, a)) e2 = normalize_vector(subtract_vectors(d, c)) # check for parallel lines if abs(dot_vectors(e1, e2)) > 1.0 - epsilon: return False # check for intersection d_vector = cross_vectors(e1, e2) if dot_vectors(d_vector, subtract_vectors(c, a)) == 0: return True return False
def matrix_from_shear(angle, direction, point, normal): """Constructs a shear matrix by an angle along the direction vector on the shear plane (defined by point and normal). Parameters ---------- angle : float The angle in radians. direction : list of float The direction vector as list of 3 numbers. It must be orthogonal to the normal vector. point : list of float The point of the shear plane as list of 3 numbers. normal : list of float The normal of the shear plane as list of 3 numbers. Raises ------ ValueError If direction and normal are not orthogonal. Notes ----- A point P is transformed by the shear matrix into P" such that the vector P-P" is parallel to the direction vector and its extent is given by the angle of P-P'-P", where P' is the orthogonal projection of P onto the shear plane (defined by point and normal). Examples -------- >>> angle = 0.1 >>> direction = [0.1, 0.2, 0.3] >>> point = [4, 3, 1] >>> normal = cross_vectors(direction, [1, 0.3, -0.1]) >>> S = matrix_from_shear(angle, direction, point, normal) """ normal = normalize_vector(normal) direction = normalize_vector(direction) if math.fabs(dot_vectors(normal, direction)) > _EPS: raise ValueError('Direction and normal vectors are not orthogonal') angle = math.tan(angle) M = [[1. if i == j else 0. for i in range(4)] for j in range(4)] for j in range(3): for i in range(3): M[i][j] += angle * direction[i] * normal[j] M[0][3], M[1][3], M[2][3] = scale_vector( direction, -angle * dot_vectors(point, normal)) return M
def lines_tangent_to_cylinder(base_point, line_vect, ref_point, dist): """Calculating of plane tangents to one cylinder See SP dissertation 3.1.3.b (p. 74) .. image:: ../images/plane_tangent_to_one_cylinder.png :scale: 80 % :align: center Parameters ---------- base_point : [type] [description] line_vect : [type] vector [other end of the axis, base_point], **the direction here is very important!** ref_point : point new point Q dist : float radius of the cylinder Returns ------- list [origin point M, vector MB, -1 * vector MB], the latter two entries represent the tangent points' local coordinate in the plane [point M, e_x, e_y] """ l_vect = normalize_vector(line_vect) line_QMprime = subtract_vectors(ref_point, base_point) # ppol = point M, project out longitutude axis component of base_point ppol = add_vectors(base_point, scale_vector(l_vect, dot_vectors(line_QMprime, l_vect))) # ppolr_vect = line QB line_QM = subtract_vectors(ref_point, ppol) e_x = normalize_vector(line_QM) e_y = cross_vectors(e_x, l_vect) if length_vector(line_QM) == 0: return None x = dist / length_vector(line_QM) # x coordinate in the local axis d_e1 = scale_vector(e_x, dist * x) # d(radius of bar section) has to be larger than l(distance from point to bar axis), otherwise the sqrt turns negative # if d < l: change ref_point if x * x < 1.0: # y coordinate in the local axis d_e2 = scale_vector(e_y, dist * math.sqrt(1.0 - x * x)) else: return None # upper tangent point d_e_add = add_vectors(d_e1, d_e2) # lower tangent point d_e_sub = subtract_vectors(d_e1, d_e2) return [ppol, d_e_add, d_e_sub]
def test_axis_and_angle_from_matrix(): axis1 = normalize_vector([-0.043, -0.254, 0.617]) angle1 = 0.1 R = matrix_from_axis_and_angle(axis1, angle1) axis2, angle2 = axis_and_angle_from_matrix(R) assert allclose(axis1, axis2) assert allclose([angle1], [angle2])
def test_axis_and_angle(): 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 assert allclose(axis1, axis2) assert allclose([angle1], [angle2])
def matrix_from_orthogonal_projection(point, normal): """Returns an orthogonal projection matrix to project onto a plane defined by point and normal. Parameters ---------- point : list of float Base point of the plane. normal : list of float Normal vector of the plane. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> P = matrix_from_orthogonal_projection(point, normal) """ T = identity_matrix(4) normal = normalize_vector(normal) for j in range(3): for i in range(3): T[i][j] -= normal[i] * normal[j] # outer_product T[0][3], T[1][3], T[2][3] = scale_vector( normal, dot_vectors(point, normal)) return T
def matrix_from_parallel_projection(point, normal, direction): """Returns an parallel projection matrix to project onto a plane defined by point, normal and direction. Parameters ---------- point : list of float Base point of the plane. normal : list of float Normal vector of the plane. direction : list of float Direction of the projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> direction = [1, 1, 1] >>> P = matrix_from_parallel_projection(point, normal, direction) """ T = identity_matrix(4) normal = normalize_vector(normal) scale = dot_vectors(direction, normal) for j in range(3): for i in range(3): T[i][j] -= direction[i] * normal[j] / scale T[0][3], T[1][3], T[2][3] = scale_vector( direction, dot_vectors(point, normal) / scale) return T
def plane_from_points(a, b, c): """Construct a plane from three points. Parameters ---------- a : sequence of float XYZ coordinates. b : sequence of float XYZ coordinates. c : sequence of float XYZ coordinates. Returns ------- plane : tuple Base point and normal vector (normalized). Examples -------- >>> """ ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = normalize_vector(cross_vectors(ab, ac)) return a, n
def project_point_plane(point, plane): """Project a point onto a plane. The projection is in the direction perpendicular to the plane. The projected point is thus the closest point on the plane to the original point. Parameters: point (sequence of float): XYZ coordinates of the original point. plane (tuple): Base poin.t and normal vector defining the plane Returns: list: XYZ coordinates of the projected point. Examples: >>> from compas.geometry.transformations import project_point_plane >>> point = [3.0, 3.0, 3.0] >>> plane = ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) # the XY plane >>> project_point_plane(point, plane) [3.0, 3.0, 3.0] References: http://stackoverflow.com/questions/8942950/how-do-i-find-the-orthogonal-projection-of-a-point-onto-a-plane http://math.stackexchange.com/questions/444968/project-a-point-in-3d-on-a-given-plane """ base, normal = plane normal = normalize_vector(normal) vector = subtract_vectors(point, base) snormal = scale_vector(normal, dot_vectors(vector, normal)) return subtract_vectors(point, snormal)
def bestfit_plane_numpy3(points): from numpy import asarray from numpy import sum from functools import partial from scipy.optimize import minimize def plane(x, y, abc): a, b, c = abc return a * x + b * y + c def error(abc, points): result = 0 for x, y, z in points: znew = plane(x, y, abc) result += (znew - z)**2 return result c = sum(asarray(points), axis=0) / len(points) objective = partial(error, points=points) res = minimize(objective, [0, 0, 0]) a, b, c = res.x u = 1.0, 0.0, a v = 0.0, 1.0, b w = normalize_vector(cross_vectors(u, v)) return c, w
def closest_point_on_plane(point, plane): """ Compute closest point on a plane to a given point. Parameters ---------- point : sequenceof float XYZ coordinates of point. plane : tuple The base point and normal defining the plane. Returns ------- list XYZ coordinates of the closest point. Examples -------- >>> plane = ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) >>> point = [1.0, 2.0, 3.0] >>> closest_point_on_plane(point, plane) References ---------- http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_plane """ base, normal = plane x, y, z = base a, b, c = normalize_vector(normal) x1, y1, z1 = point d = a * x + b * y + c * z k = (a * x1 + b * y1 + c * z1 - d) / (a**2 + b**2 + c**2) return [x1 - k * a, y1 - k * b, z1 - k * c]
def intersection_line_line(l1, l2, tol=1e-6): """Computes the intersection of two lines. Parameters ---------- l1 : tuple, list XYZ coordinates of two points defining the first line. l2 : tuple, list XYZ coordinates of two points defining the second line. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- list XYZ coordinates of the two points marking the shortest distance between the lines. If the lines intersect, these two points are identical. If the lines are skewed and thus only have an apparent intersection, the two points are different. If the lines are parallel, the return value is [None, None]. Examples -------- >>> """ a, b = l1 c, d = l2 ab = subtract_vectors(b, a) cd = subtract_vectors(d, c) n = cross_vectors(ab, cd) n1 = normalize_vector(cross_vectors(ab, n)) n2 = normalize_vector(cross_vectors(cd, n)) plane_1 = (a, n1) plane_2 = (c, n2) i1 = intersection_line_plane(l1, plane_2, tol=tol) i2 = intersection_line_plane(l2, plane_1, tol=tol) return i1, i2
def lines_tangent_to_two_cylinder(base_point1, line_vect1, base_point2, line_vect2, ref_point, dist1, dist2): planes1 = p_planes_tangent_to_cylinder(base_point1, line_vect1, ref_point, dist1) planes2 = c_planes_tangent_to_cylinder(base_point2, line_vect2, ref_point, dist2) if planes1 == None or planes2 == None: return None s1 = intersect_plane_plane_u(planes1[0][1], planes1[0][2], planes2[0][0]) s1 = normalize_vector(s1) s2 = intersect_plane_plane_u(planes1[0][1], planes1[0][2], planes2[1][0]) s2 = normalize_vector(s2) s3 = intersect_plane_plane_u(planes1[1][1], planes1[1][2], planes2[0][0]) s3 = normalize_vector(s3) s4 = intersect_plane_plane_u(planes1[1][1], planes1[1][2], planes2[1][0]) s4 = normalize_vector(s4) return [s1, s2, s3, s4]
def test_matrix_from_axis_angle_vector(): axis1 = normalize_vector([-0.043, -0.254, 0.617]) angle1 = 0.1 R = matrix_from_axis_and_angle(axis1, angle1) r = [[0.9950248278789664, -0.09200371122722178, -0.03822183963195913, 0.0], [0.09224781823368366, 0.9957251324831573, 0.004669108322156158, 0.0], [ 0.037628871037522216, -0.008171760019527692, 0.9992583701939277, 0.0 ], [0.0, 0.0, 0.0, 1.0]] assert np.allclose(R, r)
def __init__(self, point, normal): super(Reflection, self).__init__() normal = normalize_vector((list(normal))) for i in range(3): for j in range(3): self.matrix[i][j] -= 2.0 * normal[i] * normal[j] for i in range(3): self.matrix[i][3] = 2 * dot_vectors(point, normal) *\ normal[i]
def p_planes_tangent_to_cylinder( base_point, line_vect, ref_point, dist, ): """find tangent planes of a cylinder passing through a given point () .. image:: ../images/plane_tangent_to_one_cylinder.png :scale: 80 % :align: center Parameters ---------- base_point : point point M line_vect : vector direction of the existing bar's axis, direction [the other pt, base_pt], **direction very important!** ref_point : point point Q dist : float cylinder radius Returns ------- list of two [ref_point, local_y, local_x] local x = QB local_y // line_vect """ l_vect = normalize_vector(line_vect) tangent_pts = lines_tangent_to_cylinder(base_point, line_vect, ref_point, dist) if tangent_pts is None: return None base_pt, upper_tang_pt, lower_tang_pt = tangent_pts r1 = subtract_vectors(add_vectors(base_pt, upper_tang_pt), ref_point) r1 = normalize_vector(r1) r2 = subtract_vectors(add_vectors(base_pt, lower_tang_pt), ref_point) r2 = normalize_vector(r2) return [[ref_point, l_vect, r1], [ref_point, l_vect, r2]]
def tangent_from_point_one(base_point1, line_vect1, base_point2, line_vect2, ref_point, dist1, dist2, nb): """[summary] .. image:: ../images/intersection_two_bar_tangent_planes.png :scale: 80 % :align: center Parameters ---------- base_point1 : point start point of cylinder 1's axis line_vect1 : list of two points axis vector for cylinder 1, end pt 1 -> end pt 0 base_point2 : [type] [description] line_vect2 : [type] axis vector for cylinder 2 ref_point : point new vertex point Q dist1 : [type] [description] dist2 : [type] [description] nb : int tangent plane combination (two tangent planes per bar) Returns ------- list of two points a vector representing the new bar's axis """ planes1 = p_planes_tangent_to_cylinder(base_point1, line_vect1, ref_point, dist1) planes2 = c_planes_tangent_to_cylinder(base_point2, line_vect2, ref_point, dist2) if planes1 == None or planes2 == None: print("Tangent planes not found") return None if nb == 0 or nb == 1: _, plane_x_axis, plane_y_axis = planes1[0] s = intersect_plane_plane_u(plane_x_axis, plane_y_axis, planes2[nb % 2][0]) # s = intersect_plane_plane_u(planes1[0][1], planes1[0][2], planes2[1][0]) elif nb == 2 or nb == 3: _, plane_x_axis, plane_y_axis = planes1[1] s = intersect_plane_plane_u(plane_x_axis, plane_y_axis, planes2[nb % 2][0]) # s = intersect_plane_plane_u(planes1[1][1], planes1[1][2], planes2[1][0]) s = normalize_vector(s) return [s]
def calculate_offset_pos_two_side_two_point_locked(b_struct, v_key, vecs_con_1, vecs_con_2, pts_con_1, pts_con_2, d_o_1, d_o_2): """calculate offsetted plane when the bar's both ends have two contact points """ assert len(vecs_con_1) == 2 and len(pts_con_1) == 2 assert len(vecs_con_2) == 2 and len(pts_con_2) == 2 map(normalize_vector, vecs_con_1) map(normalize_vector, vecs_con_2) v1_1, v1_2 = vecs_con_1 v2_1, v2_2 = vecs_con_2 pt_1_1, pt_1_2 = pts_con_1 pt_2_1, pt_2_2 = pts_con_2 vm_1 = scale_vector(normalize_vector(add_vectors(v1_1, v1_2)), -1. * d_o_1) # original contact point (assuming two bars have the same radius) pt_1 = centroid_points([pt_1_1, pt_1_2]) pt_1_new = translate_points([pt_1], vm_1)[0] vm_2 = scale_vector(normalize_vector(add_vectors(v2_1, v2_2)), -1. * d_o_2) pt_2 = centroid_points([pt_2_1, pt_2_2]) pt_2_new = translate_points([pt_2], vm_2)[0] vec_x_new = normalize_vector(vector_from_points(pt_1_new, pt_2_new)) x_ax = b_struct.vertex[v_key]["gripping_plane"][1] if not angle_vectors(x_ax, vec_x_new, deg=True) < 90: vec_x_new = scale_vector(vec_x_new, -1.) pt_o = b_struct.vertex[v_key]["gripping_plane"][0] y_ax = b_struct.vertex[v_key]["gripping_plane"][2] vec_z = cross_vectors(vec_x_new, y_ax) l_n = (pt_1_new, pt_2_new) pt_o_n = closest_point_on_line(pt_o, l_n) return pt_o_n, vec_x_new, y_ax, vec_z
def from_basis_vectors(cls, xaxis, yaxis): """Creates a ``Rotation`` from basis vectors (= orthonormal vectors). Args: xaxis (:obj:`list` oof :obj:`float`): The x-axis of the frame. yaxis (:obj:`list` oof :obj:`float`): The y-axis of the frame. Example: >>> xaxis = [0.68, 0.68, 0.27] >>> yaxis = [-0.67, 0.73, -0.15] >>> R = Rotation.from_basis_vectors(xaxis, yaxis) """ xaxis = normalize_vector(list(xaxis)) yaxis = normalize_vector(list(yaxis)) zaxis = cross_vectors(xaxis, yaxis) yaxis = cross_vectors(zaxis, xaxis) # correction R = cls() R.matrix[0][0], R.matrix[1][0], R.matrix[2][0] = xaxis R.matrix[0][1], R.matrix[1][1], R.matrix[2][1] = yaxis R.matrix[0][2], R.matrix[1][2], R.matrix[2][2] = zaxis return R
def f_tangent_point_2(x, *args): ptM, ex, ey, radius, pt_b_1, l_1, pt_b_2, l_2, d1, d2, ind = args r_c = 2 * radius ref_point_tmp = add_vectors(scale_vector(ex, x), scale_vector(ey, math.sqrt(r_c * r_c - x * x))) ref_point = add_vectors(ref_point_tmp, ptM) vecs_l_all = tangent_from_point_one(pt_b_1, l_1, pt_b_2, l_2, ref_point, d1, d2, ind) if vecs_l_all: vec_l = vecs_l_all[0] else: print("error in f") f = 1 return f f = abs( dot_vectors(normalize_vector(vec_l), normalize_vector(vector_from_points(ptM, ref_point)))) return f
def calculate_offset_pos_two_side_one_point_locked(b_struct, v_key, pt_1, pt_2, v1, v2, d_o_1, d_o_2): """calculate offsetted plane when the bar's both sides are blocked by vector v1 and v2 # ! Note: the old y axis is kept in this function, local x axis is updated Parameters ---------- b_struct : [type] [description] v_key : int vertex key in BarStructure, representing a physical bar pt_1 : list first contact point's projection on the bar's axis pt_2 : list second contact point's projection on the bar's axis v1 : list first contact point - contact point vector v2 : list second contact point - contact point vector d_o_1 : float offset distance for end point #1 d_o_2 : float offset distance for end point #2 Returns ------- tuple offset plane's origin, x-, y-, z-axis """ pt_1_new = add_vectors(pt_1, scale_vector(v1, -1. * d_o_1)) pt_2_new = add_vectors(pt_2, scale_vector(v2, -1. * d_o_2)) vec_x_new = normalize_vector(vector_from_points(pt_1_new, pt_2_new)) x_ax = b_struct.vertex[v_key]["gripping_plane"][1] if not angle_vectors(x_ax, vec_x_new, deg=True) < 90: vec_x_new = scale_vector(vec_x_new, -1.) # transform gripping plane pt_o = b_struct.vertex[v_key]["gripping_plane"][0] y_ax = b_struct.vertex[v_key]["gripping_plane"][2] vec_z = cross_vectors(vec_x_new, y_ax) l_n = (pt_1_new, pt_2_new) pt_o_new = closest_point_on_line(pt_o, l_n) return pt_o_new, vec_x_new, y_ax, vec_z
def bestfit_plane_numpy2(points): from numpy import asarray from numpy import sum from numpy import hstack from numpy import ones from scipy.linalg import solve xyz = asarray(points).reshape((-1, 3)) n = xyz.shape[0] c = (sum(xyz, axis=0) / n).reshape((-1, 3)) A = hstack((xyz[:, 0:2], ones((xyz.shape[0], 1)))) b = xyz[:, 2:] a, b, c = solve(A.T.dot(A), A.T.dot(b)) u = 1.0, 0.0, a[0] v = 0.0, 1.0, b[0] w = normalize_vector(cross_vectors(u, v)) return c, w
def circle_from_points(a, b, c): """Construct a circle from three points. Parameters ---------- a : sequence of float XYZ coordinates. b : sequence of float XYZ coordinates. c : sequence of float XYZ coordinates. Returns ------- circle : tuple Center, radius, normal of the circle. References ---------- https://en.wikipedia.org/wiki/Circumscribed_circle Examples -------- >>> """ ab = subtract_vectors(b, a) cb = subtract_vectors(b, c) ba = subtract_vectors(a, b) ca = subtract_vectors(a, c) ac = subtract_vectors(c, a) bc = subtract_vectors(c, b) normal = normalize_vector(cross_vectors(ab, ac)) d = 2 * length_vector_sqrd(cross_vectors(ba, cb)) A = length_vector_sqrd(cb) * dot_vectors(ba, ca) / d B = length_vector_sqrd(ca) * dot_vectors(ab, cb) / d C = length_vector_sqrd(ba) * dot_vectors(ac, bc) / d Aa = scale_vector(a, A) Bb = scale_vector(b, B) Cc = scale_vector(c, C) center = sum_vectors([Aa, Bb, Cc]) radius = length_vector(subtract_vectors(a, center)) return center, radius, normal
def offset_line(line, distance, normal=[0., 0., 1.]): """Offset a line by a distance Parameters: line (tuple): Two points defining the line. distances (float or tuples of floats): The offset distance as float. A single value determines a constant offset. Alternatively, two offset values for the start and end point of the line can be used to a create variable offset. normal (tuple): The normal of the offset plane. Returns: offset line (tuple): Two points defining the offset line. Examples: .. code-block:: python line = [(0.0, 0.0, 0.0), (3.0, 3.0, 0.0)] distance = 0.2 # constant offset line_offset = offset_line(line, distance) print(line_offset) distance = [0.2, 0.1] # variable offset line_offset = offset_line(line, distance) print(line_offset) """ pt1, pt2 = line[0], line[1] vec = subtract_vectors(pt1, pt2) dir_vec = normalize_vector(cross_vectors(vec, normal)) if isinstance(distance, list): distances = distance else: distances = [distance, distance] vec_pt1 = scale_vector(dir_vec, distances[0]) vec_pt2 = scale_vector(dir_vec, distances[1]) pt1_new = add_vectors(pt1, vec_pt1) pt2_new = add_vectors(pt2, vec_pt2) return pt1_new, pt2_new
def project_point_plane(point, plane): """Project a point onto a plane. Parameters ---------- point : list of float XYZ coordinates of the point. plane : tuple Base point and normal vector defining the projection plane. Returns ------- list XYZ coordinates of the projected point. Notes ----- The projection is in the direction perpendicular to the plane. The projected point is thus the closest point on the plane to the original point [1]_. References ---------- .. [1] Math Stack Exchange. *Project a point in 3D on a given plane*. Available at: https://math.stackexchange.com/questions/444968/project-a-point-in-3d-on-a-given-plane. Examples -------- >>> from compas.geometry import project_point_plane >>> point = [3.0, 3.0, 3.0] >>> plane = ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]) # the XY plane >>> project_point_plane(point, plane) [3.0, 3.0, 0.0] """ base, normal = plane normal = normalize_vector(normal) vector = subtract_vectors(point, base) snormal = scale_vector(normal, dot_vectors(vector, normal)) return subtract_vectors(point, snormal)
def matrix_from_perspective_projection(point, normal, perspective): """Returns a perspective projection matrix to project onto a plane defined by point, normal and perspective. Parameters ---------- point : list of float Base point of the projection plane. normal : list of float Normal vector of the projection plane. perspective : list of float Perspective of the projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> perspective = [1, 1, 0] >>> P = matrix_from_perspective_projection(point, normal, perspective) """ T = identity_matrix(4) normal = normalize_vector(normal) T[0][0] = T[1][1] = T[2][2] = dot_vectors( subtract_vectors(perspective, point), normal) for j in range(3): for i in range(3): T[i][j] -= perspective[i] * normal[j] T[0][3], T[1][3], T[2][3] = scale_vector(perspective, dot_vectors(point, normal)) for i in range(3): T[3][i] -= normal[i] T[3][3] = dot_vectors(perspective, normal) return T