Ejemplo n.º 1
0
def test_utilization():
    # check that as the sphere gets larger, the utilization goes down
    mesh1 = trimesh.primitives.Sphere(radius=20)
    tree1 = bsp_tree.BSPTree(mesh1)
    mesh2 = trimesh.primitives.Sphere(radius=40)
    tree2 = bsp_tree.BSPTree(mesh2)
    mesh3 = trimesh.primitives.Sphere(radius=60)
    tree3 = bsp_tree.BSPTree(mesh3)
    print(
        f"\n{tree1.objectives['utilization']} > {tree2.objectives['utilization']} > {tree3.objectives['utilization']}"
    )
    assert tree1.objectives['utilization'] > tree2.objectives[
        'utilization'] > tree3.objectives['utilization']

    # check that a slice in the middle has a better utilization than a slice not down middle
    mesh = trimesh.primitives.Box(extents=(100., 100., 220.))
    tree1 = bsp_tree.BSPTree(mesh)
    tree1 = bsp_tree.expand_node(tree1, tree1.nodes[0].path,
                                 (np.zeros(3), np.array([0., 0., 1.])))
    tree2 = bsp_tree.BSPTree(mesh)
    tree2 = bsp_tree.expand_node(
        tree2, tree2.nodes[0].path,
        (np.array([0., 0., 100.]), np.array([0., 0., 1.])))
    objective_functions.evaluate_utilization_objective([tree1, tree2], ())
    print(
        f"\n{tree1.objectives['utilization']} < {tree2.objectives['utilization']}"
    )
    assert tree1.objectives['utilization'] < tree2.objectives['utilization']
Ejemplo n.º 2
0
    def insert_connectors(self, tree, state):
        config = Configuration.config
        new_tree = bsp_tree.BSPTree(tree.nodes[0].part)
        for node in tree.nodes:
            if node.plane is None:
                continue
            new_tree2, result = bsp_tree.expand_node(new_tree, node.path,
                                                     node.plane)
            if result != 'success':
                bsp_tree.expand_node(new_tree, node.path, node.plane)
            else:
                new_tree = new_tree2
            new_node = new_tree.get_node(node.path)
            if node.cross_section is None:
                continue
            for cc in node.cross_section.connected_components:
                pos_index, neg_index = cc.get_indices(state)
                pi = cc.positive
                ni = cc.negative
                for idx in pos_index:
                    xform = self.connectors[idx].primitive.transform
                    slot = trimesh.primitives.Box(
                        extents=np.ones(3) *
                        (cc.connector_diameter + config.connector_tolerance),
                        transform=xform)
                    try:
                        utils.trimesh_repair(new_node.children[pi].part)
                        new_part = new_node.children[pi].part.difference(
                            slot, engine='scad')
                        new_node.children[pi].part = new_part

                        utils.trimesh_repair(new_node.children[ni].part)
                        new_part = new_node.children[ni].part.union(
                            self.connectors[idx], engine='scad')
                        new_node.children[ni].part = new_part
                    except Exception as e:
                        logger.info("ignoring connector")
                for idx in neg_index:
                    xform = self.connectors[idx].primitive.transform
                    slot = trimesh.primitives.Box(
                        extents=np.ones(3) *
                        (cc.connector_diameter + config.connector_tolerance),
                        transform=xform)
                    try:
                        utils.trimesh_repair(new_node.children[ni].part)
                        new_part = new_node.children[ni].part.difference(
                            slot, engine='scad')
                        new_node.children[ni].part = new_part

                        utils.trimesh_repair(new_node.children[pi].part)
                        new_part = new_node.children[pi].part.union(
                            self.connectors[idx], engine='scad')
                        new_node.children[pi].part = new_part
                    except Exception as e:
                        logger.info("ignoring connector")

        return new_tree
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
def test_basic_separation(config):
    config.part_separation = True
    mesh = trimesh.load(
        os.path.join(os.path.dirname(__file__), 'test_meshes',
                     'separate_test.stl'))
    tree = bsp_tree.BSPTree(mesh)
    node = tree.largest_part
    plane = (np.zeros(3), np.array([1, 0, 0]))
    tree, result = bsp_tree.expand_node(tree, node.path, plane)
    # 1 root, three leaves come out of the split
    assert len(tree.nodes) == 4
Ejemplo n.º 6
0
def open_tree(tree_file):
    mesh = open_mesh()
    with open(tree_file) as f:
        data = json.load(f)

    node_data = data['nodes']
    tree = bsp_tree.BSPTree(mesh)
    for n in node_data:
        plane = (np.array(n['origin']), np.array(n['normal']))
        node = tree.get_node(n['path'])
        tree, result_code = bsp_tree.expand_node(tree, node.path, plane)
    return tree
Ejemplo n.º 7
0
def test_fragility_function_already_fragile():
    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)
    origin = np.zeros(3)
    normal = np.array([0., 0., 1.])
    plane = (origin, normal)
    trees = [bsp_tree.expand_node(tree, tree.nodes[0].path, plane)[0]]
    objective_functions.evaluate_fragility_objective(trees, tree.nodes[0].path)
    assert trees[0].objectives['fragility'] == 0
Ejemplo n.º 8
0
 def insert_connectors(self, tree, state):
     logger.info(f"inserting {state.sum()} connectors")
     config = Configuration.config
     if tree.nodes[0].plane is None:
         new_tree = utils.separate_starter(tree.nodes[0].part)
     else:
         new_tree = bsp_tree.BSPTree(tree.nodes[0].part)
     for node in tree.nodes:
         if node.plane is None:
             continue
         new_tree2, result = bsp_tree.expand_node(new_tree, node.path, node.plane)
         if result != 'success':
             # for debugging
             bsp_tree.expand_node(new_tree, node.path, node.plane)
         else:
             new_tree = new_tree2
         new_node = new_tree.get_node(node.path)
         if node.cross_section is None:
             continue
         for cc in node.cross_section.connected_components:
             pos_index, neg_index = cc.get_indices(state)
             pi = cc.positive
             ni = cc.negative
             for idx in pos_index:
                 xform = self.connectors[idx].primitive.transform
                 slot = trimesh.primitives.Box(
                     extents=np.ones(3) * (cc.connector_diameter + config.connector_tolerance),
                     transform=xform)
                 new_node.children[pi].part = insert_slot(new_node.children[pi].part, slot)
                 new_node.children[ni].part = insert_box(new_node.children[ni].part, self.connectors[idx])
             for idx in neg_index:
                 xform = self.connectors[idx].primitive.transform
                 slot = trimesh.primitives.Box(
                     extents=np.ones(3) * (cc.connector_diameter + config.connector_tolerance),
                     transform=xform)
                 new_node.children[ni].part = insert_slot(new_node.children[ni].part, slot)
                 new_node.children[pi].part = insert_box(new_node.children[pi].part, self.connectors[idx])
     return new_tree
Ejemplo n.º 9
0
def test_edge_fragility(config):
    config.connector_diameter = 3
    config.connector_wall_distance = 1
    mesh_fn = os.path.join(os.path.dirname(__file__), 'test_meshes', 'fragility_test_2.stl')
    mesh = trimesh.load(mesh_fn)
    mesh = mesh.subdivide()

    tree = bsp_tree.BSPTree(mesh)
    origin = np.zeros(3)
    normal = np.array([0., 0., 1.])
    plane = (origin, normal)
    fragile_cut_tree, result = bsp_tree.expand_node(tree, tree.nodes[0].path, plane)
    objective_functions.evaluate_fragility_objective([fragile_cut_tree], tree.nodes[0].path)
    assert fragile_cut_tree.objectives['fragility'] == np.inf
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
def test_sa_objective_2():
    """Verifies:
        - large faces prefer multiple connectors
    """
    config = Configuration.config
    config.connector_spacing = 5
    mesh = trimesh.primitives.Box(extents=[30, 30, 80])
    tree = bsp_tree.BSPTree(mesh)
    normal = np.array([0, 0, 1])
    origin = np.zeros(3)
    tree = bsp_tree.expand_node(tree, tree.nodes[0].path, (origin, normal))
    connector_placer = connector.ConnectorPlacer(tree)

    # single connector
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[12] = True
    ob1 = connector_placer.evaluate_connector_objective(state)

    # double connector in opposite corners
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[24] = True
    ob2 = connector_placer.evaluate_connector_objective(state)

    # connector in each corner
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[4] = True
    state[20] = True
    state[24] = True
    ob3 = connector_placer.evaluate_connector_objective(state)

    # connector in each corner and in middle (too many connectors)
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[4] = True
    state[12] = True
    state[20] = True
    state[24] = True
    ob4 = connector_placer.evaluate_connector_objective(state)

    assert ob1 > ob2 > ob3
    assert ob4 > ob3

    config.restore_defaults()
Ejemplo n.º 12
0
def test_sa_objective_2(config):
    """Verifies:
        - large faces prefer multiple connectors

        NOTE: every time grid_sample code changes, this will need to be changed which obviously isnt ideal
    """
    config.connector_spacing = 5
    mesh = trimesh.primitives.Box(extents=[30, 30, 80])
    tree = bsp_tree.BSPTree(mesh)
    normal = np.array([0, 0, 1])
    origin = np.zeros(3)
    tree, result = bsp_tree.expand_node(tree, tree.nodes[0].path,
                                        (origin, normal))
    connector_placer = connector.ConnectorPlacer(tree)

    # single connector
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[12] = True
    ob1 = connector_placer.evaluate_connector_objective(state)

    # double connector in opposite corners
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[24] = True
    ob2 = connector_placer.evaluate_connector_objective(state)

    # connector in each corner
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[4] = True
    state[20] = True
    state[24] = True
    ob3 = connector_placer.evaluate_connector_objective(state)

    # connector in each corner and in middle (too many connectors)
    state = np.zeros(connector_placer.n_connectors, dtype=bool)
    state[0] = True
    state[4] = True
    state[12] = True
    state[20] = True
    state[24] = True
    ob4 = connector_placer.evaluate_connector_objective(state)

    assert ob1 > ob2 > ob3
    assert ob4 > ob3
Ejemplo n.º 13
0
def test_sa_objective_1():
    """Verifies:
        - connected components without a connector are penalized
        - small connected components with a single connector have a reasonably low objective
        - connected components with a connector collision are penalized
    """
    config = Configuration.config
    mesh = trimesh.primitives.Box(extents=[10, 10, 40])
    tree = bsp_tree.BSPTree(mesh)
    normal = np.array([0, 0, 1])
    origin = np.zeros(3)
    tree = bsp_tree.expand_node(tree, tree.nodes[0].path, (origin, normal))
    connector_placer = connector.ConnectorPlacer(tree)
    assert connector_placer.evaluate_connector_objective(np.array([False, False])) >= 1 / config.empty_cc_penalty
    ob2 = connector_placer.evaluate_connector_objective(np.array([False, True]))
    ob3 = connector_placer.evaluate_connector_objective(np.array([True, False]))
    assert ob2 == ob3
    assert ob2 < 5
    assert connector_placer.evaluate_connector_objective(np.array([True, True])) >= config.connector_collision_penalty
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
def test_number_of_parts():
    # test on a small sphere
    mesh = trimesh.primitives.Sphere(radius=10)

    tree = bsp_tree.BSPTree(mesh)
    assert tree.objectives['nparts'] == 1
    assert tree.nodes[0].n_parts == 1
    # test on a large box
    mesh = trimesh.primitives.Box(extents=(50, 50, 220))

    tree = bsp_tree.BSPTree(mesh)
    assert tree.objectives['nparts'] == 1
    assert tree.nodes[0].n_parts == 2
    # test splitting the box into 2 through the middle
    tree, result = bsp_tree.expand_node(tree, tree.nodes[0].path, (np.zeros(3), np.array([0, 0, 1])))
    assert tree.objectives['nparts'] == 1
    assert tree.get_node((0,)).n_parts == 1
    assert tree.get_node((1,)).n_parts == 1
    # rotate the box
    mesh.apply_transform(trimesh.transformations.random_rotation_matrix())
    tree = bsp_tree.BSPTree(mesh)
    assert tree.objectives['nparts'] == 1
    assert tree.nodes[0].n_parts == 2