def test_colour(self, make_args, blue): # Check that colour filtering works args = make_args("--all --color #0000FF") outlines = MultiLineString(args_to_outlines(args)) expected = self.dereg(blue) assert not outlines.is_empty assert outlines.difference(expected).is_empty
def test_exclude_regmarks(self, make_args, regmarks, red, green, blue): args = make_args("--all") outlines = MultiLineString(args_to_outlines(args)) expected = cascaded_union([ self.dereg(red), self.dereg(green), self.dereg(blue), ]) assert not outlines.is_empty assert outlines.difference(expected).is_empty
def generate_pcb_bridges(dxf_modelspace, area, cutout_width, count_x, count_y): __generate_bridges(dxf_modelspace, area, count_x, count_y) frame_lines = MultiLineString(cutout_lines) for splitter in splitter_rectangles: frame_lines = frame_lines.difference(splitter) dilated = frame_lines.buffer(cutout_width / 2) for element in dilated: dxf_modelspace.add_lwpolyline(element.exterior.coords)
def test_regmarks_included_when_not_used(self, make_args, regmarks, red, green, blue): args = make_args("--all --no-regmarks") outlines = MultiLineString(args_to_outlines(args)) expected = cascaded_union([ regmarks, red, green, blue, ]) assert not outlines.is_empty assert outlines.difference(expected).is_empty
def hatchbox(rect, angle, spacing, hole=None): """ returns a Shapely geometry (MULTILINESTRING, or more rarely, GEOMETRYCOLLECTION) for a simple hatched rectangle. args: rect - a Shapely geometry for the outer boundary of the hatch Likely most useful if it really is a rectangle angle - angle of hatch lines, conventional anticlockwise -ve spacing - spacing between hatch lines GEOMETRYCOLLECTION case occurs when a hatch line intersects with the corner of the clipping rectangle, which produces a point along with the usual lines. """ (llx, lly, urx, ury) = rect.bounds centre_x = (urx + llx) / 2 centre_y = (ury + lly) / 2 diagonal_length = sqrt((urx - llx)**2 + (ury - lly)**2) number_of_lines = 2 + int(diagonal_length / spacing) hatch_length = spacing * (number_of_lines - 1) # build a square (of side hatch_length) horizontal lines # centred on centroid of the bounding box, 'spacing' units apart coords = [] for i in range(number_of_lines): # alternate lines l2r and r2l to keep HP-7470A plotter happy ☺ if i % 2: coords.extend([((centre_x - hatch_length / 2, centre_y - hatch_length / 2 + i * spacing), (centre_x + hatch_length / 2, centre_y - hatch_length / 2 + i * spacing))]) else: coords.extend([((centre_x + hatch_length / 2, centre_y - hatch_length / 2 + i * spacing), (centre_x - hatch_length / 2, centre_y - hatch_length / 2 + i * spacing))]) # turn array into Shapely object lines = MultiLineString(coords) # Rotate by angle around box centre lines = rotate(lines, angle, origin='centroid', use_radians=False) # return clipped array if holes is not None: x0, y0, dx, dy = windef.minmax_coords() rect = box(x0, y0, x0 + dx, y0 + dy) lines = lines.difference(rect) return rect.intersection(lines)
def generate_subpanel_bridges(dxf_outline_space, dxf_drill_space, area, cutout_width, count_x, count_y): __generate_bridges(dxf_outline_space, area, count_x, count_y) frame_lines = MultiLineString(cutout_lines) generate_mouse_bites(area, dxf_drill_space, frame_lines) for splitter in splitter_rectangles: frame_lines = frame_lines.difference(splitter) # Merge all lines, so the endpoints are not where lines cross frame_lines = ops.linemerge(frame_lines) inset_lines = [] for frame_line in frame_lines: line = frame_line.parallel_offset(2, 'left') # line2 = frame_line.parallel_offset(2, 'right') if frame_line.boundary and line.boundary: inset_line = LineString([frame_line.boundary[0], line.boundary[0]]) inset_lines.append(inset_line) inset_line = LineString([frame_line.boundary[1], line.boundary[1]]) inset_lines.append(inset_line) line = frame_line.parallel_offset(2, 'right') if frame_line.boundary and line.boundary: inset_line = LineString([frame_line.boundary[0], line.boundary[1]]) inset_lines.append(inset_line) inset_line = LineString([frame_line.boundary[1], line.boundary[0]]) inset_lines.append(inset_line) # frame_lines = frame_lines.union(inset_line) inset_lines = MultiLineString(inset_lines) dilated_insets = inset_lines.buffer(cutout_width / 3, join_style=1) # Remove outward bridges TODO: Needs better solution, this one is a quick hack TODO: Check if interior exterior # of polygon would work: https://gis.stackexchange.com/questions/341604/creating-shapely-polygons-with-holes # dilated_insets = clean_outer_perimeter(area, dilated_insets) # Merge cutouts and insets dilated = frame_lines.buffer(cutout_width / 2, cap_style=2, join_style=2) dilated = dilated.union(dilated_insets) # Round the corners of insets dilated = dilated.buffer(0.8, join_style=1).buffer(-0.8, join_style=1) for element in dilated: dxf_outline_space.add_lwpolyline(element.exterior.coords)
def test_no_over_cut(self, make_args, red, green, blue, extra_args): args = make_args("--all --fast-order --inside-first " + extra_args) green_shifted = MultiLineString([[ (70, 85), (70, 15), (30, 15), (30, 85), (70, 85), ]]) # Sanity check assert green_shifted.difference(green).is_empty outlines = MultiLineString(args_to_outlines(args)) expected = MultiLineString( list(self.dereg(blue).geoms) + list(self.dereg(red).geoms) + list(self.dereg(green_shifted).geoms)) assert outlines == expected
def test_inside_first_and_optimised(self, make_args, red, green, blue): args = make_args("--all --no-over-cut") # Re-order points so definition starts with bottom-right corner (which # is nearest point to end of red line) green_shifted = MultiLineString([[ (70, 85), (70, 15), (30, 15), (30, 85), (70, 85), ]]) # Sanity check assert green_shifted.difference(green).is_empty outlines = MultiLineString(args_to_outlines(args)) expected = MultiLineString( list(self.dereg(blue).geoms) + list(self.dereg(red).geoms) + list(self.dereg(green_shifted).geoms)) assert outlines == expected
def compute_partition(self, num_iters: int, delta: float, make_snapshots: bool = False ) -> Tuple[Optional[Dict], Optional[List]]: """ Computes a Markov partition for the given dynamic system by tracing stable and unstable manifolds in the hyperbolic fixed point in parallel and checking for their points of intersection. This procedure is repeated num_iters times until the partition is sufficiently fine enough to be Markov. All in all, this function implements the described algorithm in chapter 4.2 of the master's thesis. Args: num_iters (int): number of repetitions for tracing branches until points of intersections delta (float): sufficiently small integration constant for approximating branches Returns: (dict): keys are branch identifiers with lists of points as values (list): list of points of intersections between branches """ if self.dynamic_system.fixed_point is None: if self.dynamic_system.compute_fixed_point() is None: print( f"Failed calculating a partition, since no fixed point found." ) return None, None if not self.dynamic_system.is_fixed_point( self.dynamic_system.fixed_point): print( f"Failed calculating a partition, since calculated fixed point is not a real fixed point." ) return None, None if not self.dynamic_system.fixed_point_is_hyperbolic(): print( f"Failed calculating a partition, since fixed point is not a hyperbolic one." ) return None, None branches = { "W_u1": [[self.dynamic_system.fixed_point]], "W_u2": [[self.dynamic_system.fixed_point]], "W_s1": [[self.dynamic_system.fixed_point]], "W_s2": [[self.dynamic_system.fixed_point]], } unstable_branches = ["W_u1", "W_u2"] stable_branches = ["W_s1", "W_s2"] approx_funcs = { "W_u1": (self.wt_u, True), "W_u2": (self.wt_u, False), "W_s1": (self.wt_s, True), "W_s2": (self.wt_s, False), } overall_intersection_points = [] snapshot_path = "/ma_project/experimental/notebooks/results/partitions" for i in range(num_iters): print(f"ITERATION: {i+1}") stopped_branches = [] trace_branches = list(branches.keys()) while len(stopped_branches) < len(branches.keys()): trace_branches = [ branch for branch in trace_branches if branch not in stopped_branches ] for branch_key in trace_branches: last_wt = branches[branch_key][-1][-1] approx_func, rev_dir = approx_funcs[branch_key] new_wt = approx_func(last_wt, delta=delta, reverse_direction=rev_dir) if not self.dynamic_system.identification_occurs(new_wt): branches[branch_key][-1].append(new_wt) else: if len(branches[branch_key][-1]) == 1: branches[branch_key][-1].append( branches[branch_key][-1][0]) new_wt = new_wt % self.dynamic_system.m_id branches[branch_key].append([new_wt, new_wt]) for unstable_branch in unstable_branches: for stable_branch in stable_branches: if unstable_branch in stopped_branches and stable_branch in stopped_branches: continue intersection_points = MultiLineString( branches[unstable_branch]).intersection( MultiLineString(branches[stable_branch])) intersection_points = intersection_points.difference( Point(self.dynamic_system.fixed_point)) num_intersections = self.get_num_of_intersections( intersection_points) if num_intersections > i: intersection_point = self.get_new_intersection_point( np.array(intersection_points), num_intersections, np.array(overall_intersection_points)) if intersection_point is None: continue dist_unstable = self.euclidean_dist_over_torus( np.array(intersection_point), branches[unstable_branch][-1][-1]) dist_stable = self.euclidean_dist_over_torus( np.array(intersection_point), branches[stable_branch][-1][-1]) latter_branch = unstable_branch if dist_unstable < dist_stable else stable_branch if latter_branch not in stopped_branches: stopped_branches.append(latter_branch) print( f"Stop {latter_branch} at {intersection_point}." ) overall_intersection_points.append( intersection_point) self.intersection_points = overall_intersection_points if make_snapshots: self.plot_partition( branches=branches, file_path= f"{snapshot_path}/snapshot-{i}-{len(stopped_branches)}.png", ) if make_snapshots: self.plot_partition( branches=branches, file_path=f"{snapshot_path}/snapshot-{i}-total.png") self.branches = branches self.intersection_points = overall_intersection_points return self.branches, self.intersection_points
def draw(self, vsk: vsketch.Vsketch) -> None: print(os.getcwd()) vsk.size("a6", landscape=False, center=False) vsk.scale(1) vsk.penWidth(self.pen_width) glyph_poly = load_glyph(self.font, self.glyph, self.face_index) # normalize glyph size bounds = glyph_poly.bounds scale_factor = min( (vsk.width - 2 * self.glyph_margin) / (bounds[2] - bounds[0]), (vsk.height - 2 * self.glyph_margin) / (bounds[3] - bounds[1]), ) glyph_poly = scale(glyph_poly, scale_factor, scale_factor) bounds = glyph_poly.bounds glyph_poly = translate( glyph_poly, vsk.width / 2 - bounds[0] - (bounds[2] - bounds[0]) / 2, vsk.height / 2 - bounds[1] - (bounds[3] - bounds[1]) / 2 + self.glyph_voffset, ) if self.draw_glyph: vsk.strokeWeight(self.glyph_weight) if self.fill_glyph: vsk.fill(1) vsk.geometry(glyph_poly) if self.fill_glyph and self.glyph_chroma: angle = self.glyph_chroma_angle / 180.0 * math.pi glyph_poly_chroma1 = translate( glyph_poly, -self.glyph_chroma_offset * math.cos(angle), -self.glyph_chroma_offset * math.sin(angle), ).difference(glyph_poly) glyph_poly_chroma2 = translate( glyph_poly, self.glyph_chroma_offset * math.cos(angle), self.glyph_chroma_offset * math.sin(angle), ).difference(glyph_poly) vsk.strokeWeight(1) vsk.stroke(2) vsk.fill(2) vsk.geometry(glyph_poly_chroma1) vsk.stroke(3) vsk.fill(3) vsk.geometry(glyph_poly_chroma2) glyph_poly = unary_union( [glyph_poly, glyph_poly_chroma1, glyph_poly_chroma2]) vsk.strokeWeight(1) vsk.stroke(1) vsk.noFill() glyph_shadow = None if self.glyph_shadow: angle = self.glyph_chroma_angle / 180.0 * math.pi glyph_shadow = translate( glyph_poly, self.glyph_chroma_offset * math.cos(angle), self.glyph_chroma_offset * math.sin(angle), ).difference(glyph_poly) vsk.fill(3) vsk.stroke(3) vsk.geometry(glyph_shadow) vsk.noFill() vsk.stroke(1) glyph_poly = glyph_poly.union(glyph_shadow) if self.glyph_weight == 1: glyph_poly_ext = glyph_poly.buffer( self.glyph_space, join_style=JOIN_STYLE.mitre, ) glyph_poly_int = glyph_poly.buffer( -self.glyph_space_inside, join_style=JOIN_STYLE.mitre, ) else: buf_len = (self.glyph_weight - 1) / 2 * self.pen_width glyph_poly_ext = glyph_poly.buffer( buf_len * 2 + self.glyph_space, join_style=JOIN_STYLE.mitre, ) glyph_poly_int = glyph_poly.buffer( -buf_len - self.glyph_space_inside, join_style=JOIN_STYLE.mitre, ) if glyph_shadow is not None: glyph_poly_int = glyph_poly_int.difference(glyph_shadow) # horizontal stripes if self.draw_h_stripes: count = round( (vsk.height - 2 * self.margin) / self.h_stripes_pitch) corrected_pitch = (vsk.height - 2 * self.margin) / count hstripes = MultiLineString([[ (self.margin, self.margin + i * corrected_pitch), (vsk.width - self.margin, self.margin + i * corrected_pitch), ] for i in range(count + 1)]) vsk.geometry(hstripes.difference(glyph_poly_ext)) if self.h_stripes_inside: inside_stripes = translate(hstripes, 0, corrected_pitch / 2).intersection(glyph_poly_int) vsk.geometry(inside_stripes) if self.h_stripes_inside_chroma: chroma_offset = math.sqrt(2) * self.pen_width vsk.stroke(2) vsk.geometry( translate(inside_stripes, -chroma_offset, -chroma_offset)) vsk.stroke(3) vsk.geometry( translate(inside_stripes, chroma_offset, chroma_offset)) vsk.stroke(1) # concentric if self.draw_concentric: circle_count = int( math.ceil( math.hypot(vsk.width, vsk.height) / 2 / self.concentric_pitch)) circles = unary_union([ Point(vsk.width / 2, vsk.height / 2).buffer( (i + 1) * self.concentric_pitch, resolution=int(1 * (i + 1) * self.concentric_pitch), ).exterior for i in range(circle_count) ]) vsk.geometry( circles.difference(glyph_poly_ext).intersection( box( self.margin, self.margin, vsk.width - self.margin, vsk.height - self.margin, ))) # dots vsk.fill(1) if self.draw_dots or self.draw_cut_circles: v_pitch = self.pitch * math.tan(math.pi / 3) / 2 h_count = int((vsk.width - 2 * self.margin) // self.pitch) v_count = int((vsk.height - 2 * self.margin) // v_pitch) h_offset = (vsk.width - h_count * self.pitch) / 2 v_offset = (vsk.height - v_count * v_pitch) / 2 dot_array = [] for j in range(v_count + 1): odd_line = j % 2 == 1 for i in range(h_count + (0 if odd_line else 1)): dot = Point( h_offset + i * self.pitch + (self.pitch / 2 if odd_line else 0), v_offset + j * v_pitch, ).buffer(self.thickness / 2) if self.draw_dots: if not dot.buffer( self.thickness / 2).intersects(glyph_poly_ext): dot_array.append(dot) else: dot_array.append(dot) dots = unary_union(dot_array) if self.draw_dots: vsk.geometry(dots) if self.draw_cut_circles: if self.cut_circles_inside: op_func = lambda geom: geom.intersection(glyph_poly_int) else: op_func = lambda geom: geom.difference(glyph_poly_ext) vsk.geometry(op_func(dots)) if self.cut_circle_chroma: angle = math.pi / 6 dist = self.pitch * 0.1 vsk.fill(2) vsk.stroke(2) vsk.geometry( op_func( translate(dots, -dist * math.cos(angle), -dist * math.sin(angle)).difference(dots))) vsk.fill(3) vsk.stroke(3) vsk.geometry( op_func( translate(dots, dist * math.cos(angle), dist * math.sin(angle)).difference(dots))) vsk.fill(1) vsk.stroke(1) vsk.stroke(4) # apply line sort, see finalize() if self.draw_dot_matrix: h_count = int( (vsk.width - 2 * self.margin) // self.dot_matrix_pitch) + 1 v_count = int( (vsk.height - 2 * self.margin) // self.dot_matrix_pitch) + 1 h_pitch = (vsk.width - 2 * self.margin) / (h_count - 1) v_pitch = (vsk.height - 2 * self.margin) / (v_count - 1) mp = MultiPoint([ (self.margin + i * h_pitch, self.margin + j * v_pitch) for i, j in itertools.product(range(h_count), range(v_count)) if vsk.random(1) < self.dot_matrix_density ]) if self.draw_dot_matrix_inside: mp = mp.intersection(glyph_poly_int) else: mp = mp.difference(glyph_poly_ext) vsk.geometry(mp) vsk.vpype("color -l4 black") vsk.vpype("color -l1 black color -l2 cyan color -l3 magenta")
def polygon_with_holes(coordinates_dictionary): def polygon_with_holes_coordinates(coordinates, outer_ring_linestring, interceptors_op_dict): first_op = outer_ring_linestring.coords[0] if first_op in interceptors_op_dict: coordinates.append(first_op) holes = interceptors_op_dict[first_op] for hole in holes: if hole.geom_type == 'LineString': for coord in hole.coords: coordinates.append(coord) if hole.geom_type == 'MultiLineString': for coord in hole[1].coords: coordinates.append(coord) for coord in hole[0].coords[1:]: coordinates.append(coord) coordinates.append(first_op) for coord in outer_ring_linestring.coords[1:-1]: coordinates.append(coord) else: for coord in outer_ring_linestring.coords[:-1]: coordinates.append(coord) return coordinates def dist_two_points(p1, p2): distance = math.sqrt( math.pow((p1[0] - p2[0]), 2) + math.pow((p1[1] - p2[1]), 2)) return distance def inner_string(inner_ring_coordinates, irop): inner_coordinates = list(inner_ring_coordinates) for i, coord in enumerate(inner_coordinates): if coord == irop: split_position = i break if split_position == 0: inner_linestring = LineString(inner_coordinates) else: first = inner_coordinates[:(split_position + 1)] last = inner_coordinates[split_position:] inner_linestring = MultiLineString([first, last]) return inner_linestring outer_ring_coordinates = coordinates_dictionary['outer_ring'] interceptors_op_dict = dict() oi_min_linestring_list = list() for inner_ring_coordinates in coordinates_dictionary['inner_rings']: for i, orop in enumerate(outer_ring_coordinates[0][:-1]): for j, irop in enumerate(inner_ring_coordinates[:-1]): if i == 0 and j == 0: oi_min_distance = dist_two_points(orop, irop) if oi_min_distance > 0.015: oi_min_linestring = LineString([orop, irop]) inner_linestring = inner_string( inner_ring_coordinates, irop) else: oi_min_distance = 1e9 else: distance = dist_two_points(orop, irop) if (distance < oi_min_distance) and (distance > 0.015): oi_min_distance = distance oi_min_linestring = LineString([orop, irop]) inner_linestring = inner_string( inner_ring_coordinates, irop) if oi_min_linestring.coords[0] in interceptors_op_dict: interceptors_op_dict[oi_min_linestring.coords[0]].append( inner_linestring) else: interceptors_op_dict[oi_min_linestring.coords[0]] = [ inner_linestring ] oi_min_linestring_list.append(oi_min_linestring) outer_ring = MultiLineString(coordinates_dictionary['outer_ring']) for oi_min_linestring in oi_min_linestring_list: outer_ring = outer_ring.difference(oi_min_linestring) coordinates = list() if outer_ring.geom_type == 'LineString': start_end_op = outer_ring.coords[0] coordinates = polygon_with_holes_coordinates(coordinates, outer_ring, interceptors_op_dict) elif outer_ring.geom_type == 'MultiLineString': for i, linestring in enumerate(outer_ring): if i == 0: start_end_op = linestring.coords[0] coordinates = polygon_with_holes_coordinates( coordinates, linestring, interceptors_op_dict) coordinates.append(start_end_op) return [coordinates]