def closest_point_on_segment(a, b, c): """Closest point on segment. Different from projection because an extremity is yielded if the projection is on the line but outside the segment. 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. """ p, distance = closest_point_on_line(a, b, c) ab = subtract_vectors(b, a) ap = subtract_vectors(p, a) if dot_vectors(ab, ap) < 0: return a, distance_point_point(c, a) elif length_vector(ab) < length_vector(ap): return b, distance_point_point(c, b) else: return p, distance
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 polyline_point(polyline, t=.5, snap_to_point=False): """Point on a polyline at a normalised parameter. If asked, the found position is snapped to the closest polyline node. Parameters ---------- polyline: list The XYZ coordinates of the nodes of the polyline. The polyline can be closed. t: float The normalised parameter of the point on the polyline between 0 and 1. snap_to_point: bool If true, the closest node on the polyline is returned, if false, a point on a line. Returns ------- xyz: list, None The point coordinates. None if the parameter is not in [0,1] Raises ------ - """ if t < 0 or t > 1: return None length = sum([ distance_point_point(polyline[i], polyline[i + 1]) for i in range(len(polyline) - 1) ]) target_length = t * length current_length = 0 for i in range(len(polyline) - 1): current_point = polyline[i] next_point = polyline[i + 1] next_length = current_length + distance_point_point( current_point, next_point) if target_length >= current_length and target_length <= next_length: rest_length = target_length - current_length rest_t = rest_length / distance_point_point( current_point, next_point) xyz = add_vectors( current_point, scale_vector(subtract_vectors(next_point, current_point), rest_t)) if snap_to_point: if distance_point_point(xyz, current_point) < distance_point_point( xyz, next_point): return current_point else: return next_point else: return xyz else: current_length = next_length
def board_intersection(pt1, vec1, len1, pt2, vec2, len2): line1 = line_creator(pt1, vec1, len1) line2 = line_creator(pt2, vec2, len2) # to check whether the boards are parallel if vec1 != vec2: int_pt = intersection_line_line_xy(line1, line2) else: # expand here later to deal with gluing parallel boards return 0 # since intersection also hits when the lines intersect in their continuation, we have to add that one if distance_point_point(pt1, int_pt) < len1 / 2 and \ distance_point_point(pt2, int_pt) < len2 / 2: return int_pt else: return 0
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 automated_smoothing_surface_constraints(mesh, surface): """Apply automatically surface-related constraints to the vertices of a mesh to smooth: kinks, boundaries and surface. Parameters ---------- mesh : Mesh The mesh to apply the constraints to for smoothing. surface : Rhino surface guid A Rhino surface guid on which to constrain mesh vertices. Returns ------- constraints : dict A dictionary of mesh constraints for smoothing as vertex keys pointing to point, curve or surface objects. """ surface = RhinoSurface.from_guid(surface) constraints = {} points = [rs.AddPoint(point) for point in surface.kinks()] curves = surface.borders(type = 0) constraints.update({vkey: surface.guid for vkey in mesh.vertices()}) for vkey in mesh.vertices_on_boundary(): xyz = mesh.vertex_coordinates(vkey) projections = {curve: distance_point_point(xyz, RhinoCurve.from_guid(curve).closest_point(xyz)) for curve in curves} constraints.update({vkey: min(projections, key = projections.get)}) key_to_index = {i: vkey for i, vkey in enumerate(mesh.vertices_on_boundary())} vertex_coordinates = tuple(mesh.vertex_coordinates(vkey) for vkey in mesh.vertices_on_boundary()) constraints.update({key_to_index[closest_point_in_cloud(rs.PointCoordinates(point), vertex_coordinates)[2]]: point for point in points}) return constraints
def face_flatness(self, face, maxdev=0.02): """Compute the flatness of a face. Parameters ---------- face : int The identifier of the face. Returns ------- float The flatness. Note ---- compas.geometry.mesh_flatness function currently only works for quadrilateral faces. This function uses the distance between each face vertex and its projected point on the best-fit plane of the face as the flatness metric. """ deviation = 0 polygon = self.face_coordinates(face) plane = bestfit_plane(polygon) for pt in polygon: pt_proj = project_point_plane(pt, plane) dev = distance_point_point(pt, pt_proj) if dev > deviation: deviation = dev return deviation
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 xdraw_pipes(pipes, div=8): """ Draw a set of pipes. Parameters: pipes (list): {'radius':, 'start':, 'end':, 'color':, 'name':, 'layer':}. div (int): Divisions around cross-section. Returns: list: Created pipe objects. """ objects = [] bpy.ops.mesh.primitive_cylinder_add(radius=1, depth=1, vertices=div, location=[0, 0, 0]) object = bpy.context.object for pipe in pipes: radius = pipe.get('radius', 1) start = pipe.get('start', [0, 0, 0]) end = pipe.get('end', [0, 0, 1]) L = distance_point_point(start, end) pos = centroid_points([start, end]) copy = object.copy() copy.name = pipe.get('name', 'pipe') copy.rotation_euler[1] = acos((end[2] - start[2]) / L) copy.rotation_euler[2] = atan2(end[1] - start[1], end[0] - start[0]) copy.location = Vector(pos) copy.data = copy.data.copy() copy.scale = ((radius, radius, L)) copy.show_wire = True copy.data.materials.append(bpy.data.materials[pipe.get('color', 'white')]) set_objects_layer([copy], pipe.get('layer', 0)) objects.append(copy) delete_objects([object]) for object in objects: bpy.context.scene.objects.link(object) deselect_all_objects() return objects
def face_curvature(self, fkey): """Dimensionless face curvature. Face curvature is defined as the maximum face vertex deviation from the best-fit plane of the face vertices divided by the average lengths of the face vertices to the face centroid. Parameters ---------- fkey : int The face key. Returns ------- float The dimensionless curvature. """ vertices = self.face_vertices(fkey) points = [self.vertex_coordinates(key) for key in vertices] centroid = self.face_centroid(fkey) plane = bestfit_plane(points) max_deviation = max( [distance_point_plane(point, plane) for point in points]) average_distances = vector_average( [distance_point_point(point, centroid) for point in points]) return max_deviation / average_distances
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 find_point_id(query_pt, pts, tol=EPS): ids = [] for id, pt in enumerate(pts): if distance_point_point(query_pt, pt) < tol: ids.append(id) assert len(ids) == 1, 'duplicated pts!' return ids[0]
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 cell_collapse_short_edge(cell, u, v, min_length=0.1): """Collapse short edges of a cell. Parameters ---------- cell : Mesh Cell as a mesh object. u : hashable The key of the start vertex. v : hashable The key of the end vertex. min_length : float Minimum length of edges to be collapsed. Returns ------- cell : Mesh Updated cell. """ sp = cell.vertex_coordinates(u) ep = cell.vertex_coordinates(v) dist = distance_point_point(sp, ep) if dist < min_length: mp = midpoint_point_point(sp, ep) cell.vertex_update_xyz(u, mp) cell.vertex_update_xyz(v, mp) return cell
def polygon_flatness(polygon): """Comput the flatness of a polygon. Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- float The flatness. Note ---- compas.geometry.mesh_flatness function currently only works for quadrilateral faces. This function uses the distance between each face vertex and its projected point on the best-fit plane of the face as the flatness metric. """ deviation = 0 plane = bestfit_plane(polygon) for pt in polygon: pt_proj = project_point_plane(pt, plane) dev = distance_point_point(pt, pt_proj) if dev > deviation: deviation = dev return deviation
def draw_cylinders(cylinders: List[Dict], collection: Union[Text, bpy.types.Collection] = None, uv: int = 10) -> List[bpy.types.Object]: """Draw cylinder objects as mesh primitives.""" from math import acos from math import atan2 bpy.ops.mesh.primitive_cylinder_add(location=[0, 0, 0], radius=1, depth=1, vertices=uv) empty = bpy.context.object _link_object(empty, collection) objects = [0] * len(cylinders) for index, data in enumerate(cylinders): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) radius = data.get('radius', 1.0) length = distance_point_point(sp, ep) obj = empty.copy() obj.name = data.get('name', 'cylinder') obj.rotation_euler[1] = acos((ep[2] - sp[2]) / length) obj.rotation_euler[2] = atan2(ep[1] - sp[1], ep[0] - sp[0]) obj.location = mp obj.scale = ((radius, radius, length)) rgb = data.get('color', [1.0, 1.0, 1.0]) _set_object_color(obj, rgb) objects[index] = obj _link_objects(objects, collection) empty.hide_set(True) return objects
def draw_cylinders(cylinders, div=10, layer=None): # don't set the obvious defaults? bpy.ops.mesh.primitive_cylinder_add(location=[0, 0, 0], radius=1, depth=1, vertices=div) empty = bpy.context.active_object objects = [0] * len(cylinders) for index, data in enumerate(cylinders): sp = data['start'] ep = data['end'] mp = centroid_points([sp, ep]) radius = data.get('radius', 1.0) length = distance_point_point(sp, ep) obj = empty.copy() obj.name = data.get('name', 'cylinder') # get this from geometry package obj.rotation_euler[1] = acos((ep[2] - sp[2]) / length) obj.rotation_euler[2] = atan2(ep[1] - sp[1], ep[0] - sp[0]) obj.location = mp obj.scale = ((radius, radius, length)) rgb = data.get('color') or [1.0, 1.0, 1.0] set_object_color(obj, rgb) objects[index] = obj delete_object(empty) link_objects(objects, layer=layer) return objects
def face_flatness(self, fkey, maxdev=0.02): """Compute the flatness of the mesh face. Parameters ---------- fkey : int The identifier of the face. maxdev : float, optional A maximum value for the allowed deviation from flatness. Default is ``0.02``. Returns ------- float The flatness. Notes ----- Flatness is computed as the ratio of the distance between the diagonals of the face to the average edge length. A practical limit on this value realted to manufacturing is 0.02 (2%). Warnings -------- This method only makes sense for quadrilateral faces. """ vertices = self.face_vertices(fkey) f = len(vertices) points = self.vertices_attributes('xyz', keys=vertices) lengths = [distance_point_point(a, b) for a, b in pairwise(points + points[:1])] length = sum(lengths) / f d = distance_line_line((points[0], points[2]), (points[1], points[3])) return (d / length) / maxdev
def pull_to_edge(self, s_mesh, f_id): for u, v in s_mesh.c_mesh.face_halfedges(f_id): line = s_mesh.c_mesh.edge_coordinates(u, v) if cg.distance_point_point(line[0], line[1]) > TOL: if cg.distance_point_line(self.pos, line) < TOL: return (u, v) return None
def total_length_of_paths(self): """ Returns the total length of all paths. Does not consider extruder toggle. """ total_length = 0 for layer_key in self.printpoints_dict: for path_key in self.printpoints_dict[layer_key]: for prev, curr in pairwise( self.printpoints_dict[layer_key][path_key]): length = distance_point_point(prev.pt, curr.pt) total_length += length return total_length
def reorder_vertical_layers(slicer, align_with): """Re-orders the vertical layers in a specific way Parameters ---------- slicer: :class:`compas_slicer.slicers.BaseSlicer` An instance of one of the compas_slicer.slicers classes. align_with: str or :class:`compas.geometry.Point` x_axis = reorders the vertical layers starting from the positive x-axis y_axis = reorders the vertical layers starting from the positive y-axis Point(x,y,z) = reorders the vertical layers starting from a given Point """ if align_with == "x_axis": align_pt = Point(2**32, 0, 0) elif align_with == "y_axis": align_pt = Point(0, 2**32, 0) elif isinstance(align_with, Point): align_pt = align_with else: raise NameError("Unknown align_with : " + str(align_with)) logger.info( "Re-ordering vertical layers to start with the vertical layer closest to: %s" % align_with) for layer in slicer.layers: assert layer.min_max_z_height[0] is not None and layer.min_max_z_height[1] is not None, \ "To use the 'reorder_vertical_layers function you need first to calculate the layers' z_bounds. To do " \ "that use the function 'Layer.calculate_z_bounds()'" # group vertical layers based on the min_max_z_height grouped_iter = itertools.groupby(slicer.layers, lambda x: x.min_max_z_height) grouped_layer_list = [list(group) for _key, group in grouped_iter] reordered_layers = [] for grouped_layers in grouped_layer_list: distances = [] for vert_layer in grouped_layers: # recreate head_centroid_pt as compas.Point head_centroid_pt = Point(vert_layer.head_centroid[0], vert_layer.head_centroid[1], vert_layer.head_centroid[2]) # measure distance distances.append(distance_point_point(head_centroid_pt, align_pt)) # sort lists based on closest distance to align pt grouped_new = [x for _, x in sorted(zip(distances, grouped_layers))] reordered_layers.append(grouped_new) # flatten list slicer.layers = [item for sublist in reordered_layers for item in sublist]
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 shoot_rays(room): """Performs the raytracing process using the room properties. Calculates all reflections, including geometry, times and energy values per segment. Parameters ---------- room: object The room object to be analyzed. """ # TODO: Figure out if it is possible to cut ray one reflection short. # TODO: how to get rid of deepcopy? it is super slow. # TODO: should propably use some generators, is that possible? directions = room.source.directions ref_srf = [room.surfaces[gk]['guid'] for gk in room.surfaces] ref_map = {room.surfaces[sk]['guid']: sk for sk in room.surfaces} room.ray_times = {dk: {} for dk in directions} room.ray_lengths = {dk: {} for dk in directions} room.ray_powers = {dk: {} for dk in directions} room.ray_lines = {dk: {} for dk in directions} for dk in directions: dir = directions[dk] src_ = room.source.xyz w = room.source.ray_power[dk] min_w = room.source.ray_minpower[dk] time = 0 i = 0 min_power = False while time < room.ctime and not min_power: i += 1 ray = rs.ShootRay(ref_srf, src_, dir, 2) srf = rs.PointClosestObject(ray[0], ref_srf)[0] mp_list = [] if i > 0: sk = ref_map[str(srf)] abs = room.materials[room.surfaces[sk]['material']].absorption for wk in w: w[wk] *= (1 - abs[wk]) mp_list.append(w[wk] < min_w[wk]) min_power = all(mp_list) l = distance_point_point(ray[0], ray[1]) t = int((l / 343.0) * 1000) room.ray_times[dk][i] = t room.ray_lengths[dk][i] = l room.ray_powers[dk][i] = deepcopy(w) room.ray_lines[dk][i] = ((ray[0].X, ray[0].Y, ray[0].Z), (ray[1].X, ray[1].Y, ray[1].Z)) dir = vector_from_points(ray[1], ray[2]) src_ = ray[1] time += t
def total_print_time(self): """ If the print speed is defined, it returns the total time of the print, else returns None""" if self.printpoints_dict['layer_0']['path_0'][ 0].velocity is not None: # assume that all ppts are set or none total_time = 0 for layer_key in self.printpoints_dict: for path_key in self.printpoints_dict[layer_key]: for prev, curr in pairwise( self.printpoints_dict[layer_key][path_key]): length = distance_point_point(prev.pt, curr.pt) total_time += length / curr.velocity return total_time
def network_order(start, structure, network): """ Extract node and element orders from a Network for a given start-point. Parameters ---------- start : list Start point co-ordinates. structure : obj Structure object. network : obj Network object. Returns ------- list Ordered nodes. list Ordered elements. list Cumulative lengths at element mid-points. float Total length. """ gkey_key = network.gkey_key() start = gkey_key[geometric_key(start, '{0}f'.format(structure.tol))] leaves = network.leaves() leaves.remove(start) end = leaves[0] adjacency = {i: network.vertex_neighbors(i) for i in network.vertices()} weight = {(u, v): 1 for u, v in network.edges()} weight.update({(v, u): weight[(u, v)] for u, v in network.edges()}) path = dijkstra_path(adjacency, weight, start, end) nodes = [ structure.check_node_exists(network.vertex_coordinates(i)) for i in path ] elements, arclengths, length = [], [], 0 for i in range(len(nodes) - 1): sp = nodes[i] ep = nodes[i + 1] elements.append(structure.check_element_exists([sp, ep])) xyz_sp = structure.node_xyz(sp) xyz_ep = structure.node_xyz(ep) dL = distance_point_point(xyz_sp, xyz_ep) arclengths.append(length + dL / 2.) length += dL return nodes, elements, arclengths, length
def board_intersection(brd1, brd2): vec1 = Vector(brd1.length_vector[0], brd1.length_vector[1], brd1.length_vector[2]) vec2 = Vector(brd2.length_vector[0], brd2.length_vector[1], brd2.length_vector[2]) line1 = line_creator(brd1.centre_point, vec1, brd1.length) line2 = line_creator(brd2.centre_point, vec2, brd2.length) # to check whether the boards are parallel if vec1.angle(vec2) > 0.1: int_pt = intersection_line_line_xy(line1, line2) else: target_width = min(brd1.width, brd2.width) upper_boarder_1 = brd1.centre_point + brd1.width_vector*brd1.width lower_boarder_1 = brd1.centre_point # expand here later to deal with gluing parallel boards return 0 # since intersection also hits when the lines intersect in their continuation, we have to add that one if distance_point_point(brd1.centre_point, int_pt) < brd1.length / 2 and \ distance_point_point(brd2.centre_point, int_pt) < brd2.length / 2: return int_pt else: return 0
def get_dist_weights(self, point, point_cloud): dst = [] weigths = [] for c_point in point_cloud: dst.append(cg.distance_point_point(point, c_point)) if len(dst) > 1: massDistance = reduce(lambda x, y: x + y, dst) return list(map(lambda x: x / massDistance, dst)) else: return [1.0]
def astar_shortest_path(graph, root, goal): """Find the shortest path between two vertices of a graph or mesh using the A* search algorithm. Parameters ---------- graph : :class:`compas.datastructures.Network` | :class:`compas.datastructures.Mesh` A network or mesh data structure. root : hashable The identifier of the starting node. goal : hashable The identifier of the ending node. Returns ------- list[hashable] | None The path from root to goal, or None, if no path exists between the vertices. References ---------- https://en.wikipedia.org/wiki/A*_search_algorithm """ adjacency = graph.adjacency weights = {} for u, v in graph.edges(): u_coords = _get_coordinates(u, graph) v_coords = _get_coordinates(v, graph) distance = distance_point_point(u_coords, v_coords) weights[(u, v)] = distance weights[(v, u)] = distance heuristic = {} goal_coords = _get_coordinates(goal, graph) points = _get_points(graph) for u in points: u_coords = _get_coordinates(u, graph) heuristic[u] = distance_point_point(u_coords, goal_coords) return astar_lightest_path(adjacency, weights, heuristic, root, goal)
def bmesh_edge_lengths(bmesh): """ Retrieve the edge legnths of a Blender mesh. Parameters: bmesh (obj): Blender mesh object. Returns: list: Lengths of each edge. float: Total length. """ X, uv, _ = bmesh_data(bmesh) lengths = [distance_point_point(X[u], X[v]) for u, v in uv] L = sum(lengths) return lengths, L
def get_layer_ppts(self, layer, base_boundary): """ Creates the PrintPoints of a single layer.""" max_layer_height = get_param(self.parameters, key='max_layer_height', defaults_type='layers') min_layer_height = get_param(self.parameters, key='min_layer_height', defaults_type='layers') avg_layer_height = get_param(self.parameters, 'avg_layer_height', 'layers') all_pts = [pt for path in layer.paths for pt in path.points] closest_fks, projected_pts = utils.pull_pts_to_mesh_faces( self.slicer.mesh, all_pts) normals = [ Vector(*self.slicer.mesh.face_normal(fkey)) for fkey in closest_fks ] count = 0 crv_to_check = Path( base_boundary.points, True) # creation of fake path for the lower boundary layer_ppts = {} for i, path in enumerate(layer.paths): layer_ppts['path_%d' % i] = [] for p in path.points: cp = closest_point_on_polyline(p, Polyline(crv_to_check.points)) d = distance_point_point(cp, p) ppt = PrintPoint(pt=p, layer_height=avg_layer_height, mesh_normal=normals[count]) ppt.closest_support_pt = Point(*cp) ppt.distance_to_support = d ppt.layer_height = max(min(d, max_layer_height), min_layer_height) ppt.up_vector = Vector( *normalize_vector(Vector.from_start_end(cp, p))) ppt.frame = ppt.get_frame() layer_ppts['path_%d' % i].append(ppt) count += 1 crv_to_check = path return layer_ppts