def line_sdl(start, direction, length, both_sides=True): """ Creates a line from a start point with a given direction and length. It will also extend the line in the oposite direction unless both_sides is set to `False`. Parameters ---------- start : `list` of `float`, shape(n,), n<=3 A point defined by XYZ coordinates. If Z coordinate is not given, then the results are in 2D. If Y and Z coordinates are not given, then the results are in 1D. direction : `list` of `float` XYZ components of the vector to create a line. length : `float` The length of the line. both_sides : `bool`, optional Flag to produce a line in one or both directions. Deafault is set to `True`. Returns ------- a, b : `tuple` Returns the start and end points of the line. """ direction = normalize_vector(direction[:]) a = start b = add_vectors(start, scale_vector(direction, +length)) if both_sides: a = add_vectors(start, scale_vector(direction, -length)) return a, b
def framelines(origin, xaxis, yaxis, zaxis, name): lines = [] lines.append({ 'start': origin, 'end': add_vectors(origin, scale_vector(xaxis, 0.1)), 'color': (255, 0, 0), 'name': "{}.X".format(name) }) lines.append({ 'start': origin, 'end': add_vectors(origin, scale_vector(yaxis, 0.1)), 'color': (0, 255, 0), 'name': "{}.Y".format(name) }) lines.append({ 'start': origin, 'end': add_vectors(origin, scale_vector(zaxis, 0.1)), 'color': (0, 0, 255), 'name': "{}.Z".format(name) }) return lines
def plot_axes(xyz, e11, e22, e33, layer, sc=1): """ Plots a set of axes. Parameters ---------- xyz : list Origin of the axes. e11 : list Normalised first axis component [x1, y1, z1]. e22 : list Normalised second axis component [x2, y2, z2]. e33 : list Normalised third axis component [x3, y3, z3]. layer : str Layer to plot on. sc : float Size of the axis lines. Returns ------- None """ ex = rs.AddLine(xyz, add_vectors(xyz, scale_vector(e11, sc))) ey = rs.AddLine(xyz, add_vectors(xyz, scale_vector(e22, sc))) ez = rs.AddLine(xyz, add_vectors(xyz, scale_vector(e33, sc))) rs.ObjectColor(ex, [255, 0, 0]) rs.ObjectColor(ey, [0, 255, 0]) rs.ObjectColor(ez, [0, 0, 255]) rs.ObjectLayer(ex, layer) rs.ObjectLayer(ey, layer) rs.ObjectLayer(ez, layer)
def intersection_plane_plane(plane1, plane2, tol=1e-6): """Computes the intersection of two planes Parameters ---------- plane1 : tuple The base point and normal (normalized) defining the 1st plane. plane2 : tuple The base point and normal (normalized) defining the 2nd plane. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- line : tuple Two points defining the intersection line. None if planes are parallel. """ o1, n1 = plane1 o2, n2 = plane2 if fabs(dot_vectors(n1, n2)) >= 1 - tol: return None # direction of intersection line d = cross_vectors(n1, n2) # vector in plane 1 perpendicular to the direction of the intersection line v1 = cross_vectors(d, n1) # point on plane 1 p1 = add_vectors(o1, v1) x1 = intersection_line_plane((o1, p1), plane2, tol=tol) x2 = add_vectors(x1, d) return x1, x2
def draw_interface_frames(self): """Draw the frames of the interfaces. """ layer = "{}::Interfaces::Frames".format(self.layer) if self.layer else None lines = [] for (a, b), attr in self.assembly.edges(True): o = attr['interface_origin'] u, v, w = attr['interface_uvw'] lines.append({ 'start' : o, 'end' : add_vectors(o, u), 'name' : "{}.iframe.{}-{}.u".format(self.assembly.name, a, b), 'color' : (255, 0, 0), 'arrow' : 'end' }) lines.append({ 'start' : o, 'end' : add_vectors(o, v), 'name' : "{}.iframe.{}-{}.v".format(self.assembly.name, a, b), 'color' : (0, 255, 0), 'arrow' : 'end' }) lines.append({ 'start' : o, 'end' : add_vectors(o, w), 'name' : "{}.iframe.{}-{}.w".format(self.assembly.name, a, b), 'color' : (0, 0, 255), 'arrow' : 'end' }) self.draw_lines(lines, layer=layer, clear=True, redraw=True)
def plot_structure(structure): eks = [] for ep in structure.element_properties: elements = structure.element_properties[ep].elements elset = structure.element_properties[ep].elset if elements: eks = elements elif elset: eks = structure.sets[elset].selection sec = structure.element_properties[ep].section t = structure.sections[sec].geometry['t'] for ek in eks: nodes = structure.elements[ek].nodes vert = [structure.nodes[nk].xyz() for nk in nodes] n = scale_vector(normalize_vector(normal_polygon(vert)), t / 2.) n_ = scale_vector(n, -1) v_out = [add_vectors(v, n) for v in vert] v_in = [add_vectors(v, n_) for v in vert] s1 = rs.AddSrfPt(v_out) s2 = rs.AddSrfPt(v_in) srfs = [s1, s2] for i in range(len(v_in)): srf = rs.AddSrfPt( [v_in[-i], v_in[-i - 1], v_out[-i - 1], v_out[-i]]) if srf: srfs.append(srf) rs.JoinSurfaces(srfs, delete_input=True)
def line_sdl(start, direction, length, both_sides=True): direction = normalize_vector(direction[:]) a = start b = add_vectors(start, scale_vector(direction, +length)) if both_sides: a = add_vectors(start, scale_vector(direction, -length)) return a, b
def offset_line(line, distance, normal=[0.0, 0.0, 1.0]): """Offset a line by a distance. Parameters ---------- line : tuple Two points defining the line. distances : float or list 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 : vector The normal of the offset plane. Returns ------- offset line : tuple Two points defining the offset line. Notes ----- The offset direction is chosen such that if the line were along the positve X axis and the normal of the offset plane is along the positive Z axis, the offset line is in the direction of the postive Y axis. 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) """ a, b = line ab = subtract_vectors(b, a) direction = normalize_vector(cross_vectors(normal, ab)) if not is_item_iterable(distance): distance = [distance] distances = list(iterable_like(line, distance, distance[-1])) u = scale_vector(direction, distances[0]) v = scale_vector(direction, distances[1]) c = add_vectors(a, u) d = add_vectors(b, v) return c, d
def super_triangle(coords): centpt = centroid_points(coords) bbpts = bounding_box(coords) dis = distance_point_point(bbpts[0], bbpts[2]) dis = dis * 300 v1 = (0 * dis, 2 * dis, 0) v2 = (1.73205 * dis, -1.0000000000001 * dis, 0) # due to numerical issues v3 = (-1.73205 * dis, -1 * dis, 0) pt1 = add_vectors(centpt, v1) pt2 = add_vectors(centpt, v2) pt3 = add_vectors(centpt, v3) return pt1, pt2, pt3
def draw_block_frame(self, key, fkey): """Draw the frame of a specific face of a specific block of the assembly. Parameters ---------- key : int Identifier of the block. fkey : int The identifier of the face. """ block = self.assembly.blocks[key] o, uvw = block.frame(fkey) o = block.face_center(fkey) lines = [] lines.append({ 'start': o, 'end': add_vectors(o, uvw[0]), 'color': (255, 0, 0), 'arrow': 'end', 'name': '{}.block.{}.face.{}.frame.u'.format(self.assembly.name, key, fkey) }) lines.append({ 'start': o, 'end': add_vectors(o, uvw[1]), 'color': (0, 255, 0), 'arrow': 'end', 'name': '{}.block.{}.face.{}.frame.v'.format(self.assembly.name, key, fkey) }) lines.append({ 'start': o, 'end': add_vectors(o, uvw[2]), 'color': (0, 0, 255), 'arrow': 'end', 'name': '{}.block.{}.face.{}.frame.w'.format(self.assembly.name, key, fkey) }) self.draw_lines(lines, layer=self.layer, clear_layer=False, redraw=True)
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) or isinstance(distance, tuple): 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 tween_points(points1, points2, num): """Compute the interpolated points between two sets of points. Parameters ---------- points1 : list The first set of points points2 : list The second set of points num : int The number of interpolated sets to return Returns ------- list Nested list of points. Raises ------ AssertionError When the two point sets do not have the same length. Examples -------- .. plot:: :include-source: from compas.geometry import tween_points from compas_plotters import Plotter points1 = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [3.0, 0.0, 0.0]] points2 = [[0.0, 0.0, 0.0], [1.0, 3.0, 0.0], [2.0, 1.0, 0.0], [3.0, 0.0, 0.0]] tweens = tween_points(points1, points2, 5) polylines = [{'points': points1, 'width': 1.0}] for points in tweens: polylines.append({'points': points, 'width': 0.5}) polylines.append({'points': points2, 'width': 1.0}) plotter = Plotter() plotter.draw_polylines(polylines) plotter.show() Notes ----- The two point sets should have the same length. """ vectors = [vector_from_points(p1, p2) for p1, p2 in zip(points1, points2)] tweens = [] for j in range(num): tween = [] for point, vector in zip(points1, vectors): scale = (j + 1.0) / (num + 1.0) tween.append(add_vectors(point, scale_vector(vector, scale))) tweens.append(tween) return tweens
def tween_points_distance(points1, points2, dist, index=None): """Compute an interpolated set of points between two sets of points, at a given distance. Parameters ---------- points1 : list The first set of points points2 : list The second set of points dist : float The distance from the first set to the second at which to compute the interpolated set. index: int The index of the point in the first set from which to calculate the distance to the second set. If no value is given, the first point will be used. Returns ------- list List of points """ if not index: index = 0 d = distance_point_point(points1[index], points2[index]) scale = float(dist) / d tweens = [] for i in range(len(points1)): tweens.append( add_vectors( points1[i], scale_vector(vector_from_points(points1[i], points2[i]), scale))) return tweens
def line_point(line, t=.5): """Point on a lyline at a normalised parameter. Parameters ---------- polyline: list The XYZ coordinates of the extremities of the line. t: float The normalised parameter of the point on the polyline between 0 and 1. Returns ------- xyz: list, None The point coordinates. None if the parameter is not in [0,1] Raises ------ - """ u, v = line uv = subtract_vectors(v, u) return add_vectors(u, scale_vector(uv, t))
def draw(self, show_point=False, show_normal=False): """Draw the circle. Parameters ---------- show_point : bool, optional Default is ``False``. show_normal : bool, optional Default is ``False``. Returns ------- list The GUIDs of the created Rhino objects. """ point = list(self.primitive.plane.point) normal = list(self.primitive.plane.normal) plane = point, normal radius = self.primitive.radius guids = [] if show_point: points = [{'pos': point, 'color': self.color, 'name': self.primitive.name}] guids += compas_rhino.draw_points(points, layer=self.layer, clear=False, redraw=False) if show_normal: lines = [{'start': point, 'end': add_vectors(point, normal), 'arrow': 'end', 'color': self.color, 'name': self.primitive.name}] guids += compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False) circles = [{'plane': plane, 'radius': radius, 'color': self.color, 'name': self.primitive.name}] guids += compas_rhino.draw_circles(circles, layer=self.layer, clear=False, redraw=False) return guids
def tween_points_distance(points1, points2, dist, index=None): """Compute an interpolated set of points between two sets of points, at a given distance. Parameters ---------- points1 : list[[float, float, float] | :class:`compas.geometry.Point`] The first set of points. points2 : list[[float, float, float] | :class:`compas.geometry.Point`] The second set of points. dist : float The distance from the first set to the second at which to compute the interpolated set. index: int, optional The index of the point in the first set from which to calculate the distance to the second set. If no value is given, the first point will be used. Returns ------- list[list[[float, float, float]]] List of points. """ if not index: index = 0 d = distance_point_point(points1[index], points2[index]) scale = float(dist) / d tweens = [] for i in range(len(points1)): tweens.append( add_vectors( points1[i], scale_vector(subtract_vectors(points2[i], points1[i]), scale))) return tweens
def project_point_line(point, line): """Project a point onto a line. Parameters ---------- point : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates of the point. line : [point, point] | :class:`compas.geometry.Line` Two points defining the projection line. Returns ------- [float, float, float] XYZ coordinates of the projected point. Notes ----- For more info, see [1]_. References ---------- .. [1] Wiki Books. *Linear Algebra/Orthogonal Projection Onto a Line*. Available at: https://en.wikibooks.org/wiki/Linear_Algebra/Orthogonal_Projection_Onto_a_Line. """ a, b = line ab = subtract_vectors(b, a) ap = subtract_vectors(point, a) c = vector_component(ap, ab) return add_vectors(a, c)
def draw(self, color: Optional[RGBColor] = None, show_point=False, show_normal=False) -> List[bpy.types.Object]: """Draw the circle. Parameters ---------- color : tuple of float or tuple of int, optional The RGB color of the capsule. show_point : bool, optional Default is ``False``. show_normal : bool, optional Default is ``False``. Returns ------- list The objects created in Blender. """ color = color or self.color point = self.primitive.plane.point normal = self.primitive.plane.normal plane = point, normal radius = self.primitive.radius objects = [] if show_point: points = [{'pos': point, 'color': color, 'name': self.primitive.name, 'radius': 0.01}] objects += compas_blender.draw_points(points, collection=self.collection) if show_normal: end = add_vectors(point, normal) lines = [{'start': point, 'end': end, 'color': color, 'name': self.primitive.name}] objects += compas_blender.draw_lines(lines, collection=self.collection) circles = [{'plane': plane, 'radius': radius, 'color': color, 'name': self.primitive.name}] objects += compas_blender.draw_circles(circles, collection=self.collection) return objects
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 draw_vertexnormals(self, vertices=None, color=(0, 255, 0), scale=1.0): """Draw the normals at the vertices of the mesh. Parameters ---------- vertices : list, optional A selection of vertex normals to draw. Default is to draw all vertex normals. color : tuple, optional The color specification of the normal vectors. The default color is green, ``(0, 255, 0)``. scale : float, optional Scale factor for the vertex normals. Default is ``1.0``. Returns ------- list The GUIDs of the created Rhino objects. """ vertex_xyz = self.vertex_xyz vertices = vertices or list(self.mesh.vertices()) lines = [] for vertex in vertices: a = vertex_xyz[vertex] n = self.mesh.vertex_normal(vertex) b = add_vectors(a, scale_vector(n, scale)) lines.append({ 'start': a, 'end': b, 'color': color, 'name': "{}.vertexnormal.{}".format(self.mesh.name, vertex), 'arrow': 'end'}) return compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
def draw_facenormals(self, faces=None, color=(0, 255, 255), scale=1.0): """Draw the normals of the faces. Parameters ---------- faces : list, optional A selection of face normals to draw. Default is to draw all face normals. color : tuple, optional The color specification of the normal vectors. The default color is cyan, ``(0, 255, 255)``. scale : float, optional Scale factor for the face normals. Default is ``1.0``. Returns ------- list The GUIDs of the created Rhino objects. """ vertex_xyz = self.vertex_xyz faces = faces or list(self.mesh.faces()) lines = [] for face in faces: a = centroid_points([vertex_xyz[vertex] for vertex in self.mesh.face_vertices(face)]) n = self.mesh.face_normal(face) b = add_vectors(a, scale_vector(n, scale)) lines.append({ 'start': a, 'end': b, 'name': "{}.facenormal.{}".format(self.mesh.name, face), 'color': color, 'arrow': 'end'}) return compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
def closest_point_on_line(a, b, c): """Closest point on line. Same as projection. Parameters ---------- a: list First line point coordinates. b: list Second line point coordinates. c: list Point coordinates. Returns ------- tuple The projected point coordinates and the distance from the input point. """ ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) if length_vector(ab) == 0: return a, distance_point_point(c, a) p = add_vectors( a, scale_vector(ab, dot_vectors(ab, ac) / length_vector(ab)**2)) distance = distance_point_point(c, p) return p, distance
def spiral_array(n, d, anchor=[0.0, 0.0, 0.0]): # spiral parameters set to respect d spacing between consecutive points and consecutive spiral elements a, b = 0, d / (2 * pi) ts = [pi * b] for i in range(n): ts.append((2 * d / b + ts[-1] ** 2) ** .5) return [add_vectors(anchor, archimedean_spiral_evaluate(t, a, b, 0)) for t in ts]
def divide(self, number_of_segments): m, l, r = self.control_point_coordinates() points = [ list(i) for i in interpolate_bezier(m[0], r[0], l[1], m[1], number_of_segments + 1) ] return [add_vectors(self.location, point) for point in points]
def mesh_offset(mesh, offset=1.0, cls=None): """Offset a mesh. Parameters ---------- mesh : Mesh A Mesh to offset. offset : real The offset distance. Returns ------- Mesh The offset mesh. """ if cls is None: cls = type(mesh) # new coordinates of vertex keys vertex_map = {} for i, vkey in enumerate(mesh.vertices()): if len(mesh.vertex_neighbors(vkey)) == 0: vertex_map[vkey] = i, [0, 0, 0] else: vertex_map[vkey] = i, add_vectors( mesh.vertex_coordinates(vkey), scale_vector(mesh.vertex_normal(vkey), offset)) vertices = [xyz for i, xyz in vertex_map.values()] faces = [[vertex_map[vkey][0] for vkey in mesh.face_vertices(fkey)] for fkey in mesh.faces()] return cls.from_vertices_and_faces(vertices, faces)
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 get_vector_on_face_ext(self, point, f_key, name, vec=[0, 0, 0]): f_keys = [f_key] f_keys.extend(self.c_mesh.face_neighbours(f_key)) v_keys = [] pt_cloud = [] for f_key in f_keys: v_keys.extend(self.c_mesh.face_vertices(f_key)) v_keys = list(set(v_keys)) v_vectors_a = self.c_mesh.vertices_attribute(name=str(name) + '_a', keys=v_keys ) v_vectors_b = self.c_mesh.vertices_attribute(name=str(name) + '_b', keys=v_keys ) v_vectors = ut.filter_aligned_vectors(vec, v_vectors_a, v_vectors_b) for v_key in v_keys: pt_cloud.append(self.c_mesh.vertex_coordinates(v_key)) weights = ut.get_dist_weights(point, pt_cloud) new_vectors = [] for idx, vec in enumerate(v_vectors): new_vector = cg.scale_vector(vec, weights[idx]) new_vectors.append(new_vector) return reduce(lambda x, y: cg.add_vectors(x, y), new_vectors)
def draw_vertexnormals(self, keys=None, color=None, scale=None): keys = keys or list(self.mesh.vertices()) scale = scale or self.settings.get('scale.normal:vertex') color = color or self.settings.get('color.normal:vertex') lines = [] for key in keys: a = self.mesh.vertex_coordinates(key) n = self.mesh.vertex_normal(key) b = add_vectors(a, scale_vector(n, scale)) lines.append({ 'start': a, 'end': b, 'color': color, 'name': "{}.vertex.normal.{}".format(self.mesh.name, key), 'arrow': 'end' }) return compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
def draw_facenormals(self, color=None, scale=1.0): """Draw the normals of the faces. Parameters ---------- color : str (HEX) or tuple (RGB), optional The color specification of the normal vectors. String values are interpreted as hex colors (e.g. ``'#ff0000'`` for red). Tuples are interpreted as RGB component specifications (e.g. ``(255, 0, 0) for red``. The default value is ``None``, in which case the labels are assigned the default normal vector color (``self.defaults['color.normal']``). Notes ----- The face normals are named using the following template: ``"{}.face.normal.{}".format(self.datastructure.name, key)``. This name is used afterwards to identify the normals in the Rhino model. """ color = color or self.defaults.get('color.normal') lines = [] for fkey, attr in self.datastructure.faces(True): n = self.datastructure.face_normal(fkey) sp = self.datastructure.face_centroid(fkey) ep = add_vectors(sp, scale_vector(n, scale)) lines.append({ 'start': sp, 'end': ep, 'name': "{}.face.normal.{}".format(self.datastructure.name, fkey), 'color': color, 'arrow': 'end' }) return compas_rhino.draw_lines(lines, layer=self.layer, clear=False, redraw=False)
def get_vector_on_face_faces(self, point, f_key, name, vec=[0, 0, 0]): pt_cloud = [] f_keys = [f_key] f_keys.extend(self.c_mesh.face_neighbors(f_key)) f_keys = list(set(f_keys)) f_vectors_a = self.c_mesh.faces_attribute(f_keys, str(name) + '_a' ) f_vectors_b = self.c_mesh.faces_attribute(f_keys, str(name) + '_b', ) f_vectors = ut.filter_aligned_vectors(vec, f_vectors_a, f_vectors_b) for f_key in f_keys: pt_cloud.append(self.c_mesh.face_centroid(f_key)) weights = ut.get_dist_weights(point, pt_cloud) new_vectors = [] for idx, vec in enumerate(f_vectors): new_vector = cg.scale_vector(vec, weights[idx]) new_vectors.append(new_vector) return reduce(lambda x, y: cg.add_vectors(x, y), new_vectors)