def test_block_objects_methods(self):
        self.memory = MCAgentMemory()
        bo_memid = BlockObjectNode.create(self.memory, [((1, 1, 1), (1, 2)),
                                                        ((2, 2, 2), (2, 3))])
        # Test get_object_by_id
        assert (self.memory.get_mem_by_id(bo_memid).blocks ==
                self.memory.get_object_by_id(bo_memid).blocks)
        # Test get_object_info_by_xyz byt cheking with BlockObject's mean xyz
        assert self.memory.get_object_info_by_xyz(
            (1, 1, 1), "BlockObjects")[0] == bo_memid
        # Test get_block_object_ids_by_xyz
        assert (self.memory.get_block_object_ids_by_xyz(
            (1, 1, 1))[0] == self.memory.get_object_info_by_xyz(
                (1, 1, 1), "BlockObjects")[0])
        assert self.memory.get_block_object_ids_by_xyz(
            (1, 1, 1))[0] == bo_memid
        # Test get_block_object_by_xyz -> same BlockObjectNode object as what we created before
        assert (self.memory.get_block_object_by_xyz(
            (1, 1, 1)).blocks == self.memory.get_mem_by_id(bo_memid).blocks)
        # Test get_block_object_by_id
        assert (self.memory.get_block_object_by_id(bo_memid).blocks ==
                self.memory.get_mem_by_id(bo_memid).blocks)

        # Test tag_block_object_from_schematic
        sch_memid = SchematicNode.create(self.memory, (((0, 0, 1), (1, 0)),
                                                       ((0, 0, 2), (1, 0)),
                                                       ((0, 0, 3), (2, 0))))
        bo1_memid = BlockObjectNode.create(self.memory, (((0, 0, 2), (1, 0)),
                                                         ((0, 0, 3), (2, 0))))
        self.memory.tag_block_object_from_schematic(bo1_memid, sch_memid)
        assert len(self.memory.get_triples(pred_text="_from_schematic")) == 1
    def test_dance_api(self):
        self.memory = MCAgentMemory()

        def x():
            return 2

        self.memory.add_dance(x, "generate_num_dance",
                              ["generate_2", "dance_with_numbers"])
        assert len(self.memory.get_triples(obj_text="generate_2")) == 1
        assert len(self.memory.get_triples(obj_text="dance_with_numbers")) == 1
    def test_get_entity_by_id(self):
        self.memory = MCAgentMemory()
        mob_memid = MobNode.create(self.memory,
                                   Mob(10, 65, Pos(1, 2, 3), Look(0, 0)))
        player_memid = PlayerNode.create(
            self.memory, Player(20, "xyz", Pos(1, 1, 1), Look(1, 1)))

        # Test get_entity_by_eid with entityId
        assert self.memory.get_entity_by_eid(10).pos == (1.0, 2.0, 3.0)
        assert self.memory.get_entity_by_eid(20).pos == (1.0, 1.0, 1.0)
示例#4
0
class MethodsTests(unittest.TestCase):
    def setUp(self):
        self.memory = MCAgentMemory(load_minecraft_specs=False)
        # Add dances to memory
        dance.add_default_dances(self.memory)

    def test_peek_empty(self):
        self.assertEqual(self.memory.task_stack_peek(), None)

    def test_add_mob(self):
        # add mob
        chicken = {v: k for k, v in MOBS_BY_ID.items()}["chicken"]
        mob_id, mob_type, pos, look = 42, chicken, Pos(3, 4, 5), Look(0.0, 0.0)
        self.memory.set_mob_position(Mob(mob_id, mob_type, pos, look))

        # get mob
        self.assertIsNotNone(self.memory.get_entity_by_eid(mob_id))

        # update mob
        pos = Pos(6, 7, 8)
        look = Look(120.0, 50.0)
        self.memory.set_mob_position(Mob(mob_id, mob_type, pos, look))

        # get mob
        mob_node = self.memory.get_entity_by_eid(mob_id)
        self.assertIsNotNone(mob_node)
        self.assertEqual(mob_node.pos, (6, 7, 8), (120.0, 50.0))

    def test_add_guardian_mob(self):
        guardian = {v: k for k, v in MOBS_BY_ID.items()}["guardian"]
        mob_id, mob_type, pos, look = 42, guardian, Pos(3, 4, 5), Look(0.0, 0.0)
        self.memory.set_mob_position(Mob(mob_id, mob_type, pos, look))
    def test_voxel_apis(self):
        self.memory = MCAgentMemory()
        bo_memid = BlockObjectNode.create(self.memory, [((1, 1, 1), (1, 2)),
                                                        ((2, 2, 2), (2, 3))])
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 2
        # Test remove_voxel and test total number of blocks
        self.memory.remove_voxel(1, 1, 1, "BlockObjects")
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 1

        # Test upsert_block (add a new block and assert that now there are two
        self.memory.upsert_block(((3, 3, 3), (1, 1)), bo_memid, "BlockObjects")
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 2
 def test_inst_seg_node(self):
     self.memory = MCAgentMemory()
     inst_seg_memid = InstSegNode.create(self.memory,
                                         [(1, 0, 34), (1, 0, 35),
                                          (2, 0, 34), (3, 0, 34)],
                                         ["shiny", "bright"])
     # Test get_instseg_object_ids_by_xyz
     assert self.memory.get_instseg_object_ids_by_xyz(
         (1, 0, 34))[0][0] == inst_seg_memid
     assert self.memory.get_instseg_object_ids_by_xyz(
         (2, 0, 34))[0][0] == inst_seg_memid
     assert self.memory.get_instseg_object_ids_by_xyz(
         (3, 0, 34))[0][0] == inst_seg_memid
示例#7
0
    def init_memory(self):
        T = FakeMCTime(self.world)
        low_level_data = self.low_level_data.copy()
        low_level_data["check_inside"] = heuristic_perception.check_inside

        self.memory = MCAgentMemory(
            load_minecraft_specs=False,
            coordinate_transforms=self.coordinate_transforms,
            agent_time=T,
            agent_low_level_data=low_level_data,
        )
        # Add dances to memory
        dance.add_default_dances(self.memory)
 def test_items_apis(self):
     self.memory = MCAgentMemory()
     low_level_data = {"bid_to_name": {(0, 0): "sand", (10, 4): "grass"}}
     item_memid = ItemStackNode.create(
         self.memory, ItemStack(12, Item(0, 0), Pos(0, 0, 0)),
         low_level_data)
     # test update_item_stack_eid
     assert self.memory.update_item_stack_eid(item_memid, 23).eid == 23
     # test set_item_stack_position
     new_item = ItemStack(23, Item(0, 0), Pos(2, 2, 2))
     self.memory.set_item_stack_position(new_item)
     assert self.memory.get_mem_by_id(item_memid).pos == (2.0, 2.0, 2.0)
     # test get_all_item_stacks
     item_2_memid = ItemStackNode.create(
         self.memory, ItemStack(34, Item(10, 4), Pos(2, 3, 3)),
         low_level_data)
     assert len(self.memory.get_all_item_stacks()) == 2
 def test_schematic_apis(self):
     self.memory = MCAgentMemory()
     schematic_memid = SchematicNode.create(self.memory,
                                            (((2, 0, 1),
                                              (1, 0)), ((2, 0, 2), (1, 0)),
                                             ((2, 0, 3), (2, 0))))
     # test get_schematic_by_id
     assert (self.memory.get_schematic_by_id(schematic_memid).blocks ==
             self.memory.get_mem_by_id(schematic_memid).blocks)
     # test get_schematic_by_name
     self.memory.add_triple(subj=schematic_memid,
                            pred_text="has_name",
                            obj_text="house")
     assert (self.memory.get_schematic_by_name("house").blocks ==
             self.memory.get_mem_by_id(schematic_memid).blocks)
     bo1_memid = BlockObjectNode.create(self.memory, (((0, 0, 2), (1, 0)),
                                                      ((0, 0, 3), (2, 0))))
     # Test convert_block_object_to_schematic
     assert self.memory.convert_block_object_to_schematic(
         bo1_memid).NODE_TYPE == "Schematic"
class BasicTest(unittest.TestCase):
    def test_get_entity_by_id(self):
        self.memory = MCAgentMemory()
        mob_memid = MobNode.create(self.memory,
                                   Mob(10, 65, Pos(1, 2, 3), Look(0, 0)))
        player_memid = PlayerNode.create(
            self.memory, Player(20, "xyz", Pos(1, 1, 1), Look(1, 1)))

        # Test get_entity_by_eid with entityId
        assert self.memory.get_entity_by_eid(10).pos == (1.0, 2.0, 3.0)
        assert self.memory.get_entity_by_eid(20).pos == (1.0, 1.0, 1.0)

    def test_voxel_apis(self):
        self.memory = MCAgentMemory()
        bo_memid = BlockObjectNode.create(self.memory, [((1, 1, 1), (1, 2)),
                                                        ((2, 2, 2), (2, 3))])
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 2
        # Test remove_voxel and test total number of blocks
        self.memory.remove_voxel(1, 1, 1, "BlockObjects")
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 1

        # Test upsert_block (add a new block and assert that now there are two
        self.memory.upsert_block(((3, 3, 3), (1, 1)), bo_memid, "BlockObjects")
        assert len(self.memory.get_mem_by_id(bo_memid).blocks) == 2

    def test_block_objects_methods(self):
        self.memory = MCAgentMemory()
        bo_memid = BlockObjectNode.create(self.memory, [((1, 1, 1), (1, 2)),
                                                        ((2, 2, 2), (2, 3))])
        # Test get_object_by_id
        assert (self.memory.get_mem_by_id(bo_memid).blocks ==
                self.memory.get_object_by_id(bo_memid).blocks)
        # Test get_object_info_by_xyz byt cheking with BlockObject's mean xyz
        assert self.memory.get_object_info_by_xyz(
            (1, 1, 1), "BlockObjects")[0] == bo_memid
        # Test get_block_object_ids_by_xyz
        assert (self.memory.get_block_object_ids_by_xyz(
            (1, 1, 1))[0] == self.memory.get_object_info_by_xyz(
                (1, 1, 1), "BlockObjects")[0])
        assert self.memory.get_block_object_ids_by_xyz(
            (1, 1, 1))[0] == bo_memid
        # Test get_block_object_by_xyz -> same BlockObjectNode object as what we created before
        assert (self.memory.get_block_object_by_xyz(
            (1, 1, 1)).blocks == self.memory.get_mem_by_id(bo_memid).blocks)
        # Test get_block_object_by_id
        assert (self.memory.get_block_object_by_id(bo_memid).blocks ==
                self.memory.get_mem_by_id(bo_memid).blocks)

        # Test tag_block_object_from_schematic
        sch_memid = SchematicNode.create(self.memory, (((0, 0, 1), (1, 0)),
                                                       ((0, 0, 2), (1, 0)),
                                                       ((0, 0, 3), (2, 0))))
        bo1_memid = BlockObjectNode.create(self.memory, (((0, 0, 2), (1, 0)),
                                                         ((0, 0, 3), (2, 0))))
        self.memory.tag_block_object_from_schematic(bo1_memid, sch_memid)
        assert len(self.memory.get_triples(pred_text="_from_schematic")) == 1

    def test_inst_seg_node(self):
        self.memory = MCAgentMemory()
        inst_seg_memid = InstSegNode.create(self.memory,
                                            [(1, 0, 34), (1, 0, 35),
                                             (2, 0, 34), (3, 0, 34)],
                                            ["shiny", "bright"])
        # Test get_instseg_object_ids_by_xyz
        assert self.memory.get_instseg_object_ids_by_xyz(
            (1, 0, 34))[0][0] == inst_seg_memid
        assert self.memory.get_instseg_object_ids_by_xyz(
            (2, 0, 34))[0][0] == inst_seg_memid
        assert self.memory.get_instseg_object_ids_by_xyz(
            (3, 0, 34))[0][0] == inst_seg_memid

    def test_schematic_apis(self):
        self.memory = MCAgentMemory()
        schematic_memid = SchematicNode.create(self.memory,
                                               (((2, 0, 1),
                                                 (1, 0)), ((2, 0, 2), (1, 0)),
                                                ((2, 0, 3), (2, 0))))
        # test get_schematic_by_id
        assert (self.memory.get_schematic_by_id(schematic_memid).blocks ==
                self.memory.get_mem_by_id(schematic_memid).blocks)
        # test get_schematic_by_name
        self.memory.add_triple(subj=schematic_memid,
                               pred_text="has_name",
                               obj_text="house")
        assert (self.memory.get_schematic_by_name("house").blocks ==
                self.memory.get_mem_by_id(schematic_memid).blocks)
        bo1_memid = BlockObjectNode.create(self.memory, (((0, 0, 2), (1, 0)),
                                                         ((0, 0, 3), (2, 0))))
        # Test convert_block_object_to_schematic
        assert self.memory.convert_block_object_to_schematic(
            bo1_memid).NODE_TYPE == "Schematic"

    def test_set_mob_position(self):
        self.memory = MCAgentMemory()
        m = Mob(10, 65, Pos(1, 2, 3), Look(0, 0))
        assert self.memory.set_mob_position(m).pos == (1.0, 2.0, 3.0)

    def test_items_apis(self):
        self.memory = MCAgentMemory()
        low_level_data = {"bid_to_name": {(0, 0): "sand", (10, 4): "grass"}}
        item_memid = ItemStackNode.create(
            self.memory, ItemStack(12, Item(0, 0), Pos(0, 0, 0)),
            low_level_data)
        # test update_item_stack_eid
        assert self.memory.update_item_stack_eid(item_memid, 23).eid == 23
        # test set_item_stack_position
        new_item = ItemStack(23, Item(0, 0), Pos(2, 2, 2))
        self.memory.set_item_stack_position(new_item)
        assert self.memory.get_mem_by_id(item_memid).pos == (2.0, 2.0, 2.0)
        # test get_all_item_stacks
        item_2_memid = ItemStackNode.create(
            self.memory, ItemStack(34, Item(10, 4), Pos(2, 3, 3)),
            low_level_data)
        assert len(self.memory.get_all_item_stacks()) == 2

    def test_dance_api(self):
        self.memory = MCAgentMemory()

        def x():
            return 2

        self.memory.add_dance(x, "generate_num_dance",
                              ["generate_2", "dance_with_numbers"])
        assert len(self.memory.get_triples(obj_text="generate_2")) == 1
        assert len(self.memory.get_triples(obj_text="dance_with_numbers")) == 1
 def test_set_mob_position(self):
     self.memory = MCAgentMemory()
     m = Mob(10, 65, Pos(1, 2, 3), Look(0, 0))
     assert self.memory.set_mob_position(m).pos == (1.0, 2.0, 3.0)
示例#12
0
 def setUp(self):
     self.memory = MCAgentMemory(load_minecraft_specs=False)
     # Add dances to memory
     dance.add_default_dances(self.memory)
示例#13
0
class FakeAgent(DroidletAgent):
    CCW_LOOK_VECS = [(1, 0), (0, 1), (-1, 0), (0, -1)]
    default_frame = CraftAssistAgent.default_frame
    coordinate_transforms = CraftAssistAgent.coordinate_transforms

    def __init__(self, world, opts=None, do_heuristic_perception=False, prebuilt_perception=None):
        self.mark_airtouching_blocks = do_heuristic_perception
        self.head_height = HEAD_HEIGHT
        self.world = world
        self.chat_count = 0
        # use these to not have to re-init models if running many tests:
        self.prebuilt_perception = prebuilt_perception
        if not opts:
            opts = MockOpt()
        self.e2e_mode = getattr(opts, "e2e_mode", False)
        self.low_level_data = {
            "mobs": SPAWN_OBJECTS,
            "mob_property_data": craftassist_specs.get_mob_property_data(),
            "schematics": craftassist_specs.get_schematics(),
            "block_data": craftassist_specs.get_block_data(),
            "block_property_data": craftassist_specs.get_block_property_data(),
            "color_data": craftassist_specs.get_colour_data(),
            "boring_blocks": BORING_BLOCKS,
            "passable_blocks": PASSABLE_BLOCKS,
            "fill_idmeta": fill_idmeta,
            "color_bid_map": COLOR_BID_MAP,
        }
        super(FakeAgent, self).__init__(opts)
        self.do_heuristic_perception = do_heuristic_perception
        self.no_default_behavior = True
        self.last_task_memid = None
        pos = (0, 63, 0)
        if hasattr(self.world, "agent_data"):
            pos = self.world.agent_data["pos"]
        self.pos = np.array(pos, dtype="int")
        self.logical_form = None
        self.world_interaction_occurred = False
        self.world_interaction_occurred = False
        self._held_item: IDM = (0, 0)
        self._look_vec = (1, 0, 0)
        self._changed_blocks: List[Block] = []
        self._outgoing_chats: List[str] = []
        CraftAssistAgent.add_self_memory_node(self)

    def set_look_vec(self, x, y, z):
        l = np.array((x, y, z))
        l = l / np.linalg.norm(l)
        self._look_vec = (l[0], l[1], l[2])
        self.look = self.get_look()

    def init_perception(self):
        self.perception_modules = {}
        if self.prebuilt_perception:
            for k, v in self.prebuilt_perception:
                self.perception_modules[k] = v
                # make a method to do this....
                self.perception_modules[k].agent = self
        else:
            self.perception_modules["language_understanding"] = NSPQuerier(self.opts, self)
        self.perception_modules["low_level"] = LowLevelMCPerception(self, perceive_freq=1)
        self.perception_modules["heuristic"] = PerceptionWrapper(
            self,
            low_level_data=self.low_level_data,
            mark_airtouching_blocks=self.mark_airtouching_blocks,
        )

    def init_physical_interfaces(self):
        init_agent_interfaces(self)

    def init_memory(self):
        T = FakeMCTime(self.world)
        low_level_data = self.low_level_data.copy()
        low_level_data["check_inside"] = heuristic_perception.check_inside

        self.memory = MCAgentMemory(
            load_minecraft_specs=False,
            coordinate_transforms=self.coordinate_transforms,
            agent_time=T,
            agent_low_level_data=low_level_data,
        )
        # Add dances to memory
        dance.add_default_dances(self.memory)

    def init_controller(self):
        dialogue_object_classes = {}
        dialogue_object_classes["bot_capabilities"] = {"task": MCBotCapabilities, "data": {}}
        dialogue_object_classes["interpreter"] = MCInterpreter
        dialogue_object_classes["get_memory"] = MCGetMemoryHandler
        dialogue_object_classes["put_memory"] = PutMemoryHandler
        low_level_interpreter_data = {
            "block_data": craftassist_specs.get_block_data(),
            "special_shape_functions": SPECIAL_SHAPE_FNS,
            "color_bid_map": self.low_level_data["color_bid_map"],
            "get_all_holes_fn": heuristic_perception.get_all_nearby_holes,
            "get_locs_from_entity": get_locs_from_entity,
        }
        self.dialogue_manager = DialogueManager(
            self.memory,
            dialogue_object_classes,
            self.opts,
            low_level_interpreter_data=low_level_interpreter_data,
        )

    def set_logical_form(self, lf, chatstr, speaker):
        self.logical_form = {"logical_form": lf, "chatstr": chatstr, "speaker": speaker}

    def step(self):
        if hasattr(self.world, "step"):
            if self.world_interaction_occurred or self.count % WORLD_STEP == 0:
                self.world.step()
                self.world_interaction_occurred = False
        if hasattr(self, "recorder"):
            self.recorder.record_world()
        super().step()

    def perceive(self, force=False):
        if self.e2e_mode:
            perception_output = self.perception_modules["low_level"].perceive(force=force)
            self.areas_to_perceive = self.memory.update(perception_output, self.areas_to_perceive)[
                "areas_to_perceive"
            ]
            super().perceive(force=force)
            if "semseg" in self.perception_modules:
                sem_seg_perception_output = self.perception_modules["semseg"].perceive()
                self.memory.update(sem_seg_perception_output)
            return
        # clear the chat buffer
        self.get_incoming_chats()
        if self.logical_form:  # use the logical form as given...
            DroidletAgent.process_language_perception(
                self,
                self.logical_form["speaker"],
                self.logical_form["chatstr"],
                self.logical_form["chatstr"],
                self.logical_form["logical_form"],
            )
            force = True
        perception_output = self.perception_modules["low_level"].perceive(force=force)
        self.areas_to_perceive = self.memory.update(perception_output, self.areas_to_perceive)[
            "areas_to_perceive"
        ]
        if self.do_heuristic_perception:
            if force or not self.agent.memory.task_stack_peek():
                # perceive from heuristic perception module
                heuristic_perception_output = self.perception_modules["heuristic"].perceive()
                self.memory.update(heuristic_perception_output)

    def controller_step(self):
        CraftAssistAgent.controller_step(self)
        # if the logical form was set explicitly, clear it, so that it won't keep
        # being perceived and used to respawn new interpreters
        self.logical_form = None

    def setup_test(self):
        self.task_steps_count = 0

    def clear_outgoing_chats(self):
        self._outgoing_chats.clear()

    def get_last_outgoing_chat(self):
        try:
            return self._outgoing_chats[-1]
        except IndexError:
            return None

    def task_step(self):
        CraftAssistAgent.task_step(self, sleep_time=0)

    def point_at(*args):
        pass

    def get_blocks(self, xa, xb, ya, yb, za, zb):
        return self.world.get_blocks(xa, xb, ya, yb, za, zb)

    def get_local_blocks(self, r):
        x, y, z = self.pos
        return self.get_blocks(x - r, x + r, y - r, y + r, z - r, z + r)

    def get_incoming_chats(self):
        c = self.chat_count
        self.chat_count = len(self.world.chat_log)
        return self.world.chat_log[c:].copy()

    def get_player(self):
        return Player(1, "fake_agent", Pos(*self.pos), self.get_look(), Item(*self._held_item))

    def get_mobs(self):
        return self.world.get_mobs()

    def get_item_stacks(self):
        return self.world.get_item_stacks()

    def get_other_players(self):
        return self.world.get_players()

    def get_other_player_by_name(self):
        raise NotImplementedError()

    def get_vision(self):
        raise NotImplementedError()

    def get_line_of_sight(self):
        raise NotImplementedError()

    def get_look(self):
        yaw, pitch = yaw_pitch(self._look_vec)
        return Look(yaw, pitch)

    def get_player_line_of_sight(self, player_struct):
        if hasattr(self.world, "get_line_of_sight"):
            pos = (player_struct.pos.x, player_struct.pos.y + HEAD_HEIGHT, player_struct.pos.z)
            pitch = player_struct.look.pitch
            yaw = player_struct.look.yaw
            xsect = self.world.get_line_of_sight(pos, yaw, pitch)
            if xsect is not None:
                return Pos(*xsect)
        else:
            raise NotImplementedError()

    def get_changed_blocks(self) -> List[Block]:
        # need a better solution here
        r = self._changed_blocks.copy()
        self._changed_blocks.clear()
        return r

    def safe_get_changed_blocks(self) -> List[Block]:
        return self.get_changed_blocks()

    ######################################
    ## World setup
    ######################################

    def set_blocks(self, xyzbms: List[Block], boring_blocks: Tuple[int], origin: XYZ = (0, 0, 0)):
        """Change the state of the world, block by block,
        store in memory"""

        changes_to_be_updated = CraftAssistPerceptionData(changed_block_attributes={})
        for xyz, idm in xyzbms:
            abs_xyz = tuple(np.array(xyz) + origin)
            self.perception_modules["low_level"].pending_agent_placed_blocks.add(abs_xyz)
            # TODO add force option so we don't need to make it as if agent placed
            interesting, player_placed, agent_placed = self.perception_modules[
                "low_level"
            ].mark_blocks_with_env_change(xyz, idm, boring_blocks)
            changes_to_be_updated.changed_block_attributes[(abs_xyz, idm)] = [
                interesting,
                player_placed,
                agent_placed,
            ]
            self.world.place_block((abs_xyz, idm))
        # TODO: to be named to normal update function
        self.memory.update(changes_to_be_updated, self.areas_to_perceive)

    def add_object(
        self, xyzbms: List[Block], origin: XYZ = (0, 0, 0), relations={}
    ) -> VoxelObjectNode:
        """Add an object to memory as if it was placed block by block

        Args:
        - xyzbms: a list of relative (xyz, idm)
        - origin: (x, y, z) of the corner

        Returns an VoxelObjectNode
        """
        boring_blocks = self.low_level_data["boring_blocks"]
        self.set_blocks(xyzbms, boring_blocks, origin)
        abs_xyz = tuple(np.array(xyzbms[0][0]) + origin)
        memid = self.memory.get_block_object_ids_by_xyz(abs_xyz)[0]
        for pred, obj in relations.items():
            self.memory.add_triple(subj=memid, pred_text=pred, obj_text=obj)
            # sooooorrry  FIXME? when we handle triples better in interpreter_helper
            if "has_" in pred:
                self.memory.tag(memid, obj)
        return self.memory.get_object_by_id(memid)

    # WARNING!! this does not step the world, but directly fast-forwards
    # to count.  Use only in world setup, once world is running!
    def add_object_ff_time(
        self, count, xyzbms: List[Block], origin: XYZ = (0, 0, 0), relations={}
    ) -> VoxelObjectNode:
        assert count >= self.world.count
        self.world.set_count(count)
        return self.add_object(xyzbms, origin, relations=relations)

    ######################################
    ## visualization
    ######################################

    def draw_slice(self, h=None, r=5, c=None):
        if not h:
            h = self.pos[1]
        if c:
            c = [c[0], h, c[1]]
        else:
            c = [self.pos[0], h, self.pos[2]]
        C = self.world.to_world_coords(c)
        A = self.world.to_world_coords(self.pos)
        shifted_agent_pos = [A[0] - C[0] + r, A[2] - C[2] + r]
        npy = self.world.get_blocks(
            c[0] - r, c[0] + r, c[1], c[1], c[2] - r, c[2] + r, transpose=False
        )
        npy = npy[:, 0, :, 0]
        try:
            npy[shifted_agent_pos[0], shifted_agent_pos[1]] = 1024
        except:
            pass
        mobnums = {"rabbit": -1, "cow": -2, "pig": -3, "chicken": -4, "sheep": -5}
        nummobs = {-1: "rabbit", -2: "cow", -3: "pig", -4: "chicken", -5: "sheep"}
        for mob in self.world.mobs:
            # todo only in the plane?
            p = np.round(np.array(self.world.to_world_coords(mob.pos)))
            p = p - C
            try:
                npy[p[0] + r, p[1] + r] = mobnums[mob.mobname]
            except:
                pass
        mapslice = ""
        height = npy.shape[0]
        width = npy.shape[1]

        def xs(x):
            return x + int(self.pos[0]) - r

        def zs(z):
            return z + int(self.pos[2]) - r

        mapslice = mapslice + " " * (width + 2) * 3 + "\n"
        for i in reversed(range(height)):
            mapslice = mapslice + str(xs(i)).center(3)
            for j in range(width):
                if npy[i, j] > 0:
                    if npy[i, j] == 1024:
                        mapslice = mapslice + " A "
                    else:
                        mapslice = mapslice + str(npy[i, j]).center(3)
                elif npy[i, j] == 0:
                    mapslice = mapslice + " * "
                else:
                    npy[i, j] = mapslice + " " + nummobs[npy[i, j]][0] + " "
            mapslice = mapslice + "\n"
            mapslice = mapslice + "   "
            for j in range(width):
                mapslice = mapslice + " * "
            mapslice = mapslice + "\n"
        mapslice = mapslice + "   "
        for j in range(width):
            mapslice = mapslice + str(zs(j)).center(3)

        return mapslice