Beispiel #1
0
    def get_add_scene(self, scene_name: str, library: str = "") -> dict:
        """
        Returns a valid add_scene command.

        :param scene_name: The name of the scene.
        :param library: The path to the records file. If left empty, the default library will be selected. See `SceneLibrarian.get_library_filenames()` and `SceneLibrarian.get_default_library()`.

        :return An add_scene command that the controller can then send.
        """

        if self.scene_librarian is None:
            self.scene_librarian = SceneLibrarian(library=library)

        record = self.scene_librarian.get_record(scene_name)
        return {
            "$type": "add_scene",
            "name": scene_name,
            "url": record.get_url()
        }
Beispiel #2
0
    def __init__(self, root_dir: Path):
        animation_lib = HumanoidAnimationLibrarian()
        scene_lib = SceneLibrarian()
        animation_scene_matrix = json.loads(
            Path("animation_scene_matrix.json").read_text())
        self.animations_and_scenes: List[_AnimationAndScene] = []
        for a in animation_scene_matrix:
            for s in animation_scene_matrix[a]:
                combo = _AnimationAndScene(
                    animation=animation_lib.get_record(a),
                    scene=scene_lib.get_record(s),
                    position=animation_scene_matrix[a][s]["position"],
                    rotation=animation_scene_matrix[a][s]["rotation"])
                self.animations_and_scenes.append(combo)
        super().__init__()
        self.root_dir = root_dir
        if not self.root_dir.exists():
            self.root_dir.mkdir(parents=True)

        # Set global values.
        self.communicate([{
            "$type": "set_render_quality",
            "render_quality": 5
        }, {
            "$type": "set_img_pass_encoding",
            "value": False
        }, {
            "$type": "set_vignette",
            "enabled": False
        }, {
            "$type": "set_shadow_strength",
            "strength": 1.0
        }, {
            "$type": "set_screen_size",
            "width": 1280,
            "height": 720
        }])
    def run(self):
        """ Generate room using COCO_TDW dataset """
        objects_in_scene = 15
        object_ids = []
        # Get Category-Object mapping
        TDW_COCO_models = TDW_relationships.get_COCO_TDW_mapping()
        # print("TDWCOCO:", TDW_COCO_models)
        # print("KEYS:", TDW_COCO_models.keys())

        # Gets COCO categories co-occurring in a scene
        # +5 is for dealing with failed object insertion attempts
        COCO_configurations = msCOCO_matrix.get_max_co_occurrence(5, int(objects_in_scene + 5))
        configuration_1 = COCO_configurations[0]
        print("Config 1:", configuration_1)
        # TDW models/objects
        objects = []
        for COCO_object in configuration_1:
            print(COCO_object)
            print(COCO_object.split())
            if len(COCO_object.split()) > 1:
                COCO_object = COCO_object.split()[-1]
                print(COCO_object)
            # Check if COCO category is a key in the COCO-TDW mapping
            if COCO_object in TDW_COCO_models.keys():
                # Gets random TDW model (from COCO-to-TDW map) based on COCO category key
                print(TDW_COCO_models[COCO_object])
                model = TDW_COCO_models[COCO_object][random.randint(0, len(TDW_COCO_models[COCO_object]) - 1)]
                objects.append(model)

        print("COCO to TDW objects:", objects)
        # print(len(objects))

        # Stores object categories that other objects can be placed upon (e.g. table, chair, couch, bed)
        surface_properties_list = TDW_COCO_models['table'] + TDW_COCO_models['chair'] + \
                                  TDW_COCO_models['bed'] + TDW_COCO_models['couch'] + \
                                  TDW_COCO_models['bench'] + TDW_COCO_models['refrigerator']
        surface_categories = []
        for surface_properties in surface_properties_list:
            surface_categories.append(surface_properties[0])

        print("Surface Categories:", surface_categories)

        # Stores the actual surface object instances/ids alongside number of objects on the surface
        surface_object_ids = {}

        self.start()
        positions_list = []  # Stores current model locations and radii
        scene_dimensions = []  # Store scene/environment dimensions
        init_setup_commands = [{"$type": "set_screen_size",
                                "width": 640,
                                "height": 481},
                               {"$type": "set_render_quality",
                                "render_quality": 1}]
        self.communicate(init_setup_commands)

        scene_lib = SceneLibrarian()
        # Disable physics when adding in new objects (objects float)
        self.communicate({"$type": "simulate_physics",
                          "value": False})

        for scene in scenes[1:]:
            # Load in scene
            # print("Scene", scene[0])
            if scene[3] == "interior" and scene[0] == "box_room_2018":
                self.start()
                scene_record = scene_lib.get_record(scene[0])
                self.communicate({"$type": "add_scene",
                                  "name": scene_record.name,
                                  "url": scene_record.get_url()})

                # Gets dimensions of environments (e.g. inside, outside) in the scene
                # This command returns environment data in the form of a list of serialized byte arrays
                scene_bytes = self.communicate({"$type": "send_environments",
                                                "frequency": "once"})

                # Iterating through data and parsing byte array
                # Ignoring the last element (the frame count)
                for b in scene_bytes[:-1]:
                    e = Environments(b)
                    for i in range(e.get_num()):
                        center = e.get_center(i)
                        bounds = e.get_bounds(i)
                        env_id = e.get_id(i)
                    scene_dimensions = [center, bounds, env_id]  # Center, bounds are tuples

                # Must come before set_pass_masks
                avatar_position = TDWUtils.array_to_vector3([0.9 * scene_dimensions[1][0] / 2,
                                                             scene_dimensions[1][1] / 2,
                                                             0])
                # print("Avatar Position:", avatar_position)
                self.communicate(TDWUtils.create_avatar(avatar_id="avatar",
                                                        position=avatar_position,
                                                        look_at={"x": 0,
                                                                 "y": scene_dimensions[0][1] / 2,
                                                                 "z": 0}))
                # Set collision mode
                self.communicate({"$type": "set_avatar_collision_detection_mode",
                                  "mode": "continuous_speculative",
                                  "avatar_id": "avatar"})

                # Alter FOV
                self.communicate({"$type": "set_field_of_view",
                                  "field_of_view": 80,
                                  "avatar_id": "avatar"})

                # # Gets rid of header (Model: Category)
                # objects = TDW_COCO_models[1:]
                # random.shuffle(objects)
                obj_count = 0
                obj_overlaps = 0  # Number of failed attempts to place object due to over-dense objects area
                while obj_count < objects_in_scene and obj_overlaps < 5:
                    # Handles if object has been added to a flat surface
                    added_to_surface = False
                    print("Object COUNT:", obj_count)
                    # Need to have random position for Bounds Data to return meaningful info
                    valid_obj_pos = {"x": random.uniform(-1 * scene_dimensions[1][0] / 2,
                                                         0.5 * scene_dimensions[1][0] / 2),
                                     "y": scene_dimensions[1][1] / 4,
                                     "z": random.uniform(-0.9 * scene_dimensions[1][2] / 2,
                                                         0.9 * scene_dimensions[1][2] / 2)}
                    print("First random position")
                    # Add in the object at random position
                    # Object will later be removed or updated accordingly after performing collision calculations
                    record = ModelLibrarian(library="models_full.json").get_record(objects[obj_count][0])
                    print("Record gotten")
                    print(objects[obj_count][0])
                    o_id = self.communicate({"$type": "add_object",
                                             "name": objects[obj_count][0],
                                             "url": record.get_url(),
                                             "scale_factor": record.scale_factor,
                                             "position": valid_obj_pos,
                                             "rotation": TDWUtils.VECTOR3_ZERO,
                                             "category": record.wcategory,
                                             "id": obj_count})

                    print("Random first add")

                    # Returns bound data for added object
                    bounds_data = self.communicate({"$type": "send_bounds",
                                                    "frequency": "once"})

                    print("Bounds returned")

                    # Appends object, with information on position and obj_radius, to positions_list
                    # Length of buffer array should be 1
                    # print("Bounds Data:", bounds_data)
                    for b in bounds_data[:-1]:
                        # print("Buffer Loop:", b)
                        b_id = OutputData.get_data_type_id(b)
                        if b_id == "boun":
                            # print("BOUNDS")
                            o = Bounds(b)
                            # print("# of Objects:", o.get_num())
                            # print("# of Failed Attempts:", obj_overlaps)
                            # print("Buffer Array:", b)
                            # print("Bounds Object:", o)
                            for i in range(o.get_num()):
                                print("Object ID:", o.get_id(i))
                                print("obj_count:", obj_count)
                                print("Object:", objects[obj_count][0], "Category:", objects[obj_count][1])
                                # print("Object Center:", o.get_center(i))
                                # Only want to compute valid_position for object we are about to add
                                # Skip any computation if this is not the case
                                if o.get_id(i) != obj_count:
                                    continue
                                # Useful for detecting if object fits in environment
                                # print("Calculating if object fits in environment")
                                width = distance.euclidean(o.get_left(i), o.get_right(i))
                                depth = distance.euclidean(o.get_front(i), o.get_back(i))
                                height = distance.euclidean(o.get_top(i), o.get_bottom(i))
                                # print("Width:", width)
                                # print("Depth:", depth)
                                # ("Height:", height)

                                # Useful for avoiding object overlap
                                # print("Calculating Object Bounds")
                                center_to_top = distance.euclidean(o.get_center(i), o.get_top(i))
                                center_to_bottom = distance.euclidean(o.get_center(i), o.get_bottom(i))
                                center_to_left = distance.euclidean(o.get_center(i), o.get_left(i))
                                center_to_right = distance.euclidean(o.get_center(i), o.get_right(i))
                                center_to_front = distance.euclidean(o.get_center(i), o.get_front(i))
                                center_to_back = distance.euclidean(o.get_center(i), o.get_back(i))
                                # Max object radius (center to diagonal of bounding box)
                                obj_radius = \
                                    max(math.sqrt(center_to_top ** 2 + center_to_left ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_right ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_left ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_right ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_left ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_right ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_left ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_right ** 2 + center_to_back ** 2))

                                # print("Obj_Radius:", obj_radius)

                                # Set sweeping radius, based on scene plane dimensions
                                l_radius = random.uniform(0, min(0.5 * scene_dimensions[1][0] / 2,
                                                                 0.5 * scene_dimensions[1][2] / 2))

                                # Checking that object fits in scene viewing
                                if (width > min(0.7 * scene_dimensions[1][0], 0.7 * scene_dimensions[1][2]) or
                                        depth > min(0.7 * scene_dimensions[1][0], 0.7 * scene_dimensions[1][2]) or
                                        height > 0.7 * scene_dimensions[1][1]):

                                    print("Object does not fit in scene")
                                    self.communicate([{"$type": "send_images",
                                                       "frequency": "never"},
                                                      {"$type": "destroy_object",
                                                       "id": obj_count}])
                                    # Ensures next attempt to load in item is not the same item as before
                                    random.shuffle(objects)
                                    break

                                # Not possible to find valid object position -- too many overlapping objects
                                elif (not self._get_object_position(scene_dimensions=scene_dimensions,
                                                                    object_positions=positions_list,
                                                                    object_to_add_radius=obj_radius,
                                                                    max_tries=20,
                                                                    location_radius=l_radius)[0]):
                                    print("Could not calculate valid object location")
                                    self.communicate([{"$type": "send_images",
                                                       "frequency": "never"},
                                                      {"$type": "destroy_object",
                                                       "id": obj_count}])
                                    obj_overlaps += 1
                                    # Ensures next attempt to load in item is not the same item as before
                                    random.shuffle(objects)
                                    break

                                # Find appropriate, non-overlapping object position
                                # Reset object position to the valid position
                                else:
                                    print("Object fits in scene")
                                    # Check if object fits on table, chair, couch, etc.
                                    # Add object if it fits, place it somewhere on top of the surface
                                    for surface_id in surface_object_ids.keys():
                                        print("Surface ID:", surface_id)
                                        # Skip placement feasibility if the object is already a surface-type object
                                        # Ex. no chair on top of a table
                                        if objects[obj_count][0] in surface_categories:
                                            print("Object: %s is already a surface object" % objects[obj_count][0])
                                            break

                                        # Check how many objects are on surface
                                        if surface_object_ids[surface_id] >= 3:
                                            print("Too many objects on surface")
                                            print("From surface objects dict:", surface_object_ids[surface_id])
                                            continue

                                        surface_bounds = self.get_bounds_data(surface_id)
                                        surface_area = distance.euclidean(surface_bounds.get_left(0),
                                                                          surface_bounds.get_right(0)) * \
                                                       distance.euclidean(surface_bounds.get_front(0),
                                                                          surface_bounds.get_back(0))
                                        obj_area = width * height
                                        if obj_area < surface_area:
                                            s_center_to_top = distance.euclidean(surface_bounds.get_center(0),
                                                                                 surface_bounds.get_top(0))
                                            s_center_to_bottom = distance.euclidean(surface_bounds.get_center(0),
                                                                                    surface_bounds.get_bottom(0))
                                            s_center_to_left = distance.euclidean(surface_bounds.get_center(0),
                                                                                  surface_bounds.get_left(0))
                                            s_center_to_right = distance.euclidean(surface_bounds.get_center(0),
                                                                                   surface_bounds.get_right(0))
                                            s_center_to_front = distance.euclidean(surface_bounds.get_center(0),
                                                                                   surface_bounds.get_front(0))
                                            s_center_to_back = distance.euclidean(surface_bounds.get_center(0),
                                                                                  surface_bounds.get_back(0))

                                            surface_radius = \
                                                max(math.sqrt(
                                                    s_center_to_top ** 2 + s_center_to_left ** 2 + s_center_to_front ** 2),
                                                    math.sqrt(
                                                        s_center_to_top ** 2 + s_center_to_right ** 2 + s_center_to_front ** 2),
                                                    math.sqrt(
                                                        s_center_to_top ** 2 + s_center_to_left ** 2 + s_center_to_back ** 2),
                                                    math.sqrt(
                                                        s_center_to_top ** 2 + s_center_to_right ** 2 + s_center_to_back ** 2),
                                                    math.sqrt(
                                                        s_center_to_bottom ** 2 + s_center_to_left ** 2 + s_center_to_front ** 2),
                                                    math.sqrt(
                                                        s_center_to_bottom ** 2 + s_center_to_right ** 2 + s_center_to_front ** 2),
                                                    math.sqrt(
                                                        s_center_to_bottom ** 2 + s_center_to_left ** 2 + s_center_to_back ** 2),
                                                    math.sqrt(
                                                        s_center_to_bottom ** 2 + s_center_to_right ** 2 + s_center_to_back ** 2))

                                            print("Surface-type object")
                                            self.communicate({"$type": "destroy_object",
                                                              "id": obj_count})

                                            # Adding the object to the top of the surface
                                            on_pos = surface_bounds.get_top(0)
                                            on_y = on_pos[1]
                                            on_pos = TDWUtils.get_random_point_in_circle(np.array(on_pos),
                                                                                         0.7 * surface_radius)
                                            on_pos[1] = on_y
                                            on_pos = TDWUtils.array_to_vector3(on_pos)
                                            on_rot = {"x": 0, "y": random.uniform(-45, 45), "z": 0}
                                            # Add the object.
                                            print("Model Name on Surface:", objects[obj_count][0])
                                            record = ModelLibrarian(library="models_full.json").get_record(
                                                objects[obj_count][0])
                                            on_id = self.communicate({"$type": "add_object",
                                                                      "name": objects[obj_count][0],
                                                                      "url": record.get_url(),
                                                                      "scale_factor": record.scale_factor,
                                                                      "position": on_pos,
                                                                      "rotation": on_rot,
                                                                      "category": record.wcategory,
                                                                      "id": obj_count})
                                            obj_count += 1
                                            surface_object_ids[surface_id] += 1
                                            object_ids.append(obj_count)
                                            print("Object added on top of surface")
                                            added_to_surface = True
                                            # Breaking out of surface objects loop
                                            break

                                    if added_to_surface:
                                        print("Breaking out of object loop")
                                        # Breaking out of object loop
                                        break

                                    print("Post-surface")

                                    valid_obj_pos = self._get_object_position(scene_dimensions=scene_dimensions,
                                                                              object_positions=positions_list,
                                                                              object_to_add_radius=obj_radius,
                                                                              max_tries=20,
                                                                              location_radius=l_radius)[1]
                                    print("Position calculated")
                                    positions_list.append(ObjectPosition(valid_obj_pos, obj_radius))
                                    self.communicate([{"$type": "send_images",
                                                       "frequency": "never"},
                                                      {"$type": "destroy_object",
                                                       "id": obj_count}])
                                    added_object_id = self.communicate({"$type": "add_object",
                                                                        "name": objects[obj_count][0],
                                                                        "url": record.get_url(),
                                                                        "scale_factor": record.scale_factor,
                                                                        "position": valid_obj_pos,
                                                                        "rotation": {"x": 0, "y": 0, "z": 0},
                                                                        "category": record.wcategory,
                                                                        "id": obj_count})
                                    # print("Object ID:", added_object_id)
                                    print("Regular object add")
                                    object_ids.append(added_object_id)
                                    # If TDW model belongs to surface categories, store id_information
                                    if objects[obj_count][0] in surface_categories:
                                        surface_object_ids[obj_count] = 0
                                    # Rotate the object randomly
                                    print("Rotating object")
                                    self.communicate({"$type": "rotate_object_by",
                                                      "angle": random.uniform(-45, 45),
                                                      "axis": "yaw",
                                                      "id": obj_count,
                                                      "is_world": True})

                                     # Minimal rotating for position differences
                                     # Don't rotate the object if doing so will result in overlap into scene
                                     if not (o.get_bottom(i)[1] < 0 or o.get_top(i)[1] > 0.9 * scene_dimensions[1][1]):
                                         pitch_angle = random.uniform(-45, 45)
                                         self.communicate({"$type": "rotate_object_by",
                                                           "angle": pitch_angle,
                                                           "axis": "pitch",
                                                           "id": obj_count,
                                                           "is_world": True})
                                         roll_angle = random.uniform(-45, 45)
                                         self.communicate({"$type": "rotate_object_by",
                                                           "angle": roll_angle,
                                                           "axis": "roll",
                                                           "id": obj_count,
                                    
                                                           "is_world": True})
                                     # Don't need this for just changing positions
                                     # Setting random materials/textures
                                     # Looping through sub-objects and materials
                                     sub_count = 0
                                     for sub_object in record.substructure:
                                         # Loop through materials in sub-objects
                                         for j in range(len(sub_object)):
                                             # Get random material and load in
                                             material = random.choice(materials[1:])
                                             self.load_material(material)
                                             print("Material loaded")
                                    
                                             # Set random material on material of sub-object
                                             self.communicate({"$type": "set_visual_material",
                                                               "material_index": j,
                                                               "material_name": material[0],
                                                               "object_name": sub_object['name'],
                                                               "id": obj_count})
                                             print("Material set")
                                             sub_count += 1
                                             if sub_count > 10:
                                                 break
                                         break

                                    print("Updating count")
                                    obj_count += 1
                                    print("Breaking out of object_id loop")
                                    break

                            # Break out of buffer loop
                            print("Breaking out of buffer loop")
                            break

                    # Move onto next iteration of while loop (next object to load in)
                    print("Object added - next while loop iteration")
                    continue

                # for i in range(200):
                #     self.communicate({"$type": "simulate_physics",
                #                       "value": False})

                # Enable image capture
                self.communicate({"$type": "set_pass_masks",
                                  "avatar_id": "avatar",
                                  "pass_masks": ["_img", "_id"]})

                self.communicate({"$type": "send_images",
                                  "frequency": "always"})

                # Capture scene
                # NOTE: THESE SCENES GET REPLACED IN THE TARGET DIRECTORY
                scene_data = self.communicate({"$type": "look_at_position",
                                               "avatar_id": "avatar",
                                               "position": {"x": 0,
                                                            "y": scene_dimensions[0][1] / 2,
                                                            "z": 0}})
                images = Images(scene_data[0])
                TDWUtils.save_images(images, TDWUtils.zero_padding(i), output_directory=path)
                print("Object ids:", object_ids)
Beispiel #4
0
class Controller(object):
    """
    Base class for all controllers.

    Usage:

    ```python
    from tdw.controller import Controller
    c = Controller()
    c.start()
    ```
    """
    def __init__(self,
                 port: int = 1071,
                 check_version: bool = True,
                 launch_build: bool = True):
        """
        Create the network socket and bind the socket to the port.

        :param port: The port number.
        :param check_version: If true, the controller will check the version of the build and print the result.
        :param launch_build: If True, automatically launch the build. If one doesn't exist, download and extract the correct version. Set this to False to use your own build, or (if you are a backend developer) to use Unity Editor.
        """

        # Compare the installed version of the tdw Python module to the latest on PyPi.
        # If there is a difference, recommend an upgrade.
        if check_version:
            self._check_pypi_version()

        # Launch the build.
        if launch_build:
            Controller.launch_build()

        context = zmq.Context()

        self.socket = context.socket(zmq.REP)
        self.socket.bind('tcp://*:' + str(port))

        self.socket.recv()

        self.model_librarian: Optional[ModelLibrarian] = None
        self.scene_librarian: Optional[SceneLibrarian] = None
        self.material_librarian: Optional[MaterialLibrarian] = None
        self.hdri_skybox_librarian: Optional[HDRISkyboxLibrarian] = None
        self.humanoid_librarian: Optional[HumanoidLibrarian] = None
        self.humanoid_animation_librarian: Optional[
            HumanoidAnimationLibrarian] = None

        # Compare the version of the tdw module to the build version.
        if check_version and launch_build:
            self._check_build_version()

    def communicate(self, commands: Union[dict, List[dict]]) -> list:
        """
        Send commands and receive output data in response.

        :param commands: A list of JSON commands.

        :return The output data from the build.
        """

        if not isinstance(commands, list):
            commands = [commands]

        self.socket.send_multipart([json.dumps(commands).encode('utf-8')])

        return self.socket.recv_multipart()

    def start(self, scene="ProcGenScene") -> None:
        """
        Init TDW.

        :param scene: The scene to load.
        """

        self.communicate([{"$type": "load_scene", "scene_name": scene}])

    def get_add_object(self,
                       model_name: str,
                       object_id: int,
                       position={
                           "x": 0,
                           "y": 0,
                           "z": 0
                       },
                       rotation={
                           "x": 0,
                           "y": 0,
                           "z": 0
                       },
                       library: str = "") -> dict:
        """
        Returns a valid add_object command.

        :param model_name: The name of the model.
        :param position: The position of the model.
        :param rotation: The starting rotation of the model, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `ModelLibrarian.get_library_filenames()` and `ModelLibrarian.get_default_library()`.
        :param object_id: The ID of the new object.

        :return An add_object command that the controller can then send.
        """

        if self.model_librarian is None or (
                library != "" and self.model_librarian.library != library):
            self.model_librarian = ModelLibrarian(library=library)

        record = self.model_librarian.get_record(model_name)

        return {
            "$type": "add_object",
            "name": model_name,
            "url": record.get_url(),
            "scale_factor": record.scale_factor,
            "position": position,
            "rotation": rotation,
            "category": record.wcategory,
            "id": object_id
        }

    def get_add_material(self, material_name: str, library: str = "") -> dict:
        """
        Returns a valid add_material command.

        :param material_name: The name of the material.
        :param library: The path to the records file. If left empty, the default library will be selected. See `MaterialLibrarian.get_library_filenames()` and `MaterialLibrarian.get_default_library()`.

        :return An add_material command that the controller can then send.
        """

        if self.material_librarian is None:
            self.material_librarian = MaterialLibrarian(library=library)

        record = self.material_librarian.get_record(material_name)
        return {
            "$type": "add_material",
            "name": material_name,
            "url": record.get_url()
        }

    def get_add_scene(self, scene_name: str, library: str = "") -> dict:
        """
        Returns a valid add_scene command.

        :param scene_name: The name of the scene.
        :param library: The path to the records file. If left empty, the default library will be selected. See `SceneLibrarian.get_library_filenames()` and `SceneLibrarian.get_default_library()`.

        :return An add_scene command that the controller can then send.
        """

        if self.scene_librarian is None:
            self.scene_librarian = SceneLibrarian(library=library)

        record = self.scene_librarian.get_record(scene_name)
        return {
            "$type": "add_scene",
            "name": scene_name,
            "url": record.get_url()
        }

    def get_add_hdri_skybox(self, skybox_name: str, library: str = "") -> dict:
        """
        Returns a valid add_hdri_skybox command.

        :param skybox_name: The name of the skybox.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HDRISkyboxLibrarian.get_library_filenames()` and `HDRISkyboxLibrarian.get_default_library()`.

        :return An add_hdri_skybox command that the controller can then send.
        """

        if self.hdri_skybox_librarian is None:
            self.hdri_skybox_librarian = HDRISkyboxLibrarian(library=library)

        record = self.hdri_skybox_librarian.get_record(skybox_name)
        return {
            "$type": "add_hdri_skybox",
            "name": skybox_name,
            "url": record.get_url(),
            "exposure": record.exposure,
            "initial_skybox_rotation": record.initial_skybox_rotation,
            "sun_elevation": record.sun_elevation,
            "sun_initial_angle": record.sun_initial_angle,
            "sun_intensity": record.sun_intensity
        }

    def get_add_humanoid(self,
                         humanoid_name: str,
                         object_id: int,
                         position={
                             "x": 0,
                             "y": 0,
                             "z": 0
                         },
                         rotation={
                             "x": 0,
                             "y": 0,
                             "z": 0
                         },
                         library: str = "") -> dict:
        """
        Returns a valid add_humanoid command.

        :param humanoid_name: The name of the humanoid.
        :param position: The position of the humanoid.
        :param rotation: The starting rotation of the humanoid, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HumanoidLibrarian.get_library_filenames()` and `HumanoidLibrarian.get_default_library()`.
        :param object_id: The ID of the new object.

        :return An add_humanoid command that the controller can then send.
        """

        if self.humanoid_librarian is None or (
                library != "" and self.humanoid_librarian.library != library):
            self.humanoid_librarian = HumanoidLibrarian(library=library)

        record = self.humanoid_librarian.get_record(humanoid_name)

        return {
            "$type": "add_humanoid",
            "name": humanoid_name,
            "url": record.get_url(),
            "position": position,
            "rotation": rotation,
            "id": object_id
        }

    def get_add_humanoid_animation(
            self,
            humanoid_animation_name: str,
            library="") -> (dict, HumanoidAnimationRecord):
        """
        Returns a valid add_humanoid_animation command and the record (which you will need to play an animation).

        :param humanoid_animation_name: The name of the animation.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HumanoidAnimationLibrarian.get_library_filenames()` and `HumanoidAnimationLibrarian.get_default_library()`.

        return An add_humanoid_animation command that the controller can then send.
        """

        if self.humanoid_animation_librarian is None:
            self.humanoid_animation_librarian = HumanoidAnimationLibrarian(
                library=library)

        record = self.humanoid_animation_librarian.get_record(
            humanoid_animation_name)
        return {
            "$type": "add_humanoid_animation",
            "name": humanoid_animation_name,
            "url": record.get_url()
        }, record

    def load_streamed_scene(self, scene="tdw_room_2018") -> None:
        """
        Load a streamed scene. This is equivalent to: `c.communicate(c.get_add_scene(scene))`

        :param scene: The name of the streamed scene.
        """

        self.communicate(self.get_add_scene(scene))

    def add_object(self,
                   model_name: str,
                   position={
                       "x": 0,
                       "y": 0,
                       "z": 0
                   },
                   rotation={
                       "x": 0,
                       "y": 0,
                       "z": 0
                   },
                   library: str = "") -> int:
        """
        Add a model to the scene. This is equivalent to: `c.communicate(c.get_add_object())`

        :param model_name: The name of the model.
        :param position: The position of the model.
        :param rotation: The starting rotation of the model, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `ModelLibrarian.get_library_filenames()` and `ModelLibrarian.get_default_library()`.

        :return The ID of the new object.
        """

        object_id = Controller.get_unique_id()
        self.communicate(
            self.get_add_object(model_name, object_id, position, rotation,
                                library))

        return object_id

    def get_version(self) -> Tuple[str, str]:
        """
        Send a send_version command to the build.

        :return The TDW version and the Unity Engine version.
        """

        resp = self.communicate({"$type": "send_version"})
        for r in resp[:-1]:
            if Version.get_data_type_id(r) == "vers":
                v = Version(r)
                return v.get_tdw_version(), v.get_unity_version()
        if len(resp) == 1:
            raise Exception(
                "Tried receiving version output data but didn't receive anything!"
            )
        raise Exception(f"Expected output data with ID vers but got: " +
                        Version.get_data_type_id(resp[0]))

    @staticmethod
    def get_unique_id() -> int:
        """
        Generate a unique integer. Useful when creating objects.

        :return The new unique ID.
        """

        return int.from_bytes(os.urandom(3), byteorder='big')

    @staticmethod
    def get_frame(frame: bytes) -> int:
        """
        Converts the frame byte array to an integer.

        :param frame: The frame as bytes.

        :return The frame as an integer.
        """

        return int.from_bytes(frame, byteorder='big')

    @staticmethod
    def launch_build() -> None:
        """
        Launch the build. If a build doesn't exist at the expected location, download one to that location.
        """

        # Download the build.
        if not Build.BUILD_PATH.exists():
            print(
                f"Couldn't find build at {Build.BUILD_PATH}\nDownloading now..."
            )
            success = Build.download()
            if not success:
                print("You need to launch your own build.")
        else:
            success = True
        # Launch the build.
        if success:
            Popen(str(Build.BUILD_PATH.resolve()))

    def _check_build_version(self,
                             version: str = __version__,
                             build_version: str = None) -> None:
        """
        Check the version of the build. If there is no build, download it.
        If the build is of the wrong version, recommend an upgrade.


        :param version: The version of TDW. You can set this to an arbitrary version for testing purposes.
        :param build_version: If not None, this overrides the expected build version. Only override for debugging.
        """

        v = PyPi.strip_post_release(version)
        tdw_version, unity_version = self.get_version()
        # Override the build version for testing.
        if build_version is not None:
            tdw_version = build_version
        pypi_version = PyPi.get_latest_minor_release(tdw_version)
        print(
            f"Build version {tdw_version}\nUnity Engine {unity_version}\nPython tdw module version {version}"
        )
        if v < tdw_version:
            print(
                "WARNING! Your TDW build is newer than your tdw Python module. They might not be compatible."
            )
            print(
                f"To download the correct build:\n\nfrom tdw.release.build import Build\nBuild.download(version={v})"
            )
            print(
                f"\nTo upgrade your Python module (usually recommended):\n\npip3 install tdw=={pypi_version}"
            )
        elif v > tdw_version:
            print(
                "WARNING! Your TDW build is older than your tdw Python module. Downloading the correct build..."
            )
            Build.download(v)

    @staticmethod
    def _check_pypi_version(v_installed_override: str = None,
                            v_pypi_override: str = None) -> None:
        """
        Compare the version of the tdw Python module to the latest on PyPi.
        If there is a mismatch, offer an upgrade recommendation.

        :param v_installed_override: Override for the installed version. Change this to debug.
        :param v_pypi_override: Override for the PyPi version. Change this to debug.
        """

        # Get the version of the installed tdw module.
        installed_tdw_version = PyPi.get_installed_tdw_version()
        # Get the latest version of the tdw module on PyPi.
        pypi_version = PyPi.get_pypi_version()

        # Apply overrides
        if v_installed_override is not None:
            installed_tdw_version = v_installed_override
        if v_pypi_override is not None:
            pypi_version = v_pypi_override

        # If there is a mismatch, recommend an upgrade.
        if installed_tdw_version != pypi_version:
            # Strip the installed version of the post-release suffix (e.g. 1.6.3.4 to 1.6.3).
            stripped_installed_version = PyPi.strip_post_release(
                installed_tdw_version)
            # This message is here only for debugging.
            if stripped_installed_version != __version__:
                print(
                    f"Your installed version: {stripped_installed_version} "
                    f"doesn't match tdw.version.__version__: {__version__} "
                    f"(this may be because you're using code from the tdw repo that is ahead of PyPi)."
                )
            # Strip the latest PyPi version of the post-release suffix.
            stripped_pypi_version = PyPi.strip_post_release(pypi_version)
            print(
                f"You are using TDW {installed_tdw_version} but version {pypi_version} is available."
            )

            # If user is behind by a post release, recommend an upgrade to the latest.
            # (Example: installed version is 1.6.3.4 and PyPi version is 1.6.3.5)
            if stripped_installed_version == stripped_pypi_version:
                print(
                    f"Upgrade to the latest version of TDW:\npip3 install tdw -U"
                )

            # Using a version behind the latest (e.g. latest is 1.6.3 and installed is 1.6.2)
            # If the user is behind by a major or minor release, recommend either upgrading to a minor release
            # or to a major release.
            # (Example: installed version is 1.6.3.4 and PyPi version is 1.7.0.0)
            else:
                installed_major = PyPi.get_major_release(
                    stripped_installed_version)
                pypi_minor = PyPi.get_latest_minor_release(
                    stripped_installed_version)
                # Minor release mis-match.
                if PyPi.strip_post_release(
                        pypi_minor) != stripped_installed_version:
                    print(
                        f"To upgrade to the last version of 1.{installed_major}:\n"
                        f"pip3 install tdw=={pypi_minor}")
                pypi_major = PyPi.get_major_release(stripped_pypi_version)
                # Major release mis-match.
                if installed_major != pypi_major:
                    # Offer to upgrade to a major release.
                    print(
                        f"Consider upgrading to the latest version of TDW ({stripped_pypi_version}):"
                        f"\npip3 install tdw -U")
        else:
            print("Your installed tdw Python module is up to date with PyPi.")
Beispiel #5
0
    def run(self):
        self.start()
        positions_list = []  # Stores current model locations and radii
        scene_dimensions = []  # Store scene/environment dimensions
        init_setup_commands = [{
            "$type": "set_screen_size",
            "width": 1280,
            "height": 962
        }, {
            "$type": "set_render_quality",
            "render_quality": 5
        }]
        self.communicate(init_setup_commands)

        scene_lib = SceneLibrarian()
        # Disable physics when adding in new objects (objects float)
        self.communicate({"$type": "simulate_physics", "value": False})

        for scene in scenes[1:]:
            # Load in scene
            print("Scene", scene[0])
            if scene[3] == "interior" and scene[0] == "box_room_2018":
                self.start()
                scene_record = scene_lib.get_record(scene[0])
                self.communicate({
                    "$type": "add_scene",
                    "name": scene_record.name,
                    "url": scene_record.get_url()
                })

                # Gets dimensions of environments (e.g. inside, outside) in the scene
                # This command returns environment data in the form of a list of serialized byte arrays
                scene_bytes = self.communicate({
                    "$type": "send_environments",
                    "frequency": "once"
                })

                # Iterating through data and parsing byte array
                # Ignoring the last element (the frame count)
                for b in scene_bytes[:-1]:
                    e = Environments(b)
                    for i in range(e.get_num()):
                        center = e.get_center(i)
                        bounds = e.get_bounds(i)
                        env_id = e.get_id(i)
                    scene_dimensions = [center, bounds,
                                        env_id]  # Center, bounds are tuples

                # Must come before set_pass_masks
                avatar_position = TDWUtils.array_to_vector3([
                    0.9 * scene_dimensions[1][0] / 2,
                    scene_dimensions[1][1] / 2, 0
                ])
                print("Avatar Position:", avatar_position)
                self.communicate(
                    TDWUtils.create_avatar(avatar_id="avatar",
                                           position=avatar_position,
                                           look_at={
                                               "x": 0,
                                               "y": scene_dimensions[0][1] / 2,
                                               "z": 0
                                           }))
                # Set collision mode
                self.communicate({
                    "$type": "set_avatar_collision_detection_mode",
                    "mode": "continuous_speculative",
                    "avatar_id": "avatar"
                })

                # Alter FOV
                self.communicate({
                    "$type": "set_field_of_view",
                    "field_of_view": 80,
                    "avatar_id": "avatar"
                })

                # Gets rid of header (Model: Category)
                objects = models[1:]
                random.shuffle(objects)
                obj_count = 0
                obj_overlaps = 0  # Number of failed attempts to place object due to over-dense objects area
                while obj_count < 30 and obj_overlaps < 5:
                    # Need to have random position for Bounds Data to return meaningful info
                    valid_obj_pos = {
                        "x":
                        random.uniform(-1 * scene_dimensions[1][0] / 2,
                                       0.5 * scene_dimensions[1][0] / 2),
                        "y":
                        scene_dimensions[1][1] / 4,
                        "z":
                        random.uniform(-0.9 * scene_dimensions[1][2] / 2,
                                       0.9 * scene_dimensions[1][2] / 2)
                    }

                    # Add in the object at random position
                    # Object will later be removed or updated accordingly after performing collision calculations
                    record = ModelLibrarian(
                        library="models_full.json").get_record(
                            objects[obj_count][0])
                    self.communicate({
                        "$type": "add_object",
                        "name": objects[obj_count][0],
                        "url": record.get_url(),
                        "scale_factor": record.scale_factor,
                        "position": valid_obj_pos,
                        "rotation": {
                            "x": 0,
                            "y": 0,
                            "z": 0
                        },
                        "category": record.wcategory,
                        "id": obj_count
                    })

                    # Returns bound data for added object
                    bounds_data = self.communicate({
                        "$type": "send_bounds",
                        "frequency": "once"
                    })

                    # Appends object, with information on position and obj_radius, to positions_list
                    # Length of buffer array should be 1
                    print("Bounds Data:", bounds_data)
                    for b in bounds_data[:-1]:
                        print("Buffer Loop:", b)
                        b_id = OutputData.get_data_type_id(b)
                        if b_id == "boun":
                            print("BOUNDS")
                            o = Bounds(b)
                            print("# of Objects:", o.get_num())
                            print("# of Failed Attempts:", obj_overlaps)
                            print("Buffer Array:", b)
                            print("Bounds Object:", o)
                            for i in range(o.get_num()):
                                print("Object ID:", o.get_id(i))
                                print("obj_count:", obj_count)
                                print("Object:", objects[obj_count][0],
                                      "Category:", objects[obj_count][1])
                                print("Object Center:", o.get_center(i))
                                # Only want to compute valid_position for object we are about to add
                                # Skip any computation if this is not the case
                                if o.get_id(i) != obj_count:
                                    continue
                                # Useful for detecting if object fits in environment
                                print(
                                    "Calculating if object fits in environment"
                                )
                                width = distance.euclidean(
                                    o.get_left(i), o.get_right(i))
                                depth = distance.euclidean(
                                    o.get_front(i), o.get_back(i))
                                height = distance.euclidean(
                                    o.get_top(i), o.get_bottom(i))
                                print("Width:", width)
                                print("Depth:", depth)
                                print("Height:", height)

                                # Useful for avoiding object overlap
                                print("Calculating Object Bounds")
                                center_to_top = distance.euclidean(
                                    o.get_center(i), o.get_top(i))
                                center_to_bottom = distance.euclidean(
                                    o.get_center(i), o.get_bottom(i))
                                center_to_left = distance.euclidean(
                                    o.get_center(i), o.get_left(i))
                                center_to_right = distance.euclidean(
                                    o.get_center(i), o.get_right(i))
                                center_to_front = distance.euclidean(
                                    o.get_center(i), o.get_front(i))
                                center_to_back = distance.euclidean(
                                    o.get_center(i), o.get_back(i))
                                # Max object radius (center to diagonal of bounding box)
                                obj_radius = \
                                    max(math.sqrt(center_to_top ** 2 + center_to_left ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_right ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_left ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_top ** 2 + center_to_right ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_left ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_right ** 2 + center_to_front ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_left ** 2 + center_to_back ** 2),
                                        math.sqrt(center_to_bottom ** 2 + center_to_right ** 2 + center_to_back ** 2))

                                print("Obj_Radius:", obj_radius)

                                # Set sweeping radius, based on scene plane dimensions
                                l_radius = random.uniform(
                                    0,
                                    min(0.9 * scene_dimensions[1][0] / 2,
                                        0.9 * scene_dimensions[1][2] / 2))

                                # Checking that object fits in scene viewing
                                if (width > min(0.7 * scene_dimensions[1][0],
                                                0.7 * scene_dimensions[1][2])
                                        or depth > min(
                                            0.7 * scene_dimensions[1][0],
                                            0.7 * scene_dimensions[1][2]) or
                                        height > 0.7 * scene_dimensions[1][1]):

                                    print("Object does not fit in scene")
                                    self.communicate([{
                                        "$type": "send_images",
                                        "frequency": "never"
                                    }, {
                                        "$type": "destroy_object",
                                        "id": obj_count
                                    }])
                                    # Ensures next attempt to load in item is not the same item as before
                                    random.shuffle(objects)
                                    break

                                # Not possible to find valid object position -- too many overlapping objects
                                elif (not self._get_object_position(
                                        scene_dimensions=scene_dimensions,
                                        object_positions=positions_list,
                                        object_to_add_radius=obj_radius,
                                        max_tries=20,
                                        location_radius=l_radius)[0]):
                                    print(
                                        "Could not calculate valid object location"
                                    )
                                    self.communicate([{
                                        "$type": "send_images",
                                        "frequency": "never"
                                    }, {
                                        "$type": "destroy_object",
                                        "id": obj_count
                                    }])
                                    obj_overlaps += 1
                                    # Ensures next attempt to load in item is not the same item as before
                                    random.shuffle(objects)
                                    break

                                # Find appropriate, non-overlapping object position
                                # Reset object position to the valid position
                                else:
                                    print("Object fits in scene")
                                    valid_obj_pos = self._get_object_position(
                                        scene_dimensions=scene_dimensions,
                                        object_positions=positions_list,
                                        object_to_add_radius=obj_radius,
                                        max_tries=20,
                                        location_radius=l_radius)[1]
                                    print("Position calculated")
                                    positions_list.append(
                                        ObjectPosition(valid_obj_pos,
                                                       obj_radius))
                                    self.communicate([{
                                        "$type": "send_images",
                                        "frequency": "never"
                                    }, {
                                        "$type": "destroy_object",
                                        "id": obj_count
                                    }])
                                    print("Object ready to reset")
                                    self.communicate([{
                                        "$type": "send_images",
                                        "frequency": "never"
                                    }, {
                                        "$type":
                                        "add_object",
                                        "name":
                                        objects[obj_count][0],
                                        "url":
                                        record.get_url(),
                                        "scale_factor":
                                        record.scale_factor,
                                        "position":
                                        valid_obj_pos,
                                        "rotation": {
                                            "x": 0,
                                            "y": 0,
                                            "z": 0
                                        },
                                        "category":
                                        record.wcategory,
                                        "id":
                                        obj_count
                                    }])
                                    print("Object reset")

                                    # Rotate the object randomly
                                    print("Rotating object")
                                    self.communicate({
                                        "$type":
                                        "rotate_object_by",
                                        "angle":
                                        random.uniform(-45, 45),
                                        "axis":
                                        "yaw",
                                        "id":
                                        obj_count,
                                        "is_world":
                                        True
                                    })

                                    # Don't rotate the object if doing so will result in overlap into scene
                                    if not (o.get_bottom(i)[1] < 0
                                            or o.get_top(i)[1] >
                                            0.9 * scene_dimensions[1][1]):
                                        pitch_angle = random.uniform(-45, 45)
                                        self.communicate({
                                            "$type": "rotate_object_by",
                                            "angle": pitch_angle,
                                            "axis": "pitch",
                                            "id": obj_count,
                                            "is_world": True
                                        })
                                        roll_angle = random.uniform(-45, 45)
                                        self.communicate({
                                            "$type": "rotate_object_by",
                                            "angle": roll_angle,
                                            "axis": "roll",
                                            "id": obj_count,
                                            "is_world": True
                                        })

                                    # Setting random materials/textures
                                    # Looping through sub-objects and materials
                                    sub_count = 0
                                    for sub_object in record.substructure:
                                        # Loop through materials in sub-objects
                                        for j in range(len(sub_object)):
                                            # Get random material and load in
                                            material = random.choice(
                                                materials[1:])
                                            self.load_material(material)
                                            print("Material loaded")

                                            # Set random material on material of sub-object
                                            self.communicate({
                                                "$type":
                                                "set_visual_material",
                                                "material_index":
                                                j,
                                                "material_name":
                                                material[0],
                                                "object_name":
                                                sub_object['name'],
                                                "id":
                                                obj_count
                                            })
                                            print("Material set")
                                            sub_count += 1
                                            if sub_count > 10:
                                                break
                                        break

                                    print("Updating count")
                                    obj_count += 1
                                    print("Breaking out of object_id loop")
                                    break

                            # Break out of buffer loop
                            print("Breaking out of buffer loop")
                            break

                    # Move onto next iteration of while loop (next object to load in)
                    print("Object added - next while loop iteration")
                    continue

                # Enable image capture
                self.communicate({
                    "$type": "set_pass_masks",
                    "avatar_id": "avatar",
                    "pass_masks": ["_img", "_id"]
                })

                self.communicate({
                    "$type": "send_images",
                    "frequency": "always"
                })

                # Capture scene
                scene_data = self.communicate({
                    "$type": "look_at_position",
                    "avatar_id": "avatar",
                    "position": {
                        "x": 0,
                        "y": scene_dimensions[0][1] / 2,
                        "z": 0
                    }
                })
                images = Images(scene_data[0])
                TDWUtils.save_images(images,
                                     TDWUtils.zero_padding(i),
                                     output_directory=path)
    def run(self):
        # Setting up image directory
        output_directory = "multiple_objects_pics"
        parent_dir = '/Python/Leonard/multiple_objects'
        path = os.path.join(parent_dir, output_directory)
        print(path)
        if os.path.exists(path):
            shutil.rmtree(path)
            time.sleep(0.5)
            os.mkdir(path)

        # Initialize scene
        self.start()

        # Load in scene
        scene_lib = SceneLibrarian()
        scene_record = scene_lib.get_record("tdw_room_2018")
        self.communicate({"$type": "add_scene",
                          "name": scene_record.name,
                          "url": scene_record.get_url()})

        # Resize screen; note render_quality 5 is best
        self.communicate([{"$type": "set_screen_size",
                           "width": 1280,
                           "height": 962},
                          {"$type": "set_render_quality",
                           "render_quality": 2}])

        # Toggle physics
        physics_on = True
        self.communicate({"$type": "simulate_physics",
                          "value": physics_on})

        # Add in coffee table object
        table_id = self.add_object("live_edge_coffee_table",
                                   position={"x": 1.5, "y": 0, "z": 1.5},
                                   rotation={"x": 0, "y": 0, "z": 0})

        # Add two bowls on top of the table
        bowl_1 = self.add_object(model_name="b04_bowl_smooth",
                                 position={"x": 1.25, "y": 0.331, "z": 1.5},
                                 rotation=TDWUtils.VECTOR3_ZERO,
                                 library="models_full.json")

        self.add_object(model_name="b04_bowl_smooth",
                        position={"x": 1.75, "y": 0.331, "z": 1.5},
                        rotation=TDWUtils.VECTOR3_ZERO,
                        library="models_full.json")

        # Push one bowl towards the other
        self.communicate({"$type": "apply_force_to_object",
                          "force": {"x": 5, "y": 0, "z": 0},
                          "id": bowl_1})

        # Load in avatar
        self.communicate(TDWUtils.create_avatar(avatar_id="avatar",
                                                position={"x": 0, "y": 1.5, "z": 0},
                                                look_at={"x": 1.5, "y": 0, "z": 1.5}))

        # Necessary commands to avoid error in calling save_images()
        # Second command enables image capture
        self.communicate({"$type": "set_pass_masks",
                          "avatar_id": "avatar",
                          "pass_masks": ["_img", "_id"]})
        self.communicate({"$type": "send_images",
                          "frequency": "always"})

        # Capture images
        for i in range(20):
            resp = self.communicate({"$type": "look_at",
                                     "avatar_id": "avatar",
                                     "object_id": table_id,
                                     "use_centroid": True})
            images = Images(resp[0])
            TDWUtils.save_images(images, TDWUtils.zero_padding(i), output_directory=path)
    def run(self):
        # Setting up image directory
        output_directory = "rotating_guitar_images"
        parent_dir = '/Users/leonard/Desktop/TDWBase-1.5.0/Python/Leonard/rotating_guitar'
        path = os.path.join(parent_dir, output_directory)
        print(path)
        if os.path.exists(path):
            shutil.rmtree(path)
            time.sleep(0.5)
            os.mkdir(path)

        # Initialize scene
        self.start()

        init_setup_commands = [{
            "$type": "set_screen_size",
            "width": 1280,
            "height": 962
        }, {
            "$type": "set_render_quality",
            "render_quality": 5
        }]
        self.communicate(init_setup_commands)

        scene_lib = SceneLibrarian()
        scene_record = scene_lib.get_record("tdw_room_2018")
        self.communicate({
            "$type": "add_scene",
            "name": scene_record.name,
            "url": scene_record.get_url()
        })

        self.communicate({"$type": "set_gravity", "value": True})
        self.communicate({"$type": "simulate_physics", "value": True})

        self.communicate(
            TDWUtils.create_avatar(avatar_id="avatar",
                                   position={
                                       "x": 3,
                                       "y": 1.5,
                                       "z": 3
                                   },
                                   look_at={
                                       "x": 0,
                                       "y": 0.8,
                                       "z": 0
                                   }))

        guitar_id = self.add_object(model_name="b01_shovel",
                                    position={
                                        "x": 0,
                                        "y": 1,
                                        "z": 0
                                    },
                                    rotation={
                                        "x": 0,
                                        "y": 0,
                                        "z": 0
                                    },
                                    library="models_full.json")

        self.communicate({
            "$type": "rotate_object_by",
            "angle": -15.0,
            "id": guitar_id,
            "axis": "roll",
            "is_world": True
        })

        self.communicate({
            "$type": "set_pass_masks",
            "avatar_id": "avatar",
            "pass_masks": ["_img", "_id"]
        })
        self.communicate({"$type": "send_images", "frequency": "always"})

        for i in range(50):
            resp = self.communicate({
                "$type": "look_at_position",
                "avatar_id": "avatar",
                "position": {
                    "x": 0,
                    "y": 0.8,
                    "z": 0
                }
            })
            images = Images(resp[0])
            TDWUtils.save_images(images,
                                 TDWUtils.zero_padding(i),
                                 output_directory=path)
Beispiel #8
0
                      static_friction=0.2,
                      bounciness=0.9,
                      mass=5),
        PhysicsObject(model_name="duffle_bag",
                      initial_position={
                          "x": -3,
                          "y": 2,
                          "z": 0.5
                      },
                      possibility=Possibility(),
                      dynamic_friction=0.5,
                      static_friction=0.6,
                      bounciness=0.9,
                      mass=8)
    ]
    lib_scenes = SceneLibrarian()
    record_scene = lib_scenes.get_record("building_site")
    sce_stp = [{
        "$type": "add_scene",
        "name": record_scene.name,
        "url": record_scene.get_url()
    }]
    stp = Trial(occ_stp, mov_stp, sce_stp, "stp")

    # Spatio-Temporal Continuity - Impossible
    occ_sti = occ_stp
    mov_sti = [
        PhysicsObject(model_name="chair_eames_plastic_armchair",
                      initial_position={
                          "x": 3,
                          "y": 2,
Beispiel #9
0
    def run(self):
        # Setting up image directory
        output_directory = "floating_bowl_images"
        parent_dir = '/Users/leonard/Desktop/TDWBase-1.5.0/Python/Leonard/floating_bowl'
        path = os.path.join(parent_dir, output_directory)
        print(path)
        if os.path.exists(path):
            shutil.rmtree(path)
            time.sleep(0.5)
            os.mkdir(path)

        # Initialize scene
        self.start()

        # Load in scene
        scene_lib = SceneLibrarian()
        scene_record = scene_lib.get_record("tdw_room_2018")
        self.communicate({"$type": "add_scene",
                          "name": scene_record.name,
                          "url": scene_record.get_url()})

        # Resize screen; note render_quality 5 is best
        self.communicate([{"$type": "set_screen_size",
                           "width": 1280,
                           "height": 962},
                          {"$type": "set_render_quality",
                           "render_quality": 2}])

        # Toggle physics
        physics_on = True
        self.communicate({"$type": "simulate_physics",
                          "value": physics_on})

        # Load in avatar
        self.communicate(TDWUtils.create_avatar(avatar_id="avatar",
                                                position={"x": 3, "y": 1.5, "z": 3},
                                                look_at={"x": 0, "y": 0.8, "z": 0}))

        # Necessary commands to avoid error in calling save_images()
        # Second command enables image capture
        self.communicate({"$type": "set_pass_masks",
                          "avatar_id": "avatar",
                          "pass_masks": ["_img", "_id"]})
        self.communicate({"$type": "send_images",
                          "frequency": "always"})

        # Capture images
        for i in range(20):
            bowl_id = self.add_object(model_name="b04_bowl_smooth",
                                      position={"x": 0, "y": i / 10, "z": 0},
                                      rotation={"x": i * 30 / 10, "y": i / 10, "z": 0},
                                      library="models_full.json")

            resp = self.communicate({"$type": "look_at_position",
                                     "avatar_id": "avatar",
                                     "position": {"x": 0, "y": 0.8, "z": 0}})
            images = Images(resp[0])
            TDWUtils.save_images(images, TDWUtils.zero_padding(i), output_directory=path)

            self.communicate({"$type": "destroy_object",
                              "id": bowl_id})
Beispiel #10
0
class Controller(object):
    """
    Base class for all controllers.

    Usage:

    ```python
    from tdw.controller import Controller
    c = Controller()
    c.start()
    ```
    """
    def __init__(self, port: int = 1071, check_version: bool = True):
        """
        Create the network socket and bind the socket to the port.

        :param port: The port number.
        :param check_version: If true, the controller will check the version of the build and print the result.
        """

        context = zmq.Context()

        self.socket = context.socket(zmq.REP)
        self.socket.bind('tcp://*:' + str(port))

        self.socket.recv()

        self.model_librarian: Optional[ModelLibrarian] = None
        self.scene_librarian: Optional[SceneLibrarian] = None
        self.material_librarian: Optional[MaterialLibrarian] = None
        self.hdri_skybox_librarian: Optional[HDRISkyboxLibrarian] = None
        self.humanoid_librarian: Optional[HumanoidLibrarian] = None
        self.humanoid_animation_librarian: Optional[
            HumanoidAnimationLibrarian] = None

        # Compare the version of the tdw module to the build version.
        if check_version:
            tdw_version, unity_version = self.get_version()
            print(
                f"Build version {tdw_version}\nUnity Engine {unity_version}\nPython tdw module version {__version__}"
            )
            if __version__ != tdw_version:
                print(
                    "WARNING! Your Python code is not the same version as the build. They might not be compatible."
                )
                print(
                    "Either use the latest prerelease build, or use the last stable release via:\n"
                )
                print("git checkout v" + last_stable_release)

    def communicate(self, commands: Union[dict, List[dict]]) -> list:
        """
        Send commands and receive output data in response.

        :param commands: A list of JSON commands.

        :return The output data from the build.
        """

        if not isinstance(commands, list):
            commands = [commands]

        self.socket.send_multipart([json.dumps(commands).encode('utf-8')])

        return self.socket.recv_multipart()

    def start(self, scene="ProcGenScene") -> None:
        """
        Init TDW.

        :param scene: The scene to load.
        """

        self.communicate([{"$type": "load_scene", "scene_name": scene}])

    def get_add_object(self,
                       model_name: str,
                       object_id: int,
                       position={
                           "x": 0,
                           "y": 0,
                           "z": 0
                       },
                       rotation={
                           "x": 0,
                           "y": 0,
                           "z": 0
                       },
                       library: str = "") -> dict:
        """
        Returns a valid add_object command.

        :param model_name: The name of the model.
        :param position: The position of the model.
        :param rotation: The starting rotation of the model, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `ModelLibrarian.get_library_filenames()` and `ModelLibrarian.get_default_library()`.
        :param object_id: The ID of the new object.

        :return An add_object command that the controller can then send.
        """

        if self.model_librarian is None or (
                library != "" and self.model_librarian.library != library):
            self.model_librarian = ModelLibrarian(library=library)

        record = self.model_librarian.get_record(model_name)

        return {
            "$type": "add_object",
            "name": model_name,
            "url": record.get_url(),
            "scale_factor": record.scale_factor,
            "position": position,
            "rotation": rotation,
            "category": record.wcategory,
            "id": object_id
        }

    def get_add_material(self, material_name: str, library: str = "") -> dict:
        """
        Returns a valid add_material command.

        :param material_name: The name of the material.
        :param library: The path to the records file. If left empty, the default library will be selected. See `MaterialLibrarian.get_library_filenames()` and `MaterialLibrarian.get_default_library()`.

        :return An add_material command that the controller can then send.
        """

        if self.material_librarian is None:
            self.material_librarian = MaterialLibrarian(library=library)

        record = self.material_librarian.get_record(material_name)
        return {
            "$type": "add_material",
            "name": material_name,
            "url": record.get_url()
        }

    def get_add_scene(self, scene_name: str, library: str = "") -> dict:
        """
        Returns a valid add_scene command.

        :param scene_name: The name of the scene.
        :param library: The path to the records file. If left empty, the default library will be selected. See `SceneLibrarian.get_library_filenames()` and `SceneLibrarian.get_default_library()`.

        :return An add_scene command that the controller can then send.
        """

        if self.scene_librarian is None:
            self.scene_librarian = SceneLibrarian(library=library)

        record = self.scene_librarian.get_record(scene_name)
        return {
            "$type": "add_scene",
            "name": scene_name,
            "url": record.get_url()
        }

    def get_add_hdri_skybox(self, skybox_name: str, library: str = "") -> dict:
        """
        Returns a valid add_hdri_skybox command.

        :param skybox_name: The name of the skybox.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HDRISkyboxLibrarian.get_library_filenames()` and `HDRISkyboxLibrarian.get_default_library()`.

        :return An add_hdri_skybox command that the controller can then send.
        """

        if self.hdri_skybox_librarian is None:
            self.hdri_skybox_librarian = HDRISkyboxLibrarian(library=library)

        record = self.hdri_skybox_librarian.get_record(skybox_name)
        return {
            "$type": "add_hdri_skybox",
            "name": skybox_name,
            "url": record.get_url(),
            "exposure": record.exposure,
            "initial_skybox_rotation": record.initial_skybox_rotation,
            "sun_elevation": record.sun_elevation,
            "sun_initial_angle": record.sun_initial_angle,
            "sun_intensity": record.sun_intensity
        }

    def get_add_humanoid(self,
                         humanoid_name: str,
                         object_id: int,
                         position={
                             "x": 0,
                             "y": 0,
                             "z": 0
                         },
                         rotation={
                             "x": 0,
                             "y": 0,
                             "z": 0
                         },
                         library: str = "") -> dict:
        """
        Returns a valid add_humanoid command.

        :param humanoid_name: The name of the humanoid.
        :param position: The position of the humanoid.
        :param rotation: The starting rotation of the humanoid, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HumanoidLibrarian.get_library_filenames()` and `HumanoidLibrarian.get_default_library()`.
        :param object_id: The ID of the new object.

        :return An add_humanoid command that the controller can then send.
        """

        if self.humanoid_librarian is None or (
                library != "" and self.humanoid_librarian.library != library):
            self.humanoid_librarian = HumanoidLibrarian(library=library)

        record = self.humanoid_librarian.get_record(humanoid_name)

        return {
            "$type": "add_humanoid",
            "name": humanoid_name,
            "url": record.get_url(),
            "position": position,
            "rotation": rotation,
            "id": object_id
        }

    def get_add_humanoid_animation(
            self,
            humanoid_animation_name: str,
            library="") -> (dict, HumanoidAnimationRecord):
        """
        Returns a valid add_humanoid_animation command and the record (which you will need to play an animation).

        :param humanoid_animation_name: The name of the animation.
        :param library: The path to the records file. If left empty, the default library will be selected. See `HumanoidAnimationLibrarian.get_library_filenames()` and `HumanoidAnimationLibrarian.get_default_library()`.

        return An add_humanoid_animation command that the controller can then send.
        """

        if self.humanoid_animation_librarian is None:
            self.humanoid_animation_librarian = HumanoidAnimationLibrarian(
                library=library)

        record = self.humanoid_animation_librarian.get_record(
            humanoid_animation_name)
        return {
            "$type": "add_humanoid_animation",
            "name": humanoid_animation_name,
            "url": record.get_url()
        }, record

    def load_streamed_scene(self, scene="tdw_room_2018") -> None:
        """
        Load a streamed scene. This is equivalent to: `c.communicate(c.get_add_scene(scene))`

        :param scene: The name of the streamed scene.
        """

        self.communicate(self.get_add_scene(scene))

    def add_object(self,
                   model_name: str,
                   position={
                       "x": 0,
                       "y": 0,
                       "z": 0
                   },
                   rotation={
                       "x": 0,
                       "y": 0,
                       "z": 0
                   },
                   library: str = "") -> int:
        """
        Add a model to the scene. This is equivalent to: `c.communicate(c.get_add_object())`

        :param model_name: The name of the model.
        :param position: The position of the model.
        :param rotation: The starting rotation of the model, in Euler angles.
        :param library: The path to the records file. If left empty, the default library will be selected. See `ModelLibrarian.get_library_filenames()` and `ModelLibrarian.get_default_library()`.

        :return The ID of the new object.
        """

        object_id = Controller.get_unique_id()
        self.communicate(
            self.get_add_object(model_name, object_id, position, rotation,
                                library))

        return object_id

    def get_version(self) -> Tuple[str, str]:
        """
        Send a send_version command to the build.

        :return The TDW version and the Unity Engine version.
        """

        resp = self.communicate({"$type": "send_version"})
        for r in resp[:-1]:
            if Version.get_data_type_id(r) == "vers":
                v = Version(r)
                return v.get_tdw_version(), v.get_unity_version()
        if len(resp) == 1:
            raise Exception(
                "Tried receiving version output data but didn't receive anything!"
            )
        raise Exception(f"Expected output data with ID vers but got: " +
                        Version.get_data_type_id(resp[0]))

    @staticmethod
    def get_unique_id() -> int:
        """
        Generate a unique integer. Useful when creating objects.

        :return The new unique ID.
        """

        return int.from_bytes(os.urandom(3), byteorder='big')

    @staticmethod
    def get_frame(frame: bytes) -> int:
        """
        Converts the frame byte array to an integer.

        :param frame: The frame as bytes.

        :return The frame as an integer.
        """

        return int.from_bytes(frame, byteorder='big')