def test_functions(): """modify the config and verify that various functions correctly use the updated version """ config = Configuration.config mesh = trimesh.load(config.mesh, validate=True) print() # BSPNode instantiation (n_parts) n_parts_1 = bsp_node.BSPNode(mesh).n_parts config.printer_extents = np.array([20, 20, 20]) n_parts_2 = bsp_node.BSPNode(mesh).n_parts assert n_parts_1 != n_parts_2 config.restore_defaults() # get_planes (plane_spacing, default is ) node = bsp_node.BSPNode(mesh) planes_1 = bsp_tree.get_planes(node.part, np.array([0, 1, 0])) config.plane_spacing /= 2 planes_2 = bsp_tree.get_planes(node.part, np.array([0, 1, 0])) assert len(planes_2) > len(planes_1) config.restore_defaults() # uniform normals normals1 = config.normals.copy() config.n_theta = 10 normals2 = config.normals.copy() config.n_phi = 10 normals3 = config.normals.copy() assert len(normals1) < len(normals2) < len(normals3) config.restore_defaults()
def evaluate_cuts(base_tree, node): """this function returns a list of unique trees by splitting a specified node of an input tree along all planes as defined in the configuration :param base_tree: tree to split at a particular node :type base_tree: `bsp_tree.BSPTree` :param node: node of the input tree to split :type node: `bsp_node.BSPNode` :return: list of 'unique' trees resulting from splitting the input tree at the specified node :rtype: list of `bsp_tree.BSPTree` """ config = Configuration.config # collect configuration N = config.normals # collect predefined set of normal vectors Np = node.auxiliary_normals # add in the part's bounding-box-aligned vectors as normals N = utils.get_unique_normals(np.concatenate( (N, Np), axis=0)) # make sure we only have unique normals trees = [] # initial list of all trees resulting from splitting the node for i in range(N.shape[0]): trees_of_this_normal = [ ] # start a list of trees for splits along this normal normal = N[i] # current normal for plane in bsp_tree.get_planes( node.part, normal): # iterate over all valid cutting planes for the node tree, result = bsp_tree.expand_node( base_tree, node.path, plane) # split the node using the plane if tree: # only keep the tree if the split is successful trees_of_this_normal.append(tree) logger.debug( f"normal index: {i}, origin: {plane[0]}, normal: {plane[1]}, result: {result}" ) if len( trees_of_this_normal ) == 0: # avoid empty list errors during objective function evaluation logger.info( f"normal index: {i}, trees for normal: {len(trees_of_this_normal)}, total trees: {len(trees)}" ) continue # go through each objective function, evaluate the objective function for each tree in this normal's # list, fill in the data in each tree object in the list for evaluate_objective_func in objectives.values(): evaluate_objective_func(trees_of_this_normal, node.path) trees += trees_of_this_normal logger.info( f"normal index: {i}, trees for normal: {len(trees_of_this_normal)}, total trees: {len(trees)}" ) # go through the list of trees, best ones first, and throw away any that are too similar to another tree already # in the result list result_set = [] for tree in sorted(trees, key=lambda x: x.objective): if tree.sufficiently_different(node, result_set): result_set.append(tree) logger.info(f"{len(result_set)} valid trees") return result_set
def test_expand_node(): """no errors when using expand_node, need to think of better tests here""" config = Configuration.config mesh = trimesh.load(config.mesh, validate=True) # make tree, get node, get random normal, pick a plane right through middle, make sure that the slice is good tree = bsp_tree.BSPTree(mesh) node = tree.largest_part normal = np.array([0, 0, 1]) planes = bsp_tree.get_planes(node.part, normal) plane = planes[len(planes) // 2] tree1 = bsp_tree.expand_node(tree, node.path, plane) print("tree objective: ", tree1.objective) node = tree1.largest_part planes = bsp_tree.get_planes(node.part, normal) plane = planes[len(planes) // 2] tree2 = bsp_tree.expand_node(tree1, node.path, plane)
def test_get_planes(config): """verify that for the default bunny mesh, which is a single part, all planes returned by `bsp_tree.get_planes` cut through the mesh (they have a good cross section) """ mesh = trimesh.load(config.mesh, validate=True) for i in range(100): normal = trimesh.unitize(np.random.rand(3)) planes = bsp_tree.get_planes(mesh, normal) for origin, normal in planes: path3d = mesh.section(plane_origin=origin, plane_normal=normal) assert path3d is not None
def test_copy_tree(config): """Now that objectives are calculated outside of the tree (using the objective function evaluators), verify that copying a tree doesn't modify its objectives dict """ mesh = trimesh.load(config.mesh, validate=True) # make tree, get node, get random normal, pick a plane right through middle, make sure that the slice is good tree = bsp_tree.BSPTree(mesh) node = tree.largest_part normal = np.array([0, 0, 1]) planes = bsp_tree.get_planes(node.part, normal) plane = planes[len(planes) // 2] tree, result = bsp_tree.expand_node(tree, node.path, plane) new_tree = tree.copy() assert new_tree.objectives == tree.objectives
def test_different_from(): """verify that `BSPNode.different_from` has the expected behavior Get a list of planes. Split the object using the first plane, then for each of the other planes, split the object, check if the plane is far enough away given the config, then assert that `BSPNode.different_from` returns the correct value. This skips any splits that fail. """ config = Configuration.config print() mesh = trimesh.primitives.Sphere(radius=50) tree = bsp_tree.BSPTree(mesh) root = tree.nodes[0] normal = trimesh.unitize(np.random.rand(3)) planes = bsp_tree.get_planes(mesh, normal) base_node = copy.deepcopy(root) base_node = bsp_node.split(base_node, planes[0]) for plane in planes[1:]: # smaller origin offset, should not be different test_node = copy.deepcopy(root) test_node = bsp_node.split(test_node, plane) if abs((plane[0] - planes[0][0]) @ planes[0][1]) > config.different_origin_th: assert base_node.different_from(test_node) else: assert not base_node.different_from(test_node) # smaller angle difference, should not be different test_node = copy.deepcopy(root) random_vector = trimesh.unitize(np.random.rand(3)) axis = np.cross(random_vector, planes[0][1]) rotation = trimesh.transformations.rotation_matrix(np.pi / 11, axis) normal = trimesh.transform_points(planes[0][1][None, :], rotation)[0] test_plane = (planes[0][0], normal) test_node = bsp_node.split(test_node, test_plane) assert not base_node.different_from(test_node) # larger angle difference, should be different test_node = copy.deepcopy(root) random_vector = trimesh.unitize(np.random.rand(3)) axis = np.cross(random_vector, planes[0][1]) rotation = trimesh.transformations.rotation_matrix(np.pi / 9, axis) normal = trimesh.transform_points(planes[0][1][None, :], rotation)[0] test_plane = (planes[0][0], normal) test_node = bsp_node.split(test_node, test_plane) assert base_node.different_from(test_node)
def test_fragility_function_multiple_trees(config): config.plane_spacing = 5 config.connector_diameter = 5 mesh_fn = os.path.join(os.path.dirname(__file__), 'test_meshes', 'fragility_test_1.stl') mesh = trimesh.load(mesh_fn) mesh = mesh.subdivide() mesh = mesh.subdivide() tree = bsp_tree.BSPTree(mesh) normal = np.array([0., 0., 1.]) planes = bsp_tree.get_planes(mesh, normal) trees = [] for plane in planes: candidate, result = bsp_tree.expand_node(tree, tree.nodes[0].path, plane) trees.append(candidate) objective_functions.evaluate_fragility_objective(trees, tree.nodes[0].path) assert trees[0].objectives['fragility'] == np.inf assert trees[6].objectives['fragility'] == np.inf assert trees[7].objectives['fragility'] == np.inf assert trees[11].objectives['fragility'] == np.inf
def process_normal(normal, node, base_tree, config): Configuration.config = config trees_of_this_normal = [ ] # start a list of trees for splits along this normal for plane in bsp_tree.get_planes( node.part, normal): # iterate over all valid cutting planes for the node tree, result = bsp_tree.expand_node( base_tree, node.path, plane) # split the node using the plane if tree: # only keep the tree if the split is successful trees_of_this_normal.append(tree) if len( trees_of_this_normal ) == 0: # avoid empty list errors during objective function evaluation return trees_of_this_normal # go through each objective function, evaluate the objective function for each tree in this normal's # list, fill in the data in each tree object in the list for evaluate_objective_func in objectives.values(): evaluate_objective_func(trees_of_this_normal, node.path) print('.', end='') return trees_of_this_normal