Read this for a complete list of changes. TDW v1.9 introduces many changes; this document only covers cases in which you might need to adjust existing code.
All non-API documentation has been completely rewritten. Documentation is now divided into "lessons" for specified subjects such as robotics or visual perception. You can find the complete table of contents on the README. Even if you are an experienced TDW user, we recommend you read our new documentation. You might learn new techniques!
Add-ons are a new feature to TDW that will automatically inject commands into every communicate([])
call. They are very useful for commonplace tasks such as image capture as well as managing agent code.
Add-ons don't replace any existing functionality in TDW; they merely reorganize it. We recommend upgrading your code to use add-ons, but add-ons are never a necessary aspect of TDW.
This function used to send a command to initialize a scene in TDW. Now, that command is sent automatically in the Controller constructor.
In v1.8:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
c.start()
c.communicate(TDWUtils.create_empty_room(12, 12))
In v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
c.communicate(TDWUtils.create_empty_room(12, 12))
This function hasn't been the preferred way to load a stream scene for a while now because it doesn't let you send additional commands on the same frame. We recommend using Controller.get_add_scene(scene_name)
instead.
In v1.8:
from tdw.controller import Controller
c = Controller()
c.load_streamed_scene(scene="tdw_room")
In v1.9:
from tdw.controller import Controller
c = Controller()
c.communicate(c.get_add_scene(scene_name="tdw_room"))
This function hasn't been the preferred way to add an object for a while now because it doesn't let you send additional commands on the same frame. We recommend using Controller.get_add_object(model_name)
instead.
In v1.8:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
c.start()
c.communicate(TDWUtils.create_empty_room(12, 12))
object_id = c.add_object(model_name="iron_box")
In v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
c.communicate([TDWUtils.create_empty_room(12, 12),
c.get_add_object(model_name="iron_box",
object_id=c.get_unique_id())])
This allows cached librarians to be accessible outside of the controller.
self.model_librarian
has been replaced with Controller.MODEL_LIBRARIANS
, a dictionary that is automatically populated as libraries are added. Likewise, self.scene_librarian
has been replaced with Controller.SCENE_LIBRARIANS
and so on.
In v1.8:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
c.start()
c.communicate([TDWUtils.create_empty_room(12, 12),
c.get_add_object(model_name="iron_box",
library="models_core.json",
object_id=c.get_unique_id())])
print(c.model_librarian.library) # Path to models_core.json
In v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
print(Controller.MODEL_LIBRARIANS.keys()) # dict_keys([])
c.communicate([TDWUtils.create_empty_room(12, 12),
c.get_add_object(model_name="iron_box",
library="models_core.json",
object_id=c.get_unique_id())])
print(Controller.MODEL_LIBRARIANS.keys()) # dict_keys(['models_core.json'])
This was introduced in v1.8 and ended up not being that useful; it also slows down the controller too much to be practical.
PyImpact
is now an add-on and now has scrape sounds. The code for generating audio sounds is mostly the same but the code for adding PyImpact to a controller is very different, and a lot easier to use.
This is the code to required to generate impact audio in v1.8:
from pathlib import Path
from tdw.controller import Controller
from tdw.py_impact import PyImpact, AudioMaterial
from tdw.tdw_utils import TDWUtils, AudioUtils
from tdw.object_init_data import AudioInitData
from tdw.output_data import OutputData, Rigidbodies, AudioSources
c = Controller()
c.start()
commands = [{"$type": "load_scene",
"scene_name": "ProcGenScene"},
TDWUtils.create_empty_room(12, 12)]
a = AudioInitData(name="vase_02",
position={"x": 0, "y": 2, "z": 0})
object_id, object_commands = a.get_commands()
commands.extend(object_commands)
avatar_id = "a"
commands.extend(TDWUtils.create_avatar(position={"x": 0, "y": 2, "z": 2},
look_at={"x": 0, "y": 0, "z": 0},
avatar_id=avatar_id))
commands.extend([{"$type": "add_audio_sensor",
"avatar_id": avatar_id},
{"$type": "send_rigidbodies",
"frequency": "always"},
{"$type": "send_collisions",
"enter": True,
"stay": True,
"exit": True,
"collision_types": ["obj", "env"]},
{"$type": "send_audio_sources",
"frequency": "always"}])
resp = c.communicate(commands)
py_impact = PyImpact()
py_impact.set_default_audio_info(object_names={object_id: "vase_02"})
output_directory = Path.home().joinpath("tdw_example_controller_output/py_impact")
if not output_directory.exists():
output_directory.mkdir(parents=True)
AudioUtils.start(output_path=output_directory.joinpath("audio.wav"))
num_frames = 0
done = False
while not done and num_frames < 1000:
for i in range(len(resp) - 1):
r_id = OutputData.get_data_type_id(resp[i])
if r_id == "rigi":
rigi = Rigidbodies(resp[i])
for j in range(rigi.get_num()):
if rigi.get_id(j) == object_id:
if rigi.get_sleeping(j):
done = True
break
commands = py_impact.get_audio_commands(resp=resp,
floor=AudioMaterial.wood_medium,
wall=AudioMaterial.wood_medium)
resp = c.communicate(commands)
audio_playing = True
while audio_playing:
for i in range(len(resp) - 1):
r_id = OutputData.get_data_type_id(resp[i])
if r_id == "audi":
audi = AudioSources(resp[i])
for j in range(audi.get_num()):
if audi.get_object_id(j) == object_id:
if not audi.get_is_playing(j):
audio_playing = False
break
resp = c.communicate([])
AudioUtils.stop()
c.communicate({"$type": "terminate"})
This will record the exact same scenario in v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
from tdw.add_ons.py_impact import PyImpact
from tdw.add_ons.audio_initializer import AudioInitializer
from tdw.add_ons.physics_audio_recorder import PhysicsAudioRecorder
from tdw.backend.paths import EXAMPLE_CONTROLLER_OUTPUT_PATH
c = Controller()
py_impact = PyImpact()
audio = AudioInitializer(avatar_id="a")
recorder = PhysicsAudioRecorder(max_frames=1000)
c.add_ons.extend([audio, py_impact, recorder])
commands = [TDWUtils.create_empty_room(12, 12)]
commands.extend(TDWUtils.create_avatar(position={"x": 0, "y": 2, "z": 2},
look_at={"x": 0, "y": 0, "z": 0},
avatar_id="a"))
commands.extend(c.get_add_physics_object(model_name="vase_02",
object_id=0,
position={"x": 0, "y": 2, "z": 0}))
recorder.start(path=EXAMPLE_CONTROLLER_OUTPUT_PATH.joinpath("py_impact/audio.wav"))
c.communicate(commands)
while recorder.recording:
c.communicate([])
c.communicate({"$type": "terminate"})
The Floorplan
add-on is much more versatile; it can be appended to any controller.
FloorplanController
in v1.8:
from tdw.floorplan_controller import FloorplanController
c = FloorplanController()
c.communicate(c.get_scene_init_commands(scene="1a", layout=0))
Floorplan
in v1.9:
from tdw.controller import Controller
from tdw.add_ons.floorplan import Floorplan
c = Controller()
f = Floorplan()
f.init_scene(scene="1a", layout=0)
c.add_ons.append(f)
c.communicate([])
The Logger
add-on is much more versatile; it can be appended to any controller.
DebugController
in v1.8:
from tdw.debug_controller import DebugController
c = DebugController()
c.start()
Logger
in v1.9:
from tdw.controller import Controller
from tdw.add_ons.logger import Logger
c = Controller()
c.add_ons.append(Logger(record=True, path="out"))
c.communicate([])
The Keyboard
add-on is much more versatile; it can be appended to any controller.
KeyboardController
in v1.8:
from tdw.keyboard_controller import KeyboardController
def stop():
global done
done = True
c.communicate({"$type": "terminate"})
done = False
c = KeyboardController()
c.start()
c.listen(key="esc", function=stop)
while not done:
c.communicate([])
Keyboard
in v1.9:
from tdw.controller import Controller
from tdw.add_ons.keyboard import Keyboard
def stop():
global done
done = True
c.communicate({"$type": "terminate"})
done = False
c = Controller()
keyboard = Keyboard()
keyboard.listen(key="esc", function=stop)
c.add_ons.append(keyboard)
while not done:
c.communicate([])
These were tricky to use and unintuitive (because AudioInitData
didn't actually load any audio data). They have been replaced with Controller.get_add_physics_object()
.
In v1.8:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
from tdw.object_init_data import AudioInitData
c = Controller()
c.start()
commands = [TDWUtils.create_empty_room(12, 12)]
a = AudioInitData(name="rh10",
position={"x": 0, "y": 0, "z": 0},
rotation={"x": 0, "y": 0, "z": 0})
object_id, object_commands = a.get_commands()
commands.extend(object_commands)
c.communicate(commands)
In v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
c = Controller()
object_id = c.get_unique_id()
commands = [TDWUtils.create_empty_room(12, 12)]
commands.extend(c.get_add_physics_object(model_name="rh10",
position={"x": 0, "y": 0, "z": 0},
rotation={"x": 0, "y": 0, "z": 0},
object_id=object_id))
c.communicate(commands)
- Moved audio classes
AudioMaterial
,Base64Sound
andModes
fromtdw.py_impact
totdw.physics_audio.audio_material
,tdw.physics_audio.base64_sound
, andtdw.physics_audio.modes
- Renamed
ObjectInfo
toObjectAudioStatic
and moved it fromtdw.py_impact
totdw.physics_audio.object_audio_static
- Moved
AudioUtils
fromtdw.tdw_utils
totdw.audio_utils
- Moved
QuaternionUtils
fromtdw.tdw_utils
totdw.quaternion_utils
- Moved
tdw.flex.fluid_types.FluidType
totdw.flex_data.fluid_type.FluidType
- Removed
tdw.flex.fluid_types.FluidTypes
Default fluid type data is now stored in a dictionary:tdw.flex_data.fluid_type.FLUID_TYPES
This change was made because the idiom of multiple environments no longer meaningfully exists in TDW; there are scenes, and scenes have one or more rectangular regions.
Additionally, renamed Environments
output data (ID "envi"
) to SceneRegions
(ID "sreg"
)
In v1.8:
from tdw.controller import Controller
from tdw.output_data import OutputData, Environments
c = Controller()
resp = c.communicate([c.get_add_scene(scene_name="floorplan_1a"),
{"$type": "send_environments"}])
for i in range(len(resp) - 1):
r_id = OutputData.get_data_type_id(resp[i])
if r_id == "envi":
environments = Environments(resp[i])
for j in range(environments.get_num()):
environment_id = environments.get_id(j)
environment_center = environments.get_center(j)
environment_bounds = environments.get_bounds(j)
print(environment_id, environment_center, environment_bounds)
In v1.9:
from tdw.controller import Controller
from tdw.output_data import OutputData, SceneRegions
c = Controller()
resp = c.communicate([c.get_add_scene(scene_name="floorplan_1a"),
{"$type": "send_scene_regions"}])
for i in range(len(resp) - 1):
r_id = OutputData.get_data_type_id(resp[i])
if r_id == "sreg":
scene_regions = SceneRegions(resp[i])
for j in range(scene_regions.get_num()):
region_id = scene_regions.get_id(j)
region_center = scene_regions.get_center(j)
region_bounds = scene_regions.get_bounds(j)
print(region_id, region_center, region_bounds)
- Renamed
create_flex_fluid_object
toset_flex_fluid_actor
- Renamed
create_flex_fluid_source_actor
toset_flex_source_actor
- Removed Flex commands for moving and rotating objects; instead of using these, you should move and teleport objects before adding Flex actors:
rotate_flex_object_by
rotate_flex_object_by_quaternion
rotate_flex_object_to
teleport_and_rotate_flex_object
teleport_flex_object
Previously, Rigidbodies
output data included dynamic data such as velocity that can change per-frame and static data such as mass that isn't expected to change per frame.
Now, that data has been split into dynamic and static output data types. Rigidbodies
contains dynamic data and can be requested with send_rigidbodies
. StaticRigidbodies
contains static data and can be requested with send_static_rigidbodies
.
In v1.8:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
from tdw.output_data import OutputData, Rigidbodies
c = Controller()
c.start()
resp = c.communicate([TDWUtils.create_empty_room(12, 12),
c.get_add_object(model_name="iron_box",
position={"x": 0, "y": 3, "z": 0},
object_id=c.get_unique_id()),
{"$type": "send_rigidbodies",
"frequency": "always"}])
for i in range(len(resp) - 1):
r_id = OutputData.get_data_type_id(resp[i])
if r_id == "rigi":
rigi = Rigidbodies(resp[i])
for j in range(rigi.get_num()):
object_id = rigi.get_id(j)
velocity = rigi.get_velocity(j)
angular_velocity = rigi.get_angular_velocity(j)
sleeping = rigi.get_sleeping(j)
mass = rigi.get_mass(j)
kinematic = rigi.get_kinematic(j)
In v1.9:
from tdw.controller import Controller
from tdw.tdw_utils import TDWUtils
from tdw.add_ons.py_impact import PyImpact
from tdw.add_ons.audio_initializer import AudioInitializer
from tdw.add_ons.physics_audio_recorder import PhysicsAudioRecorder
from tdw.backend.paths import EXAMPLE_CONTROLLER_OUTPUT_PATH
c = Controller()
py_impact = PyImpact()
audio = AudioInitializer(avatar_id="a")
recorder = PhysicsAudioRecorder(max_frames=1000)
c.add_ons.extend([audio, py_impact, recorder])
commands = [TDWUtils.create_empty_room(12, 12)]
commands.extend(TDWUtils.create_avatar(position={"x": 0, "y": 2, "z": 2},
look_at={"x": 0, "y": 0, "z": 0},
avatar_id="a"))
commands.extend(c.get_add_physics_object(model_name="vase_02",
object_id=0,
position={"x": 0, "y": 2, "z": 0}))
recorder.start(path=EXAMPLE_CONTROLLER_OUTPUT_PATH.joinpath("py_impact/audio.wav"))
c.communicate(commands)
while recorder.recording:
c.communicate([])
c.communicate({"$type": "terminate"})
By default, audio sources are no longer parented to objects. The id
parameter is now a unique identifier for the audio source rather than an object ID. The position
parameter sets the position of the audio source.
To parent an audio source to a TDW object, send parent_audio_source_to_object
.
"Paintings" are now "textured quads".
v1.8 | v1.9 |
---|---|
create_painting |
create_textured_quad |
destroy_painting |
destroy_textured_quad |
hide_painting |
Removed; set "show" in show_textured_quad to False |
rotate_painting_by |
rotate_textured_quad_by |
rotate_painting_to_euler_angles |
Removed |
scale_painting |
scale_textured_quad |
set_painting_texture |
set_textured_quad |
show_painting |
show_textured_quad |
teleport_painting |
teleport_textured_quad |
- Renamed parameter
env_id
inset_reverb_space_simple
andset_reverb_space_expert
toregion_id
- Removed
set_proc_gen_reflection_probe
. This has been replaced withenable_reflection_probes
.
The bodies of A_Simple_Body
were centered at the position of the avatar rather than halfway above. This meant that if the avatar was created at (0, 0, 0), its body would appear halfway through the floor and then "pop" up. Now, the bodies are positioned at (0, 0.5, 0) so that the avatar's pivot is at its bottom-center (just like objects in TDW).
Additionally, the cube body of A_Simple_Body
required far more torque to turn than the other bodies. Now, it has a capsule collider, allowing it to spin easier.
single_object.py
and multi_env.py
have been removed from the repo and have been replaced with tdw_image_dataset
, a separate repo. ImageDataset
is very similar to SingleObject
with two notable changes:
- It is better organized for users to create subclasses.
- It replaces
IdPassGrayscale
data withOcclusion
data, which is overall faster and somewhat less error prone.
All example controllers have either been rewritten or removed. There are many new examples and they are grouped by documentation lesson. See here.
Magnebot 2 is a rewrite of Magnebot. The Magnebot is now an add-on that can be added to any TDW controller. It can still be optionally used as a controller like in earlier versions.
To upgrade:
- Delete this folder if it exists:
~/asset_bundle_creator
- Install Unity 2020.3.24f1 via Unity Hub
The next time you create a model asset bundle, the asset_bundle_creator
project will be recreated.
To upgrade:
- Delete this folder if it exists:
~/robot_creator
- Install Unity 2020.3.24f1 via Unity Hub
The next time you create a robot asset bundle, the robot_creator
project will be re-cloned.
tdw_physics is now compatible with TDW v1.9.0
To upgrade: git pull
Updated Unity Engine from 2020.2.7f1 to 2020.3.24f1. This update is very unlikely to affect simulation behavior; let us know if it does.
If you have access to the TDWBase repo, you need to use Unity 2020.3.24f1 instead of Unity 2020.2.7f1.