def calc_correction_vector_tip(pt_new, base_pts): """Computing correction vector to meet the distance threshold. return vector P-P_c (figure below). .. image:: ../images/vertex_correction_to_top_connection.png :scale: 80 % :align: center Parameters ---------- pt_new : [type] [description] base_pts : list of three points contact base points for the base bars """ assert len(base_pts) == 3 vec_x = normalize_vector(vector_from_points(base_pts[0], base_pts[1])) vec_y = normalize_vector(vector_from_points(base_pts[0], base_pts[2])) vec_z = normalize_vector(cross_vectors(vec_x, vec_y)) pl_test = (base_pts[0], vec_z) dist_p = distance_point_plane(pt_new, pl_test) pt_proj = project_point_plane(pt_new, pl_test) if dist_p < NODE_CORRECTION_TOP_DISTANCE: vec_m = scale_vector(normalize_vector(vector_from_points(pt_proj, pt_new)), NODE_CORRECTION_TOP_DISTANCE) pt_n = add_vectors(pt_proj, vec_m) else: pt_n = None return pt_n
def board_setup(self): for my_layer in range(layer_no): if my_layer % 2 == 0: my_frame = self.origin_fr my_grid = self.ceiling_grids[0] else: my_frame = self.sec_fr my_grid = self.ceiling_grids[1][0] my_dir1 = normalize_vector(my_frame[1]) my_dir2 = normalize_vector(my_frame[2]) # we have to separate i/board_code because of possible exceptions in the centre board_code = 0 for i, dist in enumerate(my_grid): my_board = self.timberboards[my_layer][board_code] # for the inner parts if self.skipping and 0 < my_layer < self.layer_no - 1 and my_layer%2 == 0 and \ 0 < i < len(my_grid) - 1 and i%2 != 0: continue # build the three vectors with which we later find he centre point my_vec1 = scale_vector(my_dir1, my_board.length / 2) my_vec2 = scale_vector(my_dir2, dist) my_vec3 = Vector(0, 0, my_board.z_drop - my_board.height / 2) my_centre = self.origin_pt + my_vec1 + my_vec2 + my_vec3 my_board.centre_point = my_centre my_board.drop_point = my_centre + Vector( 0, 0, my_board.height / 2) my_board.length_vector = normalize_vector(my_vec1) my_board.width_vector = normalize_vector(my_vec2) my_board.box_update() board_code += 1
def find_points_extreme(pts_all, pts_init): """update a bar's axis end point based on all the contact projected points specified in `pts_all` Parameters ---------- pts_all : list of points all the contact points projected on the axis (specified by pts_init) pts_init : list of two points the initial axis end points Returns ------- [type] [description] """ vec_init = normalize_vector(vector_from_points(*pts_init)) # * find the pair of points with maximal distance sorted_pt_pairs = sorted(combinations(pts_all, 2), key=lambda pt_pair: distance_point_point(*pt_pair)) pts_draw = sorted_pt_pairs[-1] vec_new = normalize_vector(vector_from_points(*pts_draw)) if angle_vectors(vec_init, vec_new, deg=True) > 90: # angle can only be 0 or 180 pts_draw = pts_draw[::-1] # ext_len = 30 # pts_draw = (add_vectors(pts_draw[0], scale_vector(normalize_vector(vector_from_points(pts_draw[1], pts_draw[0])), ext_len)), add_vectors(pts_draw[1], scale_vector(normalize_vector(vector_from_points(pts_draw[0], pts_draw[1])), ext_len))) return pts_draw
def set_wait_time_on_sharp_corners(print_organizer, threshold=0.5 * math.pi, wait_time=0.3): """ Sets a wait time at the sharp corners of the path, based on the angle threshold. Parameters ---------- print_organizer: :class:`compas_slicer.print_organization.BasePrintOrganizer` threshold: float angle_threshold wait_time: float Time in seconds to introduce to add as a wait time """ number_of_wait_points = 0 for printpoint, i, j, k in print_organizer.printpoints_indices_iterator(): neighbors = print_organizer.get_printpoint_neighboring_items('layer_%d' % i, 'path_%d' % j, k) prev_ppt = neighbors[0] next_ppt = neighbors[1] if prev_ppt and next_ppt: v_to_prev = normalize_vector(Vector.from_start_end(printpoint.pt, prev_ppt.pt)) v_to_next = normalize_vector(Vector.from_start_end(printpoint.pt, next_ppt.pt)) a = abs(Vector(*v_to_prev).angle(v_to_next)) if a < threshold: printpoint.wait_time = wait_time printpoint.blend_radius = 0.0 # 0.0 blend radius for points where the robot will wait number_of_wait_points += 1 logger.info('Added wait times for %d points' % number_of_wait_points)
def from_basis_vectors(cls, xaxis, yaxis): """Creates a ``Rotation`` from basis vectors (= orthonormal vectors). Parameters ---------- xaxis : compas.geometry.Vector or list The x-axis of the frame. yaxis : compas.geometry.Vector or list 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) matrix = [[xaxis[0], yaxis[0], zaxis[0], 0], [xaxis[1], yaxis[1], zaxis[1], 0], [xaxis[2], yaxis[2], zaxis[2], 0], [0, 0, 0, 1]] R = cls() R.matrix = matrix 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 match_paths_orientations(pts, reference_points, is_closed): """ Check if new curve has same direction as prev curve, otherwise reverse. Parameters ---------- pts: list, :class: 'compas.geometry.Point'. The list of points whose direction we are fixing. reference_points: list, :class: 'compas.geometry.Point'. [p1, p2] Two reference points. is_closed : bool, Determines if the path is closed or open """ if len(pts) > 2 and len(reference_points) > 2: v1 = normalize_vector(subtract_vectors(pts[0], pts[2])) v2 = normalize_vector( subtract_vectors(reference_points[0], reference_points[2])) else: v1 = normalize_vector(subtract_vectors(pts[0], pts[1])) v2 = normalize_vector( subtract_vectors(reference_points[0], reference_points[1])) if dot_vectors(v1, v2) < 0: if is_closed: items = deque(reversed(pts)) items.rotate(1) # bring last point again in the front pts = list(items) else: pts.reverse() return pts
def board_geometry_setup(self): for my_layer in range(layer_no): if my_layer % 2 == 0: my_frame = self.origin_fr my_grid = self.ceiling_grids[0][0] else: my_frame = self.sec_fr my_grid = self.ceiling_grids[1][0] my_dir1 = normalize_vector(my_frame[1]) my_dir2 = normalize_vector(my_frame[2]) # we have to separate i/board_code because of possible exceptions in the centre board_code = 0 for my_board in self.timberboards[my_layer]: dist = my_board.grid_position # build the three vectors with which we later find he centre point # one advanced case if my_board.location == "high": my_vec1 = scale_vector(my_dir1, primary_length - my_board.length / 2) # all other cases else: my_vec1 = scale_vector(my_dir1, my_board.length / 2) my_vec2 = scale_vector(my_dir2, dist) my_vec3 = Vector(0, 0, my_board.z_drop - my_board.height / 2) my_centre = self.origin_pt + my_vec1 + my_vec2 + my_vec3 my_board.centre_point = my_centre my_board.drop_point = my_centre + Vector(0, 0, my_board.height / 2) my_board.length_vector = normalize_vector(my_vec1) my_board.width_vector = normalize_vector(my_vec2) my_board.box_update() board_code += 1
def draw_circle(circle, color=None, n=100): (center, normal), radius = circle cx, cy, cz = center a, b, c = normal u = -1.0, 0.0, a v = 0.0, -1.0, b w = cross_vectors(u, v) uvw = [normalize_vector(u), normalize_vector(v), normalize_vector(w)] color = color if color else (1.0, 0.0, 0.0, 0.5) sector = 2 * pi / n glColor4f(*color) glBegin(GL_POLYGON) for i in range(n): a = i * sector x = radius * cos(a) y = radius * sin(a) z = 0 x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd() glBegin(GL_POLYGON) for i in range(n): a = -i * sector x = radius * cos(a) y = radius * sin(a) z = 0 x, y, z = global_coords_numpy(center, uvw, [[x, y, z]]).tolist()[0] glVertex3f(x, y, z) glEnd()
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 from_basis_vectors(cls, xaxis, yaxis): """Construct a rotation transformation from basis vectors (= orthonormal vectors). Parameters ---------- xaxis : [float, float, float] | :class:`compas.geometry.Vector` The x-axis of the frame. yaxis : [float, float, float] | :class:`compas.geometry.Vector` The y-axis of the frame. Returns ------- :class:`compas.geometry.Rotation` 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) matrix = [[xaxis[0], yaxis[0], zaxis[0], 0], [xaxis[1], yaxis[1], zaxis[1], 0], [xaxis[2], yaxis[2], zaxis[2], 0], [0, 0, 0, 1]] R = cls() R.matrix = matrix return R
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 separate(self, dst, boids, mxf=0.02, mxs=0.02): count = 0 sum_vec = [0, 0, 0] tol = 1e-6 for boid in boids: d = cg.distance_point_point(self.pos, boid.pos) print('distance is {}'.format(d)) if d > tol and d < dst: dif = subtract_vectors(self.pos, boid.pos) dif = cg.normalize_vector(cg.scale_vector(dif, 1/d)) sum_vec = cg.add_vectors(dif, sum_vec) count += 1 if count > 0: sum_vec = cg.normalize_vector(cg.scale_vector(sum_vec, 1/count) ) sum_vec = cg.scale_vector(sum_vec, mxs) # Reynold's steering formula steer = subtract_vectors(sum_vec, self.vel) steer = ut.limit_vector(steer, mxf) return steer return sum_vec
def _cross_edges(edge1, edge2): a, b = edge1 c, d = edge2 edge1_vec = normalize_vector(subtract_vectors(b, a)) edge2_vec = normalize_vector(subtract_vectors(d, c)) cross = cross_vectors(edge1_vec, edge2_vec) return cross
def calc_correction_vector(b_struct, pt_new, bar_pair): """Computing correction vector to meet the angle threshold. return vector P-P_c (figure below). .. image:: ../images/vertex_correction_to_base.png :scale: 80 % :align: center Parameters ---------- b_struct : [type] [description] pt_new : [type] [description] bar_pair : list of two int BarS vertex key, representing two bars Returns ------- list of two points return None if feasible (bigger than the angle threshold), otherwise return the line connecting pt_int and modified pt """ bar1 = b_struct.vertex[bar_pair[0]] bar2 = b_struct.vertex[bar_pair[1]] pt_int = intersection_bars_base(b_struct, bar_pair) vec_x = normalize_vector(vector_from_points(bar1["axis_endpoints"][0], bar1["axis_endpoints"][1])) vec_y = normalize_vector(vector_from_points(bar2["axis_endpoints"][0], bar2["axis_endpoints"][1])) # contact vector vec_z = normalize_vector(cross_vectors(vec_x, vec_y)) # test plane pl_test = (pt_int, vec_z) vec_m = correct_angle(pt_new, pt_int, pl_test) return vec_m
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) """ fabs = math.fabs normal = normalize_vector(normal) direction = normalize_vector(direction) if fabs(dot_vectors(normal, direction)) > _EPS: raise ValueError('Direction and normal vectors are not orthogonal') angle = math.tan(angle) M = identity_matrix(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 transformed_stress_vector_fields(mesh, vector_fields, stress_type, ref_vector): """ Rescales a vector field based on a plane stress transformation. """ vf1, vf2 = vector_fields # TODO: mapping is not robust! depends on naming convention stress_components = { "bending": { "names": ["mx", "my", "mxy"], "ps": "m_1" }, "axial": { "names": ["nx", "ny", "nxy"], "ps": "n_1" } } stress_names = stress_components[stress_type]["names"] vf_ps = mesh.vector_field(stress_components[stress_type]["ps"]) vf1_transf = VectorField() vf2_transf = VectorField() for fkey in mesh.faces(): # query stress components sx, sy, sxy = mesh.face_attributes(fkey, names=stress_names) # generate principal stresses and angles s1a, s1 = principal_stresses_and_angles(sx, sy, sxy) s1, angle1 = s1a vector_ps = vf_ps[fkey] vector1 = vf1[fkey] vector2 = vf2[fkey] # compute delta between reference vector and principal bending vector # TODO: will take m1 as reference. does this always hold? delta = angle1 - angle_vectors(vector_ps, ref_vector) # add delta to angles of the vector field to transform theta = delta + angle_vectors(vector1, ref_vector) # transform stresses - this becomes the scale of the vectors s1, s2, _ = transformed_stresses(sx, sy, sxy, theta) vf1_transf.add_vector(fkey, scale_vector(normalize_vector(vector1), s1)) vf2_transf.add_vector(fkey, scale_vector(normalize_vector(vector2), s2)) return vf1_transf, vf2_transf
def from_basis_vectors(cls, xaxis, yaxis): """Create rotation matrix from basis vectors (= orthonormal vectors). """ xaxis = normalize_vector(list(xaxis)) yaxis = normalize_vector(list(yaxis)) zaxis = cross_vectors(xaxis, yaxis) yaxis = cross_vectors(zaxis, xaxis) # slight correction rotation = cls() rotation.matrix[0][0], rotation.matrix[1][0], rotation.matrix[2][0] = xaxis rotation.matrix[0][1], rotation.matrix[1][1], rotation.matrix[2][1] = yaxis rotation.matrix[0][2], rotation.matrix[1][2], rotation.matrix[2][2] = zaxis return rotation
def get_normal_of_path_on_xy_plane(k, point, path, mesh): """ Finds the normal of the curve that lies on the xy plane at the point with index k Parameters ---------- k: int, index of the point point: :class: 'compas.geometry.Point' path: :class: 'compas_slicer.geometry.Path' mesh: :class: 'compas.datastructures.Mesh' Returns ---------- :class: 'compas.geometry.Vector' """ # find mesh normal is not really needed in the 2D case of planar slicer # instead we only need the normal of the curve based on the neighboring pts if (0 < k < len(path.points) - 1) or path.is_closed: prev_pt = path.points[k - 1] next_pt = path.points[(k + 1) % len(path.points)] v1 = np.array(normalize_vector(Vector.from_start_end(prev_pt, point))) v2 = np.array(normalize_vector(Vector.from_start_end(point, next_pt))) v = (v1 + v2) * 0.5 normal = [-v[1], v[0], v[2]] # rotate 90 degrees COUNTER-clockwise on the xy plane else: if k == 0: next_pt = path.points[k + 1] v = normalize_vector(Vector.from_start_end(point, next_pt)) normal = [-v[1], v[0], v[2] ] # rotate 90 degrees COUNTER-clockwise on the xy plane else: # k == len(path.points)-1: prev_pt = path.points[k - 1] v = normalize_vector(Vector.from_start_end(point, prev_pt)) normal = [v[1], -v[0], v[2]] # rotate 90 degrees clockwise on the xy plane if length_vector(normal) == 0: # When the neighboring elements happen to cancel out, then search for the true normal, # and project it on the xy plane for consistency normal = get_closest_mesh_normal_to_pt(mesh, point) normal = [normal[0], normal[1], 0] normal = normalize_vector(normal) normal = Vector(*list(normal)) return normal
def random_vector(): return scale_vector( normalize_vector([ random.choice([-1, +1]) * random.random(), random.choice([-1, +1]) * random.random(), random.choice([-1, +1]) * random.random(), ]), random.random())
def vertex_normal(self, vertex): """Return the normal vector at the vertex as the weighted average of the normals of the neighboring faces. Parameters ---------- key : int The identifier of the vertex. Returns ------- list The components of the normal vector. """ if not self.is_vertex_on_boundary(vertex): return halffaces = [] for halfface in self.vertex_halffaces(vertex): if self.is_halfface_on_boundary(halfface): halffaces.append(halfface) vectors = [ self.face_normal(halfface, False) for halfface in halffaces if halfface is not None ] return normalize_vector(centroid_points(vectors))
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 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 _draw_arc(normal_1, normal_2, origin): mid_pt = normalize_vector(add_vectors(normal_1, normal_2)) arc = Arc(Point3d(*[sum(axis) for axis in zip(normal_1, origin)]), Point3d(*[sum(axis) for axis in zip(mid_pt, origin)]), Point3d(*[sum(axis) for axis in zip(normal_2, origin)])) arc_as_curve = ArcCurve(arc) return arc_as_curve
def matrix_from_orthogonal_projection(plane): """Returns an orthogonal projection matrix to project onto a plane. Parameters ---------- plane : [point, normal] | :class:`compas.geometry.Plane` The plane to project onto. Returns ------- list[list[float]] The 4x4 transformation matrix representing an orthogonal projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> plane = (point, normal) >>> P = matrix_from_orthogonal_projection(plane) """ point, normal = plane 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 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 polygon_normal_oriented(polygon, unitized=True): """Compute the oriented normal of any closed polygon (can be convex, concave or complex). Parameters ---------- polygon : sequence The XYZ coordinates of the vertices/corners of the polygon. The vertices are assumed to be in order. The polygon is assumed to be closed: the first and last vertex in the sequence should not be the same. Returns ------- list The weighted or unitized normal vector of the polygon. """ p = len(polygon) assert p > 2, "At least three points required" w = centroid_points(polygon) normal_sum = (0, 0, 0) for i in range(-1, len(polygon) - 1): u = polygon[i] v = polygon[i + 1] uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) normal = scale_vector(cross_vectors(uv, vw), 0.5) normal_sum = add_vectors(normal_sum, normal) if not unitized: return normal_sum return normalize_vector(normal_sum)
def edge_vector(self, u, v, unitized=True): u_xyz = self.vertex_coordinates(u) v_xyz = self.vertex_coordinates(v) vector = subtract_vectors(v_xyz, u_xyz) if unitized: return normalize_vector(vector) return vector
def matrix_from_parallel_projection(plane, direction): """Returns an parallel projection matrix to project onto a plane. Parameters ---------- plane : compas.geometry.Plane or (point, normal) The plane to project onto. direction : list of float Direction of the projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> plane = (point, normal) >>> direction = [1, 1, 1] >>> P = matrix_from_parallel_projection(plane, direction) """ point, normal = plane 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