Пример #1
0
    def __init__(self):
        self.obj_name_dict: Dict[int, str] = {}
        
        # Set up the object transform data.
        object_setup_data = json.loads(Path("object_setup.json").read_text())
        self.object_setups: List[_ObjectSetup] = []
        for o in object_setup_data:
            combo = _ObjectSetup(id=int(o),
                                 model_name=object_setup_data[o]["model_name"],
                                 position=object_setup_data[o]["position"],
                                 rotation=object_setup_data[o]["rotation"],
                                 scale=object_setup_data[o]["scale"])
            self.object_setups.append(combo)

        # Parse the default objects.csv spreadsheet. 
        self.object_audio_data = PyImpact.get_object_info()

        self.temp_amp = 0.1

        # Keep track of the current trial number, for logging purposes.
        self.current_trial_num = 0

        # Fetch the ball and board model's records; we will need them later to change its material.
        self.special_models = ModelLibrarian(library="models_special.json")
        self.full_models = ModelLibrarian(library="models_full.json")
        self.ball_record = self.special_models.get_record("prim_sphere")
        self.board_record = self.full_models.get_record("wood_board")

        # Set path to write out logging info.
        self.root_dest_dir = Path("dist/mode_properties_logs")
        if not self.root_dest_dir.exists():
            self.root_dest_dir.mkdir(parents=True)

        super().__init__()
Пример #2
0
    def __init__(self):
        self.model_list = [
            "b03_db_apps_tech_08_04", "trashbin", "trunck",
            "whirlpool_akzm7630ix", "satiro_sculpture", "towel-radiator-2",
            "b03_folding-screen-panel-room-divider",
            "naughtone_pinch_stool_chair", "microwave", "trunk_6810-0009",
            "suitcase", "kayak_small", "elephant_bowl", "trapezoidal_table",
            "b05_pc-computer-printer-1", "dishwasher_4",
            "chista_slice_of_teak_table", "buddah", "b05_elsafe_infinity_ii",
            "backpack", "b06_firehydrant_lod0", "b05_ticketmachine",
            "b05_trophy", "b05_kitchen_aid_toster", "b05_heavybag",
            "bongo_drum_hr_blend", "b03_worldglobe", "ceramic_pot",
            "b04_kenmore_refr_70419", "b03_zebra", "b05_gibson_j-45",
            "b03_cow", "b03_sheep", "b04_stringer"
        ]

        # Cache the record for the receptacle.
        self.receptacle_record = ModelLibrarian(
            "models_special.json").get_record("fluid_receptacle1x1")
        # Cache the fluid types.
        self.ft = FluidTypes()

        self.full_lib = ModelLibrarian("models_full.json")

        self.pool_id = None

        super().__init__()
Пример #3
0
    def __init__(self, port: int = 1071):
        super().__init__(port=port)

        self._occluders: List[ModelRecord] = ModelLibrarian(str(Path("occluders.json").resolve())).records
        self._ball = ModelLibrarian("models_flex.json").get_record("sphere")
        self._ball_id = 0
        self._occ_id = 1
        self.material_librarian = MaterialLibrarian()
Пример #4
0
    def _get_librarian(self, description: str) -> ModelLibrarian:
        """
        Returns a librarian object.

        :param description: The description of the library.
        """

        ModelLibrarian.create_library(description, self.library_path)
        print("Adding records to the library...")
        return ModelLibrarian(str(self.library_path.resolve()))
Пример #5
0
 def __init__(self, port: int = 1071):
     super().__init__(port=port)
     self.ball_record = ModelLibrarian("models_flex.json").get_record(
         "sphere")
     if sys.argv[2] == '30':
         self.ramp_record = ModelLibrarian("models_full.json").get_record(
             "ramp_with_platform_30")
     elif sys.argv[2] == '60':
         self.ramp_record = ModelLibrarian("models_full.json").get_record(
             "ramp_with_platform_60")
     self.pin_record = ModelLibrarian("models_flex.json").get_record(
         "cylinder")
     self.ball_material = "marble_white"
     self.start()
Пример #6
0
    def run(self):
        self.start()
        self.communicate(TDWUtils.create_empty_room(20, 20))
        self.communicate(TDWUtils.create_avatar(position={"x": 0, "y": 3, "z": -6},
                                                look_at=TDWUtils.VECTOR3_ZERO))
        model_name = "rh10"

        z = -3

        x = -1.5
        print("With the add_object command (complex syntax but you have maximum control):")
        record = ModelLibrarian().get_record(model_name)
        self.communicate({"$type": "add_object",
                          "name": model_name,
                          "url": record.get_url(),
                          "scale_factor": record.scale_factor,
                          "position": {"x": x, "y": 0, "z": z},
                          "rotation": TDWUtils.VECTOR3_ZERO,
                          "category": record.wcategory,
                          "id": self.get_unique_id()})

        x = 0
        print("With the wrapper function Controller.add_object() "
              "(easy to use, but you can't add additional commands to this frame):")
        self.add_object(model_name=model_name,
                        position={"x": x, "y": 0, "z": z},
                        rotation=TDWUtils.VECTOR3_ZERO, 
                        library="models_core.json")

        x = 1.5
        print("With the wrapper function Controller.get_add_object() "
              "(harder to use, but you can add commands to this frame):")
        self.communicate(self.get_add_object(model_name=model_name,
                                             object_id=self.get_unique_id(),
                                             position={"x": x, "y": 0, "z": 0},
                                             rotation=TDWUtils.VECTOR3_ZERO,
                                             library="models_core.json"))

        print("With the add_object command, minus all optional parameters (the model won't scale properly!):")
        self.communicate({"$type": "add_object",
                          "name": model_name,
                          "url": record.get_url(),
                          "id": self.get_unique_id()})

        print("With the wrapper function Controller.add_object(), minus all optional parameters:")
        self.add_object(model_name)

        print("With the wrapper function Controller.get_add_object(), minus all optional parameters:")
        self.communicate(self.get_add_object(model_name=model_name,
                                             object_id=self.get_unique_id()))
Пример #7
0
    def __init__(self, port: int = 1071):
        # Load the objects.
        self.object_records = ModelLibrarian(str(
            Path("flex.json").resolve())).records
        # Get the cloth record.
        self.cloth_record = MODEL_LIBRARIES["models_special.json"].get_record(
            "cloth_square")
        self.cloth_id = 0

        super().__init__(port=port)
Пример #8
0
    def create_asset_bundles(self,
                             batch_size: int = 1000,
                             vhacd_resolution: int = 8000000,
                             first_batch_only: bool = False) -> None:
        """
        Convert all .obj files into asset bundles.

        :param batch_size: The number of models per batch.
        :param vhacd_resolution: Higher value=better-fitting colliders and slower build process.
        :param first_batch_only: If true, output only the first batch. Useful for testing purposes.
        """

        records = ModelLibrarian(
            library=str(self.library_path.resolve())).records
        a = AssetBundleCreator(quiet=True)

        pbar = tqdm(total=len(records))
        while len(records) > 0:
            # Get the next batch.
            batch: List[ModelRecord] = records[:batch_size]
            records = records[batch_size:]

            for record in batch:
                # If the asset bundles for this record already exist, skip it.
                urls_exist = False
                for platform in record.urls:
                    url = record.urls[platform][8:]
                    if Path(url).exists():
                        urls_exist = True
                if urls_exist:
                    continue

                # If the prefab for this record exists, skip it.
                dest_path = Path.home().joinpath(
                    f"asset_bundle_creator/Assets/Resources/models/{record.name}.obj"
                )
                if dest_path.exists():
                    continue
                # Process the .obj
                obj_path = self._get_obj(record)
                # Move the files and remove junk.
                a.move_files_to_unity_project(
                    None,
                    model_path=obj_path,
                    sub_directory=f"models/{record.name}")
            # Creating the asset bundles.
            a.create_many_asset_bundles(str(self.library_path.resolve()),
                                        cleanup=True,
                                        vhacd_resolution=vhacd_resolution)
            pbar.update(len(batch))

            # Process only the first batch of models.
            if first_batch_only:
                break
        pbar.close()
Пример #9
0
    def run(c: Controller) -> None:
        """
        Check every model for missing materials.

        :param c: The controller.
        """

        # Create a new output file.
        if MissingMaterials.OUTPUT_FILE.exists():
            MissingMaterials.OUTPUT_FILE.unlink()
        MissingMaterials.OUTPUT_FILE = str(
            MissingMaterials.OUTPUT_FILE.resolve())

        MissingMaterials.start(c)

        print(
            f"The names of models with missing materials will be saved to: {MissingMaterials.OUTPUT_FILE}"
        )
        for library_path in ModelLibrarian.get_library_filenames():
            print(library_path)
            lib = ModelLibrarian(library=library_path)
            pbar = tqdm(total=len(lib.records))
            for record in lib.records:
                if record.do_not_use:
                    pbar.update(1)
                    continue
                pbar.set_description(record.name)
                # Check for missing materials.
                if MissingMaterials.materials_are_missing(
                        c, record, record.get_url()):
                    with io.open(MissingMaterials.OUTPUT_FILE, "at") as f:
                        f.write("\n" + record.name)
                # Cleanup.
                c.communicate([{
                    "$type": "destroy_object",
                    "id": MissingMaterials.OBJECT_ID
                }, {
                    "$type": "unload_asset_bundles"
                }])
                pbar.update(1)
            pbar.close()
        c.communicate({"$type": "terminate"})
Пример #10
0
    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
        }
Пример #11
0
    def __init__(self, port: int = 1071):
        self.toy_records = ModelLibrarian(str(
            Path("toys.json").resolve())).records
        self.ramp_positions = [{
            "x": 3.5,
            "y": 0.02,
            "z": 1.5
        }, {
            "x": -1,
            "y": 0.02,
            "z": 2.38
        }, {
            "x": 4.58,
            "y": 0.02,
            "z": -2.85
        }, {
            "x": -0.94,
            "y": 0.02,
            "z": -2.83
        }, {
            "x": -3.4,
            "y": 0.02,
            "z": 0
        }]
        self.ramp_rotations = [{
            "x": 0,
            "y": -45,
            "z": 0
        }, {
            "x": 0,
            "y": -90,
            "z": 0
        }, {
            "x": 0,
            "y": 30,
            "z": 0
        }, {
            "x": -90,
            "y": -190,
            "z": 0
        }, {
            "x": -90,
            "y": -120,
            "z": 0
        }, {
            "x": 0,
            "y": 120,
            "z": 0
        }]

        super().__init__(port=port)
Пример #12
0
    def __init__(self):
        self.obj_name_dict: Dict[int, str] = {}

        # Set up the object transform data.
        object_setup_data = json.loads(Path("object_setup.json").read_text())
        self.object_setups: List[_ObjectSetup] = []
        for o in object_setup_data:
            combo = _ObjectSetup(id=int(o),
                                 model_name=object_setup_data[o]["model_name"],
                                 position=object_setup_data[o]["position"],
                                 rotation=object_setup_data[o]["rotation"],
                                 scale=object_setup_data[o]["scale"])
            self.object_setups.append(combo)

        # Parse the default objects.csv spreadsheet.
        self.object_audio_data = PyImpact.get_object_info()

        # Fetch the ball and board model's records; we will need them later to change its material.
        self.special_models = ModelLibrarian(library="models_special.json")
        self.full_models = ModelLibrarian(library="models_full.json")
        self.ball_record = self.special_models.get_record("prim_sphere")
        self.board_record = self.full_models.get_record("wood_board")

        super().__init__()
Пример #13
0
    def __init__(self, port: int = 1071):
        super().__init__(port=port)

        # Cache the ball data.
        self._ball = ModelLibrarian("models_special.json").get_record(
            "prim_sphere")
        self._ball_id = 0

        # The position the ball starts in and the position the ball is directed at.
        self._p0: Dict[str, float] = {}
        self._p1: Dict[str, float] = {}

        # Cache the skybox records.
        skybox_lib = HDRISkyboxLibrarian()
        self._skyboxes: List[str] = [
            r.name for r in skybox_lib.records if r.sun_intensity >= 0.8
        ]
Пример #14
0
    def _initialize_scene(self, c: Controller) -> List[dict]:
        c.model_librarian = ModelLibrarian("models_full.json")
        # Initialize the scene.
        commands = super()._initialize_scene(c)
        chair_name = "brown_leather_dining_chair"
        # Create the the table.
        commands.extend(self._init_object(c=c,
                                          name="quatre_dining_table",
                                          pos=TDWUtils.VECTOR3_ZERO,
                                          rot=TDWUtils.VECTOR3_ZERO))
        # Create 8 chairs around the table.
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": 0, "y": 0, "z": -1.55},
                                          rot={"x": 0, "y": 0, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": 0, "y": 0, "z": 1.55},
                                          rot={"x": 0, "y": 180, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": -1, "y": 0, "z": -0.85},
                                          rot={"x": 0, "y": 90, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": -1, "y": 0, "z": 0},
                                          rot={"x": 0, "y": 90, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": -1, "y": 0, "z": 0.85},
                                          rot={"x": 0, "y": 90, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": 1, "y": 0, "z": -0.85},
                                          rot={"x": 0, "y": -90, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": 1, "y": 0, "z": 0},
                                          rot={"x": 0, "y": -90, "z": 0}))
        commands.extend(self._init_object(c=c,
                                          name=chair_name,
                                          pos={"x": 1, "y": 0, "z": 0.85},
                                          rot={"x": 0, "y": -90, "z": 0}))

        return commands
Пример #15
0
    def __init__(self, port: int = 1071):
        super().__init__(port=port)
        self.model_librarian = ModelLibrarian("models_flex.json")

        # A list of functions that will return commands to initialize a trial.
        self.scenarios = [
            self.drop_onto_floor, self.drop_onto_object, self.throw_into_wall,
            self.push_into_other
        ]

        # Size of the room bounds (used for placing objects near walls).
        self.env_bounds = {"x": 4, "y": 0, "z": 2}

        # Cached pressure parameters per model.
        self.pressures = loads(Path("squish_pressures.json").read_text())
        # Only use records in the pressures dictionary.
        self.records = [
            r for r in self.model_librarian.records if r.name in self.pressures
        ]
Пример #16
0
    def run(self):
        # Parse the substructure of fridge_large.
        record = ModelLibrarian().get_record("fridge_large")

        self.start()
        self.communicate(TDWUtils.create_empty_room(12, 12))

        # Create the object.
        o_id = self.add_object("fridge_large")

        # Add an avatar.
        self.communicate(
            TDWUtils.create_avatar(position={
                "x": 0,
                "y": 1.886,
                "z": -0.15
            },
                                   look_at=TDWUtils.VECTOR3_ZERO))

        # Disable post-processing, so the changes to the material aren't blurry.
        self.communicate({"$type": "set_post_process", "value": False})

        for quality in ["low", "med", "high"]:
            # Set the visual materials.
            self.communicate(
                TDWUtils.set_visual_material(self,
                                             record.substructure,
                                             o_id,
                                             "parquet_long_horizontal_clean",
                                             quality=quality))

            sleep(5)
            self.communicate({
                "$type": "unload_asset_bundles",
                "bundle_type": "materials"
            })
        self.communicate({
            "$type": "unload_asset_bundles",
            "bundle_type": "models"
        })
Пример #17
0
    def _initialize_scene(self, c: Controller) -> List[dict]:
        c.model_librarian = ModelLibrarian("models_full.json")
        commands = super()._initialize_scene(c)

        # Add a table, chair, and boxes.
        commands.extend(self._init_object(c, "b05_table_new",
                                          pos={"x": 0, "y": 0, "z": 4.33},
                                          rot=TDWUtils.VECTOR3_ZERO))
        commands.extend(self._init_object(c, "chair_willisau_riale",
                                          pos={"x": 0, "y": 0, "z": 3.7},
                                          rot=TDWUtils.VECTOR3_ZERO))
        commands.extend(self._init_object(c, "iron_box",
                                          pos={"x": 0.13, "y": 0.65, "z": 4.83},
                                          rot=TDWUtils.VECTOR3_ZERO))
        commands.extend(self._init_object(c, "iron_box",
                                          pos={"x": -0.285, "y": 1.342, "z": 4.79},
                                          rot={"x": 90, "y": 0, "z": 0}))
        # Add a shelf with a custom scale.
        shelf_id = c.get_unique_id()
        shelf_name = "metal_lab_shelf"
        Scene.OBJECT_IDS.update({shelf_id: shelf_name})
        commands.extend([c.get_add_object(shelf_name,
                         object_id=shelf_id,
                         rotation={"x": 0, "y": -90, "z": 0},
                         position={"x": 0, "y": 0, "z": 4.93}),
                         {"$type": "set_mass",
                          "id": shelf_id,
                          "mass": 400},
                         {"$type": "set_physic_material",
                          "id": shelf_id,
                          "bounciness": Scene._OBJECT_INFO[shelf_name].bounciness,
                          "static_friction": 0.1,
                          "dynamic_friction": 0.8},
                         {"$type": "scale_object",
                          "id": shelf_id,
                          "scale_factor": {"x": 1, "y": 1.5, "z": 1.8}}])
        return commands
Пример #18
0
from pathlib import Path
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
from tdw.librarian import ModelLibrarian
from tdw.output_data import OutputData, Bounds, Images
"""
1. Add a table and place an object on the table.
2. Add a camera and receive an image.
"""

lib = ModelLibrarian("models_core.json")
# Get the record for the table.
table_record = lib.get_record("small_table_green_marble")

c = Controller()

table_id = 0

# 1. Load the scene.
# 2. Create an empty room (using a wrapper function)
# 3. Add the table.
# 4. Request Bounds data.
resp = c.communicate([{
    "$type": "load_scene",
    "scene_name": "ProcGenScene"
},
                      TDWUtils.create_empty_room(12, 12),
                      c.get_add_object(model_name=table_record.name,
                                       object_id=table_id,
                                       position={
                                           "x": 0,
Пример #19
0
    def run(self):
        self.start()
        init_setup_commands = [{"$type": "set_screen_size",
                                "width": 640,
                                "height": 480},
                               {"$type": "set_render_quality",
                                "render_quality": 5}]
        self.communicate(init_setup_commands)

        self.communicate({"$type": "create_empty_environment",
                          "center": {"x": 0, "y": 0, "z": 0},
                          "bounds": {"x": 15, "y": 15, "z": 15}})

        # Disable physics.
        self.communicate({"$type": "set_gravity",
                          "value": False})

        # Add the avatar.
        self.communicate(TDWUtils.create_avatar(position={"x": -1.5, "y": 1.4, "z": 0.6},
                                                look_at=TDWUtils.array_to_vector3([3, 0.5, 0.5]),
                                                avatar_id="avatar"))

        self.communicate(self.get_add_hdri_skybox("table_mountain_1_4k"))

        self.communicate({"$type": "set_field_of_view",
                          "field_of_view": 68.0,
                          "avatar_id": "avatar"})

        cube_record = ModelLibrarian("models_special.json").get_record("prim_cube")
        self.communicate({"$type": "add_object",
                          "name": "prim_cube",
                          "url": cube_record.get_url(),
                          "scale_factor": cube_record.scale_factor,
                          "position": {"x": 0, "y": 0, "z": 0},
                          "rotation": {"x": 0, "y": 0, "z": 0},
                          "category": cube_record.wcategory,
                          "id": 1})

        self.communicate({"$type": "scale_object",
                          "scale_factor": {"x": 30, "y": 0.0001, "z": 30},
                          "id": 1})

        grass_record = MaterialLibrarian("materials_high.json").get_record("grass_countryside")
        self.communicate({"$type": "add_material", "name": "grass_countryside", "url": grass_record.get_url()})
        self.communicate(TDWUtils.set_visual_material(c=self, substructure=cube_record.substructure, object_id=1,
                                                      material="grass_countryside"))

        self.add_object(model_name="b03_horse",
                        position={"x": 3, "y": 0.0001, "z": 0.5},
                        rotation={"x": 0, "y": 0, "z": 0},
                        library="models_full.json")

        bird = self.add_object(model_name="polysurface63",
                               position={"x": 1.2, "y": 0.0001, "z": 1},
                               rotation={"x": 0, "y": 0, "z": 0},
                               library="models_full.json")

        self.communicate({"$type": "scale_object",
                          "scale_factor": {"x": 2, "y": 2, "z": 2},
                          "id": bird})

        bird = self.add_object(model_name="b03_realistic_pigeon_max",
                               position={"x": 1.8, "y": 0.0001, "z": 0.45},
                               rotation={"x": 0, "y": -95, "z": 0},
                               library="models_full.json")

        self.communicate({"$type": "scale_object",
                          "scale_factor": {"x": 2, "y": 2, "z": 2},
                          "id": bird})

        bird = self.add_object(model_name="rockdove_polysurface77",
                               position={"x": 1, "y": 0.0001, "z": 0.7},
                               rotation={"x": 0, "y": -80, "z": 0},
                               library="models_full.json")

        self.communicate({"$type": "scale_object",
                          "scale_factor": {"x": 2, "y": 2, "z": 2},
                          "id": bird})

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

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

        scene_data = self.communicate({"$type": "look_at_position",
                                       "avatar_id": "avatar",
                                       "position": TDWUtils.array_to_vector3([3, 0.5, 0])})

        images = Images(scene_data[0])
        TDWUtils.save_images(images, "bird2",
                             output_directory="/Users/leonard/Desktop/TDWBase-1.5.0/Python/Leonard/compare_COCO_TDW/replicated_images/exterior")
Пример #20
0
class RubeGoldbergDemo(Controller):

    def __init__(self):
        self.obj_name_dict: Dict[int, str] = {}
        
        # Set up the object transform data.
        object_setup_data = json.loads(Path("object_setup.json").read_text())
        self.object_setups: List[_ObjectSetup] = []
        for o in object_setup_data:
            combo = _ObjectSetup(id=int(o),
                                 model_name=object_setup_data[o]["model_name"],
                                 position=object_setup_data[o]["position"],
                                 rotation=object_setup_data[o]["rotation"],
                                 scale=object_setup_data[o]["scale"])
            self.object_setups.append(combo)

        # Parse the default objects.csv spreadsheet. 
        self.object_audio_data = PyImpact.get_object_info()

        self.temp_amp = 0.1

        # Keep track of the current trial number, for logging purposes.
        self.current_trial_num = 0

        # Fetch the ball and board model's records; we will need them later to change its material.
        self.special_models = ModelLibrarian(library="models_special.json")
        self.full_models = ModelLibrarian(library="models_full.json")
        self.ball_record = self.special_models.get_record("prim_sphere")
        self.board_record = self.full_models.get_record("wood_board")

        # Set path to write out logging info.
        self.root_dest_dir = Path("dist/mode_properties_logs")
        if not self.root_dest_dir.exists():
            self.root_dest_dir.mkdir(parents=True)

        super().__init__()
 
 
    def run(self, num_trials: int):
        """
        Build a "Rube Goldberg" machine to produce impact sounds.
        """

        # Load the photorealistic "archviz_house" environment.
        self.load_streamed_scene(scene="archviz_house_2018")

        # Organize all initialization commands into a single list.
        # Set global values, including the desired screen size and aspect ratio (720P).
        # Frame rate is set to 60 fps to facilitate screen / video recording.
        init_commands = [{"$type": "set_render_quality",
                          "render_quality": 5},
                         {"$type": "set_screen_size",
                          "width": 1280,
                          "height": 720},
                         {"$type": "set_target_framerate",
                          "framerate": 60},
                         {"$type": "set_time_step",
                          "time_step": 0.02}]

        # Create the avatar. 
        init_commands.extend(TDWUtils.create_avatar(avatar_type="A_Img_Caps_Kinematic",
                                                    position={"x": -15.57, "y": 1.886, "z": -4.97}))

        # Aim the avatar camera to frame the desired view.
        init_commands.extend([{"$type": "rotate_sensor_container_by",
                               "axis": "yaw",
                               "angle": 109.13,
                               "sensor_name": "SensorContainer",
                               "avatar_id": "a"},
                              {"$type": "rotate_sensor_container_by",
                               "axis": "pitch",
                               "angle": 6.36,
                               "sensor_name": "SensorContainer",
                               "avatar_id": "a"}])

        # Add the audio sensor.
        init_commands.extend([{"$type": "add_audio_sensor",
                               "avatar_id": "a"}])

        # Adjust post-processing settings.
        init_commands.extend([{"$type": "set_post_exposure",
                               "post_exposure": 0.35},
                             {"$type": "set_screen_space_reflections",
                              "enabled": True},
                             {"$type": "set_vignette",
                              "enabled": False},
                             {"$type": "set_ambient_occlusion_intensity",
                              "intensity": 0.175},
                             {"$type": "set_ambient_occlusion_thickness_modifier",
                              "thickness": 5.0}])

        # Set the shadow strength to maximum.
        init_commands.extend([{"$type": "set_shadow_strength",
                               "strength": 1.0}])

        # Send all of the initialization commands.
        self.communicate(init_commands)

        for i in range(num_trials):
            self.do_trial()
            

    def do_trial(self):
        # Initialize PyImpact and pass in the "master gain" amplitude value. This value must be betweem 0 and 1.
        # The relative amplitudes of all scene objects involved in collisions will be scaled relative to this value.
        # Note -- if this value is too high, waveform clipping can occur and the resultant audio will be distorted.
        # For this reason, the value used here is considerably smaller than the corresponding value used in the
        # impact_sounds.py example controller. Here we have a large number of closely-occuring collisions resulting in
        # a rapid series of "clustered" impact sounds, as opposed to a single object falling from a height;
        # using a higher value such as the 0.5 used in the example controller will definitely result in unpleasant
        # distortion of the audio.
        # Note that logging is also enabled.

        # Keep track of trial number.
        self.current_trial_num += 1

        # Create folder for this trial's logging info.
        dest_dir = self.root_dest_dir.joinpath(str(self.current_trial_num))
        if not dest_dir.exists():
            dest_dir.mkdir(parents=True)
        dest_dir_str = str(dest_dir.resolve())

        p = PyImpact(0.25, logging=True)

        self.add_all_objects()

        # "Aim" the ball at the monkey and apply the force.
        # Note that this force value was arrived at through a number of trial-and-error iterations.
        resp = self.communicate([{"$type": "object_look_at_position",
                                  "id": 0,
                                  "position": {"x": -12.95, "y": 1.591, "z": -5.1}},
                                 {"$type": "apply_force_magnitude_to_object",
                                  "id": 0,
                                  "magnitude": 98.0}])

        for i in range(400):
            collisions, environment_collision, rigidbodies = PyImpact.get_collisions(resp)
            # Sort the objects by mass.
            masses: Dict[int, float] = {}
            for j in range(rigidbodies.get_num()):
                masses.update({rigidbodies.get_id(j): rigidbodies.get_mass(j)})

            # If there was a collision, create an impact sound.
            if len(collisions) > 0 and PyImpact.is_valid_collision(collisions[0]):
                collider_id = collisions[0].get_collider_id()
                collidee_id = collisions[0].get_collidee_id()
                # The "target" object should have less mass than the "other" object.
                if masses[collider_id] < masses[collidee_id]:
                    target_id = collider_id
                    other_id = collidee_id
                else:
                    target_id = collidee_id
                    other_id = collider_id

                target_name = self.obj_name_dict[target_id]
                other_name = self.obj_name_dict[other_id]

                impact_sound_command = p.get_impact_sound_command(
                    collision=collisions[0],
                    rigidbodies=rigidbodies,
                    target_id=target_id,
                    target_mat=self.object_audio_data[target_name].material,
                    other_id=other_id,
                    other_mat=self.object_audio_data[other_name].material,
                    target_amp=self.object_audio_data[target_name].amp,
                    other_amp=self.object_audio_data[other_name].amp,
                    resonance=self.object_audio_data[target_name].resonance)
                resp = self.communicate(impact_sound_command)
            # Continue to run the trial.
            else:
                resp = self.communicate([])

        # Get the logging info for this trial and write it out.
        log = p.get_log()
        json_dest = dest_dir.joinpath("mode_properties_log.json")
        json_dest.write_text(json.dumps(log, indent=2))

        for obj_setup in self.object_setups:
            self.communicate({"$type": "destroy_object", "id": obj_setup.id})

		

    def add_all_objects(self):
        object_commands = []
        rigidbodies = []

        # Set the mass and scale of the objects, from  the data files we parsed earlier.
        # Enable output of collision and rigid body data.
        # Build dictionary of id,,name so we can retrieve object names during collisions.
        # Cache the ids for objects we need to change materials for later
        for obj_setup in self.object_setups:
            self.obj_name_dict[obj_setup.id] = obj_setup.model_name
            rigidbodies.append(obj_setup.id)
            if obj_setup.model_name == "prim_sphere":
                ball_id = obj_setup.id
            elif obj_setup.model_name == "102_pepsi_can_12_fl_oz_vray":
                coke_can_id = obj_setup.id
            elif obj_setup.model_name == "wood_board":
                board_id = obj_setup.id
            elif obj_setup.model_name == "bench":
                table_id = obj_setup.id
            elif obj_setup.model_name == "camera_box":
                camera_box_id = obj_setup.id

            # Set up to add all objects
            object_commands.extend([self.get_add_object(
                                    model_name=obj_setup.model_name, 
                                    object_id=obj_setup.id,
                                    position=obj_setup.position,
                                    rotation=obj_setup.rotation,
                                    library=self.object_audio_data[obj_setup.model_name].library),
                                    {"$type": "set_mass",
                                    "id": obj_setup.id,
                                    "mass": self.object_audio_data[obj_setup.model_name].mass},
                                    {"$type": "set_object_collision_detection_mode", 
                                    "id": obj_setup.id, 
                                    "mode": "continuous_speculative"},
                                    {"$type": "scale_object", 
                                     "id":  obj_setup.id, 
                                     "scale_factor": obj_setup.scale}])

        object_commands.extend([{"$type": "send_collisions",
                                 "enter": True,
                                 "stay": False,
                                 "exit": False},
                                {"$type": "send_rigidbodies",
                                 "frequency": "always",
                                 "ids": rigidbodies}])

        # Scale the ball and set a suitable drag value to "tune" how hard it will hit the monkey.
        # This combined with the force applied below hits the monkey just hard enough to set the sequnce of
        # collisions in motion.
        object_commands.extend([{"$type": "scale_object",
                                 "id": ball_id,
                                 "scale_factor": {"x": 0.1, "y": 0.1, "z": 0.1}},
                                {"$type": "set_object_drag",
                                 "angular_drag": 5.0,
                                 "drag": 1.0,
                                 "id": ball_id}])

        # Set physics material parameters to enable rolling motion for the coke can and board after being hit,
        # so they collide with the row of "dominos"
        object_commands.extend([{"$type": "set_physic_material",
                                 "dynamic_friction": 0.4,
                                 "static_friction": 0.4,
                                 "bounciness": 0.6,
                                 "id": table_id},
                                {"$type": "set_physic_material",
                                 "dynamic_friction": 0.2,
                                 "static_friction": 0.2,
                                 "bounciness": 0.6,
                                 "id": coke_can_id},
                                {"$type": "set_physic_material",
                                 "dynamic_friction": 0.2,
                                 "static_friction": 0.2,
                                 "bounciness": 0.7,
                                 "id": board_id}])

        # Set the camera box and table to be kinematic, we don't need them to respond to physics.
        object_commands.extend([{"$type": "set_kinematic_state",
                                 "id": table_id,
                                 "is_kinematic": True},
                                {"$type": "set_kinematic_state",
                                 "id": camera_box_id,
                                 "is_kinematic": True}])

        # Set the visual material of the ball to metal and the board to a different wood than the bench.
        object_commands.extend(TDWUtils.set_visual_material(self, self.ball_record.substructure, ball_id,
                                                            "dmd_metallic_fine", quality="high"))
        object_commands.extend(TDWUtils.set_visual_material(self, self.board_record.substructure, board_id,
                                                            "wood_tropical_hardwood", quality="high"))

        # Send all of the object setup commands.
        self.communicate(object_commands)
Пример #21
0
 def __init__(self, port: int = 1071, launch_build: bool = True):
     super().__init__(port=port, launch_build=launch_build)
     self.cube = ModelLibrarian("models_flex.json").get_record("cube")
Пример #22
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)
Пример #23
0
    def run(self):
        rng = np.random.RandomState(0)
        self.model_librarian = ModelLibrarian("models_special.json")
        self.start()
        commands = [TDWUtils.create_empty_room(100, 100)]

        # The starting height of the objects.
        y = 10
        # The radius of the circle of objects.
        r = 7.0
        # The mass of each object.
        mass = 5

        # Get all points within the circle defined by the radius.
        p0 = np.array((0, 0))
        o_id = 0
        for x in np.arange(-r, r, 1):
            for z in np.arange(-r, r, 1):
                p1 = np.array((x, z))
                dist = np.linalg.norm(p0 - p1)
                if dist < r:
                    # Add an object.
                    # Set its mass, physics properties, and color.
                    commands.extend([self.get_add_object("prim_cone",
                                                         object_id=o_id,
                                                         position={"x": x, "y": y, "z": z},
                                                         rotation={"x": 0, "y": 0, "z": 180}),
                                     {"$type": "set_mass",
                                      "id": o_id,
                                      "mass": mass},
                                     {"$type": "set_physic_material",
                                      "dynamic_friction": 0.8,
                                      "static_friction": 0.7,
                                      "bounciness": 0.5,
                                      "id": o_id},
                                     {"$type": "set_color",
                                      "color": {"r": rng.random_sample(),
                                                "g": rng.random_sample(),
                                                "b": rng.random_sample(),
                                                "a": 1.0},
                                      "id": o_id}])
                    o_id += 1
        # Request transforms per frame.
        commands.extend([{"$type": "send_transforms",
                          "frequency": "always"}])
        # Create an avatar to observe the grisly spectacle.
        avatar_position = {"x": -20, "y": 8, "z": 18}
        commands.extend(TDWUtils.create_avatar(position=avatar_position, look_at=TDWUtils.VECTOR3_ZERO))
        commands.append({"$type": "set_focus_distance",
                         "focus_distance": TDWUtils.get_distance(avatar_position, TDWUtils.VECTOR3_ZERO)})
        resp = self.communicate(commands)

        # If an objects are this far away from (0, 0, 0) the forcefield "activates".
        forcefield_radius = 5
        # The forcefield will bounce objects away at this force.
        forcefield_force = -10
        zeros = np.array((0, 0, 0))
        for i in range(1000):
            transforms = Transforms(resp[0])
            commands = []
            for j in range(transforms.get_num()):
                pos = transforms.get_position(j)
                pos = np.array(pos)
                # If the object is in the forcefield, apply a force.
                if TDWUtils.get_distance(TDWUtils.array_to_vector3(pos), TDWUtils.VECTOR3_ZERO) <= forcefield_radius:
                    # Get the normalized directional vector and multiply it by the force magnitude.
                    d = zeros - pos
                    d = d / np.linalg.norm(d)
                    d = d * forcefield_force
                    commands.append({"$type": "apply_force_to_object",
                                     "id": transforms.get_id(j),
                                     "force": TDWUtils.array_to_vector3(d)})
            resp = self.communicate(commands)
Пример #24
0
    def __init__(self,
                 output_dir: Path = Path("D:/audio_dataset"),
                 total: int = 28602,
                 port: int = 1071):
        """
        :param output_dir: The output directory for the files.
        :param port: The socket port.
        :param total: The total number of files per sub-set.
        """

        self.total = total

        self.output_dir = output_dir
        if not self.output_dir.exists():
            self.output_dir.mkdir(parents=True)

        db_path = self.output_dir.joinpath('results.db')
        self.conn = sqlite3.connect(str(db_path.resolve()))
        self.db_c = self.conn.cursor()
        # Sound20K table.
        if self.db_c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sound20k'").\
                fetchone() is None:
            self.db_c.execute(
                "CREATE TABLE sound20k (path text, scene integer, cam_x real, cam_y real, cam_z real,"
                "obj_x real, obj_y real, obj_z real, mass real, static_friction real, dynamic_friction "
                "real, yaw real, pitch real, roll real, force real)")
        # Scenes table.
        if self.db_c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='scenes'").\
                fetchone() is None:
            self.db_c.execute(
                "CREATE TABLE scenes (id integer, commands text)")

        self.py_impact = PyImpact()

        self.object_info = PyImpact.get_object_info()
        sound20k_object_info = PyImpact.get_object_info(
            Path("models/object_info.csv"))
        for obj_info in sound20k_object_info:
            if obj_info in self.object_info:
                continue
            else:
                self.object_info.update(
                    {obj_info: sound20k_object_info[obj_info]})

        self.libs: Dict[str, ModelLibrarian] = {}
        # Load all model libraries into memory.
        for lib_name in ModelLibrarian.get_library_filenames():
            self.libs.update({lib_name: ModelLibrarian(lib_name)})
        # Add the custom model library.
        self.libs.update({
            "models/models.json":
            ModelLibrarian(str(Path("models/models.json").resolve()))
        })

        super().__init__(port=port)

        # Global settings.
        self.communicate([{
            "$type": "set_screen_size",
            "width": 256,
            "height": 256
        }, {
            "$type": "set_time_step",
            "time_step": 0.02
        }, {
            "$type": "set_target_framerate",
            "framerate": 60
        }, {
            "$type": "set_physics_solver_iterations",
            "iterations": 20
        }])
Пример #25
0
class FlexDominoes(Dominoes, FlexDataset):

    FLEX_RECORDS = ModelLibrarian(str(Path("flex.json").resolve())).records
    CLOTH_RECORD = MODEL_LIBRARIES["models_special.json"].get_record("cloth_square")
    SOFT_RECORD = MODEL_LIBRARIES["models_flex.json"].get_record("sphere")
    RECEPTACLE_RECORD = MODEL_LIBRARIES["models_special.json"].get_record("fluid_receptacle1x1")
    FLUID_TYPES = FluidTypes()

    def __init__(self, port: int = 1071,
                 all_flex_objects=True,
                 use_cloth=False,
                 use_squishy=False,
                 use_fluid=False,
                 step_physics=False,
                 middle_scale_range=0.5,
                 **kwargs):

        Dominoes.__init__(self, port=port, **kwargs)
        self._clear_flex_data()

        self.all_flex_objects = all_flex_objects
        self._set_add_physics_object()

        self.step_physics = step_physics
        self.use_cloth = use_cloth
        self.use_squishy = use_squishy
        self.use_fluid = use_fluid

        self.middle_scale_range = middle_scale_range
        print("MIDDLE SCALE RANGE", self.middle_scale_range)

        if self.use_fluid:
            self.ft_selection = random.choice(self.FLUID_TYPES.fluid_type_names)

    def _set_add_physics_object(self):
        if self.all_flex_objects:
            self.add_physics_object = self.add_flex_solid_object
            self.add_primitive = self.add_flex_solid_object
        else:
            self.add_physics_object = self.add_rigid_physics_object


    def get_scene_initialization_commands(self) -> List[dict]:

        commands = Dominoes.get_scene_initialization_commands(self)
        commands[0].update({'convexify': True})
        create_container = {
            "$type": "create_flex_container",
            # "collision_distance": 0.001,
            "collision_distance": 0.025,
            # "collision_distance": 0.1,
            "static_friction": 1.0,
            "dynamic_friction": 1.0,
            "radius": 0.1875,
            'max_particles': 50000}
            # 'max_particles': 250000}

        if self.use_fluid:
            create_container.update({
                'viscosity': self.FLUID_TYPES.fluid_types[self.ft_selection].viscosity,
                'adhesion': self.FLUID_TYPES.fluid_types[self.ft_selection].adhesion,
                'cohesion': self.FLUID_TYPES.fluid_types[self.ft_selection].cohesion,
                'fluid_rest': 0.05,
                'damping': 0.01,
                'subsetp_count': 5,
                'iteration_count': 8,
                'buoyancy': 1.0})

        commands.append(create_container)

        if self.use_fluid:
            commands.append({"$type": "set_time_step", "time_step": 0.005})

        return commands

    def get_trial_initialization_commands(self) -> List[dict]:

        # clear the flex data
        FlexDataset.get_trial_initialization_commands(self)
        return Dominoes.get_trial_initialization_commands(self)

    def _get_send_data_commands(self) -> List[dict]:
        commands = Dominoes._get_send_data_commands(self)
        commands.extend(FlexDataset._get_send_data_commands(self))
        return commands

    def add_rigid_physics_object(self, *args, **kwargs):
        """
        Make sure controller knows to treat probe, zone, target, etc. as non-flex objects
        """

        o_id = kwargs.get('o_id', None)
        if o_id is None:
            o_id: int = self.get_unique_id()
            kwargs['o_id'] = o_id

        commands = Dominoes.add_physics_object(self, *args, **kwargs)
        self.non_flex_objects.append(o_id)

        print("Add rigid physics object", o_id)

        return commands

    def add_flex_solid_object(self,
                              record: ModelRecord,
                              position: Dict[str, float],
                              rotation: Dict[str, float],
                              mesh_expansion: float = 0,
                              particle_spacing: float = 0.035,
                              mass: float = 1,
                              scale: Optional[Dict[str, float]] = {"x": 0.1, "y": 0.5, "z": 0.25},
                              material: Optional[str] = None,
                              color: Optional[list] = None,
                              exclude_color: Optional[list] = None,
                              o_id: Optional[int] = None,
                              add_data: Optional[bool] = True,
                              **kwargs) -> List[dict]:

        # so objects don't get stuck in each other -- an unfortunate feature of FLEX
        position = {'x': position['x'], 'y': position['y'] + 0.1, 'z': position['z']}

        commands = FlexDataset.add_solid_object(
            self,
            record = record,
            position = position,
            rotation = rotation,
            scale = scale,
            mesh_expansion = mesh_expansion,
            particle_spacing = particle_spacing,
            mass_scale = 1,
            o_id = o_id)

        # set mass
        commands.append({"$type": "set_flex_object_mass",
                         "mass": mass,
                         "id": o_id})

        # set material and color
        commands.extend(
            self.get_object_material_commands(
                record, o_id, self.get_material_name(material)))

        color = color if color is not None else self.random_color(exclude=exclude_color)
        commands.append(
            {"$type": "set_color",
             "color": {"r": color[0], "g": color[1], "b": color[2], "a": 1.},
             "id": o_id})

        # step physics
        if bool(self.step_physics):
            print("stepping physics forward", self.step_physics)
            commands.append({"$type": "step_physics",
                             "frames": self.step_physics})

        # add data
        print("Add FLEX physics object", o_id)
        if add_data:
            self._add_name_scale_color(record, {'color': color, 'scale': scale, 'id': o_id})
            self.masses = np.append(self.masses, mass)

        return commands

    # def _place_and_push_probe_object(self):
    #     return []

    def _get_push_cmd(self, o_id, position_or_particle=None):
        if not self.all_flex_objects:
            return Dominoes._get_push_cmd(self, o_id, position_or_particle)
        cmd = {"$type": "apply_force_to_flex_object",
               "force": self.push_force,
               "id": o_id,
               "particle": -1}
        print("PUSH CMD FLEX")
        print(cmd)
        return cmd

    def drop_cloth(self) -> List[dict]:

        self.cloth = self.CLOTH_RECORD
        self.cloth_id = self._get_next_object_id()
        self.cloth_position = copy.deepcopy({'x':1.0, 'y':1.5,'z':0.0})
        self.cloth_color = [0.8,0.5,1.0]
        self.cloth_scale = {'x': 1.0, 'y': 1.0, 'z': 1.0}
        self.cloth_mass = 0.5

        commands = self.add_cloth_object(
            record = self.cloth,
            position = self.cloth_position,
            rotation = {k:0 for k in ['x','y','z']},
            scale=self.cloth_scale,
            mass_scale = 1,
            mesh_tesselation = 1,
            tether_stiffness = 1.,
            bend_stiffness = 1.,
            stretch_stiffness = 1.,
            o_id = self.cloth_id)

        # set mass
        commands.append({"$type": "set_flex_object_mass",
                         "mass": self.cloth_mass,
                         "id": self.cloth_id})

        # color cloth
        commands.append(
            {"$type": "set_color",
             "color": {"r": self.cloth_color[0], "g": self.cloth_color[1], "b": self.cloth_color[2], "a": 1.},
             "id": self.cloth_id})

        self._add_name_scale_color(
            self.cloth, {'color': self.cloth_color, 'scale': self.cloth_scale, 'id': self.cloth_id})
        self.masses = np.append(self.masses, self.cloth_mass)

        return commands

    def drop_squishy(self) -> List[dict]:

        self.squishy = self.SOFT_RECORD
        self.squishy_id = self._get_next_object_id()
        self.squishy_position = {'x': 0., 'y': 1.0, 'z': 0.}
        rotation = {k:0 for k in ['x','y','z']}

        self.squishy_color = [0.0,0.8,1.0]
        self.squishy_scale = get_random_xyz_transform(self.middle_scale_range)
        self.squishy_mass = 2.0

        commands = self.add_soft_object(
            record = self.squishy,
            position = self.squishy_position,
            rotation = rotation,
            scale=self.squishy_scale,
            o_id = self.squishy_id)

        # set mass
        commands.append({"$type": "set_flex_object_mass",
                         "mass": self.squishy_mass,
                         "id": self.squishy_id})

        commands.append(
            {"$type": "set_color",
             "color": {"r": self.squishy_color[0], "g": self.squishy_color[1], "b": self.squishy_color[2], "a": 1.},
             "id": self.squishy_id})

        self._add_name_scale_color(
            self.squishy, {'color': self.squishy_color, 'scale': self.squishy_scale, 'id': self.squishy_id})
        self.masses = np.append(self.masses, self.squishy_mass)

        return commands

    def drop_fluid(self) -> List[dict]:

        commands = []

        # create a pool for the fluid
        self.pool_id = self._get_next_object_id()
        print("POOL ID", self.pool_id)
        self.non_flex_objects.append(self.pool_id)
        commands.append(self.add_transforms_object(record=self.RECEPTACLE_RECORD,
                                                   position=TDWUtils.VECTOR3_ZERO,
                                                   rotation=TDWUtils.VECTOR3_ZERO,
                                                   o_id=self.pool_id,
                                                   add_data=True))
        commands.append({"$type": "set_kinematic_state",
                         "id": self.pool_id,
                         "is_kinematic": True,
                         "use_gravity": False})

        # add the fluid; this will also step physics forward 500 times
        self.fluid_id = self._get_next_object_id()
        print("FLUID ID", self.fluid_id)
        commands.extend(self.add_fluid_object(
            position={"x": 0.0, "y": 1.0, "z": 0.0},
            rotation=TDWUtils.VECTOR3_ZERO,
            o_id=self.fluid_id,
            fluid_type=self.ft_selection))
        self.fluid_object_ids.append(self.fluid_id)

        # restore usual time step
        commands.append({"$type": "set_time_step", "time_step": 0.01})

        return commands

    def _place_ramp_under_probe(self) -> List[dict]:

        cmds = Dominoes._place_ramp_under_probe(self)
        self.non_flex_objects.append(self.ramp_id)
        if self.ramp_base_height >= 0.01:
            self.non_flex_objects.append(self.ramp_base_id)
        return cmds

    def _build_intermediate_structure(self) -> List[dict]:

        commands = []
        # step physics
        # if self.all_flex_objects:
        #     commands.append({"$type": "step_physics",
        #                      "frames": 50})

        commands.extend(self.drop_fluid() if self.use_fluid else [])
        commands.extend(self.drop_cloth() if self.use_cloth else [])
        commands.extend(self.drop_squishy() if self.use_squishy else [])

        return commands
Пример #26
0
    def run(self):
        self.start()
        init_setup_commands = [{
            "$type": "set_screen_size",
            "width": 600,
            "height": 480
        }, {
            "$type": "set_render_quality",
            "render_quality": 5
        }]
        self.communicate(init_setup_commands)

        # Create an empty room.
        self.communicate({
            "$type": "create_empty_environment",
            "center": {
                "x": 0,
                "y": 0,
                "z": 0
            },
            "bounds": {
                "x": 8,
                "y": 8,
                "z": 8
            }
        })
        self.communicate({"$type": "set_gravity", "value": False})

        cube_record = ModelLibrarian("models_special.json").get_record(
            "prim_cube")
        self.communicate({
            "$type": "add_object",
            "name": "prim_cube",
            "url": cube_record.get_url(),
            "scale_factor": cube_record.scale_factor,
            "position": {
                "x": 0,
                "y": 0,
                "z": 0
            },
            "rotation": {
                "x": 0,
                "y": 0,
                "z": 0
            },
            "category": cube_record.wcategory,
            "id": 1
        })

        self.communicate({
            "$type": "scale_object",
            "scale_factor": {
                "x": 30,
                "y": 0.0001,
                "z": 30
            },
            "id": 1
        })

        # Add the avatar.
        self.communicate(
            TDWUtils.create_avatar(position={
                "x": 0,
                "y": 0.6,
                "z": 0
            },
                                   look_at=TDWUtils.array_to_vector3(
                                       [0.5, 0.5, 0]),
                                   avatar_id="avatar"))

        self.communicate(self.get_add_hdri_skybox("table_mountain_1_4k"))
        self.communicate({"$type": "rotate_hdri_skybox_by", "angle": 90})

        lib = MaterialLibrarian(library="materials_med.json")
        record = lib.get_record("bricks_chatham_gray_used")
        self.communicate({
            "$type": "add_material",
            "name": "bricks_chatham_gray_used",
            "url": record.get_url()
        })
        self.communicate(
            TDWUtils.set_visual_material(c=self,
                                         substructure=cube_record.substructure,
                                         object_id=1,
                                         material="bricks_chatham_gray_used"))

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

        bench = self.add_object(model_name="b04_wood_metal_park_bench",
                                position={
                                    "x": 2,
                                    "y": 0,
                                    "z": 0.5
                                },
                                rotation={
                                    "x": 0,
                                    "y": -90,
                                    "z": 0
                                },
                                library="models_full.json")

        self.add_object(model_name="b04_wood_metal_park_bench",
                        position={
                            "x": 5,
                            "y": 0,
                            "z": 0.5
                        },
                        rotation={
                            "x": 0,
                            "y": -90,
                            "z": 0
                        },
                        library="models_full.json")

        bench_bounds = self.get_bounds_data(bench)
        top = bench_bounds.get_top(0)

        self.add_object(model_name="cgaxis_models_65_06_vray",
                        position={
                            "x": 1.8,
                            "y": top[1] - 0.42,
                            "z": 0.35
                        },
                        rotation={
                            "x": 0,
                            "y": 0,
                            "z": 0
                        },
                        library="models_full.json")

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

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

        scene_data = self.communicate({
            "$type":
            "look_at_position",
            "avatar_id":
            "avatar",
            "position":
            TDWUtils.array_to_vector3([0.5, 0.5, 0])
        })

        images = Images(scene_data[0])
        TDWUtils.save_images(
            images,
            "bench_book",
            output_directory=
            "/Users/leonard/Desktop/TDWBase-1.5.0/Python/Leonard/compare_COCO_TDW/replicated_images/exterior"
        )
Пример #27
0
    def __init__(self, port: int = 1071):
        lib = ModelLibrarian(str(Path("toys.json").resolve()))
        self.records = lib.records
        self._target_id: int = 0

        super().__init__(port=port)
Пример #28
0
from typing import Dict, List
import random
from tdw.librarian import ModelLibrarian
from tdw.tdw_utils import TDWUtils

# Every model library, sorted by name.
MODEL_LIBRARIES: Dict[str, ModelLibrarian] = {}
for filename in ModelLibrarian.get_library_filenames():
    MODEL_LIBRARIES.update({filename: ModelLibrarian(filename)})


def get_move_along_direction(pos: Dict[str, float], target: Dict[str, float], d: float, noise: float = 0) -> \
        Dict[str, float]:
    """
    :param pos: The object's position.
    :param target: The target position.
    :param d: The distance to teleport.
    :param noise: Add a little noise to the teleport.

    :return: A position from pos by distance d along a directional vector defined by pos, target.
    """
    direction = TDWUtils.array_to_vector3(
        (TDWUtils.vector3_to_array(target) - TDWUtils.vector3_to_array(pos)) /
        TDWUtils.get_distance(pos, target))

    return {
        "x": pos["x"] + direction["x"] * d + random.uniform(-noise, noise),
        "y": pos["y"],
        "z": pos["z"] + direction["z"] * d + random.uniform(-noise, noise)
    }
Пример #29
0
    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)
Пример #30
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.")