def _compute_toplevel_tour(self): """ find cycle starting at origin and passing through one point of each toplevel pocket. return list of 2d points. this will also sort all children by order of visit of the tour and change each cycle starting point as the visited point. """ origin = Point([0, 0]) graph = Graph() children = {} # record to which child each point belongs for child in self.children: end = nearest_point(child.old_pocket, origin) children[end] = child graph.add_edge_between(origin, child.old_pocket, Segment([origin, end])) for child1, child2 in combinations(self.children, 2): start, end = nearest_points(child1.old_pocket, child2.old_pocket) children[start] = child1 children[end] = child2 graph.add_edge_between(child1.old_pocket, child2.old_pocket, Segment([start, end])) cycle = tsp(graph) tour = self._convert_cycle_to_tour(cycle, children, origin) return tour
def remove_loops(self): """ iterate on edge, remove small loops (un-millable zones). """ left = [] for segment1, arc, segment2 in self.arcs(): if arc.reversed_direction: i = segment1.intersection_with_segment(segment2) if i is None: left.append(arc.endpoints[0]) left.append(arc) left.append(arc.endpoints[1]) else: left.append(i) else: left.append(arc.endpoints[0]) left.append(arc) left.append(arc.endpoints[1]) simpler_edge = [] position = left[-1] for part in left: if isinstance(part, Arc): simpler_edge.append(part) position = part.endpoints[1] else: if position != part: simpler_edge.append(Segment([position, part])) position = part self.edge = simpler_edge
def _odd_segments_on_line(self, line_hash): """ sweeps through line of aligned segments. keeping the ones we want """ # associate #starting - #ending segments to each point self.counters = defaultdict(int) segments = self.lines[line_hash] self._compute_points_and_counters(segments) # now iterate through each point # we record on how many segments we currently are previously_on = 0 # we record where interesting segment started previous_point = None odd_segments = [] for point in self.sorted_points: now_on = previously_on now_on += self.counters[point] if previously_on % 2 == 1: if now_on % 2 == 0: odd_segments.append(Segment([previous_point, point])) else: previous_point = point previously_on = now_on if __debug__: if is_module_debugged(__name__): tycat(self.segments, odd_segments) return odd_segments
def join_raw_segments(self, raw_segments): """ reconnect all parallel segments. """ for neighbouring_tuples in all_two_elements(raw_segments): first_segment, second_segment = [p[0] for p in neighbouring_tuples] end = first_segment.endpoints[1] start = second_segment.endpoints[0] if end.is_almost(start): first_segment = Segment([first_segment.endpoints[0], start]) else: # original point connecting the original segments center_point = neighbouring_tuples[0][1].endpoints[1] # add arc try: binding = Arc(self.radius, [end, start], center_point) binding.adjust_center() binding.correct_endpoints_order() except: print("failed joining segments") tycat(self.polygon, center_point, first_segment, second_segment) raise self.edge.append(first_segment) self.edge.append(binding) if __debug__: if is_module_debugged(__name__): print("joined segments") tycat(self.polygon, self.edge)
def tycat(self): """ graphical display for debugging """ # compute intersections intersections = [] for small_intersections in self.intersections.values(): intersections.extend(small_intersections) intersections = list(set(intersections)) # compute current vertical line bbox = BoundingBox.empty_box(2) for path in self.paths: bbox.update(path.get_bounding_box()) ymin, ymax = bbox.limits(1) height = ymax - ymin line_y_min = ymin - height / 20 line_y_max = ymax + height / 20 current_x = self.current_point.coordinates[0] vertical_line = Segment( [Point([current_x, line_y_min]), Point([current_x, line_y_max])]) # display figure tycat(self.paths, intersections, [self.current_point, vertical_line], *self.crossed_paths) print(list(self.key(p) for p in self.crossed_paths))
def complete_graph(cls, points): """ builds complete graph from set of points. """ graph = cls() for points in combinations(points, 2): graph.add_edge(Segment(points)) return graph
def overlapping_run(size): """ one run, of given size. return time taken. """ paths = [ Segment([Point([0, i]), Point([1, i + 0.5])]) for i in range(size) ] return run(paths)
def _merge_toplevel(self, toplevel_tour): """ tour all points in tour. at each point (except first) we go down in subtree and back up to continue touring. """ final_paths = [] for i in range(len(toplevel_tour) - 1): start = toplevel_tour[i] end = toplevel_tour[i + 1] if not start.is_almost(end): final_paths.append(Segment([start, end])) final_paths.append(VerticalPath(-1)) final_paths.extend(self.children[i].content.elementary_paths) final_paths.append(VerticalPath(1)) # back to origin final_paths.append(Segment([toplevel_tour[-1], toplevel_tour[0]])) return Path(final_paths)
def non_overlapping_run(size): """ one run, of given size. return time taken. """ paths = [ Segment([Point([2 * i, 0]), Point([2 * i + 1, 0])]) for i in range(size) ] return run(paths)
def _horizontal_edges(aligned_vertices): """ iterate through all horizontal edges (containing segments) between given horizontally aligned vertices. """ for index in range(len(aligned_vertices) - 1): vertices = [ aligned_vertices[index], aligned_vertices[(index + 1) % len(aligned_vertices)] ] objects = [v.bound_object for v in vertices] yield Edge(*vertices, real_path=Segment(objects))
def horizontal_intersections_at(self, intersecting_y, xmin, xmax): """ intersections with horizontal line at given y. returns array of points. """ segment = Segment( [Point([xmin, intersecting_y]), Point([xmax, intersecting_y])]) intersections = self.intersections_with_segment(segment) intersections = [ Point([i.get_x(), intersecting_y]) for i in intersections ] return intersections
def quadratic(size): """ n^2 brute force algorithm """ paths = [ Segment([Point([2 * i, 0]), Point([2 * i + 1, 0])]) for i in range(size) ] start_time = clock() for p_1, p_2 in combinations(paths, r=2): p_1.intersections_with(p_2) end_time = clock() return end_time - start_time
def merge_path(outer_path, inner_path, position): """ merge inner path inside outer path at given position. Note that since positions contains array indices you need to merge starting from last path. """ paths = outer_path.elementary_paths outer_point = position.outer_position.point inner_point = position.inner_position.point if __debug__: if is_module_debugged(__name__): print("merging at", position.outer_position.index) tycat(outer_path, inner_path, position.outer_position.elementary_path) arrival_path = paths[position.outer_position.index] assert arrival_path.contains(outer_point), "no merging here" sub_path = [] before, after = arrival_path.split_around(outer_point) if before is not None: sub_path.append(before) if not outer_point.is_almost(inner_point): sub_path.append(Segment([outer_point, inner_point])) sub_path.append(VerticalPath(-1)) sub_path.extend(inner_path.elementary_paths) sub_path.append(VerticalPath(1)) if not outer_point.is_almost(inner_point): sub_path.append(Segment([inner_point, outer_point])) if after is not None: sub_path.append(after) paths[position.outer_position.index:position.outer_position.index] = \ sub_path outer_path.set_elementary_paths(paths)
def border_2d(stl_model, margin): """ return 2d enclosing (starting at origin) for given model and margin. """ # build four points xmin, ymin = 0, 0 xmax, ymax = stl_model.dimensions(margin) points = [] points.append(Point([xmin, ymin])) points.append(Point([xmin, ymax])) points.append(Point([xmax, ymax])) points.append(Point([xmax, ymin])) return [Segment([p, q]) for p, q in all_two_elements(points)]
def displace(cls, path, distance): """ displace given path by given distance on outer side. """ if isinstance(path, Segment): parallel_segment = path.parallel_segment(distance, -1) hashed_segment = Segment( [ROUNDER2D.hash_point(p) for p in parallel_segment.endpoints]) return cls(hashed_segment, path) else: if path.reversed_direction: new_radius = 2 * path.radius new_points = [p * 2 - path.center for p in path.endpoints] hashed_points = [ROUNDER2D.hash_point(p) for p in new_points] inflated_path = Arc(new_radius, hashed_points, path.center, True) else: inflated_path = ROUNDER2D.hash_point(path.center) return cls(inflated_path, path)
def project(self, point): """ find where point on stored path projects itself on origin. """ if isinstance(self.origin, Point): result = self.origin elif isinstance(self.origin, Segment): result = self.origin.point_projection(point) else: intersections = self.origin.intersections_with_segment( Segment([self.origin.center, point])) assert len(intersections) == 1 result = intersections[0] if __debug__: if is_module_debugged(__name__): print("project from envelope back to original path") tycat(self.path, self.origin, point, result) return result
def test(seconds=None): """ intersect a bunch of random segments and display result. """ display = True if seconds is None: display = False seconds = clock() seed(float(seconds)) paths = [ Segment([ ROUNDER2D.hash_point(Point([random(), random()])), ROUNDER2D.hash_point(Point([random(), random()])) ]) for _ in range(10) ] for _ in range(0): center = ROUNDER2D.hash_point(Point([random(), random()])) radius = 0 while radius < 0.02: radius = random() / 4 points = [ center + ROUNDER2D.hash_point(Point([cos(a), sin(a)]) * radius) for a in (random() * 10, random() * 10) ] paths.append(Arc(radius, points, center).correct_endpoints_order()) # print(",\n ".join([str(s) for s in paths])) if display: tycat(paths) try: intersections = compute_intersections(paths) except: print("seed", seconds) tycat(paths) raise intersections.append(paths) return intersections
def offset(self): """ extend polygon's edges by moving parallel to them and reconnect pieces. """ raw_segments = [] for segment in self.polygon.segments(): parallel_segment = segment.parallel_segment(self.radius) hashed_segment = Segment( [ROUNDER2D.hash_point(p) for p in parallel_segment.endpoints]) raw_segments.append((hashed_segment, segment)) if __debug__: if is_module_debugged(__name__): print("unjoined raw segments") segments = [t[0] for t in raw_segments] tycat(self.polygon, segments) try: self.join_raw_segments(raw_segments) except: print("failed joining edges in offsetter") segments = [t[0] for t in raw_segments] tycat(self.polygon, segments) raise try: # for performance reasons we can remove now the small loop pockets # created by sharp angles. # they would be removed anyway later on but it is much better # to remove them now so they do not appear in the O(n^2) algorithm # used later on self.remove_loops() except: print("failed removing loops") tycat(self.polygon, self.edge) raise return self.edge
def _skip_seen_vertices(cycle): """ change cycle so that we do not pass twice at same vertex (except for completing the cycle). achieves that by shortcutting to next point using a segment. CAREFUL: vertices are not updated in the process. """ seen_vertices = {} start = cycle[0].vertices[0] current_vertex = start seen_vertices[current_vertex] = True resulting_cycle = [] for edge in cycle: next_vertex = edge.vertices[1] if next_vertex not in seen_vertices: if current_vertex == edge.vertices[0]: resulting_cycle.append(edge) else: objects = [ v.bound_object for v in (current_vertex, next_vertex) ] if isinstance(objects[0], Point) \ and isinstance(objects[0], Point): point1, point2 = objects else: point1, point2 = nearest_points(objects[0], objects[1]) resulting_cycle.append( Edge(current_vertex, next_vertex, Segment([point1, point2])) ) current_vertex = next_vertex seen_vertices[current_vertex] = True # add last segment to go back at start resulting_cycle.append(cycle[-1]) return resulting_cycle
#!/usr/bin/env python3 from jimn.point import Point from jimn.segment import Segment from jimn.displayable import tycat print("**************************") print("*testing segments overlap*") print("**************************") print("non aligned") s1 = Segment([Point([0, 3]), Point([0, 6])]) s2 = Segment([Point([1, 3]), Point([0, 6])]) tycat(s1, s2) assert not s1.overlaps(s2) print("aligned, no overlap") s1 = Segment([Point([0, 0]), Point([3, 3])]) s2 = Segment([Point([4, 4]), Point([6, 6])]) tycat(s1, s2) assert not s1.overlaps(s2) print("overlap, no one disappears") s1 = Segment([Point([0, 0]), Point([3, 3])]) s2 = Segment([Point([2, 2]), Point([6, 6])]) tycat(s1, s2)
#!/usr/bin/env python3 from jimn.point import Point from jimn.segment import Segment from jimn.polygon import Polygon from jimn.pocket import Pocket from jimn.envelope import Envelope from jimn.arc import Arc from jimn.displayable import tycat from jimn.displayable import tycat_start, tycat_end print("testing intersection") s = Polygon.square(0, 0, 8) s = s.orient(False) path_in = Pocket(list(s.segments())) path_out = Segment([Point([-3, 3]), Point([12, 5])]) e1 = Envelope(path_in, 1) e2 = Envelope(path_out, 1) tycat(e1, e2) points = e2.junction_points(e1) tycat(e1, e2, points)
def segments(self): """ iterate through all segments. """ for points in all_two_elements(self.points): yield Segment(points)
#!/usr/bin/env python3 from jimn.point import Point from jimn.segment import Segment from jimn.displayable import tycat from math import cos, sin, pi points = [ Point([cos((i * 2 * pi) / 8), sin(i * (2 * pi) / 8)]) for i in range(8) ] tycat(*points) origin = Point([0, 0]) for p in points: print(origin.angle_with(p)) for p in points: s = Segment([origin, p]) tycat(points, s) print("key angle is:", s.key_angle()) print("********************")
arcs = (Arc(3, [Point([1, 2]), Point([4, -1])], Point([4, 2])), Arc(3, [Point([3, 3]), Point([6, 0])], Point([6, 3])), Arc(3, [Point([4, 3]), Point([7, 0])], Point([7, 3])), Arc(2, [Point([0, 1]), Point([2, 3])], Point([0, 3]))) print("*** trying intersection with arcs ***") for i, a in enumerate(arcs): print(labels[i]) intersection = a1.intersections_with_arc(a) tycat(a1, a, intersection) segments = ( Segment([Point([-1, 2]), Point([3, 1])]), Segment([Point([3.2, 3]), Point([0.2, 0])]), Segment([Point([5.2, 3]), Point([2.2, 0])]), Segment([Point([-1, 0]), Point([3, 0])]), ) s_labels = ( "single intersection", "double intersection", "no intersection", "tangent", ) print("*** trying intersection with segments ***") for i, s in enumerate(segments): print(s_labels[i]) intersections = a1.intersections_with_segment(s) tycat(a1, s, intersections)
def main(): s1 = Segment([Point([0, 0]), Point([1, 1])]) s2 = Segment([Point([0, 0]), Point([1, -1])]) s3 = Segment([Point([0.5, -2]), Point([0.5, 2])]) compute_intersections([s1, s2, s3])
including = Polygon.square(-1, 4, 3) including_pocket = Pocket(list(including.segments())) not_including = [ Point([-2.0, 3.0]), Point([-2.0, 2.0]), Point([9.0, 2.0]), Point([9.0, 10.0]), Point([-2.0, 10.0]), Point([-2.0, 9.0]), Point([8.0, 9.0]), Point([8.0, 3.0]), ] not_including_pocket = Pocket( [Segment([a, b]) for a, b in all_two_elements(not_including)]) vertically_aligned = [ Point([-1, 3.5]), Point([2, 3.5]), Point([4, 5.5]), Point([2, 7.5]), Point([-1, 7.5]), Point([-4, 5.5]), ] va_pocket = Pocket( [Segment([a, b]) for a, b in all_two_elements(vertically_aligned)]) if tested_pocket.is_included_in(including_pocket): print("red is included in green (ok)")