Example #1
0
def run(starter):
    """This function goes through the complete Pychop3D process, including the beam search for the optimal
    cutting planes, determining the connector locations, adding the connectors to the part meshes, then saving the
    STLs, the tree json and the configuration file.

    :param starter: Either an unpartitioned mesh or an already partitioned tree to begin the process using
    :type starter: `trimesh.Trimesh`
    :type starter: `bsp_tree.BSPTree`
    """
    config = Configuration.config
    # basic logging setup
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s  %(name)s  [%(levelname)s]  %(message)s",
        handlers=[
            logging.FileHandler(os.path.join(config.directory, "info.log")),
            logging.StreamHandler()
        ])
    # mark starting time
    t0 = time.time()
    # complete the beam search using the starter, no search will take place if the starter tree is already
    # adequately partitioned
    tree = beam_search(starter)
    logger.info(f"Best BSP-tree found in {time.time() - t0} seconds")
    # save the tree now in case the connector placement fails
    utils.save_tree(tree, "final_tree.json")

    # mark starting time
    t0 = time.time()
    logger.info("finding best connector arrangement")
    # create connector placer object, this creates all potential connectors and determines their collisions
    connector_placer = connector.ConnectorPlacer(tree)
    if connector_placer.n_connectors > 0:
        # use simulated annealing to determine the best combination of connectors
        state = connector_placer.simulated_annealing_connector_placement()
        logger.info(
            f"Best connector arrangement found in {time.time() - t0} seconds")
        # save the final tree including the state
        utils.save_tree(tree, "final_tree_with_connectors.json", state)
        # add the connectors / subtract the slots from the parts of the partitioned input object
        logger.info(f"inserting {state.sum()} connectors...")
        tree = connector_placer.insert_connectors(tree, state)

    # export the parts of the partitioned object
    utils.export_tree_stls(tree)
    logger.info("Finished")
Example #2
0
def main(mesh_filepath):

    # set configuration options
    config = Configuration.config
    config.name = 'output'
    config.mesh = mesh_filepath
    config.beam_width = 3
    config.connector_diameter = 6
    config.connector_spacing = 10
    config.part_separation = True
    config.scale_factor = 5
    config.save()

    # open the input mesh as the starter
    starter = utils.open_mesh()

    # separate pieces
    if config.part_separation and starter.body_count > 1:
        starter = utils.separate_starter(starter)

    # complete the beam search using the starter, no search will take place if the starter tree is adequately partitioned
    tree = beam_search(starter)
    # save the tree now in case the connector placement fails
    utils.save_tree(tree, "final_tree.json")

    try:
        # mark starting time
        t0 = time.time()
        # create connector placer object, this creates all potential connectors and determines their collisions
        connector_placer = connector.ConnectorPlacer(tree)
        if connector_placer.n_connectors > 0:
            # use simulated annealing to determine the best combination of connectors
            state = connector_placer.simulated_annealing_connector_placement()
            # save the final tree including the state
            utils.save_tree(tree, "final_tree_with_connectors.json", state)
            # add the connectors / subtract the slots from the parts of the partitioned input object
            tree = connector_placer.insert_connectors(tree, state)
    except Exception as e:
        # fail gently so that the STLs still get exported
        warnings.warn(e, Warning, stacklevel=2)

    # export the parts of the partitioned object
    utils.export_tree_stls(tree)
Example #3
0
def run(starter):
    """This function goes through the complete Pychop3D process, including the beam search for the optimal
    cutting planes, determining the connector locations, adding the connectors to the part meshes, then saving the
    STLs, the tree json and the configuration file.

    :param starter: Either an unpartitioned mesh or an already partitioned tree to begin the process using
    :type starter: `trimesh.Trimesh`
    :type starter: `bsp_tree.BSPTree`
    """
    # mark starting time
    t0 = time.time()
    # complete the beam search using the starter, no search will take place if the starter tree is already
    # adequately partitioned
    tree = beam_search(starter)
    logger.info(f"Best BSP-tree found in {time.time() - t0} seconds")
    # save the tree now in case the connector placement fails
    utils.save_tree(tree, "final_tree.json")

    try:
        # mark starting time
        t0 = time.time()
        # create connector placer object, this creates all potential connectors and determines their collisions
        connector_placer = connector.ConnectorPlacer(tree)
        if connector_placer.n_connectors > 0:
            # use simulated annealing to determine the best combination of connectors
            state = connector_placer.simulated_annealing_connector_placement()
            # save the final tree including the state
            utils.save_tree(tree, "final_tree_with_connectors.json", state)
            # add the connectors / subtract the slots from the parts of the partitioned input object
            tree = connector_placer.insert_connectors(tree, state)
        logger.info(
            f"Best connector arrangement found in {time.time() - t0} seconds")
    except Exception as e:
        # fail gently so that the STLs still get exported
        logger.error("Connector placement failed")
        logger.error(e)

    # export the parts of the partitioned object
    utils.export_tree_stls(tree)
    logger.info("Finished")
Example #4
0
def beam_search(starter):
    """This function executes the beam search to find a good BSPTree partitioning of the input object

    :param starter: Either an unpartitioned mesh or an already partitioned tree to begin the process using
    :type starter: `trimesh.Trimesh`
    :type starter: `bsp_tree.BSPTree`
    :return: a BSPTree which adequately partitions the input object
    :rtype: `bsp_tree.BSPTree`
    """
    config = Configuration.config  # collect configuration
    # open up starter, this can either be a trimesh or an already partitioned object as a tree
    if isinstance(starter, trimesh.Trimesh):
        current_trees = [bsp_tree.BSPTree(starter)]
    elif isinstance(starter, bsp_tree.BSPTree):
        current_trees = [starter]
    else:
        raise NotImplementedError

    logger.info(f"Starting beam search with an instance of {type(starter)}")
    if isinstance(starter, trimesh.Trimesh):
        logger.info("Trimesh stats:")
        logger.info(
            f"verts: {starter.vertices.shape[0]} extents: {starter.extents}")
    else:
        logger.info(f"n_leaves: {len(starter.leaves)}")
        logger.info(f"Largest part trimesh stats:")
        logger.info(
            f"verts: {starter.largest_part.part.vertices.shape[0]}, extents: {starter.largest_part.part.extents}"
        )

    if utils.all_at_goal(current_trees):
        raise Exception("Input mesh already small enough to fit in printer")

    # keep track of n_leaves, in each iteration we will only consider trees with the same number of leaves
    # I think the trees become less comparable when they don't have the same number of leaves
    n_leaves = 1
    while not utils.all_at_goal(
            current_trees
    ):  # continue until we have at least {beam_width} trees
        new_bsps = []  # list of new bsps
        for tree in utils.not_at_goal_set(
                current_trees):  # look at all trees that haven't terminated
            if len(
                    tree.leaves
            ) != n_leaves:  # only consider trees with a certain number of leaves
                continue
            current_trees.remove(
                tree
            )  # remove the current tree (we will replace it with its best partition)
            largest_node = tree.largest_part  # split the largest node
            new_bsps += evaluate_cuts(
                tree, largest_node
            )  # consider many different cutting planes for the node

        n_leaves += 1  # on the next iteration, look at trees with more leaves
        current_trees += new_bsps
        current_trees = sorted(
            current_trees, key=lambda x: x.objective
        )  # sort all of the trees including the new ones
        # if we are considering part separation, some of the trees may have more leaves, put those away for later
        if config.part_separation:
            extra_leaves_trees = [
                t for t in current_trees if len(t.leaves) > n_leaves
            ]
        current_trees = current_trees[:config.
                                      beam_width]  # only keep the best {beam_width} trees
        if config.part_separation:  # add back in the trees with extra leaves
            current_trees += [
                t for t in extra_leaves_trees if t not in current_trees
            ]

        if len(current_trees) == 0:  # all of the trees failed
            raise Exception("No valid chops found")

        logger.info(
            f"Leaves: {n_leaves}, best objective: {current_trees[0].objective}, estimated number of parts: "
            f"{sum([p.n_parts for p in current_trees[0].leaves])}")

        # save progress
        for i, tree in enumerate(current_trees[:config.beam_width]):
            utils.save_tree(tree, f"{config.name}_{i}.json")
        utils.export_tree_stls(current_trees[0])

    return current_trees[0]