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")
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)
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")
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]