def offset_polyline(polyline, distance, normal=[0.0, 0.0, 1.0]): """Offset a polyline by a distance. Parameters ---------- polyline : list of point The XYZ coordinates of the vertices of a polyline. distance : float or list of tuples of floats The offset distance as float. A single value determines a constant offset globally. Alternatively, pairs of local offset values per line segment can be used to create variable offsets. Distance > 0: offset to the "left", distance < 0: offset to the "right". normal : vector The normal of the offset plane. Returns ------- offset polyline : list of point The XYZ coordinates of the resulting polyline. """ p = len(polyline) if isinstance(distance, (list, tuple)): distances = distance else: distances = [distance] * p d = len(distances) if d < p: distances.extend(distances[-1:] * (p - d)) offset = [] for line, distance in zip(pairwise(polyline), distances): offset.append(offset_line(line, distance, normal)) points = [offset[0][0]] for l1, l2 in pairwise(offset): x1, x2 = intersection_line_line(l1, l2) if x1 and x2: points.append(centroid_points([x1, x2])) else: points.append(x1) points.append(offset[-1][1]) return points
def offset_polyline(polyline, distance, normal=[0., 0., 1.]): """Offset a polyline by a distance. Parameters ---------- polyline : list of point The XYZ coordinates of the vertices of a polyline. distance : float or list of tuples of floats The offset distance as float. A single value determines a constant offset globally. Alternatively, pairs of local offset values per line segment can be used to create variable offsets. Distance > 0: offset to the "left", distance < 0: offset to the "right". normal : tuple The normal of the offset plane. Returns ------- offset polyline : list of point The XYZ coordinates of the resulting polyline. """ if isinstance(distance, list) or isinstance(distance, tuple): distances = distance if len(distances) < len(polyline): distances = distances + [distances[-1] ] * (len(polyline) - len(distances) - 1) else: distances = [[distance, distance]] * len(polyline) lines = [polyline[i:i + 2] for i in range(len(polyline[:-1]))] lines_offset = [] for i, line in enumerate(lines): lines_offset.append(offset_line(line, distances[i], normal)) polyline_offset = [] polyline_offset.append(lines_offset[0][0]) for i in range(len(lines_offset[:-1])): intx_pt1, intx_pt2 = intersection_line_line(lines_offset[i], lines_offset[i + 1]) if intx_pt1 and intx_pt2: polyline_offset.append(centroid_points([intx_pt1, intx_pt2])) else: polyline_offset.append(lines_offset[i][0]) polyline_offset.append(lines_offset[-1][1]) return polyline_offset
def get_intersection_pts_two_polyline(poly_0, poly_1, touch=True): """ search intersection points of two polylines with using compas Poly This intersection point is the position where these two polylines are touching each other. If "touch = False", can get intersection points that is not where they are touching. """ pts = [] for i in range(len(poly_0) - 1): line1 = poly_0[i:i + 2] for j in range(len(poly_1) - 1): line2 = poly_1[j:j + 2] interPts = intersection_line_line(line1, line2) ## coordinate should be rounded to check if these two points are the same point or not ## interPts_coord = [] for interPt in interPts: interPt_coord = [ round(interPt[0], 2), round(interPt[1], 2), round(interPt[2], 2) ] interPts_coord.append(interPt_coord) # print(interPts_coord) if touch: if interPts_coord[0] == interPts_coord[ 1] and interPts_coord[0] != None: coord = interPts[0] interPt = Point(coord[0], coord[1], coord[2]) # print("interPt :", interPt) pts.append(interPt) else: count = 0 for interPt in interPts: if interPt.on_polyline(poly_0) or interPt.on_polyline( poly_1): count += 1 if count == len(interPts): pts.extend(interPts) # print(len(pts)) if len(pts) == 1: return pts[0] elif len(pts) == 0: return None else: return pts
def is_polygon_self_intersecting(polygon): """Computes if as polygon is self intersecting in plane, or self overlapping in space. Parameters ---------- polygon : list of lists A list of polygon point coordinates. Returns ------- bool_1 ``True`` if self overlapping. ``False`` otherwise. bool_2 ``True`` if self intersecting. ``False`` otherwise. """ edges = [] for i in range(-1, len(polygon) - 1): edges.append((i, i + 1)) for u1, v1 in edges: for u2, v2 in edges: if u1 == u2 or v1 == v2 or u1 == v2 or u2 == v1: continue else: a = polygon[u1] b = polygon[v1] c = polygon[u2] d = polygon[v2] int_1, int_2 = intersection_line_line((a, b), (c, d)) if int_1 or int_2: if distance_point_point(int_1, int_2) > 0: overlapping = True if is_point_on_segment(int_1, (a, b)) or is_point_on_segment( int_2, (c, d)): intersecting = True return overlapping, intersecting
def offset_polygon(polygon, distance): """Offset a polygon (closed) by a distance. Parameters ---------- polygon : list of point The XYZ coordinates of the corners of the polygon. The first and last coordinates must be identical. distance : float or list of tuples of floats The offset distance as float. A single value determines a constant offset globally. Alternatively, pairs of local offset values per line segment can be used to create variable offsets. Distance > 0: offset to the outside, distance < 0: offset to the inside. Returns ------- offset polygon : list of point The XYZ coordinates of the corners of the offset polygon. The first and last coordinates are identical. Notes ----- The offset direction is determined by the normal of the polygon. The algorithm works also for spatial polygons that do not perfectly fit a plane. Examples -------- .. code-block:: python polygon = [ (0.0, 0.0, 0.0), (3.0, 0.0, 1.0), (3.0, 3.0, 2.0), (1.5, 1.5, 2.0), (0.0, 3.0, 1.0), (0.0, 0.0, 0.0) ] distance = 0.5 # constant offset polygon_offset = offset_polygon(polygon, distance) print(polygon_offset) distance = [ (0.1, 0.2), (0.2, 0.3), (0.3, 0.4), (0.4, 0.3), (0.3, 0.1) ] # variable offset polygon_offset = offset_polygon(polygon, distance) print(polygon_offset) """ normal = normal_polygon(polygon) if isinstance(distance, list) or isinstance(distance, tuple): distances = distance if len(distances) < len(polygon): distances = distances + [distances[-1] ] * (len(polygon) - len(distances) - 1) else: distances = [[distance, distance]] * len(polygon) lines = [polygon[i:i + 2] for i in range(len(polygon[:-1]))] lines_offset = [] for i, line in enumerate(lines): lines_offset.append(offset_line(line, distances[i], normal)) polygon_offset = [] for i in range(len(lines_offset)): intx_pt1, intx_pt2 = intersection_line_line(lines_offset[i - 1], lines_offset[i]) if intx_pt1 and intx_pt2: polygon_offset.append(centroid_points([intx_pt1, intx_pt2])) else: polygon_offset.append(lines_offset[i][0]) polygon_offset.append(polygon_offset[0]) return polygon_offset
def intersect_lines(l1, l2, tol): """ """ x1, x2 = intersection_line_line(l1, l2, tol) if x1 and x2: return centroid_points([x1, x2])
def offset_polygon(polygon, distance): """Offset a polygon (closed) by a distance. Parameters ---------- polygon : list of point The XYZ coordinates of the corners of the polygon. The first and last coordinates must not be identical. distance : float or list of float The offset distance as float. A single value determines a constant offset globally. Alternatively, pairs of local offset values per line segment can be used to create variable offsets. Distance > 0: offset to the outside, distance < 0: offset to the inside. Returns ------- offset polygon : list of point The XYZ coordinates of the corners of the offset polygon. The first and last coordinates are identical. Notes ----- The offset direction is determined by the normal of the polygon. If the polygon is in the XY plane and the normal is along the positive Z axis, positive offset distances will result in an offset towards the inside of the polygon. The algorithm works also for spatial polygons that do not perfectly fit a plane. Examples -------- .. code-block:: python polygon = [ (0.0, 0.0, 0.0), (3.0, 0.0, 1.0), (3.0, 3.0, 2.0), (1.5, 1.5, 2.0), (0.0, 3.0, 1.0), (0.0, 0.0, 0.0) ] distance = 0.5 # constant offset polygon_offset = offset_polygon(polygon, distance) print(polygon_offset) distance = [ (0.1, 0.2), (0.2, 0.3), (0.3, 0.4), (0.4, 0.3), (0.3, 0.1) ] # variable offset polygon_offset = offset_polygon(polygon, distance) print(polygon_offset) """ p = len(polygon) if isinstance(distance, (list, tuple)): distances = distance else: distances = [distance] * p d = len(distances) if d < p: distances.extend(distances[-1:] * (p - d)) normal = normal_polygon(polygon) offset = [] for line, distance in zip(pairwise(polygon + polygon[:1]), distances): offset.append(offset_line(line, distance, normal)) points = [] for l1, l2 in pairwise(offset[-1:] + offset): x1, x2 = intersection_line_line(l1, l2) if x1 and x2: points.append(centroid_points([x1, x2])) else: points.append(x1) return points
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)