def gluepath_creator(int_surf, path_width): def interval_checker(dimension): underflow = dimension % path_width if underflow > 0.2: no_paths = dimension // path_width + 1 new_path_width = dimension / no_paths return new_path_width else: return path_width wid_gap = int_surf[1] - int_surf[0] wid_vec = Vector(wid_gap[0], wid_gap[1], wid_gap[2]) wid = wid_vec.length wid_vec.unitize() len_gap = int_surf[2] - int_surf[1] len_vec = Vector(len_gap[0], len_gap[1], len_gap[2]) len = len_vec.length len_vec.unitize() wid_path = interval_checker(wid) len_path = interval_checker(len) path_dims = [wid_path, len_path] path_points = [] iteration = 0 path_unfinished = True current_pt = int_surf[0] + scale_vector( wid_vec, wid_path / 2) + scale_vector(len_vec, len_path / 2) current_vec = len_vec.unitized() len_left = len - len_path wid_left = wid - wid_path dims_left = [len_left, wid_left] path_points.append(current_pt) R = Rotation.from_axis_and_angle([0, 0, 1], -math.pi / 2) while path_unfinished: current_index = iteration % 2 current_dim = dims_left[current_index] if iteration > 2: current_dim -= path_dims[current_index] dims_left[current_index] = current_dim if current_dim < path_width * 0.95: break current_pt = current_pt + scale_vector(current_vec, current_dim) path_points.append(current_pt) current_vec.transform(R) current_vec.unitize() iteration += 1 if not is_point_in_polygon_xy(current_pt, int_surf): print("Error: Point not in polygon") return path_points
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 board_geometry_setup(self): for my_layer in range(self.layer_no): if my_layer % 2 == 0: my_frame = self.origin_fr else: my_frame = self.sec_fr 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 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 layer_standard_length = my_board.layer.characteristic_length if my_board.location == "high": if not my_board.supporter: length_attribute_1 = layer_standard_length - my_board.length / 2 else: length_attribute_1 = layer_standard_length - my_board.width / 2 elif my_board.location == "low": if not my_board.supporter: length_attribute_1 = my_board.length / 2 else: length_attribute_1 = my_board.width / 2 else: length_attribute_1 = layer_standard_length / 2 # position parallel to the boards (if not sup) my_vec1 = scale_vector(my_dir1, length_attribute_1) # position perpendicular to board direction (if not sup) my_vec2 = scale_vector(my_dir2, dist) # height vector my_vec3 = Vector( 0, 0, my_board.layer.z_drop_point - 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) if not my_board.supporter: my_board.length_vector = normalize_vector(my_vec1) my_board.width_vector = normalize_vector(my_vec2) else: my_board.length_vector = normalize_vector(my_vec2) my_board.width_vector = normalize_vector(my_vec1) my_board.box_update()
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 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 mirror_vector_vector(v1, v2): """Mirrors vector about vector. Parameters ---------- v1 : [float, float, float] | :class:`compas.geometry.Vector` The vector. v2 : [float, float, float] | :class:`compas.geometry.Vector` The normalized vector as mirror axis Returns ------- [float, float, float] The mirrored vector. Notes ----- For more info, see [1]_. References ---------- .. [1] Math Stack Exchange. *How to get a reflection vector?* Available at: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector. """ return subtract_vectors(v1, scale_vector(v2, 2 * dot_vectors(v1, v2)))
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 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)
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 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 create_overhang_texture(slicer, overhang_distance): """Creates a cool overhang texture""" print("Creating cool texture") for i, layer in enumerate(slicer.layers): if i % 10 == 0 and i > 0: # for every 10th layer, except for the brim # print(layer) for j, path in enumerate(layer.paths): # print(path) # create an empty layer in which we can store our modified points new_path = [] for k, pt in enumerate(path.points): # for every second point (only even points) if k % 2 == 0: # get the normal of the point in relation to the mesh normal = get_normal_of_path_on_xy_plane(k, pt, path, mesh=None) # scale the vector by a number to move the point normal_scaled = scale_vector(normal, -overhang_distance) # create a new point by adding the point and the normal vector new_pt = add_vectors(pt, normal_scaled) # recreate the new_pt values as compas_points pt = Point(new_pt[0], new_pt[1], new_pt[2]) # append the points to the new path new_path.append(pt) # replace the current path with the new path that we just created layer.paths[j] = Path(new_path, is_closed=path.is_closed)
def smooth_network_length(network, lmin, lmax, fixed=None, kmax=1, d=0.5, callback=None, callback_args=None): fixed = fixed or [] fixed = set(fixed) if callback: if not callable(callback): raise Exception('The callback is not callable.') for k in range(kmax): key_xyz = {key: network.vertex_coordinates(key) for key in network.vertices()} for key in network: if key in fixed: continue ep = key_xyz[key] points = [] for nbr in network.vertex_neighbours(key): sp = key_xyz[nbr] vec = subtract_vectors(ep, sp) lvec = length_vector(vec) scale = max(lmin, min(lvec, lmax)) p = add_vectors(sp, scale_vector(vec, scale / lvec)) points.append(p) x, y, z = centroid_points(points) attr = network.vertex[key] attr['x'] += d * (x - ep[0]) attr['y'] += d * (y - ep[1]) attr['z'] += d * (z - ep[2]) if callback: callback(network, k, callback_args)
def test_remeshing(): FILE = os.path.join(HERE, '../..', 'data', 'Bunny.ply') # ============================================================================== # Get the bunny and construct a mesh # ============================================================================== bunny = TriMesh.from_ply(FILE) bunny.cull_vertices() # ============================================================================== # Move the bunny to the origin and rotate it upright. # ============================================================================== vector = scale_vector(bunny.centroid, -1) T = Translation.from_vector(vector) S = Scale.from_factors([100, 100, 100]) R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90)) bunny.transform(R * S * T) # ============================================================================== # Remesh # ============================================================================== length = bunny.average_edge_length bunny.remesh(4 * length) bunny.to_mesh()
def mesh_offset( mesh, distance=1.0, ): """Offset a mesh. Parameters ---------- mesh : Mesh A Mesh to offset. distance : float The offset distance. Returns ------- Mesh The offset mesh. """ offset = mesh.copy() for key in offset.vertices(): normal = mesh.vertex_normal(key) xyz = mesh.vertex_coordinates(key) offset.vertex_attributes( key, 'xyz', add_vectors(xyz, scale_vector(normal, distance))) return offset
def draw_vectors(self, points, vectors, scale): rs_lines = [] for idx, point in enumerate(points): vector_a, vector_b = vectors[idx] vector_a = cg.scale_vector(vector_a, scale) vector_b = cg.scale_vector(vector_b, scale) pt_a = cg.add_vectors(point, vector_a) pt_b = cg.add_vectors(point, vector_b) line = rs.AddLine( rs.AddPoint(pt_a), # rs rs.AddPoint(pt_b)) rs_lines.append(line) # rs return rs_lines
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 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 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 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 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 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 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 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 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 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
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 polygon_area_footprint(polygon): """Compute the non-oriented area of a polygon (can be convex or concave). Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- float The non-oriented area of the polygon. """ area = 0 w = centroid_points(polygon) 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) area += length_vector(normal) return area
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 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 beam_plane(beam): p1 = SHELL.vertex_coordinates(beam[1]) p0 = SHELL.vertex_coordinates(beam[0]) xaxis = subtract_vectors(p1, p0) yaxis = cross_vectors(ZAXIS, xaxis) normal = normalize_vector(yaxis) origin = add_vectors(p0, scale_vector(normal, OFFSET)) return origin, normal