def create_planar_paths(mesh, planes): """ Creates planar contours. Does not rely on external libraries. It is currently the only method that can return identify OPEN versus CLOSED paths. Parameters ---------- mesh: :class: 'compas.datastructures.Mesh' The mesh to be sliced planes: list, :class: 'compas.geometry.Plane' """ layers = [] with progressbar.ProgressBar(max_value=len(planes)) as bar: for i, plane in enumerate(planes): intersection = PlanarContours(mesh, plane) intersection.compute() paths = [] if len(intersection.sorted_point_clusters ) > 0 and intersection.is_valid: for key in intersection.sorted_point_clusters: is_closed = intersection.closed_paths_booleans[key] path = Path(points=intersection.sorted_point_clusters[key], is_closed=is_closed) paths.append(path) layers.append(Layer(paths)) bar.update(i) return layers
def add_to_vertical_layers_manager(self, vertical_layers_manager): for key in self.sorted_point_clusters: pts = self.sorted_point_clusters[key] if len(pts) > 3: # discard curves that are too small path = Path(pts, is_closed=self.closed_paths_booleans[key]) vertical_layers_manager.add(path)
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 create_planar_paths_numpy(mesh, min_z, max_z, planes): """ Creates planar contours using the compas mesh_contours_numpy function. To be replaced with a better alternative Considers all resulting paths as CLOSED paths Parameters ---------- mesh : compas.datastructures.Mesh A compas mesh. min_z: float max_z: float planes: list, compas.geometry.Plane """ # initializes progress_bar for measuring progress progress_bar = Bar('Slicing', max=len(planes), suffix='Layer %(index)i/%(max)i - %(percent)d%%') levels = [plane.point[2] for plane in planes] levels, np_layers = compas.datastructures.mesh_contours_numpy(mesh, levels=levels, density=20) layers = [] for i, layer in enumerate(np_layers): for path in layer: paths_per_layer = [] for polygon2d in path: points = [Point(p[0], p[1], levels[i]) for p in polygon2d[:-1]] if len(points) > 0: # threshold_closed = 25.0 # TODO: Threshold should not be hardcoded # is_closed = distance_point_point(points[0], points[-1]) < threshold_closed # print_points = [PrintPoint(pt=p, layer_height=layer_height) for p in points] path = Path(points=points, is_closed=True) paths_per_layer.append(path) l = Layer(paths_per_layer) layers.append(l) # advance progressbar progress_bar.next() # finish progressbar progress_bar.finish() return layers
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
def from_data(cls, data): """Construct a vertical layer from its data representation. Parameters ---------- data: dict The data dictionary. Returns ------- layer The constructed vertical layer. """ paths_data = data['paths'] paths = [Path.from_data(paths_data[key]) for key in paths_data] layer = cls(id=None) layer.paths = paths layer.min_max_z_height = data['min_max_z_height'] return layer
def from_data(cls, data): """Construct a layer from its data representation. Parameters ---------- data: dict The data dictionary. Returns ------- layer The constructed layer. """ paths_data = data['paths'] paths = [Path.from_data(paths_data[key]) for key in paths_data] layer = cls(paths=paths) layer.is_brim = data['is_brim'] layer.number_of_brim_offsets = data['number_of_brim_offsets'] layer.min_max_z_height = data['min_max_z_height'] return layer
def create_planar_paths_igl(mesh, n): """ Creates planar contours using the libigl function: https://libigl.github.io/libigl-python-bindings/igl_docs/#isolines TODO: ??? It is currently the only method that can return identify OPEN versus CLOSED paths. Parameters ---------- mesh: :class: 'compas.datastructures.Mesh' The mesh to be sliced n: number of contours """ utils.check_package_is_installed('igl') import igl v, f = mesh.to_vertices_and_faces() ds = np.array([vertex[2] for vertex in v]) # save distances of each vertex from the floor: this will be the scalar field for the slicing operation. # --- generate disconnected segments of isolines using the libigl function v = np.array(v) f = np.array(f) isoV, isoE = igl.isolines(v, f, ds, n) # --- group resulting segments per level print('Grouping segments per level') segments_per_level = {} tol = 1e-6 for e in isoE: v0 = isoV[e[0]] v1 = isoV[e[1]] assert(abs(v0[2] - v1[2]) < tol) h = v0[2] found = False for key in segments_per_level: if abs(key - h) < tol: if e[0] != e[1]: # do not add null segments segments_per_level[key].append(e) found = True if not found: segments_per_level[h] = [e] #assert(len(segments_per_level) == n) utils.save_to_json(utils.point_list_to_dict(isoV), "/examples/1_planar_slicing_simple/data/output", 'isoV.json') sorted_keys = [key for key in segments_per_level] sorted(sorted_keys) layers = [] # --- Create connectivity graph G for every group, and sort edges based on G with progressbar.ProgressBar(max_value=len(segments_per_level)) as bar: for index, key in enumerate(sorted_keys): #print(' Creating connectivity for level z = ', key) es = segments_per_level[key] # list of the current edges, which whose indices we are working below G = nx.Graph() connections_found = [[False, False] for _ in es] for i, e in enumerate(es): G.add_node(i) # node, attribute for i, ei in enumerate(es): # ei here is the edge, not the index! for side_i in range(2): if not connections_found[i][side_i]: for jj, ej in enumerate(es[i+1:]): # ej here is the edge, not the index! j = i + jj + 1 for side_j in range(2): if not connections_found[j][side_j]: vi = isoV[ei[side_i]] vj = isoV[ej[side_j]] if distance_point_point_sqrd(vi, vj) < tol: G.add_edge(i, j) connections_found[i][side_i] = True connections_found[j][side_j] = True # sort connected components of G sorted_indices_dict = sort_graph_connected_components(G) # for i, s_key in enumerate(sorted_indices_dict): # sorted_eis = sorted_indices_dict[s_key] # this_segment_pts = [] # # for j, ei in enumerate(sorted_eis): # e = es[ei] # # segment pts # for k in range(2): # this_segment_pts.append(isoV[e[k]]) # # utils.save_to_json(utils.point_list_to_dict(this_segment_pts), # "C:/dev/compas_slicer/examples/1_planar_slicing_simple/data/output", 'isoV.json') # print('saved') # get points list from sorted edge indices (for each connected component) paths_pts = [[] for _ in sorted_indices_dict] are_closed = [True for _ in sorted_indices_dict] for i, s_key in enumerate(sorted_indices_dict): sorted_indices = sorted_indices_dict[s_key] for j, s_ei in enumerate(sorted_indices): v0 = isoV[es[s_ei][0]] v1 = isoV[es[s_ei][1]] if j == 0: s_ei_next = sorted_indices[j+1] v0_next = isoV[es[s_ei_next][0]] v1_next = isoV[es[s_ei_next][1]] v_mid_next = (Point(*v0_next) + Point(*v1_next)) * 0.5 if distance_point_point_sqrd(v0, v_mid_next) < distance_point_point_sqrd(v1, v_mid_next): paths_pts[i].extend([Point(*v1), Point(*v0)]) else: paths_pts[i].extend([Point(*v0), Point(*v1)]) else: v_prev = paths_pts[i][-1] if distance_point_point_sqrd(v0, v_prev) < distance_point_point_sqrd(v1, v_prev): paths_pts[i].append(Point(*v1)) else: paths_pts[i].append(Point(*v0)) if distance_point_point_sqrd(paths_pts[i][0], paths_pts[i][1]) > tol: are_closed[i] = False paths = [] for i in range(len(sorted_indices_dict)): paths.append(Path(points=paths_pts[i], is_closed=are_closed[i])) layers.append(Layer(paths)) bar.update(index) return layers
def generate_raft(slicer, raft_offset=10, distance_between_paths=10, direction="xy_diagonal", raft_layers=1, raft_layer_height=None): """Creates a raft. Parameters ---------- slicer: :class:`compas_slicer.slicers.BaseSlicer` An instance of one of the compas_slicer.slicers.BaseSlicer classes. raft_offset: float Distance (in mm) that the raft should be offsetted from the first layer. Defaults to 10mm distance_between_paths: float Distance (in mm) between the printed lines of the raft. Defaults to 10mm direction: str x_axis: Create a raft aligned with the x_axis y_axis: Create a raft aligned with the y_axis xy_diagonal: Create a raft int the diagonal direction in the xy_plane raft_layers: int Number of raft layers to add. Defaults to 1 raft_layer_height: float Layer height of the raft layers. Defaults to same value as used in the slicer. """ # check if a raft_layer_height is specified, if not, use the slicer.layer_height value if not raft_layer_height: raft_layer_height = slicer.layer_height logger.info("Generating raft") # find if slicer has vertical or horizontal layers, and select which paths are to be offset. if isinstance(slicer.layers[0], compas_slicer.geometry.VerticalLayer): # Vertical layers # then find all paths that lie on the print platform and make them brim. paths_to_offset, _ = slicer.find_vertical_layers_with_first_path_on_base() else: # Horizontal layers # then replace the first layer with a raft layer. paths_to_offset = slicer.layers[0].paths # get flat lists of points in bottom layer all_pts = [] for path in paths_to_offset: for pt in path.points: all_pts.append(pt) # get xy bounding box of bottom layer and create offset bb_xy = bounding_box_xy(all_pts) bb_xy_offset = offset_polygon(bb_xy, -raft_offset) # bring points in the xy_offset to the correct height for pt in bb_xy_offset: pt[2] = slicer.layers[0].paths[0].points[0][2] # calculate x range, y range, and number of steps x_range = abs(bb_xy_offset[0][0] - bb_xy_offset[1][0]) y_range = abs(bb_xy_offset[0][1] - bb_xy_offset[3][1]) # get maximum values of the bounding box bb_max_x_right = bb_xy_offset[1][0] bb_max_y_top = bb_xy_offset[3][1] # get point in bottom left corner as raft start point raft_start_pt = Point(bb_xy_offset[0][0], bb_xy_offset[0][1], bb_xy_offset[0][2]) # create starting line for diagonal direction if direction == "xy_diagonal": c = math.sqrt(2*(distance_between_paths**2)) pt1 = Point(raft_start_pt[0] + c, raft_start_pt[1], raft_start_pt[2]) pt2 = Point(pt1[0] - y_range, pt1[1] + y_range, pt1[2]) line = Line(pt1, pt2) # move all points in the slicer up so that raft layers can be inserted for i, layer in enumerate(slicer.layers): for j, path in enumerate(layer.paths): for k, pt in enumerate(path.points): slicer.layers[i].paths[j].points[k] = Point(pt[0], pt[1], pt[2] + (raft_layers)*raft_layer_height) for i in range(raft_layers): iter = 0 raft_points = [] # create raft points depending on the chosen direction while iter < 9999: # to avoid infinite while loop in case something is not correct # =============== # VERTICAL RAFT # =============== if direction == "y_axis": raft_pt1 = Point(raft_start_pt[0] + iter*distance_between_paths, raft_start_pt[1], raft_start_pt[2] + i*raft_layer_height) raft_pt2 = Point(raft_start_pt[0] + iter*distance_between_paths, raft_start_pt[1] + y_range, raft_start_pt[2] + i*raft_layer_height) if raft_pt2[0] > bb_max_x_right or raft_pt1[0] > bb_max_x_right: break # =============== # HORIZONTAL RAFT # =============== elif direction == "x_axis": raft_pt1 = Point(raft_start_pt[0], raft_start_pt[1] + iter*distance_between_paths, raft_start_pt[2] + i*raft_layer_height) raft_pt2 = Point(raft_start_pt[0] + x_range, raft_start_pt[1] + iter*distance_between_paths, raft_start_pt[2] + i*raft_layer_height) if raft_pt2[1] > bb_max_y_top or raft_pt1[1] > bb_max_y_top: break # =============== # DIAGONAL RAFT # =============== elif direction == "xy_diagonal": # create offset of the initial diagonal line offset_l = offset_line(line, iter*distance_between_paths, Vector(0, 0, -1)) # get intersections for the initial diagonal line with the left and bottom of the bb int_left = intersection_line_line(offset_l, [bb_xy_offset[0], bb_xy_offset[3]]) int_bottom = intersection_line_line(offset_l, [bb_xy_offset[0], bb_xy_offset[1]]) # get the points at the intersections raft_pt1 = Point(int_left[0][0], int_left[0][1], int_left[0][2] + i*raft_layer_height) raft_pt2 = Point(int_bottom[0][0], int_bottom[0][1], int_bottom[0][2] + i*raft_layer_height) # if the intersection goes beyond the height of the left side of the bounding box: if int_left[0][1] > bb_max_y_top: # create intersection with the top side int_top = intersection_line_line(offset_l, [bb_xy_offset[3], bb_xy_offset[2]]) raft_pt1 = Point(int_top[0][0], int_top[0][1], int_top[0][2] + i*raft_layer_height) # if intersection goes beyond the length of the top side, break if raft_pt1[0] > bb_max_x_right: break # if the intersection goes beyond the length of the bottom side of the bounding box: if int_bottom[0][0] > bb_max_x_right: # create intersection with the right side int_right = intersection_line_line(offset_l, [bb_xy_offset[1], bb_xy_offset[2]]) raft_pt2 = Point(int_right[0][0], int_right[0][1], int_right[0][2] + i*raft_layer_height) # if intersection goes beyond the height of the right side, break if raft_pt2[1] > bb_xy_offset[2][1]: break # append to list alternating if iter % 2 == 0: raft_points.extend((raft_pt1, raft_pt2)) else: raft_points.extend((raft_pt2, raft_pt1)) iter += 1 # create raft layer raft_layer = Layer([Path(raft_points, is_closed=False)]) raft_layer.is_raft = True # insert raft layer in the correct position into the slicer slicer.layers.insert(i, raft_layer)