Esempio n. 1
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
Esempio n. 2
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
Esempio n. 3
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
Esempio n. 4
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
Esempio n. 5
0
 def __init__(self, file_name):
     self.heights_hash = CoordinatesHash(wanted_precision=5)
     self.facets = []
     self.bounding_box = BoundingBox.empty_box(3)
     if __debug__:
         if is_module_debugged(__name__):
             print('loading stl file')
     self.parse_stl(file_name)
     if __debug__:
         if is_module_debugged(__name__):
             print('stl file loaded')
Esempio n. 6
0
def build_paths_tree(milling_radius, pockets):
    """
    transform pockets tree into paths tree.
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("building paths tree")
    paths = PathTree.build(pockets, milling_radius)
    if __debug__:
        if is_module_debugged(__name__):
            paths.tycat()
    return paths
Esempio n. 7
0
def build_polygons_tree(milling_radius, slices_polygons):
    """
    turn slices into polygons tree.
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("building polygon tree")
    tree = PolygonTree.build(milling_radius, slices_polygons)
    if __debug__:
        if is_module_debugged(__name__):
            tree.tycat()
    return tree
Esempio n. 8
0
def build_pockets_tree(milling_radius, tree):
    """
    transform polygons tree into pockets tree.
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("building pockets tree")
    pockets = PocketTree.build(tree, milling_radius)
    if pockets.is_empty():
        print("nothing left : milling radius is too high !")
        sys.exit()
    if __debug__:
        if is_module_debugged(__name__):
            pockets.tycat()
    return pockets
Esempio n. 9
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
Esempio n. 10
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")
Esempio n. 11
0
    def uncompress(self, translation):
        """
        initialize an uncompressed_tree out of a compressed one.
        """

        if self.content is not None:
            translated_content = self.content.translate(translation)
            translated_pocket = self.old_pocket.translate(translation)
            new_node = PathTree(translated_content, translated_pocket)
        else:
            new_node = PathTree()

        # generate children
        for child in self.children:
            for child_translation in child.translations:
                new_translation = child_translation + translation
                new_node.children.append(child.uncompress(new_translation))

        if __debug__:
            if is_module_debugged(__name__):
                if self.content is None:
                    # toplevel node
                    print("decompressed path tree")
                    new_node.tycat()

        return new_node
Esempio n. 12
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
Esempio n. 13
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
Esempio n. 14
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)
Esempio n. 15
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
Esempio n. 16
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
Esempio n. 17
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)
Esempio n. 18
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)
Esempio n. 19
0
def build_inclusion_tree(polygons):
    """
    turn a set of polygons hashed by height into a polygon tree.
    """
    builder = InclusionTreeBuilder(polygons)
    if __debug__:
        if is_module_debugged(__name__):
            print("inclusion tree")
            builder.tree.tycat()
    return builder.tree
Esempio n. 20
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)
Esempio n. 21
0
def compute_milling_path(stl_file, slice_size, milling_radius):
    """
    main procedure.
    loads stl file ;
    cuts in slices of thickness 'slice_size' ;
    compute polygons and offset them with milling radius ;
    returns global milling path
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("computing path ; thickness is", slice_size, "radius is",
                  milling_radius)

    VerticalPath.milling_height = slice_size
    slices_polygons = slice_stl_file(stl_file, slice_size, milling_radius)
    tree = build_polygons_tree(milling_radius, slices_polygons)
    pockets = build_pockets_tree(milling_radius, tree)
    paths = build_paths_tree(milling_radius, pockets)

    if __debug__:
        if is_module_debugged(__name__):
            print("merging all paths")
    return paths.global_path(milling_radius)
Esempio n. 22
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
Esempio n. 23
0
def _augment_path(graph, start_vertex):
    # this is a very simple way to find the best augmenting path
    # it is in no way optimized
    # and has a complexity of O(n^2)
    distances, predecessors = bellman_ford(graph, start_vertex)
    destination = _find_nearest_odd_vertex(graph, start_vertex, distances)
    current_point = destination
    if __debug__:
        if is_module_debugged(__name__):
            added_edges = []
    while current_point != start_vertex:
        edge = predecessors[current_point.unique_id]
        edge.add_directly_to_graph()
        if __debug__:
            if is_module_debugged(__name__):
                added_edges.append(edge)
        previous_point = edge.vertices[0]
        current_point = previous_point
    if __debug__:
        if is_module_debugged(__name__):
            print("new augmenting path")
            tycat(graph, added_edges)
            print("graph is now")
            tycat(graph)
Esempio n. 24
0
    def execute(self):
        """
        run bentley ottmann
        """
        while self.events:
            event_point = self.events.pop(0)

            # remove ending paths
            self.remove_paths(self.events_data[1][event_point])

            self.current_point = event_point
            if __debug__:
                if is_module_debugged(__name__):
                    print("current point is now", self.current_point)

            # add starting paths
            for starting_path in self.events_data[0][event_point]:
                self.add_path(starting_path)

            if __debug__:
                if is_module_debugged(__name__):
                    self.tycat()

        return self
Esempio n. 25
0
def tsp(graph):
    """
    christofides algorithm.
    careful: this modifies initial graph.
    """
    if __debug__:
        if is_module_debugged(__name__):
            print("starting christofides")

    spanning_tree = min_spanning_tree(graph)
    path_graph = _adjust_degree(spanning_tree)

    cycle = find_eulerian_cycle(path_graph)
    if __debug__:
        if is_module_debugged(__name__):
            print("cycle with duplicated vertices")
            tycat(cycle)

    cycle = _skip_seen_vertices(cycle)
    if __debug__:
        if is_module_debugged(__name__):
            print("cycle")
            tycat(cycle)
    return cycle
Esempio n. 26
0
    def add_path(self, path):
        """
        new path handler. check each time if new polygon.
        """
        polygon_id = path.get_polygon_id()
        self.current_paths[polygon_id].append(path)

        if polygon_id not in self.seen_polygons:
            # this guy is new, categorize it
            # add it in tree
            self.add_polygon_in_tree(path)

            # mark it as seen
            self.seen_polygons.add(polygon_id)
            if __debug__:
                if is_module_debugged(__name__):
                    print("added polygon", id(path.polygon),
                          "( h =", path.height, ")")
                    self.tree.tycat()
Esempio n. 27
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
Esempio n. 28
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
Esempio n. 29
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
Esempio n. 30
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