Exemplo n.º 1
0
    def __init__(self, inside_content, distance):
        """
        inflates path by given distance
        """
        self.distance = distance
        self.inside_content = inside_content  # for debug only

        if isinstance(inside_content, Pocket):
            inside = inside_content.paths
        else:
            inside = (inside_content, inside_content.reverse())

        try:
            self.paths = []
            # just follow path, moving away
            raw_paths = [DisplacedPath.displace(p, distance) for p in inside]

            # and then reconnecting everything.
            for path1, path2 in all_two_elements(raw_paths):
                self.paths.extend(path1.reconnect(path2, distance))

        except:
            print("failed compute envelope for", self.inside_content)
            tycat(self.inside_content, [p.path for p in raw_paths])
            raise

        if __debug__:
            if is_module_debugged(__name__):
                print("inflating")
                tycat(self)
Exemplo n.º 2
0
def min_spanning_tree(graph):
    """
    prim minimum spanning tree algorithm.
    """
    start = graph.get_any_vertex()
    heap = []
    _add_edges_in_heap(heap, start)

    reached_vertices = {}
    reached_vertices[start] = True
    added_edges = []

    limit = graph.vertices_number - 1  # stop after this many edges
    while heap:
        if len(added_edges) == limit:
            if __debug__:
                if is_module_debugged(__name__):
                    print("spanning tree")
                    tycat(graph, added_edges)
            return added_edges
        edge = heappop(heap)
        destination = edge.vertices[1]
        if destination not in reached_vertices:
            added_edges.append(edge)
            reached_vertices[destination] = True
            _add_edges_in_heap(heap, destination)
    print("added", len(added_edges), "need", limit)
    tycat(graph, added_edges)
    raise Exception("not enough edges")
Exemplo n.º 3
0
    def build_pocket(self, start_path):
        """
        start with start_path and follow edge building the pocket.
        """
        current_path = []

        start_point, current_point = start_path.endpoints
        current_path.append(start_path)
        self.marked_paths[start_path] = start_path

        previous_point = start_point

        while current_point != start_point:
            # find where to go
            next_path = self.find_next_path(current_point, previous_point)
            if __debug__:
                if next_path.reverse() == current_path[-1]:
                    tycat(self.paths, current_path, next_path)
                    raise Exception("path going back")

            current_path.append(next_path)
            self.marked_paths[next_path] = next_path
            # continue moving
            previous_point = current_point
            current_point = next_path.endpoints[1]

        return Pocket(current_path)
Exemplo n.º 4
0
    def animate(self, milling_radius):
        """
        step by step animation for carving the path with
        given milling radius.
        """
        total_length = self.length()
        steps_length = total_length / PATH_IMAGES

        # all paths strings up to now (by height)
        current_strings = SortedDict()
        bounding_box = BoundingBox.empty_box(2)  # bounding box up to now

        current_height = 0
        current_length = 0
        heights_hash = CoordinatesHash()

        for path in self.elementary_paths:
            new_length = current_length + path.length()

            if isinstance(path, VerticalPath):
                current_height = path.update_height(current_height)
                current_height = heights_hash.hash_coordinate(current_height)
            else:
                envelope = Envelope(path, milling_radius)
                if current_height not in current_strings:
                    current_strings[current_height] = []
                current_strings[current_height].append(envelope.svg_content())
                bounding_box.update(envelope.get_bounding_box())

            if floor(current_length / steps_length) != \
                    floor(new_length / steps_length):
                tycat(*list(reversed(current_strings.values())),
                      bounding_box=bounding_box)

            current_length = new_length
Exemplo n.º 5
0
    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))
Exemplo n.º 6
0
    def split_at(self, points):
        """
        split path at given points.
        returns list of same type objects ;
        orientation is kept ;
        assumes points are on path ;
        input points can be duplicated but no output paths are.

        example:

        points = [Point([c, c]) for c in range(4)]
        segment = Segment([points[0], points[3]])
        small_segments = segment.split_at(points)
        # small_segments contains segments (0,0) -- (1,1)
                                           (1,1) -- (2,2)
                                           (2,2) -- (3,3)
                                           (3,3) -- (4,4)
        """
        # pylint: disable=no-member
        points = set([p for a in (self.endpoints, points) for p in a])
        sorted_points = sorted(points, key=self.distance_from_start)

        paths = []
        for point1, point2 in zip(sorted_points[:-1], sorted_points[1:]):
            path_chunk = self.copy()
            path_chunk.endpoints[0] = point1.copy()
            path_chunk.endpoints[1] = point2.copy()
            paths.append(path_chunk)

        if __debug__:
            if is_module_debugged(__name__):
                print("splitting path:")
                tycat(self, *paths)
        return paths
Exemplo n.º 7
0
    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
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
def _offset_polygons(poly_tree, carving_radius):
    if __debug__:
        if is_module_debugged(__name__):
            print("building pockets tree from")
            poly_tree.tycat()
    # start with children
    subtrees = []
    for child in poly_tree.children:
        pockets = _offset_polygons(child, carving_radius)
        for pocket in pockets:
            pocket.copy_translations(child)
        subtrees.extend(pockets)

    holed_poly = poly_tree.content
    if holed_poly is not None:
        # now, offset ourselves (if we are not root)
        polygons = list(holed_poly.holes)
        polygons.append(holed_poly.polygon)
        pockets = offset_holed_polygon(carving_radius, *polygons)

        if __debug__:
            if is_module_debugged(__name__):
                print("offsetting")
                tycat(polygons)
                print("into")
                tycat(pockets)
        return _build_offsetted_tree(pockets, subtrees)
    else:
        root = PocketTree()
        root.children = subtrees
        return root
Exemplo n.º 10
0
def offset_to_elementary_paths(radius, polygons):
    """
    compute all paths obtained when offsetting.
    handle overlaps and intersections and return
    a set of elementary paths ready to be used for
    rebuilding pockets.
    """
    # offset each polygon
    pockets = [_offset(radius, p) for p in polygons]

    # remove overlapping segments
    for pocket1, pocket2 in combinations(pockets, r=2):
        pocket1.remove_overlap_with(pocket2)

    # compute intersections
    intersections = defaultdict(list)  # to each path a list of intersections
    for pocket1, pocket2 in combinations(pockets, r=2):
        pocket1.intersections_with(pocket2, intersections)

    # compute self intersections and generate elementary paths
    for pocket in pockets:
        pocket.self_intersections(intersections)
        pocket.split_at(intersections)

    paths = []
    for pocket in pockets:
        paths.extend(pocket.paths)

    if __debug__:
        if is_module_debugged(__name__):
            print("elementary paths")
            tycat(paths)

    return paths
Exemplo n.º 11
0
def find_eulerian_cycle(graph):
    """
    eulerian cycle classical algorithm.
    requires all degrees to be even.
    """
    # we loop finding cycles until graph is empty
    possible_starts = {}  # where to search for a new cycle
    start_vertex = graph.get_any_vertex()
    # we constrain possible starting points
    # to be only a previous cycles
    # this will enable easier merging of all cycles
    possible_starts[start_vertex] = start_vertex.degree()
    # we just need to remember for each cycle its starting point
    cycle_starts = defaultdict(list)  # where do found cycles start

    first_cycle = None
    while not graph.is_empty():
        cycle = _find_cycle(graph, possible_starts)
        if __debug__:
            if is_module_debugged(__name__):
                print("found new cycle")
                tycat(graph, cycle)
        if first_cycle is None:
            first_cycle = cycle
        else:
            cycle_start = cycle[0].vertices[0]
            cycle_starts[cycle_start].append(cycle)

    final_cycle = _fuse_cycles(first_cycle, cycle_starts)
    return final_cycle
Exemplo n.º 12
0
 def build(cls, milling_radius, polygons):
     """
     figures out which polygon is included in which other.
     returns tree of all holed polygons to mill such that
     each node contains a holed polygon (except root) and
     each node cannot be milled before any of its ancestors.
     """
     inclusion_tree = build_inclusion_tree(polygons)
     inclusion_tree.ascend_polygons()
     poly_tree = cls()
     _convert_inclusion_tree(poly_tree, inclusion_tree)
     if __debug__:
         if is_module_debugged(__name__):
             print("initial holed polygon tree")
             poly_tree.tycat()
     poly_tree.prune(milling_radius)
     if __debug__:
         if is_module_debugged(__name__):
             print("pruned holed polygon tree")
             poly_tree.tycat()
     poly_tree.normalize_polygons()
     poly_tree.compress()
     if __debug__:
         if is_module_debugged(__name__):
             print("compressed polygons")
             tycat(*list(
                 reversed([
                     n.content.polygon
                     for n in poly_tree.breadth_first_exploration()
                     if n.content is not None
                 ])))
     return poly_tree
Exemplo n.º 13
0
    def junction_points(self, inner_envelope):
        """
        return couple of points interfering.
        first one on outer envelope, other on inner enveloppe.
        first one is furthest possible interference point in outer enveloppe
        """
        points_couples = []
        for our_path in self.paths:
            for envelope_path in inner_envelope.paths:
                interferences = our_path.interferences_with(envelope_path)
                if __debug__:
                    if is_module_debugged(__name__) and interferences:
                        print("interferences")
                        tycat(self, inner_envelope,
                              our_path.path, envelope_path.path, interferences)

                for i in interferences:
                    points_couples.append((our_path.project(i),
                                           envelope_path.project(i)))

        if not points_couples:
            return None, None

        if self.inside_content.endpoints[0] < self.inside_content.endpoints[1]:
            last_couple = max(points_couples, key=lambda c: c[0])
        else:
            last_couple = min(points_couples, key=lambda c: c[0])

        return last_couple
Exemplo n.º 14
0
    def execute_event(self, event):
        """
        execute start path or end path event
        """
        event_key, event_path = event
        event_point, event_type = event_key[0:2]

        if event_type == START_EVENT:
            self.current_point = event_point
            self.start_path(event_path)
        else:
            self.end_path(event_path)
            self.current_point = event_point

        if __debug__:
            # very slow
            paths = iter(self.crossed_paths)
            previous_path = next(paths, None)
            for path in paths:
                if self.key(previous_path) >= self.key(path):
                    paths = list(self.crossed_paths)
                    print(paths)
                    print("previous", previous_path, self.key(previous_path))
                    print("current", path, self.key(path))
                    tycat(self.current_point, paths, previous_path, path)
                    raise Exception("pb ordre")
                previous_path = path
Exemplo n.º 15
0
def slice_stl_file(stl_file, slice_size, milling_radius):
    """
    load stl file. cut into slices of wanted size and build polygons.
    return polygons arrays indexed by slice height.
    """
    model = Stl(stl_file)
    margin = 2 * milling_radius + 0.01
    border = border_2d(model, margin)

    if __debug__:
        if is_module_debugged(__name__):
            print("model loaded")
            print("slices are:")

    slices = model.compute_slices(slice_size, model.translation_vector(margin))

    slices_polygons = {}  # polygons in each slice, indexed by height

    for height in sorted(slices):
        stl_slice = slices[height]
        stl_slice.extend(border)
        if __debug__:
            if is_module_debugged(__name__):
                tycat(stl_slice)
        simpler_slice = merge_segments(stl_slice)
        slice_polygons = build_polygons(simpler_slice)
        slices_polygons[height] = slice_polygons

    return slices_polygons
Exemplo n.º 16
0
def _create_vertices(milled_pocket, milling_diameter, built_graph):
    # first cut by horizontal lines spaced by milling_diameter
    split_pocket = milled_pocket.split_at_milling_points(milling_diameter)
    # ok, now create graph, each segment point becomes a vertex
    # and we add all external edges
    for path in split_pocket.paths:
        built_graph.add_edge(path, frontier_edge=True)

    if __debug__:
        if is_module_debugged(__name__):
            print("created vertices")
            tycat(built_graph)
Exemplo n.º 17
0
def main():
    """
    automated tests or nice display
    """
    if len(sys.argv) > 1:
        print("using seed", sys.argv[1])
        tycat(*test(sys.argv[1]))
    else:
        for iteration in range(2000):
            print(iteration)
            test()
            ROUNDER2D.clear()
        print("done")
Exemplo n.º 18
0
    def intersections_with(self, other):
        """
        return array of intersections with arc or segment.
        """
        if isinstance(other, Segment):
            intersections = self.intersections_with_segment(other)
        else:
            intersections = self.intersections_with_arc(other)

        if __debug__:
            if is_module_debugged(__name__):
                print("intersections are:")
                tycat(self, other, intersections)
        return intersections
Exemplo n.º 19
0
def test_one_level():
    """
    very basic test with all polygons at same height
    """
    outside_square = Polygon.square(0, 0, 10)
    inner_square = Polygon.square(2, 2, 2)
    inner_square2 = Polygon.square(6, 6, 2)
    inner_inner = Polygon.square(2.1, 2.1, 0.3)
    polygons = {0: [outside_square, inner_square, inner_square2, inner_inner]}
    tycat(polygons[0])
    print("outside square", id(outside_square))
    print("inner square (top left)", id(inner_square))
    print("inner square (bottom right)", id(inner_square2))
    print("inner inner square (inside top left)", id(inner_inner))
    tree = build_inclusion_tree(polygons)
    tree.tycat()
Exemplo n.º 20
0
def _create_internal_edges_in_slice(graph, milling_y, vertices):
    """
    move on slice line. when inside add edge.
    """
    current_position = Position(milling_y, outside=True)
    for edge in _horizontal_edges(vertices):
        current_position.update(edge)
        if current_position.is_inside():
            edge.add_directly_to_graph()
            if __debug__:
                if is_module_debugged(__name__):
                    print("adding horizontal edge", str(current_position))
                    tycat(graph, edge)
        else:
            if __debug__:
                if is_module_debugged(__name__):
                    print("not adding horizontal edge", str(current_position))
                    tycat(graph, edge)
Exemplo n.º 21
0
    def build_pockets(self):
        """
        run the algorithm.
        """
        for start_path in self.paths:
            if start_path in self.marked_paths:
                continue  # skip paths already used
            try:
                pocket = self.build_pocket(start_path)
            except:
                print("failed building pocket")
                raise

            self.pockets.append(pocket)
            if __debug__:
                if is_module_debugged(__name__):
                    print("added pocket")
                    tycat(self.paths, pocket)

        return self.pockets
Exemplo n.º 22
0
def __tour():
    description = "we provide a 'Segment' class encoding oriented segments."
    example = """
from jimn.point import Point
from jimn.segment import Segment
from jimn.displayable import tycat
segment1 = Segment([Point([0, 0]), Point([5, 5])])
segment2 = Segment([Point([0, 3]), Point([7, 1])])
tycat(segment1, segment2, segment1.intersection_with_segment(segment2))
    """
    tour("jimn.segment", description, example)
Exemplo n.º 23
0
    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
Exemplo n.º 24
0
def _adjust_degree(spanning_tree):
    """
    return graph obtained after making degrees even.
    """
    left = Graph.subgraph(_odd_degree_vertices(spanning_tree))
    make_degrees_even(left)
    if __debug__:
        if is_module_debugged(__name__):
            print("christofides : matching")
            tycat(left)

    for edge in left.double_edges():
        edge.change_multiplicity(-1)  # set multiplicity back to 1
        spanning_tree.append(edge)
    path_graph = Graph()
    for edge in spanning_tree:
        objects = [v.bound_object for v in edge.vertices]
        path_graph.add_edge_between(*objects, edge_path=edge.path)

    return path_graph
Exemplo n.º 25
0
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
Exemplo n.º 26
0
    def vertical_intersection_at(self, intersecting_x):
        """return y of lowest intersection given vertical line"""
        min_point, max_point = sorted(self.endpoints)
        if is_almost(min_point.get_x(), intersecting_x):
            return min_point.get_y()
        elif is_almost(max_point.get_x(), intersecting_x):
            return max_point.get_y()

        intersections = \
            vline_circle_intersections(intersecting_x,
                                       self.center, self.radius)

        candidates = [
            i for i in intersections if self.contains_circle_point(i)
        ]
        if __debug__ and not candidates:
            print(self, intersecting_x, [str(i) for i in intersections])
            tycat(self, intersections)
            raise Exception("no intersections")
        intersecting_ys = [i.get_y() for i in candidates]
        return min(intersecting_ys)
Exemplo n.º 27
0
def offset_holed_polygon(radius, *polygons):
    """
    take a holed polygon and routing radius.
    remove non accessible surfaces and return
    a set of disjoint holed pockets.
    """

    paths = offset_to_elementary_paths(radius, polygons)

    try:
        pockets = build_pockets(paths)
    except:
        tycat(paths, *polygons)
        raise

    final_pockets = _merge_included_pockets(pockets)
    if __debug__:
        if is_module_debugged(__name__):
            print("final pockets")
            tycat(final_pockets)
    return final_pockets
Exemplo n.º 28
0
def _build_offsetted_tree(pockets, subtrees):
    """
    takes a set of trees from sublevel and a set of pockets
    at current level ;
    figures out in which pocket each tree is included
    and rebuilds a global tree
    """
    new_trees = {}
    for pocket in pockets:
        new_trees[id(pocket)] = PocketTree(pocket)

    for node in subtrees:
        for pocket in pockets:
            if node.content.is_included_in(pocket):
                new_trees[id(pocket)].children.append(node)
                break
        else:
            tycat(node.content, *pockets)
            raise Exception("subtree does not belong here")

    return list(new_trees.values())
Exemplo n.º 29
0
def build_graph(milled_pocket, milling_diameter, fast_algorithm=False):
    """
    return graph which will be used to compute milling path.
    you can choose between fast (linear) algorithm with good enough quality
    or get the optimal solution (in n^3) using "fast_algorithm" parameter.
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("creating graph out of pocket")
            tycat(milled_pocket)

    # fill all vertices
    graph = Graph()
    _create_vertices(milled_pocket, milling_diameter, graph)

    # finish by adding horizontal internal edges
    create_internal_edges(graph, milling_diameter)
    if __debug__:
        if is_module_debugged(__name__):
            print("created internal edges")
            tycat(graph)

    # prepare for eulerian path
    if fast_algorithm:
        make_degrees_even_fast(graph, milling_diameter)
    else:
        make_degrees_even(graph)

    if __debug__:
        if is_module_debugged(__name__):
            print("degrees made even")
            tycat(graph)

    return graph
Exemplo n.º 30
0
def run_slicing_events(events, translation_vector):
    """
    executes all events, adding, removing and intersecting facets
    """
    slices = dict()
    facets = set()
    for height, event_type, facet in events:
        if event_type == START:
            facets.add(facet)
        elif event_type == END:
            facets.remove(facet)
        else:
            segments = []
            for facet in facets:
                facet.intersect(height, segments, translation_vector)
            slices[height] = segments
            if is_module_debugged(__name__):
                print(height)
                # print("\n".join(str(f) for f in facets))
                tycat(segments)

    return slices