diff --git a/ILE_API.md b/ILE_API.md index 15957a5..9cf9c11 100644 --- a/ILE_API.md +++ b/ILE_API.md @@ -68,9 +68,9 @@ randomly chosen for each scene. Default: no pointing VectorFloatConfig dicts): The position of this agent in each scene. If configured as a list, a new position will be randomly chosen for each scene. Default: random -- `rotation_y` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints -and/or MinMaxInt dicts): The rotation of this agent in each scene. If -configured as a list, a new rotation will be randomly chosen for each +- `rotation_y` (float, or [MinMaxFloat](#MinMaxFloat) dict, or list of +floats and/or MinMaxFloat dicts): The rotation of this agent in each scene. +If configured as a list, a new rotation will be randomly chosen for each scene. Default: random - `type` (string, or list of strings) The model ("type") of the agent. Please see the list in our [schema doc]( @@ -194,8 +194,11 @@ On default, a blocking wall is on that platform, forcing the performer to choose a side to drop off of the platform, but this can be disabled. - `has_blocking_wall` (bool): Enables the blocking wall so that the performer has to stop and choose a side of the room. Default: True -- `has_long_blocking_wall` (bool): Enables the long blocking wall used in -Spatial Reorientation tasks. Overrides `has_blocking_wall`. Default: False +- `has_double_blocking_wall` (bool): Enables the double blocking wall used +in Spatial Reorientation tasks. Overrides both `has_blocking_wall` and +`has_long_blocking_wall`. Default: False +- `has_long_blocking_wall` (bool): Enables the extra-long blocking wall. +Overrides `has_blocking_wall`. Default: False - `is_short` (bool): Makes the platform short (a Y scale of 0.5 rather than 1). Default: False - `is_thin` (bool): Makes the platform thin (an X scale of 0.5 rather @@ -204,6 +207,9 @@ than 1). Default: False dict, or list of StructuralPlatformConfig dicts): Configurations to generate other platforms that may intersect with the bisecting platform. Default: No other platforms +- `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) +dict, or list of MinMaxFloat dicts): The starting Z position for the +performer agent. Default: 0.5 away from the back wall #### DoubleDoorConfig @@ -225,23 +231,28 @@ back of the room where the AI will start on. Default: True - `door_material` (string, or list of strings): The material or material type for the doors. - `occluder_wall_position_z` (float, or list of floats, or -[MinMaxFloat](#MinMaxFloat) dict: Where the occluder wall will cross the -z-axis in the room. `performer_distance_from_occluder` will override this +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): +Where the occluder wall will cross the +z-axis in the room. `occluder_distance_from_performer` will override this value. Default: 0 (middle of the room) - `occluder_distance_from_performer` (float, or list of floats, or -[MinMaxFloat](#MinMaxFloat) dict: If there is a platform the, the performer +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): +If there is a platform the, the performer will start on top of the platform and the occluder wall will be placed this distance (`occluder_distance_from_performer`) from the performer. Must be greater than 1 or null Default: 6.5 - `platform_height` (float, or list of floats, or -[MinMaxFloat](#MinMaxFloat) dict: The height (y scale) of the platform +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): +The height (y scale) of the platform Default: 1.5 - `platform_length` (float, or list of floats, or -[MinMaxFloat](#MinMaxFloat) dict: The lenth (z scale) of the platform +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): +The lenth (z scale) of the platform Default: 1 - `platform_width` (float, or list of floats, or -[MinMaxFloat](#MinMaxFloat) dict: The width (z scale) of the platform +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): +The width (z scale) of the platform Default: 1 - `start_drop_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): Step number to start dropping the bisecting @@ -342,16 +353,29 @@ what teleports in the scene after the agent performs its imitation sequence to and the performer is kidnapped. Options are: 1) agent_only: The imitation agent teleports away from the containers but in view. Nothing else is teleported. -2) containers: The containers are teleported but still in view. The -containers are still aligned with their start rotation. The imitation agent -is teleported away from the containers but in view. -3) containers_rotate: The containers are teleported but still in view. The -containers are rotated 90 degrees to be perpendicular to how they started. +2) containers: The containers are teleported anr rotated but still in view. The imitation agent is teleported away from the containers but in view. 4) performer: The performer is teleported to a random part of the room but looks at the center of the room where the containers still are. The imitation agent is teleported away from the containers but in view. - +Default: random +- `relative_container_rotation`: (int, or list of int): Dictates +what degree of rotation change the containers will rotate relative to +their clockwise starting rotation. For example, if the they are facing +270 (starting on the right side) and container_rotation of 45 will make +the final rotation 315. Examples of what rotations containers cannot +rotate: +1) 360 and 180 for both left and right because the containers will +face identical or opposite from their start. +2) 90 on right side, 270 on left which will make the containers face +directly away from the performer. +Direction containers can rotate for right side: [45, 135, 225, 270, 315] +Direction containers can rotate for left side: [45, 90, 135, 225, 315] +Default: random +- `global_container_rotation`: (int, or list of int): Dictates +what degree of rotation change the containers will rotate on based on +the absolute global rotation. Options are: [45, 135, 180, 225, 315]. +Will override `relative_container_rotation` Default: random #### InteractableObjectConfig @@ -405,8 +429,8 @@ positioning this object relative to another object, rather than using `position`. If configuring this as a list, then all listed options will be applied to each scene in the listed order, with later options overriding earlier options if necessary. Default: not used -- `rotation` ([VectorIntConfig](#VectorIntConfig) dict, or list of -VectorIntConfig dicts): The rotation of this object in each scene. For a +- `rotation` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of +VectorFloatConfig dicts): The rotation of this object in each scene. For a list, a new rotation will be randomly chosen for each scene. Default: random - `scale` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, @@ -444,6 +468,12 @@ Default: Use `separate_lid` - `shape` (string, or list of strings): The shape (object type) of this object in each scene. For a list, a new shape will be randomly chosen for each scene. Default: random +- `surrounded_by_lava` (bool, or list of bools): Whether or not this +object will be surrounded by lava. Default: False +- `surrounding_lava_size`: (int, or list of ints): The width of the +surrounding lava. Only used if `surrounded_by_lava` is set to True. In +that case, default is 1, otherwise the value will be None. Note that +this value will need to fit within room bounds, otherwise may error. - `rotate_cylinders` (bool): Whether or not to rotate cylindrical shapes along their x axis so that they are placed on their round sides (needed for collision scenes). This would only apply to these shapes: 'cylinder', @@ -508,6 +538,12 @@ be one of the following: object must be referenced by the 'relative_object_label' field, and the distance away from the relative object must be set by the `adjacent_distance` field. + - `adjacent_corner` - The object will be placed near the corner + referenced by the 'relative_object_label' field. The corner labels + are 'front_left' (-X, +Z), 'front_right' (+X, +Z), + 'back_left' (-X, -Z), and 'back_right' (+X, -Z). The + distance away from the corner will be determined by the + `adjacent_distance` field. - `adjacent_performer` - The object will be placed next to the performer. The object can be placed in 'front', 'back', left, or 'right' of the performer using the 'direction'. The object @@ -576,9 +612,13 @@ be one of the following: provided, a wall will be chosen at random. - `adjacent_distance` (VectorFloatConfig, or list of VectorFloatConfigs): The X/Z distance in global coordinates between this object's position and -the relative object's position. Only usable with the `adjacent` `keyword`. -By default, this object will be positioned 0.1 away from the relative -object in a random, valid direction. +the relative object's (or corner's) position. Only usable with the +`adjacent` or `adjacent_corner` keyword. By default, this object will be +positioned 0.1 away from the relative object (or corner) in a random, +valid direction. Note that if using this for a corner with the +`surrounded_by_lava` property, you'll need to use the same dimensions for +x/z and that they're divisible by 0.5 to make sure they line up correctly +with the lava island. - `container_label` (string, or list of strings): The label of a container object that already exists in your configuration. Only required by some keyword locations. @@ -654,8 +694,8 @@ positioning this object relative to another object, rather than using `position`. If configuring this as a list, then all listed options will be applied to each scene in the listed order, with later options overriding earlier options if necessary. Default: not used -- `rotation` ([VectorIntConfig](#VectorIntConfig) dict, or list of -VectorIntConfig dicts): The rotation of these objects in each scene. If +- `rotation` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of +VectorFloatConfig dicts): The rotation of these objects in each scene. If given a list, a rotation will be randomly chosen for each object and each scene. Is overridden by the `keyword_location`. Default: random @@ -702,7 +742,8 @@ performer will be randomly placed in the room. They will not be placed in the lava or the island. If the `tool_type` is inaccessible the performer will randomly start on the side of the room where the target is where they cannot access the tool. Default: False -- `tool_rotation` (int, or list of ints, or [MinMaxInt](#MinMaxInt): +- `tool_rotation` (float, or list of floats, or +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Angle that tool should be rotated out of alignment with target. This option cannot be used with `guide_rails`. Default: 0 - `distance_between_performer_and_tool` (float, or list of floats, @@ -710,13 +751,13 @@ or [MinMaxFloat](#MinMaxFloat): The distance away the performer is from the tool at start. The performer will be at random point around a rectangular perimeter surrounding the tool. This option cannot be used with `random_performer_position`. Default: None -- `tool_offset_backward_from_lava` ( -[RandomizableFloat](#RandomizableFloat)): The vertical offset of tool -either away from the lava pool. Must be greater than or equal to 0 +- `tool_offset_backward_from_lava` (float, or [MinMaxFloat](#MinMaxFloat) +dict, or list of floats and/or MinMaxFloat dicts): The vertical offset of +tool either away from the lava pool. Must be greater than or equal to 0 Default: 0 -- `tool_horizontal_offset` ([RandomizableFloat](#RandomizableFloat)): -The horizontal offset of tool either -left or right from being aligned with the target. If `tool_type` is +- `tool_horizontal_offset` (float, or [MinMaxFloat](#MinMaxFloat) dict, or +list of floats and/or MinMaxFloat dicts): The horizontal offset of tool +either left or right from being aligned with the target. If `tool_type` is inaccessible this has alternate behavior. See `inaccessible_tool_blocking_wall_horizontal_offset` for description. Default: 0 @@ -819,6 +860,8 @@ options for this group. The options include the following: - `throwers`: A random thrower that throws a random object between step 0 and step 10. Throw force is 500 to 1000 times the mass of the thrown object. + - `tube_occluders`: A random tube occluder. + - `turntables`: A random turntable. - `walls`: A random interior room wall. Default: All types - `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt)): The number of @@ -892,15 +935,17 @@ will guess and go towards one of the buckets behind itself. Contains data to describe the performer's sidesteps. -- `begin` ([RandomizableInt](#RandomizableInt)): +- `begin` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints and/or +MinMaxInt dicts): The step where the performer agent starts being frozen and can only use the `"Pass"` action. For example, if 1, the performer agent must pass immediately at the start of the scene. This is an inclusive limit. -- `object_label` ([RandomizableString](#RandomizableString)): +- `object_label` (string, or list of strings): The label of the object the performer will sidestep around. The performer must be 3 distance away from the object's center for sidesteps to work. -- `degrees` ([RandomizableInt](#RandomizableInt)): +- `degrees` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints and/or +MinMaxInt dicts): The positive or negative degrees the performer will sidestep around the object. Positive forces the performer to sidestep right while negative sidesteps left. The degree value must always be in 90 or -90 degree @@ -991,6 +1036,10 @@ the ceiling where the dropper should be placed. - `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Position in the z direction of the of the ceiling where the dropper should be placed. +- `projectile_dimensions` (float, or list of floats, or +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Dimensions +of the projectile. Overrides the `projectile_scale` if configured. +Default: use `projectile_scale` - `projectile_labels` (string, or list of strings): A label for an existing object in your ILE configuration that will be used as this device's projectile, or new label(s) to associate with a new projectile object. @@ -1006,6 +1055,9 @@ material or material type. the projectile. Default is based on the shape. - `projectile_shape` (string, or list of strings): The shape or type of the projectile. +- `no_projectile` (bool, or list of bools): If `true`, this device will +not be holding a projectile; other "projectile" options will be ignored. +Default: `false` #### StructuralLOccluderConfig @@ -1091,9 +1143,9 @@ as the number of steps to wait. Default: false - `reverse_direction` (bool, or list of bools): Reverse the rotation direction of a sideways wall by rotating the wall 180 degrees. Only used if `origin` is set to a wall and not `top`. Default: [true, false] -- `rotation_y` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, -or list of MinMaxInt dicts): Y rotation of a non-sideways occluder wall; -only used if any `origin` is set to `top`. Default is 0 to 359. +- `rotation_y` (float, or list of floats, or [MinMaxInt](#MinMaxFloat) +dict, or list of MinMaxFloat dicts): Y rotation of a non-sideways occluder +wall; only used if any `origin` is set to `top`. Default is 0 to 359. - `wall_material` (string, or list of strings): Material of the occluder wall (cube) @@ -1160,21 +1212,22 @@ MinMaxInt dicts): Number of areas to be used with these parameters - `activate_after`: (str, or list of strs): Overrides the `activation_step` (overriding the manual config) based on the movement of other object(s) in the scene. Should be set to one or more labels for mechanical objects that -may move or rotate, like placers or turntables. The `activation_step` of -this object will be set to the step immediately after ALL of the objects -finish moving and rotating. If multiple labels are configured, all labels -will be used. Default: Use `activation_step` +may move or rotate, like placers or turntables, as well as agents. The +`activation_step` of this object will be set to the step immediately after +ALL of the objects and agents finish moving and rotating. If multiple +labels are configured, all labels will be used. Default: Use +`activation_step` - `activate_on_start_or_after`: (str, or list of strs, or bool): Overrides the `activation_step` (overriding the manual config) based on the movement of other object(s) in the scene. Should be set to one or more labels for -mechanical objects that can move or rotate, like placers or turntables. If -ANY of the objects begin moving or rotating immediately at the start of the -scene (step 1), then the `activation_step` of this object will be set to -the step immediately after ALL of the objects finish moving and rotating; -otherwise, if ALL of the objects begin moving and rotating after step 1, -then the `activation_step` of this object will be set to 1. If multiple -labels are configured, all labels will be used. Default: Use -`activation_step` +mechanical objects that can move or rotate, like placers or turntables, as +well as agents. If ANY of the objects or agents begin moving or rotating +immediately at the start of the scene (step 1), then the `activation_step` +of this object will be set to the step immediately after ALL of the objects +and agents finish moving and rotating; otherwise, if ALL of the objects and +agents begin moving and rotating after step 1, then the `activation_step` +of this object will be set to 1. If multiple labels are configured, all +labels will be used. Default: Use `activation_step` - `activation_step`: (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): Step on which the placer should begin its downward movement. Default: between 0 and 10 @@ -1233,8 +1286,8 @@ new material will be randomly chosen for each scene. Default: random - `placed_object_position`: ([VectorFloatConfig](#VectorFloatConfig) dict, or list of VectorFloatConfig dicts): The placed object's position in the scene -- `placed_object_rotation`: (int, or list of ints, or -[MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): The placed +- `placed_object_rotation`: (float, or list of floats, or +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): The placed object's rotation on the y axis. - `placed_object_scale`: (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Placed @@ -1409,6 +1462,10 @@ overriding earlier options if necessary. Default: not used - `position_wall` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): The position along the wall that the thrower will be placed. +- `projectile_dimensions` (float, or list of floats, or +[MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Dimensions +of the projectile. Overrides the `projectile_scale` if configured. +Default: use `projectile_scale` - `projectile_labels` (string, or list of strings): A label for an existing object in your ILE configuration that will be used as this device's projectile, or new label(s) to associate with a new projectile object. @@ -1454,6 +1511,51 @@ may cause unexpected behavior, so we recommend using values of 5 or more in your custom config files. - `wall` (string, or list of strings): Which wall the thrower should be placed on. Options are: left, right, front, back. +- `no_projectile` (bool, or list of bools): If `true`, this device will +not be holding a projectile; other "projectile" options will be ignored. +Default: `false` + +#### StructuralTubeOccluderConfig + +Defines details of a structural tube occluder (or "tube-cluder"). + +- `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of +MinMaxInt dicts): Number of structures to generate with these options. +- `labels` (string, or list of strings): A label or labels to assign to +these object(s). Always automatically assigned "tube_occluders" +- `down_after`: (string, or list of strings): Overrides the `down_step` +(overriding the manual config) based on the movement of other object(s) in +the scene. Should be set to one or more labels for mechanical objects that +may move or rotate, like placers or turntables, as well as agents. The +`down_step` of this object will be set to the step immediately after ALL of +the objects and agents finish moving and rotating. If multiple labels are +configured, all labels will be used. Default: Use `down_step` +- `down_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or +list of MinMaxInt dicts): The step on which the tube occluder should begin +to move down from the ceiling to the floor. Note that this should happen +before `up_step`. Default: between 1 and 10 +- `material` (string, or list of strings): The structure's material or +material type. Default: random +- `position_x` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) +dict, or list of MinMaxFloat dicts): The structure's X position. +Default: random +- `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) +dict, or list of MinMaxFloat dicts): The structure's Z position. +Default: random +- `radius` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) +dict, or list of MinMaxFloat dicts): The structure's radius. Default: +between 0.5 and 5 +- `up_after`: (string, or list of strings): Overrides the `up_step` +(overriding the manual config) based on the movement of other object(s) in +the scene. Should be set to one or more labels for mechanical objects that +may move or rotate, like placers or turntables, as well as agents. The +`up_step` of this object will be set to the step immediately after ALL of +the objects and agents finish moving and rotating. If multiple labels are +configured, all labels will be used. Default: Use `up_step` +- `up_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or +list of MinMaxInt dicts): The step on which the tube occluder should begin +to move up from the floor to the ceiling. Note that this should happen +after `down_step`. Default: between 51 and 60 #### StructuralTurntableConfig @@ -1544,28 +1646,44 @@ without stepping in lava. Possible values: 'no_tool', 'too_short'. Defines details of a tool object. - `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of -MinMaxInt dicts): Number of structures to be created with these parameters -- `guide_rails` (bool, or list of bools): If True, guide rails will be -generated to guide the tool in the direction it is oriented. If a target -exists, the guide rails will extend to the target. Default: random +MinMaxInt dicts): Number of tools to generate with these parameters +- `align_distance` (float, or [MinMaxFloat](#MinMaxFloat) dict, or list of +floats and/or MinMaxFloat dicts): The distance separating the edges of two +objects. Only used if `align_with` is also set. Default: between `1` and +`2` +- `align_with` (string, or list of strings): The label of an object (that +already exists in your configuration) with which this tool should be +aligned (this tool will be positioned behind the object, so you can push +this tool forward into the object). Overrides any configured `position` +and `rotation_y`. Use `align_distance` to set the distance separating the +edges of the two objects. Default: Use `position` - `labels` (string, or list of strings): A label or labels to be assigned -to this object. Always automatically assigned "platforms" +to this object. Always automatically assigned "tools" +- `length` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list +of MinMaxInt dicts): The length of the tool. Tools only have specific +sizes and the values much match exactly. Valid lengths are integers +4 to 9. If shape is set, this value is ignored. Default: random - `position` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of -VectorFloatConfig dicts): The structure's position in the scene +VectorFloatConfig dicts): The tool's position in the scene - `rotation_y` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) -dict, or list of MinMaxFloat dicts): The structure's rotation in the scene +dict, or list of MinMaxFloat dicts): The tool's rotation in the scene - `shape` (string, or list of strings): The shape (object type) of this object in each scene. For a list, a new shape will be randomly chosen for -each scene. Must be a valid [tool shape](#Lists). If set, `length` and -`width` are ignored. Default: random -- `length` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list -of MinMaxInt dicts): The length of the tool. Tools only have specific -sizes and the values much match exactly. Valid lengths are integers -4 to 9. If shape is set, this value is ignored. Default: Use shape +each scene. Must be a valid [tool shape](#Lists). If set, ignores `length`, +`width`, and `tool_type`. Default: Use `tool_type` +- `tool_type` (str, or list of strs): The type of tool to generate, either +`rectangular`, `hooked`, or `small`. Default: `rectangular` or `hooked` - `width` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): The width of the tool. Tools only have specific sizes and the values much match exactly. Valid widths are -0.5, 0.75, 1.0. If shape is set, this value is ignored. Default: Use shape +0.5, 0.75, 1.0. If shape is set, this value is ignored. Default: random +- `material` (string, or list of strings): The material (color/texture) or +material category to use on this tool in each scene. For lists, a new +material will be randomly chosen for each scene. +Default: TOOL_MATERIALS +- `surrounded_by_lava` (bool, or list of bools): Whether or not this +tool will be surrounded by lava. If True, width of lava will be 1. +Default: False #### TripleDoorConfig @@ -2342,7 +2460,7 @@ performer_start_position: #### performer_start_rotation -([VectorIntConfig](#VectorIntConfig) dict, or list of VectorIntConfig +([VectorFloatConfig](#VectorFloatConfig) dict, or list of VectorFloatConfig dicts): The starting rotation of the performer agent, or a list of rotations, from which one is chosen at random for each scene. The (required) `y` is left/right and (optional) `x` is up/down. Default: random @@ -2476,7 +2594,6 @@ random_structural_objects: - platforms - ramps - throwers - - tools - turntables - walls num: @@ -3219,6 +3336,39 @@ structural_throwers: projectile_scale: 0.85 ``` +#### structural_tube_occluders + +([StructuralTubeOccluderConfig](#StructuralTubeOccluderConfig), or list +of [StructuralTubeOccluderConfig](#StructuralTubeOccluderConfig) dict) -- +One or more templates containing properties needed to generate tube +occluders. Default: None + + +Simple Example: +``` +structural_tube_occluders: + - num: 0 +``` + +Advanced Example: +``` +structural_tube_occluders: + - num: + min: 1 + max: 3 + - num: 1 + position: + x: [1, 2] + y: 0 + z: + min: -3 + max: 3 + radius: 4 + material: WOOD_MATERIALS + down_step: 21 + up_step: 81 +``` + #### structural_turntables ([StructuralTurntableConfig](#StructuralTurntableConfig), or list @@ -3385,25 +3535,24 @@ tools: min: 1 max: 3 - num: 1 - shape: tool_rect_1_00_x_9_00 - position: + shape: tool_rect_1_00_x_9_00 + position: x: [1,2] y: 0 z: - min: -3 - max: 3 + min: -3 + max: 3 - num: [1, 3] - shape: + shape: - tool_rect_0_50_x_4_00 - tool_rect_0_75_x_4_00 - tool_rect_1_00_x_4_00 - position: + position: x: [4, 5] y: 0 z: - min: -5 - max: -4 - + min: -5 + max: -4 ``` diff --git a/README.md b/README.md index f1b3387..173cf87 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,32 @@ python ile.py -c ile_config.yaml -n 10 -p scene ### Latest Release Notes +#### Release 1.9 + +Changelog: +- Added ILE example configs for Eval 7 tasks: + - `ile_configs/interactive_hidden_set_rotation.yaml` + - `ile_configs/interactive_multi_tool_use.yaml` +- Added "isosceles" tools for tool tasks: similar to "hooked" tools, which have one side of exactly length 3, and one side of variable length, "isosceles" tools have two sides of equal length. +- Added `adjacent_corner` keyword for `keyword_location` options to position an object in the corner of the room. +- Added `align_with` option in ToolConfig to position a tool in alignment with another object. +- Added `labels` option to AgentConfig and KeywordObjectsConfig to set custom labels on those entities. +- Added `materials` option in ToolConfig to set tool color/texture. By default, this is randomized among all available tool materials (currently: grey, brown, green, or pink). +- Added `no_projectile` option in StructuralDropperConfig and StructuralThrowerConfig to generate droppers/throwers without projectiles. +- Added `projectile_dimensions` option in StructuralDropperConfig and StructuralThrowerConfig to generate random projectiles with specific dimensions. +- Added `surrounded_by_lava` and `surrounding_lava_size` options in InteractableObjectConfig and ToolConfig to automatically surround an object with lava. +- Added new open-topped container objects (like trash bins and lidless crates) for Spatial Reorientation and other scenes. +- Updated `ile_configs/interactive_arithmetic.yaml` and `ile_configs/interactive_number_comparison.yaml` to have occluders that come down at the start of the scene. See the comments in these files for more information, including instructions on how to remove them if desired. +- Updated `ile_configs/interactive_spatial_reference.yaml` to correctly "freeze" the performer agent until everything else in the scene stops moving. +- Updated `ile_configs/interactive_collision.yaml` and `ile_configs/interactive_trajectory.yaml` to generate rooms of varying depths. +- Updated `ile_configs/interactive_imitation.yaml` to rotate containers similarly to the eval scenes. +- Updated `ile_configs/interactive_moving_target_prediction.yaml` to use `shortcut_start_on_platform`. +- Updated `ile_configs/interactive_spatial_reorientation.yaml` to have the correct starting Z position for the performer agent, as well as the correct black "blocking walls" on top of the platform. +- Bug fix: Doors should always be different colors from their surrounding walls (including door occluder sections). +- Bug fix: Lids can now be placed on top of containers after they have been rotated. +- Bug fix: Rotations are allowed to be floats. +- Bug fix: Turntable (rotating_cog) material should default to GreyWoodMCS. + #### Release 1.8 Changelog: @@ -398,7 +424,7 @@ List of example ILE configuration files for generating scenes similar to specifi - [interactive_collision.yaml](./ile_configs/interactive_collision.yaml) Generates scenes similar to the interactive collision eval tasks: start on a platform, with lava bisecting the room; a thrower rolls a green ball that may or may not collide with a soccer ball; a two-door-occluder descends from the ceiling to obstruct your view of the collision/trajectory; you can only open one door and must determine which side of the room contains the soccer ball. - [interactive_imitation.yaml](./ile_configs/interactive_imitation.yaml) Generates scenes similar to the interactive imitation eval tasks. See the config file for details. - [interactive_number_comparison.yaml](./ile_configs/interactive_number_comparison.yaml) Generates scenes similar to the interactive number comparison eval tasks: start on a platform bisecting the room; one or more soccer ball multi-retrieval targets on one side; fewer soccer balls on the other side. -- [interactive_set_rotation.yaml](./ile_configs/interactive_set_rotation.yaml) Generates scenes similar to the interactive set rotation eval tasks: start in a room with one or more identical containers positioned on top of a turntable (giant cog); a soccer ball retrieval target is placed inside a container; lids are placed on all containers; the turntable rotates between 90 and 360 degrees. +- [interactive_set_rotation.yaml](./ile_configs/interactive_set_rotation.yaml) Generates scenes similar to the interactive set rotation eval tasks: start in a room with one or more identical containers positioned on top of a turntable (giant cog); a soccer ball retrieval target is placed inside a container; lids are placed on all containers; the turntable rotates between 90 and 360 degrees (alternatively, the performer agent is forced to move around the turntable between 90 and 360 degrees). - [interactive_shell_game.yaml](./ile_configs/interactive_shell_game.yaml) Generates scenes similar to the interactive shell game eval tasks: start in a room with one or more identical containers; a soccer ball retrieval target is placed inside a container; lids are placed on all containers; a placer drags the target's container to a new location. - [interactive_spatial_reference.yaml](./ile_configs/interactive_spatial_reference.yaml) Generates scenes similar to the interactive spatial reference eval tasks: start on a platform bisecting the room; identical closed containers on both sides; an agent walks and points at the container hiding the soccer ball retrieval target; a turntable (giant cog) rotates a non-agent object so it "points" at a container, which may be the same container, or the opposite container. - [interactive_spatial_reorientation.yaml](./ile_configs/interactive_spatial_reorientation.yaml) Generates scenes similar to the interactive spatial reorientation eval tasks: start on a platform bisecting the room; identical bins on either side of the room; a placer drops a soccer ball retrieval target into one bin; the performer agent is kidnapped and sometimes teleported to the other side of the room; sometimes one room wall is a different color, and/or the room is trapezoidal. @@ -406,6 +432,17 @@ List of example ILE configuration files for generating scenes similar to specifi - [interactive_tool_choice.yaml](./ile_configs/interactive_tool_choice.yaml) Generates scenes similar to the interactive tool choice eval tasks: start on a platform bisecting the room; soccer balls surrounded by lava on both sides; one side has a useful tool, but the other side does not have a tool, or has a tool that is too small to use. - [passive_seeing_leads_to_knowing.yaml](./ile_configs/passive_seeing_leads_to_knowing.yaml) Generates scenes similar to the passive seeing leads to knowing eval tasks. See the config file for details. +Eval 7 Tasks: + +| Eval 7 Task | MCS Core Domains | Example Config Files | +| --- | --- | --- | +| Hidden Set Rotation (Interactive) | O1, O4, P5 | [interactive_hidden_set_rotation.yaml](./ile_configs/interactive_hidden_set_rotation.yaml) | +| Knowledgeable Agents (Interactive) | TODO | TODO | +| Multi-Tool-Use (Interactive) | O5 | [interactive_multi_tool_use.yaml](./ile_configs/interactive_multi_tool_use.yaml) | + +- [interactive_hidden_set_rotation.yaml](./ile_configs/interactive_hidden_set_rotation.yaml) Generates scenes similar to the interactive hidden set rotation eval tasks: start in a room with one or more identical containers positioned on top of a turntable (giant cog); a soccer ball retrieval target is placed inside a container; lids are placed on all containers; a giant "tube occluder" occludes the turntable and containers while the turntable rotates between 90 and 360 degrees (alternatively, the performer agent is forced to move around the turntable between 90 and 360 degrees). +- [interactive_multi_tool_use.yaml](./ile_configs/interactive_multi_tool_use.yaml) Generates scenes similar to the interactive multi tool use eval tasks: a soccer ball retrieval target starts in a corner of the room and surrounded by lava; a hooked tool starts in the middle of the room and surrounded by lava; a rectangular tool starts somewhere in the room; you must first use the rectangular tool to push the hooked tool out from the lava, then use the hooked tool to pull the soccer ball out from the lava. + ## Scene Validation In the `MCS` repository, in the `scripts` folder, use one of the following scripts to run all of the new scenes, depending on if you're running an interactive or passive (intuitive physics, agents, etc.) hypercube. diff --git a/generator/__init__.py b/generator/__init__.py index 838b7d3..625ebc5 100644 --- a/generator/__init__.py +++ b/generator/__init__.py @@ -16,12 +16,20 @@ TypeChoice ) from .exceptions import SceneException -from .geometry import MAX_TRIES, ObjectBounds +from .geometry import ( + MAX_TRIES, + PERFORMER_HALF_WIDTH, + PERFORMER_HEIGHT, + PERFORMER_WIDTH, + ObjectBounds +) from .interactive_goals import ( InteractiveGoal, + MultiRetrievalGoal, RetrievalGoal, TransferralGoal, TraversalGoal ) from .materials import MaterialTuple +from .objects import SceneObject from .scene import PartitionFloor, Scene, get_step_limit_from_dimensions diff --git a/generator/agents.py b/generator/agents.py index 84a8023..3f0a334 100644 --- a/generator/agents.py +++ b/generator/agents.py @@ -2,14 +2,17 @@ import math import uuid from dataclasses import dataclass -from typing import Any, Dict, List, Tuple +from enum import Enum, auto +from random import choice, randint, uniform +from typing import Dict, List, NamedTuple, Tuple from machine_common_sense.config_manager import Vector3d from generator.exceptions import SceneException +from . import materials from .geometry import create_bounds -from .materials import MaterialTuple +from .objects import SceneObject # Agents were scaled down to 0.6 from their original size. However, lets # retain the original bounding box size though and just apply that scale. @@ -17,9 +20,25 @@ AGENT_DIMENSIONS = {'x': 0.5 * AGENT_SCALE, 'y': 1.6 * AGENT_SCALE, 'z': 0.5 * AGENT_SCALE} -AGENT_TYPES = [ - 'agent_female_01', 'agent_female_02', 'agent_female_04', - 'agent_male_02', 'agent_male_03', 'agent_male_04'] + +# TODO MCS-1591 Update novel agent types +NOVEL_AGENTS_FEMALE = ['agent_female_05', 'agent_female_06'] +NOVEL_AGENTS_MALE = ['agent_male_07', 'agent_male_08'] +NOVEL_AGENTS = NOVEL_AGENTS_FEMALE + NOVEL_AGENTS_MALE +FAMILIAR_AGENTS_FEMALE = [ + 'agent_female_01', + 'agent_female_02', + 'agent_female_03', + 'agent_female_04' +] +FAMILIAR_AGENTS_MALE = [ + 'agent_male_01', + 'agent_male_02', + 'agent_male_03', + 'agent_male_04' +] +FAMILIAR_AGENTS = FAMILIAR_AGENTS_FEMALE + FAMILIAR_AGENTS_MALE +AGENT_TYPES = FAMILIAR_AGENTS + NOVEL_AGENTS AGENT_MOVEMENT_ANIMATIONS = ['TPM_walk', 'TPM_run', 'TPF_walk', 'TPF_run'] @@ -28,7 +47,9 @@ 'type': '', 'debug': { 'color': [], - 'info': [] + 'info': [], + 'offset': {'x': 0, 'y': 0, 'z': 0}, + 'positionY': 0 }, 'mass': 75, 'agentSettings': {}, @@ -56,6 +77,8 @@ # Should equal MOVE_MAGNITUDE in MCSSimulationAgent.cs MOVE_DISTANCE = 0.04 +POINT_START_FRAME_COUNT = 7 + @dataclass class BlobInfo(): @@ -85,7 +108,8 @@ def __post_init__(self): 'type': '', 'debug': { 'color': [], - 'info': [] + 'info': [], + 'offset': {'x': 0, 'y': 0, 'z': 0} }, 'mass': 75, 'materials': [], @@ -110,7 +134,486 @@ def __post_init__(self): }] } -POINT_START_FRAME_COUNT = 7 +MIN_BLOB_HEIGHT = AGENT_DIMENSIONS['y'] * 0.75 +MAX_BLOB_HEIGHT = AGENT_DIMENSIONS['y'] * 1.25 + + +class AgentGroup(Enum): + FEMALE_ADULT = auto() + FEMALE_TEEN = auto() + MALE_ADULT = auto() + MALE_TEEN = auto() + + +def get_agent_group(agent_type: str) -> AgentGroup: + if agent_type in [ + 'agent_male_01', 'agent_male_02', 'agent_male_03', 'agent_male_04' + ]: + return AgentGroup.MALE_ADULT + if agent_type in [ + 'agent_male_05', 'agent_male_06', 'agent_male_07', 'agent_male_08' + ]: + return AgentGroup.MALE_TEEN + if agent_type in [ + 'agent_female_01', 'agent_female_02', 'agent_female_03', + 'agent_female_04' + ]: + return AgentGroup.FEMALE_ADULT + if agent_type in [ + 'agent_female_05', 'agent_female_06', 'agent_female_07', + 'agent_female_08' + ]: + return AgentGroup.FEMALE_TEEN + raise Exception(f'agent type not recognized: {agent_type}') + + +class Clothing(NamedTuple): + with_dark_skin: bool + with_light_skin: bool + color: List[str] + + +# Note whether to use specific clothing materials with dark skin and/or light +# skin tone agents (chosen arbitrarily based on looking at the clothing). +FEMALE_ADULT_DRESSES = { + 0: Clothing(True, False, ['yellow']), + 1: Clothing(True, True, ['red']), + 2: Clothing(False, False, ['black', 'white']), + 3: Clothing(False, True, ['black', 'white']), + 4: Clothing(False, True, ['red', 'black']), + 5: Clothing(False, False, ['black', 'yellow']), + 6: Clothing(True, False, ['white', 'orange']), + 7: Clothing(False, True, ['black']), + 8: Clothing(False, False, ['brown']), + 9: Clothing(False, True, ['red', 'black']), + 10: Clothing(False, True, ['blue']), + 11: Clothing(True, False, ['white']), + 12: Clothing(True, True, ['blue']), + 13: Clothing(True, True, ['green']) +} +FEMALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS = { + 0: Clothing(False, False, ['orange']), + 1: Clothing(True, True, ['blue', 'white']), + 2: Clothing(False, False, ['red']), + 3: Clothing(True, False, ['red']), + 4: Clothing(True, True, ['red']), + 5: Clothing(True, False, ['white']), + 6: Clothing(False, False, ['black', 'brown']), + 7: Clothing(True, False, ['white']), + 8: Clothing(True, True, ['green']), + 9: Clothing(True, False, ['green', 'white', 'yellow']), + 10: Clothing(False, True, ['blue']), + 11: Clothing(False, True, ['green']) +} +FEMALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS = { + 0: Clothing(True, True, ['purple']), + 1: Clothing(False, False, ['black', 'white']), + 2: Clothing(True, True, ['blue']), + 3: Clothing(False, False, ['blue']), + 4: Clothing(True, False, ['white']), + 5: Clothing(False, True, ['black']), + 6: Clothing(True, False, ['grey']), + 7: Clothing(True, False, ['green']), + 8: Clothing(True, False, ['yellow']), + 9: Clothing(False, True, ['grey']), + 10: Clothing(True, True, ['red']) +} +FEMALE_ADULT_NO_BUTTON_SHIRTS = { + 0: Clothing(True, False, ['yellow']), + 1: Clothing(False, True, ['red']), + 2: Clothing(False, True, ['black']), + 3: Clothing(True, False, ['yellow', 'blue']), + 4: Clothing(False, True, ['purple', 'black']), + 5: Clothing(True, True, ['green']), + 6: Clothing(False, True, ['blue', 'red']), + 7: Clothing(False, True, ['blue', 'black']), + 8: Clothing(True, False, ['yellow', 'pink', 'purple', 'blue']), + 9: Clothing(False, False, ['black', 'white']), + 10: Clothing(True, False, ['white']), + 11: Clothing(True, True, ['blue']), + 12: Clothing(True, False, ['orange']), + 13: Clothing(True, True, ['blue', 'green']), + 14: Clothing(True, True, ['blue']) +} +FEMALE_ADULT_TOPS = { + # Dress + 0: FEMALE_ADULT_DRESSES, + # Tank-top + 1: FEMALE_ADULT_DRESSES, + # Long-sleeve formal button-down + 2: FEMALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS, + # Short-sleeve formal button-down + 3: FEMALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS, + # Long-sleeve casual button-down + 4: FEMALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS, + # Short-sleeve casual button-down + 5: FEMALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS, + # High-neck long-sleeve no-button + 6: FEMALE_ADULT_NO_BUTTON_SHIRTS, + # Long-sleeve no-button + 7: FEMALE_ADULT_NO_BUTTON_SHIRTS, + # Short-sleeve no-button t-shirt + 8: FEMALE_ADULT_NO_BUTTON_SHIRTS +} +MALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS = { + 0: Clothing(False, True, ['grey']), + 1: Clothing(False, True, ['blue', 'red']), + 2: Clothing(True, False, ['blue', 'white']), + 3: Clothing(True, False, ['red', 'white']), + 4: Clothing(True, True, ['red']), + 5: Clothing(False, True, ['grey']), + 6: Clothing(False, True, ['red', 'black']), + 7: Clothing(False, True, ['green', 'black']), + 8: Clothing(True, False, ['green', 'yellow']), + 9: Clothing(False, True, ['blue', 'white']), + 10: Clothing(True, False, ['white']) +} +MALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS = { + 0: Clothing(True, False, ['yellow']), + 1: Clothing(True, False, ['white']), + 2: Clothing(False, True, ['black']), + 3: Clothing(True, False, ['white', 'blue']), + 4: Clothing(False, False, ['white', 'black']), + 5: Clothing(False, False, ['white', 'black']), + 6: Clothing(False, False, ['grey']), + 7: Clothing(True, True, ['blue']), + 8: Clothing(True, True, ['purple']), + 9: Clothing(False, False, ['brown']) +} +MALE_ADULT_NO_BUTTON_SHIRTS = { + 0: Clothing(True, True, ['red']), + 1: Clothing(True, True, ['green']), + 2: Clothing(False, True, ['black']), + 3: Clothing(False, True, ['blue']), + 4: Clothing(True, True, ['red', 'blue']), + 5: Clothing(True, False, ['green', 'blue', 'orange']), + 6: Clothing(False, False, ['black', 'white']), + 7: Clothing(True, False, ['green']), + 8: Clothing(True, False, ['yellow', 'pink', 'purple', 'blue']), + 9: Clothing(False, False, ['yellow', 'black']), + 10: Clothing(True, False, ['white']), + 11: Clothing(False, True, ['green', 'black']), + 12: Clothing(True, False, ['white']), + 13: Clothing(True, True, ['purple']) +} +MALE_ADULT_TOPS = { + # Long-sleeve formal button-down + 0: MALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS, + # Short-sleeve formal button-down + 1: MALE_ADULT_FORMAL_BUTTON_DOWN_SHIRTS, + # Long-sleeve casual button-down + 2: MALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS, + # Short-sleeve casual button-down + 3: MALE_ADULT_CASUAL_BUTTON_DOWN_SHIRTS, + # High-neck long-sleeve no-button + 4: MALE_ADULT_NO_BUTTON_SHIRTS, + # Long-sleeve no-button + 5: MALE_ADULT_NO_BUTTON_SHIRTS, + # Short-sleeve no-button t-shirt + 6: MALE_ADULT_NO_BUTTON_SHIRTS +} +FEMALE_TEEN_SHIRTS = { + 0: Clothing(True, False, ['yellow']), + 1: Clothing(False, True, ['black', 'white']), + 2: Clothing(True, False, ['white', 'yellow', 'red', 'blue']), + 3: Clothing(False, True, ['black', 'white', 'red']), + 4: Clothing(True, True, ['blue', 'white']), + 5: Clothing(True, True, ['purple', 'pink']), + 6: Clothing(True, False, ['white', 'grey']), + 7: Clothing(True, True, ['blue', 'green']), + 8: Clothing(True, True, ['red']), + 9: Clothing(True, True, ['blue', 'green']), + 10: Clothing(True, True, ['green']), + 11: Clothing(True, True, ['green', 'white']), + 12: Clothing(True, True, ['red', 'white']), + 13: Clothing(True, True, ['blue', 'red', 'white']), + 14: Clothing(True, False, ['yellow', 'black']), + 15: Clothing(True, True, ['green', 'yellow']) +} +MALE_TEEN_SHIRTS = { + 0: Clothing(True, True, ['red']), + 1: Clothing(True, True, ['red', 'blue']), + 2: Clothing(False, True, ['black']), + 3: Clothing(True, False, ['yellow']), + 4: Clothing(True, False, ['white', 'grey']), + 5: Clothing(True, True, ['green', 'black', 'white']), + 6: Clothing(False, False, ['grey']), + 7: Clothing(False, False, ['black', 'blue', 'green']), + 8: Clothing(True, True, ['purple']), + 9: Clothing(True, True, ['blue', 'white']), + 10: Clothing(False, True, ['black', 'green']), + 11: Clothing(True, False, ['orange', 'black']), + 12: Clothing(True, True, ['red', 'white']), + 13: Clothing(True, True, ['blue', 'red', 'white']), + 14: Clothing(True, False, ['yellow', 'black']), + 15: Clothing(True, True, ['green', 'yellow']) +} +TEEN_SWEATERS = { + 0: Clothing(True, False, ['white', 'grey']), + 1: Clothing(False, True, ['black']), + 2: Clothing(False, True, ['black', 'grey']), + 3: Clothing(True, True, ['purple']), + 4: Clothing(True, False, ['orange', 'black', 'white']), + 5: Clothing(True, True, ['blue', 'white']), + 6: Clothing(False, True, ['black', 'blue', 'white']), + 7: Clothing(True, False, ['yellow']), + 8: Clothing(True, True, ['red', 'white']), + 9: Clothing(True, True, ['green']), + 10: Clothing(True, True, ['blue', 'green']), + 11: Clothing(True, True, ['purple', 'pink']) +} +FEMALE_TEEN_TOPS = { + 0: FEMALE_TEEN_SHIRTS, + 1: FEMALE_TEEN_SHIRTS, + 2: FEMALE_TEEN_SHIRTS, + 3: TEEN_SWEATERS, + 4: FEMALE_TEEN_SHIRTS, + 5: FEMALE_TEEN_SHIRTS, + 6: FEMALE_TEEN_SHIRTS, + 7: FEMALE_TEEN_SHIRTS, + 8: FEMALE_TEEN_SHIRTS, + 9: FEMALE_TEEN_SHIRTS, + 10: FEMALE_TEEN_SHIRTS +} +MALE_TEEN_TOPS = { + 0: TEEN_SWEATERS, + 1: MALE_TEEN_SHIRTS, + 2: MALE_TEEN_SHIRTS, + 3: MALE_TEEN_SHIRTS, + 4: MALE_TEEN_SHIRTS, + 5: MALE_TEEN_SHIRTS, + 6: MALE_TEEN_SHIRTS, + 7: MALE_TEEN_SHIRTS +} + + +def _filter_chest_materials( + data: Dict[int, Dict[int, Clothing]], + dark: bool +) -> Dict[int, List[int]]: + return dict([(chest_index, [ + index for index, clothing in chest_materials.items() if + getattr(clothing, 'with_dark_skin' if dark else 'with_light_skin') + ]) for chest_index, chest_materials in data.items()]) + + +# High-contrast chest materials for light or dark skin agents. +MALE_ADULT_DARK_SKIN_TOPS = _filter_chest_materials(MALE_ADULT_TOPS, True) +MALE_ADULT_LIGHT_SKIN_TOPS = _filter_chest_materials(MALE_ADULT_TOPS, False) +FEMALE_ADULT_DARK_SKIN_TOPS = _filter_chest_materials(FEMALE_ADULT_TOPS, True) +FEMALE_ADULT_LIGHT_SKIN_TOPS = _filter_chest_materials( + FEMALE_ADULT_TOPS, + False +) +MALE_TEEN_DARK_SKIN_TOPS = _filter_chest_materials(MALE_TEEN_TOPS, True) +MALE_TEEN_LIGHT_SKIN_TOPS = _filter_chest_materials(MALE_TEEN_TOPS, False) +FEMALE_TEEN_DARK_SKIN_TOPS = _filter_chest_materials(FEMALE_TEEN_TOPS, True) +FEMALE_TEEN_LIGHT_SKIN_TOPS = _filter_chest_materials(FEMALE_TEEN_TOPS, False) + + +class SkinTone(Enum): + LIGHTEST = -3 + LIGHT = -2 + MEDIUM_LIGHT = -1 + MEDIUM_DARK = 1 + DARK = 2 + DARKEST = 3 + + +def get_skin_tone(agent_type: str, skin: int) -> SkinTone: + """Returns the SkinTone for the agent with the given type and skin + index.""" + # The adult female models have skin material options with makeup. + female_adult = (get_agent_group(agent_type) == AgentGroup.FEMALE_ADULT) + # Lightest tone + if (not female_adult and skin == 4) or (female_adult and skin == 12): + return SkinTone.LIGHTEST + # Light tone + if (not female_adult and skin == 5) or (female_adult and skin == 13): + return SkinTone.LIGHT + # Medium-light tone + if ( + (not female_adult and skin == 0) or + (female_adult and skin in [0, 4, 8]) + ): + return SkinTone.MEDIUM_LIGHT + # Medium-dark tone + if ( + (not female_adult and skin == 2) or + (female_adult and skin in [2, 6, 10]) + ): + return SkinTone.MEDIUM_DARK + # Dark tone + if ( + (not female_adult and skin == 3) or + (female_adult and skin in [3, 7, 11]) + ): + return SkinTone.DARK + # Darkest tone + if ( + (not female_adult and skin == 1) or + (female_adult and skin in [1, 5, 9]) + ): + return SkinTone.DARKEST + + +def get_agent(scene): + for obj in scene.objects: + if 'agent' in obj['type']: + return obj + + +def get_random_agent_settings( + agent_type: str, + short_sleeves_only: bool = False, + high_contrast_only: bool = False, + settings: Dict[str, int] = None +) -> Dict[str, int]: + """Returns random settings for the agent with the given type. Only chooses + short-sleeved shirts and dresses if short_sleeves_only is True. Only + chooses shirt and dress materials with high contract compared to skin tone + if high_contrast_only is True. Will attempt to properly use the given + settings, but will not override illegal values or combinations.""" + output = (settings or {}).copy() + group = get_agent_group(agent_type) + if output.get('chest') is None: + if group == AgentGroup.FEMALE_ADULT: + output['chest'] = choice( + [0, 1, 3, 5, 8] if short_sleeves_only else list(range(0, 9)) + ) + if group == AgentGroup.MALE_ADULT: + output['chest'] = choice( + [1, 3, 6] if short_sleeves_only else list(range(0, 7)) + ) + if group == AgentGroup.FEMALE_TEEN: + output['chest'] = choice( + [0, 1, 4, 5, 7, 8, 9] if short_sleeves_only else + list(range(0, 11)) + ) + if group == AgentGroup.MALE_TEEN: + output['chest'] = choice( + [1, 4, 5, 7] if short_sleeves_only else list(range(0, 8)) + ) + if output.get('skin') is None: + if group == AgentGroup.FEMALE_ADULT: + output['skin'] = choice([0, 1, 2, 3, 12, 13]) + else: + output['skin'] = randint(0, 5) + dark = get_skin_tone(agent_type, output['skin']).value > 0 + if output.get('chestMaterial') is None: + if high_contrast_only: + if group == AgentGroup.FEMALE_ADULT: + output['chestMaterial'] = choice(( + FEMALE_ADULT_DARK_SKIN_TOPS if dark else + FEMALE_ADULT_LIGHT_SKIN_TOPS + )[output['chest']]) + if group == AgentGroup.MALE_ADULT: + output['chestMaterial'] = choice(( + MALE_ADULT_DARK_SKIN_TOPS if dark else + MALE_ADULT_LIGHT_SKIN_TOPS + )[output['chest']]) + else: + if group == AgentGroup.FEMALE_ADULT: + chest_material_max = 14 + if output['chest'] in [0, 1]: + chest_material_max = 13 + if output['chest'] in [2, 3]: + chest_material_max = 10 + if output['chest'] in [4, 5]: + chest_material_max = 11 + if group == AgentGroup.MALE_ADULT: + chest_material_max = 11 + if output['chest'] in [0, 1]: + chest_material_max = 9 + if output['chest'] in [2, 3]: + chest_material_max = 10 + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + chest_material_max = 15 + if ( + group == AgentGroup.FEMALE_TEEN and output['chest'] == 3 or + group == AgentGroup.MALE_TEEN and output['chest'] == 0 + ): + chest_material_max = 11 + output['chestMaterial'] = randint(0, chest_material_max) + if output.get('eyes') is None: + output['eyes'] = randint(0, 3) + if output.get('feet') is None: + if group == AgentGroup.FEMALE_ADULT: + output['feet'] = randint(0, 1) + if group == AgentGroup.MALE_ADULT: + output['feet'] = randint(0, 2) + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + output['feet'] = randint(0, 3) + if output.get('feetMaterial') is None: + feet_material_max = 11 + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + if output['feet'] == 1: + feet_material_max = 9 + if output['feet'] == 2: + feet_material_max = 10 + output['feetMaterial'] = randint(0, feet_material_max) + if output.get('hair') is None: + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + output['hair'] = randint(0, 9) + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + # TODO MCS-1724 Replace + # output['hair'] = randint(0, 6) + output['hair'] = randint( + 0, + 5 if group == AgentGroup.MALE_TEEN else 6 + ) + if output.get('hairMaterial') is None: + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + output['hairMaterial'] = ( + 0 if output['hair'] == 5 else randint(0, 3) + ) + if group == AgentGroup.FEMALE_TEEN: + output['hairMaterial'] = randint(0, 4) + if group == AgentGroup.MALE_TEEN: + hair_material_max = 3 if output['hair'] in [3, 5] else 4 + output['hairMaterial'] = randint(0, hair_material_max) + if output.get('hatMaterial') is None: + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + output['hatMaterial'] = ( + randint(0, 5 if output['hair'] == 9 else 11) + if output['hair'] >= 7 else -1 + ) + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + output['hatMaterial'] = ( + randint(0, 11) if output['hair'] >= 5 else -1 + ) + if output.get('hideHair') is None: + output['hideHair'] = choice([False] * 4 + [True]) + if output.get('isElder') is None: + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + output['isElder'] = choice([False] * 4 + [True]) + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + output['isElder'] = False + if output.get('legs') is None: + if group == AgentGroup.FEMALE_ADULT: + output['legs'] = randint(0, 3) + if group == AgentGroup.MALE_ADULT: + output['legs'] = randint(0, 1) + if group == AgentGroup.FEMALE_TEEN: + output['legs'] = randint(0, 9) + if group == AgentGroup.MALE_TEEN: + output['legs'] = randint(0, 6) + if output.get('legsMaterial') is None: + if group in [AgentGroup.FEMALE_ADULT, AgentGroup.MALE_ADULT]: + output['legsMaterial'] = randint( + 0, + 13 if output['legs'] == 3 else 14 + ) + if group in [AgentGroup.FEMALE_TEEN, AgentGroup.MALE_TEEN]: + output['legsMaterial'] = randint(0, 23) + # Do not use the following properites unless configured. + for prop in ['glasses', 'jacket', 'jacketMaterial', 'tie', 'tieMaterial']: + if output.get(prop) is None: + output[prop] = 0 + for prop in ['showBeard', 'showGlasses', 'showJacket', 'showTie']: + if output.get(prop) is None: + output[prop] = False + return output def create_agent( @@ -120,14 +623,15 @@ def create_agent( rotation_y: float, settings: dict = None, position_y_modifier: float = 0 -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of an agent without any actions.""" - agent = copy.deepcopy(AGENT_TEMPLATE) + agent = SceneObject(copy.deepcopy(AGENT_TEMPLATE)) agent['id'] += str(uuid.uuid4()) + agent['type'] = type agent['shows'][0]['position'] = { 'x': position_x, - 'y': position_y_modifier, + 'y': position_y_modifier + agent['debug']['positionY'], 'z': position_z } agent['shows'][0]['rotation'] = { @@ -145,8 +649,8 @@ def create_agent( standing_y=0 ) agent['debug']['dimensions'] = copy.deepcopy(AGENT_DIMENSIONS) - agent['debug']['offset'] = {'x': 0, 'y': 0, 'z': 0} - agent['debug']['positionY'] = 0 + if type in NOVEL_AGENTS: + agent['debug']['untrainedShape'] = True return agent @@ -155,15 +659,16 @@ def create_blob( position_x: float, position_z: float, rotation_y: float, - material_tuple: MaterialTuple, - height: float = 0.9, - position_y_modifier: float = 0 -) -> Dict[str, Any]: + material_tuple: materials.MaterialTuple, + height: float = None, + position_y_modifier: float = 0, + with_nose: bool = False +) -> SceneObject: """Create and return an instance of a blob. By default, it will be approximately as tall as a normal agent.""" - blob = copy.deepcopy(BLOB_TEMPLATE) + blob = SceneObject(copy.deepcopy(BLOB_TEMPLATE)) blob['id'] += str(uuid.uuid4()) - blob['type'] = type + blob['type'] = type + ('_nose' if with_nose else '') blob['shows'][0]['rotation'] = { 'x': 0, 'y': rotation_y, @@ -171,7 +676,8 @@ def create_blob( } base_dimensions = BLOB_SHAPES[type].dimensions - scale = round(height / base_dimensions.y, 4) + blob_height = height or uniform(MIN_BLOB_HEIGHT, MAX_BLOB_HEIGHT) + scale = round(blob_height / base_dimensions.y, 4) blob['shows'][0]['scale'] = {'x': scale, 'y': scale, 'z': scale} blob['debug']['dimensions'] = { 'x': round(base_dimensions.x * scale, 4), @@ -185,6 +691,7 @@ def create_blob( 'y': standing_y + position_y_modifier, 'z': position_z } + blob['debug']['positionY'] = standing_y blob['materials'] = [material_tuple.material] blob['debug']['color'] = material_tuple.color @@ -206,7 +713,9 @@ def _check_steps( step_end: int, is_loop: bool ) -> None: - if step_begin is None or step_begin < 0 or not isinstance(step_begin, int): + if step_begin is None or step_begin < 0 or ( + (not isinstance(step_begin, int)) and (not step_begin.is_integer()) + ): raise SceneException( f"The step_begin must be an integer greater than or equal to 0, " f"but was: {step_begin}" @@ -243,7 +752,7 @@ def _check_steps( ) -def add_agent_action(agent: dict, action_id, step_begin, +def add_agent_action(agent: SceneObject, action_id, step_begin, step_end=None, is_loop=False): actions = agent.get('actions', []) _check_steps(actions, step_begin, step_end, is_loop) @@ -265,7 +774,7 @@ def add_agent_action(agent: dict, action_id, step_begin, def add_agent_movement( - agent: dict, step_begin: int, points: List[Tuple[float, float]], + agent: SceneObject, step_begin: int, points: List[Tuple[float, float]], animation: str = "TPM_walk", repeat=False): sequence = [{ "animation": animation, @@ -281,7 +790,7 @@ def add_agent_movement( } -def add_agent_pointing(agent: dict, step_begin: int) -> None: +def add_agent_pointing(agent: SceneObject, step_begin: int) -> None: """Adds a pointing animation (the agent points in whatever direction it's facing) that begins at the given step and is held indefinitely.""" @@ -489,3 +998,79 @@ def estimate_move_step_length(begin: dict, end: dict) -> int: 'TPM_walkbackwards', 'TPM_wave' ] + + +# Note whether to use specific wall materials with dark skin and/or light +# skin tone agents (chosen arbitrarily based on looking at the materials). +WALL_MATERIALS_DARK_SKIN = [ + materials.DRYWALL, + materials.DRYWALL_BEIGE, + materials.DRYWALL_GREEN, + materials.DRYWALL_ORANGE, + materials.DRYWALL_4_TILED, + materials.EGGSHELL_DRYWALL, + materials.WALL_DRYWALL_GREY, + materials.YELLOW_DRYWALL, + materials.BEDROOM_FLOOR_1, + materials.TEXTURES_COM_WOOD_FINE_50_1_SEEMLESS, + materials.WOOD_FLOORS_CROSS, + materials.WOOD_GRAIN_TAN, + materials.KINDERGARTEN_BLUE_WOOD, + materials.KINDERGARTEN_RED_WOOD, + materials.KINDERGARTEN_GREEN_WOOD, + materials.KINDERGARTEN_YELLOW_WOOD, + materials.NURSERY_BROWN_WOOD, + materials.CONCRETE_BOARDS_1, + materials.GREY_GRANITE, + materials.PINK_CONCRETE_BEDROOM_1, + materials.WHITE_COUNTERTOP +] + [item for item in ( + materials.CUSTOM_DRYWALL_MATERIALS + materials.CUSTOM_WOOD_MATERIALS +) if ( + not item.material.startswith('Custom/Materials/Black') and + not item.material.startswith('Custom/Materials/Brown') +)] +WALL_MATERIALS_LIGHT_SKIN = [ + materials.BROWN_DRYWALL, + materials.DRYWALL, + materials.DRYWALL_BEIGE, + materials.DRYWALL_GREEN, + materials.DRYWALL_4_TILED, + materials.EGGSHELL_DRYWALL, + materials.RED_DRYWALL, + materials.WALL_DRYWALL_GREY, + materials.DARK_WOOD_2, + materials.DARK_WOOD_SMOOTH_2, + materials.WORN_WOOD, + materials.KINDERGARTEN_BLUE_WOOD, + materials.KINDERGARTEN_RED_WOOD, + materials.KINDERGARTEN_GREEN_WOOD, + materials.KINDERGARTEN_YELLOW_WOOD, + materials.CONCRETE_BOARDS_1, + materials.CONCRETE_FLOOR, + materials.GREY_GRANITE, + materials.WHITE_COUNTERTOP +] + [item for item in ( + materials.CUSTOM_DRYWALL_MATERIALS + materials.CUSTOM_WOOD_MATERIALS +)] +WALL_MATERIALS_NO_AGENTS = [ + materials.BLACK_WOOD, + materials.WHITE_WOOD, + # Ignore most metal textures due to the reflection. + materials.BLACK_SMOOTH_METAL, + materials.BRASS_1, + materials.BROWN_METAL_1, + materials.BRUSHED_ALUMINUM_BLUE, + materials.BRUSHED_IRON_ALBEDO, + materials.GENERIC_STAINLESS_STEEL, + materials.HAMMERED_METAL_ALBEDO, + materials.METAL, + materials.WHITE_METAL, + materials.NURSERY_CABINET_METAL, + # Ignore some brown textures. + materials.LIGHT_WOOD_COUNTERS_1, + materials.LIGHT_WOOD_COUNTERS_3, + materials.LIGHT_WOOD_COUNTERS_4, + materials.WOOD_GRAIN_BROWN, + materials.BROWN_MARBLE_FAKE_1 +] diff --git a/generator/base_objects.py b/generator/base_objects.py index 7ba408a..e86ec0e 100644 --- a/generator/base_objects.py +++ b/generator/base_objects.py @@ -30,6 +30,7 @@ SOFA_CHAIR_1_MATERIALS, SOFA_CHAIR_8_MATERIALS, SOFA_THORKEA_MATERIALS, + TOOL_MATERIALS, WOOD_MATERIALS, MaterialTuple ) @@ -218,6 +219,30 @@ class _FunctionArgs(): ) ] ) +_ANTIQUE_ARMCHAIR_2_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=1.4, y=1.7, z=1.4), + mass=10, + offset=Vector3d(x=0, y=0.85, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.94, y=0, z=0.94), + position=Vector3d(x=0, y=0.72, z=0.14), + ) + ] +) +_ANTIQUE_ARMCHAIR_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=1.3, y=1.7, z=1.6), + mass=10, + offset=Vector3d(x=0, y=0.85, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.02, y=0, z=1.12), + position=Vector3d(x=0, y=0.7, z=0.2), + ) + ] +) _ANTIQUE_CHAIR_1_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.76, y=1.26, z=0.64), mass=10, @@ -230,6 +255,30 @@ class _FunctionArgs(): ) ] ) +_ANTIQUE_CHAIR_2_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.8, y=1.72, z=1), + mass=5, + offset=Vector3d(x=0, y=0.86, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.64, y=0, z=0.64), + position=Vector3d(x=0, y=0.88, z=0.12), + ) + ] +) +_ANTIQUE_CHAIR_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.84, y=1.72, z=0.84), + mass=5, + offset=Vector3d(x=0, y=0.86, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.64, y=0, z=0.68), + position=Vector3d(x=0, y=0.8, z=0.1), + ) + ] +) _ANTIQUE_SOFA_1_SIZE = ObjectBaseSize( dimensions=Vector3d(x=2, y=1.4, z=0.68), mass=20, @@ -294,6 +343,30 @@ class _FunctionArgs(): ) ] ) +_BARREL_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.73, y=0.93, z=0.73), + mass=10, + offset=Vector3d(x=0, y=0.465, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.38, y=0.6, z=0.38), + position=Vector3d(x=0, y=0.38, z=0) + ) + ] +) +_BARREL_4_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.73, y=0.82, z=0.73), + mass=10, + offset=Vector3d(x=0, y=0.41, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.38, y=0.64, z=0.38), + position=Vector3d(x=0, y=0.41, z=0) + ) + ] +) _BED_1_SIZE = ObjectBaseSize( dimensions=Vector3d(x=1.299, y=1.015, z=2.108), mass=20, @@ -408,6 +481,54 @@ class _FunctionArgs(): ) ] ) +_BED_11_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=1.45, z=3.2), + mass=50, + offset=Vector3d(x=0, y=0.725, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.9, y=0, z=2.8), + position=Vector3d(x=0, y=1.1, z=-0.145) + ) + ] +) +_BED_12_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=2.2, z=3.2), + mass=50, + offset=Vector3d(x=0, y=1.1, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.9, y=0, z=2.8), + position=Vector3d(x=0, y=1.1, z=-0.145) + ) + ] +) +_BIN_1_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.5, y=0.38, z=0.34), + mass=5, + offset=Vector3d(x=0, y=0.19, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.383, y=0.338, z=0.218), + position=Vector3d(x=0, y=0.203, z=0) + ) + ] +) +_BIN_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.44, y=0.46, z=0.28), + mass=5, + offset=Vector3d(x=0, y=0.23, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.33, y=0.382, z=0.188), + position=Vector3d(x=0, y=0.24, z=0) + ) + ] +) _BLOCK_BLANK_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.1, y=0.1, z=0.1), mass=0.333, @@ -630,6 +751,34 @@ class _FunctionArgs(): ) ] ) +_CASE_6_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.195, y=0.042, z=0.1), + dimensions=Vector3d(x=0.195, y=0.042, z=0.144), + mass=8, + offset=Vector3d(x=0, y=0.021, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.185, y=0.034, z=0.085), + position=Vector3d(x=0, y=0.021, z=-0.002) + ) + ] +) +_CASE_7_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.195, y=0.036, z=0.1), + dimensions=Vector3d(x=0.195, y=0.036, z=0.134), + mass=8, + offset=Vector3d(x=0, y=0.018, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.185, y=0.028, z=0.085), + position=Vector3d(x=0, y=0.017, z=-0.002) + ) + ] +) _CART_2_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.42, y=0.7, z=0.51), mass=2, @@ -805,6 +954,30 @@ class _FunctionArgs(): ) ] ) +_CHAIR_15_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.42, y=0.94, z=0.4), + mass=6, + offset=Vector3d(x=0, y=0.47, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.34, y=0, z=0.3), + position=Vector3d(x=0, y=0.74, z=0.04) + ) + ] +) +_CHAIR_16_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.5, y=0.92, z=0.5), + mass=6, + offset=Vector3d(x=0, y=0.46, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.4, y=0, z=0.4), + position=Vector3d(x=0, y=0.75, z=0) + ) + ] +) _CHANGING_TABLE_SIZE = ObjectBaseSize( # These are the closed dimensions and offset, if we ever need them. # dimensions=Vector3d(x=1.1, y=0.96, z=0.58), @@ -1329,6 +1502,58 @@ class _FunctionArgs(): ) ] ) +_CRATE_3_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.8, y=0.8, z=0.8), + dimensions=Vector3d(x=0.8, y=0.8, z=0.98), + mass=5, + offset=Vector3d(x=0, y=0.4, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.61, y=0.6, z=0.61), + position=Vector3d(x=0, y=0.4, z=0) + ) + ] +) +_CRATE_4_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.76, y=0.74, z=0.76), + dimensions=Vector3d(x=0.76, y=0.74, z=0.88), + mass=5, + offset=Vector3d(x=0, y=0.37, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.46, y=0.56, z=0.46), + position=Vector3d(x=0, y=0.36, z=0) + ) + ] +) +_CRATE_OPEN_TOPPED_1_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.8, y=0.8, z=0.8), + mass=5, + offset=Vector3d(x=0, y=0.4, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.61, y=0.6, z=0.61), + position=Vector3d(x=0, y=0.4, z=0) + ) + ] +) +_CRATE_OPEN_TOPPED_2_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.72, y=0.64, z=0.72), + mass=5, + offset=Vector3d(x=0, y=0.32, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.39, y=0.52, z=0.39), + position=Vector3d(x=0, y=0.31, z=0) + ) + ] +) _CRAYON_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.01, y=0.085, z=0.01), mass=0.125, @@ -1430,6 +1655,89 @@ class _FunctionArgs(): placer_offset_y=[0.35], positionY=0.005 ) +_DOUBLE_BOOKCASE_1_SHELF_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=1, z=0.5), + mass=12, + offset=Vector3d(x=0, y=0.5, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.04, z=0) + ), + ObjectInteractableArea( + area_id='shelf_1', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.52, z=0) + ) + ] +) +_DOUBLE_BOOKCASE_2_SHELF_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=1.5, z=0.5), + mass=24, + offset=Vector3d(x=0, y=0.75, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.04, z=0) + ), + ObjectInteractableArea( + area_id='shelf_1', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.52, z=0) + ), + ObjectInteractableArea( + area_id='shelf_2', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=1.02, z=0) + ) + ] +) +_DOUBLE_BOOKCASE_3_SHELF_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=2, z=0.5), + mass=36, + offset=Vector3d(x=0, y=1, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.04, z=0) + ), + ObjectInteractableArea( + area_id='shelf_1', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.52, z=0) + ), + ObjectInteractableArea( + area_id='shelf_2', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=1.02, z=0) + ) + ] +) +_DOUBLE_BOOKCASE_4_SHELF_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=2.5, z=0.5), + mass=48, + offset=Vector3d(x=0, y=1, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.04, z=0) + ), + ObjectInteractableArea( + area_id='shelf_1', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=0.52, z=0) + ), + ObjectInteractableArea( + area_id='shelf_2', + dimensions=Vector3d(x=0.9, y=0, z=0.45), + position=Vector3d(x=0, y=1.02, z=0) + ) + ] +) _DUCK_ON_WHEELS_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.21, y=0.17, z=0.065), mass=1, @@ -1483,6 +1791,34 @@ class _FunctionArgs(): ) ] ) +_MILITARY_CASE_3_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=1.26, y=0.54, z=0.54), + dimensions=Vector3d(x=1.26, y=0.54, z=0.76), + mass=5, + offset=Vector3d(x=0, y=0.27, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.14, y=0.38, z=0.44), + position=Vector3d(x=0, y=0.21, z=0) + ) + ] +) +_MILITARY_CASE_4_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.86, y=0.32, z=0.58), + dimensions=Vector3d(x=0.86, y=0.32, z=0.68), + mass=5, + offset=Vector3d(x=0, y=0.16, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.76, y=0.24, z=0.48), + position=Vector3d(x=0, y=0.14, z=0) + ) + ] +) _PACIFIER_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.07, y=0.04, z=0.05), mass=0.125, @@ -1739,6 +2075,30 @@ class _FunctionArgs(): ) ] ) +_SOFA_11_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=3.3, y=1.7, z=1.5), + mass=30, + offset=Vector3d(x=0, y=0.85, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=2, y=0, z=0.86), + position=Vector3d(x=0, y=0.7, z=0.18) + ) + ] +) +_SOFA_12_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.8, y=1.08, z=2.22), + mass=30, + offset=Vector3d(x=0, y=0.54, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.8, y=0, z=1.66), + position=Vector3d(x=0, y=0.55, z=0.26) + ) + ] +) _SOFA_CHAIR_1_SIZE = ObjectBaseSize( dimensions=Vector3d(x=1.43, y=1.15, z=1.23), mass=30, @@ -2039,21 +2399,57 @@ class _FunctionArgs(): ) ] ) -_TABLE_26_SIZE = ObjectBaseSize( - dimensions=Vector3d(x=0.65, y=0.68, z=0.75), - mass=15, - offset=Vector3d(x=0, y=0.34, z=0), +_TABLE_21_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=1.286, y=0.913, z=0.822), + mass=10, + offset=Vector3d(x=0, y=0.456, z=0), positionY=0, open_area_list=[ ObjectInteractableArea( - dimensions=Vector3d(x=0.65, y=0, z=0.75), - position=Vector3d(x=0, y=0.68, z=0) + dimensions=Vector3d(x=1.2, y=0, z=0.76), + position=Vector3d(x=0, y=0.69, z=0) ) ] ) -_TABLE_27_SIZE = ObjectBaseSize( - dimensions=Vector3d(x=1.2, y=0.7, z=1.2), - mass=5, +_TABLE_22_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=1.431, y=0.652, z=0.695), + mass=15, + offset=Vector3d(x=0, y=0.326, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.4, y=0, z=0.6), + position=Vector3d(x=0, y=0.41, z=0) + ) + ] +) +_TABLE_25_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.65, y=0.68, z=0.75), + mass=15, + offset=Vector3d(x=0, y=0.34, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.64, y=0, z=0.64), + position=Vector3d(x=0, y=0.68, z=0) + ) + ] +) +_TABLE_26_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.65, y=0.68, z=0.75), + mass=15, + offset=Vector3d(x=0, y=0.34, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.65, y=0, z=0.75), + position=Vector3d(x=0, y=0.68, z=0) + ) + ] +) +_TABLE_27_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=1.2, y=0.7, z=1.2), + mass=5, offset=Vector3d(x=0, y=0.35, z=0), positionY=0, open_area_list=[ @@ -2063,6 +2459,18 @@ class _FunctionArgs(): ) ] ) +_TABLE_28_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=2, y=0.82, z=2), + mass=25, + offset=Vector3d(x=0, y=0.41, z=0), + positionY=0, + open_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=1.4, y=0, z=1.4), + position=Vector3d(x=0, y=0.82, z=0) + ) + ] +) _TOOLBOX_1_SIZE = ObjectBaseSize( # These are the closed dimensions, if we ever need them. # dimensions=Vector3d(x=0.51, y=0.29, z=0.21), @@ -2119,6 +2527,34 @@ class _FunctionArgs(): ) ] ) +_TOOLBOX_5_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.25, y=0.1, z=0.1), + dimensions=Vector3d(x=0.25, y=0.1, z=0.14), + mass=5, + offset=Vector3d(x=0, y=0.05, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.23, y=0.07, z=0.09), + position=Vector3d(x=0, y=0.04, z=0) + ) + ] +) +_TOOLBOX_6_SIZE = ObjectBaseSize( + # These are the closed dimensions, if we ever need them. + # dimensions=Vector3d(x=0.22, y=0.108, z=0.142), + dimensions=Vector3d(x=0.22, y=0.108, z=0.192), + mass=5, + offset=Vector3d(x=0, y=0.054, z=0), + positionY=0, + enclosed_area_list=[ + ObjectInteractableArea( + dimensions=Vector3d(x=0.195, y=0.08, z=0.12), + position=Vector3d(x=0, y=0.045, z=0) + ) + ] +) _TOY_BOBCAT_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.13, y=0.061, z=0.038), mass=0.5, @@ -2146,6 +2582,21 @@ class _FunctionArgs(): placer_offset_y=[0], positionY=0.005 ) +_TOY_CAR_4_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.13, y=0.052, z=0.036), + mass=0.5, + offset=Vector3d(x=0, y=0.026, z=0), + placer_offset_y=[0], + positionY=0.005 +) +_TOY_CAR_5_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.9, y=0.36, z=0.42), + mass=1.5, + offset=Vector3d(x=0, y=0.18, z=0), + # TODO + placer_offset_y=[0], + positionY=0.005 +) _TOY_JEEP_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.06, y=0.057, z=0.098), mass=0.5, @@ -2153,12 +2604,28 @@ class _FunctionArgs(): placer_offset_y=[0], positionY=0.005 ) +_TOY_POWER_SHOVEL_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.18, y=0.08, z=0.042), + mass=0.5, + offset=Vector3d(x=0, y=0.04, z=0), + # TODO + placer_offset_y=[0], + positionY=0.005 +) _TOY_RACECAR_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.07, y=0.06, z=0.15), mass=0.5, offset=Vector3d(x=0, y=0.03, z=0), positionY=0.005 ) +_TOY_ROAD_SCRAPER_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.136, y=0.066, z=0.038), + mass=0.5, + offset=Vector3d(x=0, y=0.033, z=0), + # TODO + placer_offset_y=[0], + positionY=0.005 +) _TOY_ROLLER_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.102, y=0.062, z=0.047), mass=0.5, @@ -2186,6 +2653,13 @@ class _FunctionArgs(): placer_offset_y=[0], positionY=0.005 ) +_TOY_TANK_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.21, y=0.12, z=0.094), + mass=1, + offset=Vector3d(x=0, y=0.06, z=0), + placer_offset_y=[0], + positionY=0.005 +) _TOY_TRAIN_1_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.16, y=0.2, z=0.23), mass=1, @@ -2199,6 +2673,22 @@ class _FunctionArgs(): placer_offset_y=[0], positionY=0.005 ) +_TOY_TRAIN_3_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.4, y=0.26, z=0.22), + mass=1.5, + offset=Vector3d(x=0, y=0.13, z=0), + # TODO + placer_offset_y=[0], + positionY=0.005 +) +_TOY_TRIKE_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.04, y=0.074, z=0.112), + mass=0.5, + offset=Vector3d(x=0, y=0.037, z=0), + # TODO + placer_offset_y=[0], + positionY=0.005 +) _TOY_TROLLEY_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.16, y=0.2, z=0.23), mass=1, @@ -2232,6 +2722,12 @@ class _FunctionArgs(): placer_offset_y=[0], positionY=0.005 ) +_TOY_TRUCK_5_SIZE = ObjectBaseSize( + dimensions=Vector3d(x=0.19, y=0.072, z=0.04), + mass=0.5, + offset=Vector3d(x=0, y=0.036, z=0), + positionY=0.005 +) _TROPHY_SIZE = ObjectBaseSize( dimensions=Vector3d(x=0.19, y=0.3, z=0.14), mass=1, @@ -2339,6 +2835,44 @@ def _create_antique_armchair_1(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_antique_armchair_2(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='antique_armchair_2', + untrainedShape=True, + attributes=['moveable', 'receptacle'], + color=['blue', 'purple', 'yellow'], + occluder=True, + materialCategory=[], + salientMaterials=['fabric', 'wood'], + shape=['sofa chair'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _ANTIQUE_ARMCHAIR_2_SIZE.make(size) + for size in args.size_multiplier_list + ] + ) + + +def _create_antique_armchair_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='antique_armchair_3', + untrainedShape=True, + attributes=['moveable', 'receptacle'], + color=['black', 'brown', 'yellow'], + occluder=True, + materialCategory=[], + salientMaterials=['fabric', 'wood'], + shape=['sofa chair'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _ANTIQUE_ARMCHAIR_3_SIZE.make(size) + for size in args.size_multiplier_list + ] + ) + + def _create_antique_chair_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='antique_chair_1', @@ -2354,6 +2888,44 @@ def _create_antique_chair_1(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_antique_chair_2(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='antique_chair_2', + untrainedShape=True, + attributes=['moveable', 'receptacle'], + color=['black', 'brown'], + materialCategory=[], + obstacle=True, + salientMaterials=['fabric', 'wood'], + shape=['chair'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _ANTIQUE_CHAIR_2_SIZE.make(size) + for size in args.size_multiplier_list + ] + ) + + +def _create_antique_chair_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='antique_chair_3', + untrainedShape=True, + attributes=['moveable', 'receptacle'], + color=['red', 'brown'], + materialCategory=[], + obstacle=True, + salientMaterials=['fabric', 'wood'], + shape=['chair'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _ANTIQUE_CHAIR_3_SIZE.make(size) + for size in args.size_multiplier_list + ] + ) + + def _create_antique_sofa_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='antique_sofa_1', @@ -2427,7 +2999,6 @@ def _create_ball(args: _FunctionArgs) -> ObjectDefinition: def _create_barrel_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='barrel_1', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['barrel'], @@ -2442,7 +3013,6 @@ def _create_barrel_1(args: _FunctionArgs) -> ObjectDefinition: def _create_barrel_2(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='barrel_2', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['barrel'], @@ -2454,6 +3024,36 @@ def _create_barrel_2(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_barrel_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='barrel_3', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['barrel'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BARREL_3_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_barrel_4(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='barrel_4', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['barrel'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BARREL_4_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_bed_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bed_1', @@ -2540,7 +3140,6 @@ def _create_bed_6(args: _FunctionArgs) -> ObjectDefinition: def _create_bed_7(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bed_7', - untrainedShape=True, attributes=['receptacle'], occluder=True, shape=['bed'], @@ -2555,7 +3154,6 @@ def _create_bed_7(args: _FunctionArgs) -> ObjectDefinition: def _create_bed_8(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bed_8', - untrainedShape=True, attributes=['receptacle'], occluder=True, shape=['bed'], @@ -2570,7 +3168,6 @@ def _create_bed_8(args: _FunctionArgs) -> ObjectDefinition: def _create_bed_9(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bed_9', - untrainedShape=True, attributes=['receptacle'], occluder=True, shape=['bed'], @@ -2585,7 +3182,6 @@ def _create_bed_9(args: _FunctionArgs) -> ObjectDefinition: def _create_bed_10(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bed_10', - untrainedShape=True, attributes=['receptacle'], occluder=True, shape=['bed'], @@ -2597,6 +3193,62 @@ def _create_bed_10(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_bed_11(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='bed_11', + untrainedShape=True, + attributes=['receptacle'], + occluder=True, + shape=['bed'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BED_11_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_bed_12(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='bed_12', + untrainedShape=True, + attributes=['receptacle'], + occluder=True, + shape=['bed'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BED_12_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_bin_1(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='bin_1', + attributes=['receptacle'], + occluder=True, + shape=['bin'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BIN_1_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_bin_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='bin_3', + attributes=['receptacle'], + occluder=True, + shape=['bin'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _BIN_3_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_block_blank_cube(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='block_blank_wood_cube', @@ -2719,6 +3371,66 @@ def _create_bookcase_4_shelf(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_double_bookcase_1_shelf(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='double_bookcase_1_shelf', + attributes=['receptacle'], + occluder=True, + shape=['shelf'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _DOUBLE_BOOKCASE_1_SHELF_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_double_bookcase_2_shelf(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='double_bookcase_2_shelf', + attributes=['receptacle'], + occluder=True, + shape=['shelf'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _DOUBLE_BOOKCASE_2_SHELF_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_double_bookcase_3_shelf(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='double_bookcase_3_shelf', + attributes=['receptacle'], + occluder=True, + shape=['shelf'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _DOUBLE_BOOKCASE_3_SHELF_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_double_bookcase_4_shelf(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='double_bookcase_4_shelf', + attributes=['receptacle'], + occluder=True, + shape=['shelf'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _DOUBLE_BOOKCASE_4_SHELF_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_bookcase_1_shelf_sideless(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bookcase_1_shelf_sideless', @@ -2792,7 +3504,7 @@ def _create_bowl_3(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _BOWL_3_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 if args.type.endswith('static') else 1 + massMultiplier=(3 if args.type.endswith('static') else 1) ) @@ -2809,7 +3521,7 @@ def _create_bowl_4(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _BOWL_4_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 if args.type.endswith('static') else 1 + massMultiplier=(3 if args.type.endswith('static') else 1) ) @@ -2826,7 +3538,7 @@ def _create_bowl_6(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _BOWL_6_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 if args.type.endswith('static') else 1 + massMultiplier=(3 if args.type.endswith('static') else 1) ) @@ -2902,7 +3614,6 @@ def _create_case_3(args: _FunctionArgs) -> ObjectDefinition: def _create_case_4(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='case_4', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['case'], @@ -2916,7 +3627,6 @@ def _create_case_4(args: _FunctionArgs) -> ObjectDefinition: def _create_case_5(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='case_5', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['case'], @@ -2927,6 +3637,34 @@ def _create_case_5(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_case_6(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='case_6', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['case'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CASE_6_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_case_7(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='case_7', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['case'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CASE_7_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_cart_2(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='cart_2', @@ -3062,7 +3800,6 @@ def _create_chair_8(args: _FunctionArgs) -> ObjectDefinition: def _create_chair_9(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='chair_9', - untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], @@ -3076,74 +3813,99 @@ def _create_chair_9(args: _FunctionArgs) -> ObjectDefinition: def _create_chair_10(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='chair_10', - untrainedShape=True, + type='chair_10', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['chair'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CHAIR_10_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_chair_11(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='chair_11', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['chair'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CHAIR_11_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_chair_12(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='chair_12', attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _CHAIR_10_SIZE.make(size) for size in + _CHAIR_12_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_chair_11(args: _FunctionArgs) -> ObjectDefinition: +def _create_chair_13(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='chair_11', - untrainedShape=True, + type='chair_13', attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _CHAIR_11_SIZE.make(size) for size in + _CHAIR_13_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_chair_12(args: _FunctionArgs) -> ObjectDefinition: +def _create_chair_14(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='chair_12', - untrainedShape=True, + type='chair_14', attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _CHAIR_12_SIZE.make(size) for size in + _CHAIR_14_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_chair_13(args: _FunctionArgs) -> ObjectDefinition: +def _create_chair_15(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='chair_13', + type='chair_15', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _CHAIR_13_SIZE.make(size) for size in + _CHAIR_15_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_chair_14(args: _FunctionArgs) -> ObjectDefinition: +def _create_chair_16(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='chair_14', + type='chair_16', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, shape=['chair'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _CHAIR_14_SIZE.make(size) for size in + _CHAIR_16_SIZE.make(size) for size in args.size_multiplier_list ] ) @@ -3289,36 +4051,6 @@ def _create_chest_9(args: _FunctionArgs) -> ObjectDefinition: ) -def _create_crate_1(args: _FunctionArgs) -> ObjectDefinition: - return ObjectDefinition( - type='crate_1', - untrainedShape=True, - attributes=['receptacle', 'openable'], - occluder=True, - shape=['crate'], - chooseMaterialList=[item.copy() for item in args.chosen_material_list], - chooseSizeList=[ - _CRATE_1_SIZE.make(size) for size in - args.size_multiplier_list - ] - ) - - -def _create_crate_2(args: _FunctionArgs) -> ObjectDefinition: - return ObjectDefinition( - type='crate_2', - untrainedShape=True, - attributes=['receptacle', 'openable'], - occluder=True, - shape=['crate'], - chooseMaterialList=[item.copy() for item in args.chosen_material_list], - chooseSizeList=[ - _CRATE_2_SIZE.make(size) for size in - args.size_multiplier_list - ] - ) - - def _create_container_asymmetric_01(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='container_asymmetric_01', @@ -3631,6 +4363,92 @@ def _create_container_symmetric_12(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_crate_1(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_1', + attributes=['receptacle', 'openable'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_1_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_crate_2(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_2', + attributes=['receptacle', 'openable'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_2_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_crate_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_3', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_3_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_crate_4(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_4', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_4_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_crate_open_topped_1(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_open_topped_1', + attributes=['receptacle'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_OPEN_TOPPED_1_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_crate_open_topped_2(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='crate_open_topped_2', + attributes=['receptacle'], + occluder=True, + shape=['crate'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _CRATE_OPEN_TOPPED_2_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_crayon(args: _FunctionArgs) -> ObjectDefinition: color = args.type.replace('crayon_', '') return ObjectDefinition( @@ -3673,7 +4491,7 @@ def _create_cup_2(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _CUP_2_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 + massMultiplier=(3 if args.type.endswith('static') else 1) ) @@ -3690,7 +4508,7 @@ def _create_cup_3(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _CUP_3_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 + massMultiplier=(3 if args.type.endswith('static') else 1) ) @@ -3707,14 +4525,13 @@ def _create_cup_6(args: _FunctionArgs) -> ObjectDefinition: chooseSizeList=[ _CUP_6_SIZE.make(size) for size in args.size_multiplier_list ], - massMultiplier=3 + massMultiplier=(3 if args.type.endswith('static') else 1) ) def _create_desk_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='desk_1', - untrainedShape=True, attributes=['moveable', 'receptacle'], occluder=True, shape=['desk'], @@ -3730,7 +4547,6 @@ def _create_desk_1(args: _FunctionArgs) -> ObjectDefinition: def _create_desk_2(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='desk_2', - untrainedShape=True, attributes=['moveable', 'receptacle'], occluder=True, shape=['desk'], @@ -3746,7 +4562,6 @@ def _create_desk_2(args: _FunctionArgs) -> ObjectDefinition: def _create_desk_3(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='desk_3', - untrainedShape=True, attributes=['moveable', 'receptacle'], occluder=True, shape=['desk'], @@ -3762,7 +4577,6 @@ def _create_desk_3(args: _FunctionArgs) -> ObjectDefinition: def _create_desk_4(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='desk_4', - untrainedShape=True, attributes=['moveable', 'receptacle'], occluder=True, shape=['desk'], @@ -3791,7 +4605,6 @@ def _create_dog_on_wheels(args: _FunctionArgs) -> ObjectDefinition: def _create_dog_on_wheels_2(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='dog_on_wheels_2', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['dog'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -3818,7 +4631,6 @@ def _create_duck_on_wheels(args: _FunctionArgs) -> ObjectDefinition: def _create_duck_on_wheels_2(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='duck_on_wheels_2', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['duck'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -3917,7 +4729,6 @@ def _create_lid_square(args: _FunctionArgs) -> ObjectDefinition: def _create_military_case_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='military_case_1', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['military_case'], @@ -3932,7 +4743,6 @@ def _create_military_case_1(args: _FunctionArgs) -> ObjectDefinition: def _create_military_case_2(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='military_case_2', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['military_case'], @@ -3944,6 +4754,36 @@ def _create_military_case_2(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_military_case_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='military_case_3', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['military_case'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _MILITARY_CASE_3_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_military_case_4(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='military_case_4', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['military_case'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _MILITARY_CASE_4_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_pacifier(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='pacifier', @@ -4114,7 +4954,6 @@ def _create_separate_container(args: _FunctionArgs) -> ObjectDefinition: def _create_skateboard(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='skateboard', - untrainedShape=True, attributes=['moveable', 'pickupable'], color=['black'], salientMaterials=['metal', 'plastic'], @@ -4238,7 +5077,6 @@ def _create_sofa_7(args: _FunctionArgs) -> ObjectDefinition: def _create_sofa_8(args: _FunctionArgs) -> ObjectDefinition: return turn_around(ObjectDefinition( type='sofa_8', - untrainedShape=True, attributes=['receptacle'], color=['grey'], materialCategory=[], @@ -4255,7 +5093,6 @@ def _create_sofa_8(args: _FunctionArgs) -> ObjectDefinition: def _create_sofa_9(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='sofa_9', - untrainedShape=True, attributes=['receptacle'], color=['grey'], materialCategory=[], @@ -4269,6 +5106,40 @@ def _create_sofa_9(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_sofa_11(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='sofa_11', + untrainedShape=True, + attributes=['receptacle'], + color=['brown', 'orange', 'red'], + materialCategory=[], + occluder=True, + salientMaterials=['leather'], + shape=['sofa'], + stackTarget=True, + chooseSizeList=[ + _SOFA_11_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_sofa_12(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='sofa_12', + untrainedShape=True, + attributes=['receptacle'], + color=['blue', 'green', 'grey', 'yellow'], + materialCategory=[], + occluder=True, + salientMaterials=['fabric'], + shape=['sofa'], + stackTarget=True, + chooseSizeList=[ + _SOFA_12_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_sofa_chair_1(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='sofa_chair_1', @@ -4382,7 +5253,6 @@ def _create_sofa_chair_7(args: _FunctionArgs) -> ObjectDefinition: def _create_sofa_chair_8(args: _FunctionArgs) -> ObjectDefinition: return turn_around(ObjectDefinition( type='sofa_chair_8', - untrainedShape=True, attributes=['receptacle'], color=['white'], materialCategory=[], @@ -4399,7 +5269,6 @@ def _create_sofa_chair_8(args: _FunctionArgs) -> ObjectDefinition: def _create_sofa_chair_9(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='sofa_chair_9', - untrainedShape=True, attributes=['receptacle'], color=['white'], materialCategory=[], @@ -4600,48 +5469,110 @@ def _create_table_15(args: _FunctionArgs) -> ObjectDefinition: item.copy(2) for item in args.chosen_material_list ], chooseSizeList=[ - _TABLE_15_RECT_SIZE.make(size) for size in + _TABLE_15_RECT_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_table_16(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='table_16', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['table'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TABLE_16_CIRCLE_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_table_17(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='table_17', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['table'], + stackTarget=True, + chooseMaterialList=[ + item.copy(2) for item in args.chosen_material_list + ], + chooseSizeList=[ + _TABLE_17_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_table_18(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='table_18', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['table'], + stackTarget=True, + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TABLE_18_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_table_19(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='table_19', + attributes=['moveable', 'receptacle'], + obstacle=True, + shape=['table'], + stackTarget=True, + chooseMaterialList=[ + item.copy(3) for item in args.chosen_material_list + ], + chooseSizeList=[ + _TABLE_19_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_16(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_20(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_16', + type='table_20', attributes=['moveable', 'receptacle'], obstacle=True, shape=['table'], stackTarget=True, chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_16_CIRCLE_SIZE.make(size) for size in + _TABLE_20_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_17(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_21(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_17', + type='table_21', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, shape=['table'], stackTarget=True, - chooseMaterialList=[ - item.copy(2) for item in args.chosen_material_list - ], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_17_SIZE.make(size) for size in + _TABLE_21_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_18(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_22(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_18', + type='table_22', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, @@ -4649,65 +5580,61 @@ def _create_table_18(args: _FunctionArgs) -> ObjectDefinition: stackTarget=True, chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_18_SIZE.make(size) for size in + _TABLE_22_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_19(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_25(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_19', + type='table_25', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, shape=['table'], stackTarget=True, - chooseMaterialList=[ - item.copy(3) for item in args.chosen_material_list - ], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_19_SIZE.make(size) for size in + _TABLE_25_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_20(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_26(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_20', - untrainedShape=True, + type='table_26', attributes=['moveable', 'receptacle'], obstacle=True, shape=['table'], stackTarget=True, chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_20_SIZE.make(size) for size in + _TABLE_26_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_26(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_27(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_26', - untrainedShape=True, + type='table_27', attributes=['moveable', 'receptacle'], obstacle=True, shape=['table'], stackTarget=True, chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_26_SIZE.make(size) for size in + _TABLE_27_SIZE.make(size) for size in args.size_multiplier_list ] ) -def _create_table_27(args: _FunctionArgs) -> ObjectDefinition: +def _create_table_28(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( - type='table_27', + type='table_28', untrainedShape=True, attributes=['moveable', 'receptacle'], obstacle=True, @@ -4715,7 +5642,7 @@ def _create_table_27(args: _FunctionArgs) -> ObjectDefinition: stackTarget=True, chooseMaterialList=[item.copy() for item in args.chosen_material_list], chooseSizeList=[ - _TABLE_27_SIZE.make(size) for size in + _TABLE_28_SIZE.make(size) for size in args.size_multiplier_list ] ) @@ -4723,45 +5650,90 @@ def _create_table_27(args: _FunctionArgs) -> ObjectDefinition: # Maps each tool type to its base X/Z dimensions. LARGE_BLOCK_TOOLS_TO_DIMENSIONS = { + 'tool_hooked_0_50_x_4_00': (1.5, 4), + 'tool_hooked_0_50_x_5_00': (1.5, 5), + 'tool_hooked_0_50_x_6_00': (1.5, 6), + 'tool_hooked_0_50_x_7_00': (1.5, 7), + 'tool_hooked_0_50_x_8_00': (1.5, 8), + 'tool_hooked_0_50_x_9_00': (1.5, 9), + 'tool_hooked_0_75_x_4_00': (2.25, 4), + 'tool_hooked_0_75_x_5_00': (2.25, 5), + 'tool_hooked_0_75_x_6_00': (2.25, 6), + 'tool_hooked_0_75_x_7_00': (2.25, 7), + 'tool_hooked_0_75_x_8_00': (2.25, 8), + 'tool_hooked_0_75_x_9_00': (2.25, 9), + 'tool_hooked_1_00_x_4_00': (3, 4), + 'tool_hooked_1_00_x_5_00': (3, 5), + 'tool_hooked_1_00_x_6_00': (3, 6), + 'tool_hooked_1_00_x_7_00': (3, 7), + 'tool_hooked_1_00_x_8_00': (3, 8), + 'tool_hooked_1_00_x_9_00': (3, 9), + 'tool_isosceles_0_50_x_4_00': (4, 4), + 'tool_isosceles_0_50_x_5_00': (5, 5), + 'tool_isosceles_0_50_x_6_00': (6, 6), + 'tool_isosceles_0_50_x_7_00': (7, 7), + 'tool_isosceles_0_50_x_8_00': (8, 8), + 'tool_isosceles_0_50_x_9_00': (9, 9), + 'tool_isosceles_0_75_x_4_00': (4, 4), + 'tool_isosceles_0_75_x_5_00': (5, 5), + 'tool_isosceles_0_75_x_6_00': (6, 6), + 'tool_isosceles_0_75_x_7_00': (7, 7), + 'tool_isosceles_0_75_x_8_00': (8, 8), + 'tool_isosceles_0_75_x_9_00': (8, 9), + 'tool_isosceles_1_00_x_4_00': (4, 4), + 'tool_isosceles_1_00_x_5_00': (5, 5), + 'tool_isosceles_1_00_x_6_00': (6, 6), + 'tool_isosceles_1_00_x_7_00': (7, 7), + 'tool_isosceles_1_00_x_8_00': (8, 8), + 'tool_isosceles_1_00_x_9_00': (9, 9), 'tool_rect_0_50_x_1_00': (0.5, 1), 'tool_rect_0_75_x_1_00': (0.75, 1), 'tool_rect_1_00_x_1_00': (1, 1), + 'tool_rect_0_50_x_3_00': (0.5, 3), 'tool_rect_0_50_x_4_00': (0.5, 4), 'tool_rect_0_50_x_5_00': (0.5, 5), 'tool_rect_0_50_x_6_00': (0.5, 6), 'tool_rect_0_50_x_7_00': (0.5, 7), 'tool_rect_0_50_x_8_00': (0.5, 8), 'tool_rect_0_50_x_9_00': (0.5, 9), + 'tool_rect_0_75_x_3_00': (0.75, 3), 'tool_rect_0_75_x_4_00': (0.75, 4), 'tool_rect_0_75_x_5_00': (0.75, 5), 'tool_rect_0_75_x_6_00': (0.75, 6), 'tool_rect_0_75_x_7_00': (0.75, 7), 'tool_rect_0_75_x_8_00': (0.75, 8), 'tool_rect_0_75_x_9_00': (0.75, 9), + 'tool_rect_1_00_x_3_00': (1, 3), 'tool_rect_1_00_x_4_00': (1, 4), 'tool_rect_1_00_x_5_00': (1, 5), 'tool_rect_1_00_x_6_00': (1, 6), 'tool_rect_1_00_x_7_00': (1, 7), 'tool_rect_1_00_x_8_00': (1, 8), - 'tool_rect_1_00_x_9_00': (1, 9), - 'tool_hooked_0_50_x_4_00': (1.5, 4), - 'tool_hooked_0_50_x_5_00': (1.5, 5), - 'tool_hooked_0_50_x_6_00': (1.5, 6), - 'tool_hooked_0_50_x_7_00': (1.5, 7), - 'tool_hooked_0_50_x_8_00': (1.5, 8), - 'tool_hooked_0_50_x_9_00': (1.5, 9), - 'tool_hooked_0_75_x_4_00': (2.25, 4), - 'tool_hooked_0_75_x_5_00': (2.25, 5), - 'tool_hooked_0_75_x_6_00': (2.25, 6), - 'tool_hooked_0_75_x_7_00': (2.25, 7), - 'tool_hooked_0_75_x_8_00': (2.25, 8), - 'tool_hooked_0_75_x_9_00': (2.25, 9), - 'tool_hooked_1_00_x_4_00': (3, 4), - 'tool_hooked_1_00_x_5_00': (3, 5), - 'tool_hooked_1_00_x_6_00': (3, 6), - 'tool_hooked_1_00_x_7_00': (3, 7), - 'tool_hooked_1_00_x_8_00': (3, 8), - 'tool_hooked_1_00_x_9_00': (3, 9), + 'tool_rect_1_00_x_9_00': (1, 9) +} + +LARGE_BLOCK_NOVEL_TOOLS_TO_DIMENSIONS = { + 'tool_rect_0_63_x_1_00': (0.63, 1), + 'tool_rect_0_88_x_1_00': (0.88, 1), + 'tool_rect_1_13_x_1_00': (1.13, 1), + 'tool_rect_0_63_x_4_00': (0.63, 4), + 'tool_rect_0_63_x_5_00': (0.63, 5), + 'tool_rect_0_63_x_6_00': (0.63, 6), + 'tool_rect_0_63_x_7_00': (0.63, 7), + 'tool_rect_0_63_x_8_00': (0.63, 8), + 'tool_rect_0_63_x_9_00': (0.63, 9), + 'tool_rect_0_88_x_4_00': (0.88, 4), + 'tool_rect_0_88_x_5_00': (0.88, 5), + 'tool_rect_0_88_x_6_00': (0.88, 6), + 'tool_rect_0_88_x_7_00': (0.88, 7), + 'tool_rect_0_88_x_8_00': (0.88, 8), + 'tool_rect_0_88_x_9_00': (0.88, 9), + 'tool_rect_1_13_x_4_00': (1.13, 4), + 'tool_rect_1_13_x_5_00': (1.13, 5), + 'tool_rect_1_13_x_6_00': (1.13, 6), + 'tool_rect_1_13_x_7_00': (1.13, 7), + 'tool_rect_1_13_x_8_00': (1.13, 8), + 'tool_rect_1_13_x_9_00': (1.13, 9) } @@ -4817,7 +5789,6 @@ def _create_toolbox_2(args: _FunctionArgs) -> ObjectDefinition: def _create_toolbox_3(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='toolbox_3', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['toolbox'], @@ -4832,7 +5803,6 @@ def _create_toolbox_3(args: _FunctionArgs) -> ObjectDefinition: def _create_toolbox_4(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='toolbox_4', - untrainedShape=True, attributes=['receptacle', 'openable'], occluder=True, shape=['toolbox'], @@ -4844,10 +5814,39 @@ def _create_toolbox_4(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_toolbox_5(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='toolbox_5', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['toolbox'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOOLBOX_5_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + +def _create_toolbox_6(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='toolbox_6', + untrainedShape=True, + attributes=['receptacle', 'openable'], + occluder=True, + shape=['toolbox'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOOLBOX_6_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_toy_bobcat(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='bobcat', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['bobcat'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4884,7 +5883,6 @@ def _create_toy_car_2(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_car_3(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='car_3', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['car'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4894,10 +5892,35 @@ def _create_toy_car_3(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_toy_car_4(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='car_4', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['car'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_CAR_4_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_toy_car_5(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='car_5', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['car'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_CAR_5_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_toy_jeep(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='jeep', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['jeep'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4907,6 +5930,20 @@ def _create_toy_jeep(args: _FunctionArgs) -> ObjectDefinition: )) +def _create_toy_power_shovel(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='power_shovel', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['power_shovel'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_POWER_SHOVEL_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_toy_racecar(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='racecar_red', @@ -4920,10 +5957,23 @@ def _create_toy_racecar(args: _FunctionArgs) -> ObjectDefinition: )) +def _create_toy_road_scraper(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='road_scraper', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['road_scraper'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_ROAD_SCRAPER_SIZE.make(size) for size in + args.size_multiplier_list + ] + ) + + def _create_toy_roller(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='roller', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['roller'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4948,7 +5998,6 @@ def _create_toy_sedan(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_tank_1(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='tank_1', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['tank'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4961,7 +6010,6 @@ def _create_toy_tank_1(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_tank_2(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='tank_2', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['tank'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4971,6 +6019,19 @@ def _create_toy_tank_2(args: _FunctionArgs) -> ObjectDefinition: )) +def _create_toy_tank_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='tank_3', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['tank'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_TANK_3_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_toy_train_1(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='train_1', @@ -4986,7 +6047,6 @@ def _create_toy_train_1(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_train_2(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='train_2', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['train'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -4996,6 +6056,32 @@ def _create_toy_train_2(args: _FunctionArgs) -> ObjectDefinition: ) +def _create_toy_train_3(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='train_3', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['train'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_TRAIN_3_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + +def _create_toy_trike(args: _FunctionArgs) -> ObjectDefinition: + return turn_sideways(ObjectDefinition( + type='trike', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['trike'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_TRIKE_SIZE.make(size) for size in args.size_multiplier_list + ] + )) + + def _create_toy_trolley(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='trolley_1', @@ -5037,7 +6123,6 @@ def _create_toy_truck_2(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_truck_3(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='truck_3', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['truck'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -5050,7 +6135,6 @@ def _create_toy_truck_3(args: _FunctionArgs) -> ObjectDefinition: def _create_toy_truck_4(args: _FunctionArgs) -> ObjectDefinition: return turn_sideways(ObjectDefinition( type='truck_4', - untrainedShape=True, attributes=['moveable', 'pickupable'], shape=['truck'], chooseMaterialList=[item.copy() for item in args.chosen_material_list], @@ -5060,6 +6144,19 @@ def _create_toy_truck_4(args: _FunctionArgs) -> ObjectDefinition: )) +def _create_toy_truck_5(args: _FunctionArgs) -> ObjectDefinition: + return ObjectDefinition( + type='truck_5', + untrainedShape=True, + attributes=['moveable', 'pickupable'], + shape=['truck'], + chooseMaterialList=[item.copy() for item in args.chosen_material_list], + chooseSizeList=[ + _TOY_TRUCK_5_SIZE.make(size) for size in args.size_multiplier_list + ] + ) + + def _create_trophy(args: _FunctionArgs) -> ObjectDefinition: return ObjectDefinition( type='trophy', @@ -5128,10 +6225,26 @@ class TypeDetailsTuple(NamedTuple): _create_antique_armchair_1, LEATHER_ARMCHAIR_MATERIALS ), + 'antique_armchair_2': TypeDetailsTuple( + _create_antique_armchair_2, + [] # No material + ), + 'antique_armchair_3': TypeDetailsTuple( + _create_antique_armchair_3, + [] # No material + ), 'antique_chair_1': TypeDetailsTuple( _create_antique_chair_1, WOOD_MATERIALS ), + 'antique_chair_2': TypeDetailsTuple( + _create_antique_chair_2, + [] # No material + ), + 'antique_chair_3': TypeDetailsTuple( + _create_antique_chair_3, + [] # No material + ), 'antique_sofa_1': TypeDetailsTuple( _create_antique_sofa_1, WOOD_MATERIALS @@ -5156,6 +6269,12 @@ class TypeDetailsTuple(NamedTuple): ), 'barrel_2': TypeDetailsTuple( _create_barrel_2, WOOD_MATERIALS + ), 'barrel_3': TypeDetailsTuple( + _create_barrel_3, + WOOD_MATERIALS + ), 'barrel_4': TypeDetailsTuple( + _create_barrel_4, + WOOD_MATERIALS ), 'bed_1': TypeDetailsTuple( _create_bed_1, WOOD_MATERIALS @@ -5186,6 +6305,18 @@ class TypeDetailsTuple(NamedTuple): ), 'bed_10': TypeDetailsTuple( _create_bed_10, WOOD_MATERIALS + ), 'bed_11': TypeDetailsTuple( + _create_bed_11, + WOOD_MATERIALS + ), 'bed_12': TypeDetailsTuple( + _create_bed_12, + WOOD_MATERIALS + ), 'bin_1': TypeDetailsTuple( + _create_bin_1, + PLASTIC_MATERIALS + ), 'bin_3': TypeDetailsTuple( + _create_bin_3, + PLASTIC_MATERIALS ), 'block_blank_blue_cube': TypeDetailsTuple( _create_block_blank_cube, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS @@ -5303,6 +6434,12 @@ class TypeDetailsTuple(NamedTuple): ), 'car_3': TypeDetailsTuple( _create_toy_car_3, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'car_4': TypeDetailsTuple( + _create_toy_car_4, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'car_5': TypeDetailsTuple( + _create_toy_car_5, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'cart_1': TypeDetailsTuple( _create_cart, METAL_MATERIALS @@ -5321,6 +6458,12 @@ class TypeDetailsTuple(NamedTuple): ), 'case_5': TypeDetailsTuple( _create_case_5, METAL_MATERIALS + PLASTIC_MATERIALS + ), 'case_6': TypeDetailsTuple( + _create_case_6, + METAL_MATERIALS + PLASTIC_MATERIALS + ), 'case_7': TypeDetailsTuple( + _create_case_7, + METAL_MATERIALS + PLASTIC_MATERIALS ), 'cart_2': TypeDetailsTuple( _create_cart_2, METAL_MATERIALS + PLASTIC_MATERIALS @@ -5365,7 +6508,13 @@ class TypeDetailsTuple(NamedTuple): METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS ), 'chair_14': TypeDetailsTuple( _create_chair_14, + METAL_MATERIALS + PLASTIC_MATERIALS + ), 'chair_15': TypeDetailsTuple( + _create_chair_15, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'chair_16': TypeDetailsTuple( + _create_chair_16, + METAL_MATERIALS + PLASTIC_MATERIALS ), 'changing_table': TypeDetailsTuple( _create_changing_table, WOOD_MATERIALS @@ -5396,12 +6545,6 @@ class TypeDetailsTuple(NamedTuple): ), 'chest_9': TypeDetailsTuple( _create_chest_9, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS - ), 'crate_1': TypeDetailsTuple( - _create_crate_1, - WOOD_MATERIALS - ), 'crate_2': TypeDetailsTuple( - _create_crate_2, - WOOD_MATERIALS ), 'container_asymmetric_01': TypeDetailsTuple( _create_container_asymmetric_01, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS @@ -5474,6 +6617,24 @@ class TypeDetailsTuple(NamedTuple): ), 'container_symmetric_12': TypeDetailsTuple( _create_container_symmetric_12, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'crate_1': TypeDetailsTuple( + _create_crate_1, + WOOD_MATERIALS + ), 'crate_2': TypeDetailsTuple( + _create_crate_2, + WOOD_MATERIALS + ), 'crate_3': TypeDetailsTuple( + _create_crate_3, + WOOD_MATERIALS + ), 'crate_4': TypeDetailsTuple( + _create_crate_4, + WOOD_MATERIALS + ), 'crate_open_topped_1': TypeDetailsTuple( + _create_crate_open_topped_1, + WOOD_MATERIALS + ), 'crate_open_topped_2': TypeDetailsTuple( + _create_crate_open_topped_2, + WOOD_MATERIALS ), 'crayon_black': TypeDetailsTuple( _create_crayon, [] # No material @@ -5528,6 +6689,18 @@ class TypeDetailsTuple(NamedTuple): ), 'dog_on_wheels': TypeDetailsTuple( _create_dog_on_wheels, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'double_bookcase_1_shelf': TypeDetailsTuple( + _create_double_bookcase_1_shelf, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'double_bookcase_2_shelf': TypeDetailsTuple( + _create_double_bookcase_2_shelf, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'double_bookcase_3_shelf': TypeDetailsTuple( + _create_double_bookcase_3_shelf, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'double_bookcase_4_shelf': TypeDetailsTuple( + _create_double_bookcase_4_shelf, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS ), 'dog_on_wheels_2': TypeDetailsTuple( _create_dog_on_wheels_2, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS @@ -5553,6 +6726,12 @@ class TypeDetailsTuple(NamedTuple): ), 'military_case_2': TypeDetailsTuple( _create_military_case_2, METAL_MATERIALS + PLASTIC_MATERIALS + ), 'military_case_3': TypeDetailsTuple( + _create_military_case_3, + METAL_MATERIALS + PLASTIC_MATERIALS + ), 'military_case_4': TypeDetailsTuple( + _create_military_case_4, + METAL_MATERIALS + PLASTIC_MATERIALS ), 'pacifier': TypeDetailsTuple( _create_pacifier, [] # No material @@ -5565,9 +6744,15 @@ class TypeDetailsTuple(NamedTuple): ), 'plate_4': TypeDetailsTuple( _create_plate_4, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'power_shovel': TypeDetailsTuple( + _create_toy_power_shovel, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'racecar_red': TypeDetailsTuple( _create_toy_racecar, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'road_scraper': TypeDetailsTuple( + _create_toy_road_scraper, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'roller': TypeDetailsTuple( _create_toy_roller, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS @@ -5614,6 +6799,12 @@ class TypeDetailsTuple(NamedTuple): ), 'sofa_9': TypeDetailsTuple( _create_sofa_9, SOFA_9_MATERIALS + ), 'sofa_11': TypeDetailsTuple( + _create_sofa_11, + [] # No material + ), 'sofa_12': TypeDetailsTuple( + _create_sofa_12, + [] # No material ), 'sofa_chair_1': TypeDetailsTuple( _create_sofa_chair_1, SOFA_CHAIR_1_MATERIALS @@ -5695,18 +6886,33 @@ class TypeDetailsTuple(NamedTuple): ), 'table_20': TypeDetailsTuple( _create_table_20, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'table_21': TypeDetailsTuple( + _create_table_21, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'table_22': TypeDetailsTuple( + _create_table_22, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS + ), 'table_25': TypeDetailsTuple( + _create_table_25, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS ), 'table_26': TypeDetailsTuple( _create_table_26, METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS ), 'table_27': TypeDetailsTuple( _create_table_27, PLASTIC_MATERIALS + ), 'table_28': TypeDetailsTuple( + _create_table_28, + METAL_MATERIALS + PLASTIC_MATERIALS + WOOD_MATERIALS ), 'tank_1': TypeDetailsTuple( _create_toy_tank_1, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'tank_2': TypeDetailsTuple( _create_toy_tank_2, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'tank_3': TypeDetailsTuple( + _create_toy_tank_3, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'toolbox_1': TypeDetailsTuple( _create_toolbox_1, METAL_MATERIALS + PLASTIC_MATERIALS @@ -5719,12 +6925,24 @@ class TypeDetailsTuple(NamedTuple): ), 'toolbox_4': TypeDetailsTuple( _create_toolbox_4, METAL_MATERIALS + PLASTIC_MATERIALS + ), 'toolbox_5': TypeDetailsTuple( + _create_toolbox_5, + METAL_MATERIALS + PLASTIC_MATERIALS + ), 'toolbox_6': TypeDetailsTuple( + _create_toolbox_6, + METAL_MATERIALS + PLASTIC_MATERIALS ), 'train_1': TypeDetailsTuple( _create_toy_train_1, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'train_2': TypeDetailsTuple( _create_toy_train_2, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'train_3': TypeDetailsTuple( + _create_toy_train_3, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'trike': TypeDetailsTuple( + _create_toy_trike, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'trolley_1': TypeDetailsTuple( _create_toy_trolley, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS @@ -5743,6 +6961,9 @@ class TypeDetailsTuple(NamedTuple): ), 'truck_4': TypeDetailsTuple( _create_toy_truck_4, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS + ), 'truck_5': TypeDetailsTuple( + _create_toy_truck_5, + BLOCK_BLANK_MATERIALS + WOOD_MATERIALS ), 'turtle_on_wheels': TypeDetailsTuple( _create_turtle_on_wheels, BLOCK_BLANK_MATERIALS + WOOD_MATERIALS @@ -5757,7 +6978,8 @@ class TypeDetailsTuple(NamedTuple): for tool_type in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.keys(): - _TYPES_TO_DETAILS[tool_type] = TypeDetailsTuple(_create_tool, []) + _TYPES_TO_DETAILS[tool_type] = TypeDetailsTuple( + _create_tool, TOOL_MATERIALS) _PRIMITIVE_TUPLES = [ @@ -5788,6 +7010,10 @@ class TypeDetailsTuple(NamedTuple): ('rollable_2', _create_primitive_non_cylinder), ('rollable_3', _create_primitive_non_cylinder), ('rollable_4', _create_primitive_non_cylinder), + ('rollable_5', _create_primitive_non_cylinder), + ('rollable_6', _create_primitive_non_cylinder), + ('rollable_7', _create_primitive_non_cylinder), + ('rollable_8', _create_primitive_non_cylinder), ('sphere', _create_primitive_non_cylinder), ('square_frustum', _create_primitive_non_cylinder), ('square_frustum_with_base', _create_primitive_tall), @@ -5964,16 +7190,3 @@ def create_soccer_ball(size: float = 1) -> ObjectDefinition: size_multiplier_list=[size], type='soccer_ball' )) - - -def _get_types_by_prefix(prefix) -> List[str]: - return list(filter(lambda type: type.startswith( - prefix), _TYPES_TO_DETAILS.keys())) - - -def get_asymmetric_types() -> List[str]: - return _get_types_by_prefix("container_asymmetric") - - -def get_symmetric_types() -> List[str]: - return _get_types_by_prefix("container_symmetric") diff --git a/generator/containers.py b/generator/containers.py index 9addf84..03edff1 100644 --- a/generator/containers.py +++ b/generator/containers.py @@ -7,11 +7,12 @@ create_bounds, get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector ) +from .objects import SceneObject def put_object_in_container( - instance: Dict[str, Any], - container: Dict[str, Any], + instance: SceneObject, + container: SceneObject, area_index: int, rotation: Optional[float] = None ) -> None: @@ -64,9 +65,9 @@ class Orientation(Enum): def put_objects_in_container( - object_a: Dict[str, Any], - object_b: Dict[str, Any], - container: Dict[str, Any], + object_a: SceneObject, + object_b: SceneObject, + container: SceneObject, area_index: int, orientation: Orientation, rotation_a: float, diff --git a/generator/definitions.py b/generator/definitions.py index b1d1913..c923983 100644 --- a/generator/definitions.py +++ b/generator/definitions.py @@ -12,6 +12,7 @@ from machine_common_sense.config_manager import Vector3d from . import materials, tags +from .objects import SceneObject MAX_SIZE_DIFF = 0.05 @@ -340,9 +341,12 @@ def assign_chosen_type(self, choice: TypeChoice) -> None: def _assign_chosen_option(self, choice: _DefinitionChoice) -> None: for prop in (choice.get_props() + choice.get_debug_props()): - if getattr(choice, prop) is not None: + data = getattr(choice, prop) + if data is not None: + if prop == 'massMultiplier' and self.massMultiplier: + data *= self.massMultiplier # This will override the existing property, if set. - setattr(self, prop, getattr(choice, prop)) + setattr(self, prop, data) class ChosenMaterial(Enum): @@ -579,19 +583,19 @@ def finalize_object_materials_and_colors( def _create_size_list( - definition_or_instance_1: Union[ObjectDefinition, Dict[str, Any]], - definition_or_instance_2: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance_1: Union[ObjectDefinition, SceneObject], + definition_or_instance_2: Union[ObjectDefinition, SceneObject], only_diagonal_size: bool ) -> List[Tuple[float, float]]: - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_1, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_1, (SceneObject, dict)): dimensions_1 = definition_or_instance_1['debug']['dimensions'] else: dimensions_1 = vars(definition_or_instance_1.dimensions) - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_2, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_2, (SceneObject, dict)): dimensions_2 = definition_or_instance_2['debug']['dimensions'] else: dimensions_2 = vars(definition_or_instance_2.dimensions) @@ -626,15 +630,15 @@ def do_materials_match( def is_similar_except_in_color( - definition_or_instance_1: Union[ObjectDefinition, Dict[str, Any]], - definition_or_instance_2: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance_1: Union[ObjectDefinition, SceneObject], + definition_or_instance_2: Union[ObjectDefinition, SceneObject], only_diagonal_size: bool = False ) -> bool: """Return whether the two given objects are similar in shape (type) and size (dimensions) but not color.""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_1, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_1, (SceneObject, dict)): type_1 = definition_or_instance_1['type'] material_1 = definition_or_instance_1['materials'] or [] color_1 = definition_or_instance_1['debug']['color'] or [] @@ -643,8 +647,8 @@ def is_similar_except_in_color( material_1 = definition_or_instance_1.materials or [] color_1 = definition_or_instance_1.color or [] - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_2, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_2, (SceneObject, dict)): type_2 = definition_or_instance_2['type'] material_2 = definition_or_instance_2['materials'] or [] color_2 = definition_or_instance_2['debug']['color'] or [] @@ -678,15 +682,15 @@ def is_similar_except_in_color( def is_similar_except_in_shape( - definition_or_instance_1: Union[ObjectDefinition, Dict[str, Any]], - definition_or_instance_2: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance_1: Union[ObjectDefinition, SceneObject], + definition_or_instance_2: Union[ObjectDefinition, SceneObject], only_diagonal_size: bool = False ) -> bool: """Return whether the two given objects are similar in color and size (dimensions) but not shape (type).""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_1, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_1, (SceneObject, dict)): type_1 = definition_or_instance_1['type'] material_1 = definition_or_instance_1['materials'] or [] color_1 = definition_or_instance_1['debug']['color'] or [] @@ -695,8 +699,8 @@ def is_similar_except_in_shape( material_1 = definition_or_instance_1.materials or [] color_1 = definition_or_instance_1.color or [] - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_2, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_2, (SceneObject, dict)): type_2 = definition_or_instance_2['type'] material_2 = definition_or_instance_2['materials'] or [] color_2 = definition_or_instance_2['debug']['color'] or [] @@ -730,15 +734,15 @@ def is_similar_except_in_shape( def is_similar_except_in_size( - definition_or_instance_1: Union[ObjectDefinition, Dict[str, Any]], - definition_or_instance_2: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance_1: Union[ObjectDefinition, SceneObject], + definition_or_instance_2: Union[ObjectDefinition, SceneObject], only_diagonal_size: bool = False ) -> bool: """Return whether the two given objects are similar in color and shape (type) but not size (dimensions).""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_1, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_1, (SceneObject, dict)): type_1 = definition_or_instance_1['type'] material_1 = definition_or_instance_1['materials'] or [] color_1 = definition_or_instance_1['debug']['color'] or [] @@ -747,8 +751,8 @@ def is_similar_except_in_size( material_1 = definition_or_instance_1.materials or [] color_1 = definition_or_instance_1.color or [] - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance_2, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance_2, (SceneObject, dict)): type_2 = definition_or_instance_2['type'] material_2 = definition_or_instance_2['materials'] or [] color_2 = definition_or_instance_2['debug']['color'] or [] @@ -782,7 +786,7 @@ def is_similar_except_in_size( def get_similar_definition( - target_object: Union[ObjectDefinition, Dict[str, Any]], + target_object: Union[ObjectDefinition, SceneObject], definition_dataset: DefinitionDataset, # We should only ever set unshuffled to True in a unit test. unshuffled: bool = False @@ -1034,6 +1038,50 @@ def _callback(definition: ImmutableObjectDefinition) -> bool: return self.filter_on_custom(_callback) + def dataset_unique_shape_scale(self, keep: int = 1) -> DefinitionDataset: + """Function for unit tests: Return a new dataset containing all of the + definitions in this dataset, but only keep one (or the given number) + definition with the same shape and scale combination (ignore color).""" + unique = {} + groups = [] + for definition_selections in self._definition_groups: + selections = [] + for definition_variations in definition_selections: + variations = [] + for definition in definition_variations[:keep]: + if definition.type not in unique: + unique[definition.type] = {} + scale_str = str(definition.scale) + if scale_str not in unique[definition.type]: + unique[definition.type][scale_str] = 0 + if unique[definition.type][scale_str] < keep: + unique[definition.type][scale_str] += 1 + variations.append(definition) + if variations: + selections.append(variations) + if selections: + groups.append(selections) + return DefinitionDataset(groups) + + def definitions_unique_shape_scale(self) -> List[ObjectDefinition]: + """Function for unit tests: Return a list of all of the object + definitions in this dataset, but only keep one definition with the same + shape and scale combination (ignore color).""" + unique = {} + output = [] + for definition_selections in self._definition_groups: + for definition_variations in definition_selections: + for definition in definition_variations[:1]: + if definition.type not in unique: + unique[definition.type] = {} + scale_str = str(definition.scale) + if scale_str not in unique[definition.type]: + unique[definition.type][scale_str] = 0 + if unique[definition.type][scale_str] < 1: + unique[definition.type][scale_str] += 1 + output.append(ObjectDefinition(**definition._asdict())) + return output + DATASETS: Dict[str, DefinitionDataset] = {} DATASETS_UNSHUFFLED: Dict[str, DefinitionDataset] = {} diff --git a/generator/geometry.py b/generator/geometry.py index 6b2c35a..f64c833 100644 --- a/generator/geometry.py +++ b/generator/geometry.py @@ -14,6 +14,7 @@ ImmutableObjectDefinition, ObjectDefinition ) +from .objects import SceneObject from .separating_axis_theorem import sat_entry MAX_TRIES = 50 @@ -45,6 +46,11 @@ LEFT_WALL_LABEL = "left_wall" RIGHT_WALL_LABEL = "right_wall" +FRONT_RIGHT_CORNER = "front_right" +FRONT_LEFT_CORNER = "front_left" +BACK_RIGHT_CORNER = "back_right" +BACK_LEFT_CORNER = "back_left" + ORIGIN = { "x": 0.0, "y": 0.0, @@ -123,7 +129,6 @@ def __dict_to_vector(data: Dict[str, float]) -> Vector3d: def create_bounds( - # TODO MCS-697 MCS-698 Use Vector3d instead of Dict dimensions: Dict[str, float], offset: Optional[Dict[str, float]], position: Dict[str, float], @@ -132,7 +137,7 @@ def create_bounds( ) -> ObjectBounds: """Creates and returns an ObjectBounds for the object with the given size properties in the given location.""" - # TODO MCS-697 MCS-698 Use class props directly instead of converting + # TODO MCS-697 Use class props directly instead of converting dimensions = __dict_to_vector(dimensions) offset = __dict_to_vector(offset) if offset else Vector3d() position = __dict_to_vector(position) @@ -202,8 +207,7 @@ def random_rotation() -> float: def calc_obj_pos( performer_position: Dict[str, float], bounds_list: List[ObjectBounds], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance: Union[ObjectDefinition, SceneObject], x_func: Callable[[Dict[str, float]], float] = random_position_x, z_func: Callable[[Dict[str, float]], float] = random_position_z, rotation_func: Callable[[], float] = None, @@ -213,8 +217,8 @@ def calc_obj_pos( """Returns new object with rotation & position if we can place the object in the frame, None otherwise.""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(definition_or_instance, (SceneObject, dict)): debug = definition_or_instance['debug'] dimensions = debug['dimensions'] offset = debug.get('offset', {'x': 0, 'z': 0}) @@ -313,8 +317,7 @@ def _get_visible_segment( def get_location_in_front_of_performer( performer_start: Dict[str, Dict[str, float]], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - target_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + target_definition_or_instance: Union[ObjectDefinition, SceneObject], rotation_func: Callable[[], float] = random_rotation, room_dimensions: Dict[str, float] = None ) -> Optional[Dict[str, Any]]: @@ -345,16 +348,15 @@ def segment_xz(_room_dimensions: Dict[str, float]) -> Tuple[float, float]: def get_location_in_back_of_performer( performer_start: Dict[str, Dict[str, float]], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - target_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + target_definition_or_instance: Union[ObjectDefinition, SceneObject], rotation_func: Callable[[], float] = random_rotation, room_dimensions: Dict[str, float] = None ) -> Optional[Dict[str, Any]]: """Return a random location in the 180-degree-arc directly in back of the performer agent's starting position and rotation.""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(target_definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(target_definition_or_instance, (SceneObject, dict)): debug = target_definition_or_instance['debug'] dimensions = debug['dimensions'] offset = debug.get('offset', {'x': 0, 'z': 0}) @@ -451,8 +453,7 @@ def compute_xz(_room_dimensions: Dict[str, float]) -> Tuple[float, float]: def get_location_adjacent_to_performer( performer_start: Dict[str, Dict[str, float]], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - target_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + target_definition_or_instance: Union[ObjectDefinition, SceneObject], distance: float, direction_rotation: int, room_dimensions: Dict[str, float] = None ) -> Optional[Dict[str, Any]]: @@ -460,7 +461,7 @@ def get_location_adjacent_to_performer( placed in a direction based on the `direction_rotation` relative to the performers starting rotation. The `distance` field is the dimensions edge to edge distance.""" - if isinstance(target_definition_or_instance, dict): + if isinstance(target_definition_or_instance, (SceneObject, dict)): debug = target_definition_or_instance['debug'] dimensions = debug['dimensions'] offset = debug.get('offset', {'x': 0, 'z': 0}) @@ -506,9 +507,70 @@ def compute_rot(): ) +def get_location_adjacent_to_corner( + performer_start: Dict[str, Dict[str, float]], + instance: SceneObject, + room_dimensions: Dict[str, Any], + distance_from_corner: Vector3d, + corner_label: str) -> Dict[str, Any]: + """returns an object with a new location near a specified corner + for a given room.""" + dimensions = instance['debug']['dimensions'] + offset_x = instance['debug']['offset']['x'] + offset_z = instance['debug']['offset']['z'] + + def compute_xz(_room_dimensions: Dict[str, float]) -> Tuple[float, float]: + return get_adjacent_to_corner_xz( + corner_label, _room_dimensions, dimensions, offset_x, offset_z, + distance_from_corner.x, distance_from_corner.z) + + return calc_obj_pos( + performer_start['position'], + [], + instance, + xz_func=compute_xz, + room_dimensions=(room_dimensions or DEFAULT_ROOM_DIMENSIONS) + ) + + +def get_adjacent_to_corner_xz( + corner_label, room_dimensions, object_dimensions, + offset_x=0, offset_z=0, distance_from_corner_x=0, + distance_from_corner_z=0): + """Return an x, z coordinate for a location adjacent to a corner.""" + + x_limit = room_dimensions['x'] / 2 + z_limit = room_dimensions['z'] / 2 + + obj_dim_x = object_dimensions['x'] + obj_dim_z = object_dimensions['z'] + + x_min = -x_limit + obj_dim_x / 2 + offset_x + abs(distance_from_corner_x) + x_max = x_limit - obj_dim_x / 2 - offset_x - abs(distance_from_corner_x) + z_min = -z_limit + obj_dim_z / 2 + offset_z + abs(distance_from_corner_z) + z_max = z_limit - obj_dim_z / 2 - offset_z - abs(distance_from_corner_z) + + if corner_label == FRONT_LEFT_CORNER: + x = x_min + z = z_max + elif corner_label == FRONT_RIGHT_CORNER: + x = x_max + z = z_max + elif corner_label == BACK_LEFT_CORNER: + x = x_min + z = z_min + elif corner_label == BACK_RIGHT_CORNER: + x = x_max + z = z_min + else: + raise Exception(f"{corner_label} is not a valid corner label.") + + return x, z + + def get_location_along_wall( performer_start: Dict[str, Dict[str, float]], - wall: str, instance: Dict[str, Any], + wall: str, instance: SceneObject, room_dimensions: Dict[str, Any]) -> Dict[str, Any]: """returns an object with a new location along a wall for a given room. The `wall` parameter must be either `back_wall`, `front_wall`, @@ -567,10 +629,8 @@ def get_along_wall_xz(wall_label, room_dimensions, dimensions, def generate_location_adjacent_to( - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - adjacent_instance: Dict[str, Any], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - relative_instance: Dict[str, Any], + adjacent_instance: SceneObject, + relative_instance: SceneObject, distance_x: float, distance_z: float, performer_start: Dict[str, Dict[str, float]], @@ -662,23 +722,22 @@ def generate_location_adjacent_to( def generate_location_on_object( - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - object_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - static_instance: Dict[str, Any], + object_definition_or_instance: Union[ObjectDefinition, SceneObject], + static_instance: SceneObject, performer_start: Dict[str, Dict[str, float]], bounds_list: List[ObjectBounds], room_dimensions: Dict[str, float] = None, center: bool = False, position_relative_to_start: Dict[str, float] = None ) -> Dict[str, Any]: - """Creates a location for the object to place it on top of the static object. + """Creates a location for the object to place it on top of the static + object. Returns: Dict[str, Any]: Location dict or None if it cannot be determined. """ - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(object_definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(object_definition_or_instance, (SceneObject, dict)): debug = object_definition_or_instance['debug'] dimensions = debug['dimensions'] offset = debug.get('offset', {'x': 0, 'y': 0, 'z': 0}) @@ -690,7 +749,7 @@ def generate_location_on_object( position_y = object_definition_or_instance.positionY rotation = vars(object_definition_or_instance.rotation) - if not isinstance(static_instance, dict): + if not isinstance(static_instance, (SceneObject, dict)): raise Exception( "Generate_location_on_object() must be passed a static instance") @@ -706,7 +765,7 @@ def generate_location_on_object( if center: x, z = static_bounds.polygon_xz.centroid.coords[0] - if(position_relative_to_start is not None): + if (position_relative_to_start is not None): relative_obj_dim = static_instance['debug']['dimensions'] buffer = max(dimensions['x'], dimensions['z']) / 2.0 @@ -793,10 +852,8 @@ def generate_location_on_object( def generate_location_in_line_with_object( - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - object_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - static_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + object_definition_or_instance: Union[ObjectDefinition, SceneObject], + static_definition_or_instance: Union[ObjectDefinition, SceneObject], static_location: Dict[str, Any], performer_start: Dict[str, Dict[str, float]], bounds_list: List[ObjectBounds], @@ -816,11 +873,11 @@ def generate_location_in_line_with_object( object so that the performer cannot reach the static object. Assume only one bool flag is ever used.""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(object_definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(object_definition_or_instance, (SceneObject, dict)): debug = object_definition_or_instance['debug'] dimensions = debug['dimensions'] - offset = debug.get('offset', {'x': 0, 'z': 0}) + offset = debug.get('offset', {'x': 0, 'y': 0, 'z': 0}) position_y = debug.get('positionY', 0) rotation = debug.get('rotation', {'x': 0, 'y': 0, 'z': 0}) else: @@ -829,8 +886,8 @@ def generate_location_in_line_with_object( position_y = object_definition_or_instance.positionY rotation = vars(object_definition_or_instance.rotation) - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(static_definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(static_definition_or_instance, (SceneObject, dict)): static_debug = static_definition_or_instance['debug'] static_dimensions = static_debug['dimensions'] static_offset = static_debug.get('offset', {'x': 0, 'y': 0, 'z': 0}) @@ -983,8 +1040,7 @@ def generate_location_in_line_with_object( def retrieve_obstacle_occluder_definition_list( - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - target_definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + target_definition_or_instance: Union[ObjectDefinition, SceneObject], definition_dataset: DefinitionDataset, is_occluder: bool ) -> Optional[Tuple[ObjectDefinition, int]]: @@ -994,8 +1050,8 @@ def retrieve_obstacle_occluder_definition_list( is returned. If is_occluder is True, each matching definition must be an occluder; otherwise, each matching definition must be an obstacle.""" - # TODO MCS-697 Define an ObjectInstance class extending ObjectDefinition. - if isinstance(target_definition_or_instance, dict): + # TODO MCS-697 Use dot notation for SceneObject + if isinstance(target_definition_or_instance, (SceneObject, dict)): debug = target_definition_or_instance['debug'] target_dimensions = debug['dimensions'] else: @@ -1048,7 +1104,7 @@ def _callback(definition: ImmutableObjectDefinition) -> bool: def get_bounding_polygon( - object_or_location: Dict[str, Any] + object_or_location: Union[SceneObject, Dict[str, Any]] ) -> geometry.Polygon: if 'boundingBox' in object_or_location: return object_or_location['boundingBox'].polygon_xz @@ -1057,7 +1113,7 @@ def get_bounding_polygon( return show['boundingBox'].polygon_xz -def are_adjacent(obj_a: Dict[str, Any], obj_b: Dict[str, Any], +def are_adjacent(obj_a: SceneObject, obj_b: SceneObject, distance: float = MAX_OBJECTS_ADJACENT_DISTANCE) -> bool: poly_a = get_bounding_polygon(obj_a) poly_b = get_bounding_polygon(obj_b) @@ -1092,7 +1148,7 @@ def find_performer_bounds( def does_fully_obstruct_target(performer_start_position: Dict[str, float], - target_or_location: Dict[str, Any], + target_or_location: Union[SceneObject, dict], object_poly: geometry.Polygon) -> bool: """Returns whether the given object_poly obstructs each line between the given performer_start_position and @@ -1107,7 +1163,7 @@ def does_fully_obstruct_target(performer_start_position: Dict[str, float], def does_partly_obstruct_target(performer_start_position: Dict[str, float], - target_or_location: Dict[str, Any], + target_or_location: Union[SceneObject, dict], object_poly: geometry.Polygon) -> bool: """Returns whether the given object_poly obstructs one line between the given performer_start_position and @@ -1122,7 +1178,7 @@ def does_partly_obstruct_target(performer_start_position: Dict[str, float], def _does_obstruct_target_helper(performer_start_position: Dict[str, float], - target_or_location: Dict[str, Any], + target_or_location: Union[SceneObject, dict], object_poly: geometry.Polygon, fully: bool = False) -> bool: @@ -1197,24 +1253,34 @@ def validate_location_rect( def move_to_location( - object_instance: Dict[str, Any], + object_instance: SceneObject, object_location: Dict[str, Any] -) -> Dict[str, Any]: +) -> SceneObject: """Move the given object to the given location and return the object.""" location = copy.deepcopy(object_location) - location['position']['x'] -= object_instance['debug']['offset']['x'] - location['position']['z'] -= object_instance['debug']['offset']['z'] + offset = {'x': 0, 'y': 0, 'z': 0} + standing_y = object_instance['shows'][0]['position']['y'] + + if ('offset' in object_instance['debug']): + offset = object_instance['debug']['offset'] + location['position']['x'] -= object_instance['debug']['offset']['x'] + location['position']['z'] -= object_instance['debug']['offset']['z'] + + if ('positionY' in object_instance['debug']): + standing_y = object_instance['debug']['positionY'] + original_rotation = object_instance['debug'].get('originalRotation', {}) for axis in ['x', 'y', 'z']: location['rotation'][axis] += original_rotation.get(axis, 0) object_instance['shows'][0]['position'] = location['position'] object_instance['shows'][0]['rotation'] = location['rotation'] + object_instance['shows'][0]['boundingBox'] = create_bounds( dimensions=object_instance['debug']['dimensions'], - offset=object_instance['debug']['offset'], + offset=offset, position=location['position'], rotation=location['rotation'], - standing_y=object_instance['debug']['positionY'] + standing_y=standing_y ) return object_instance @@ -1487,7 +1553,7 @@ def get_position_distance_away_from_hooked_tool( raise Exception( f"Failed to find valid performer location " - f"with distance away: ({distance}) from object: ({tool['id']})" + f"with distance away: ({distance}) from object: ({tool['id']}) " f"because location is obstructed or outside of room bounds") @@ -1518,11 +1584,13 @@ def get_valid_starts_near_position_on_perimeter( segment = ops.substring( line, i, i + seperation_between_spawn_points) mp = mp.union(segment.boundary) - x = [point.x for point in mp] - y = [point.y for point in mp] + x = [point.x for point in (mp.geoms if hasattr(mp, 'geoms') else mp)] + y = [point.y for point in (mp.geoms if hasattr(mp, 'geoms') else mp)] pos = Vector3d(x=0, y=0, z=0) - for _ in range(MAX_TRIES): - index = random.randint(0, len(x) - 1) + indexes = list(range(len(x))) + random.shuffle(indexes) + for i in range(len(indexes)): + index = indexes[i] pos.x = x[index] pos.z = y[index] """ @@ -1563,7 +1631,7 @@ def get_valid_starts_near_position_on_perimeter( """ (valid, start_difference) = \ distance_between_point_and_bounding_box_is_valid( - pos.x, pos.z, polygon, distance_away) + pos.x, pos.z, polygon, distance_away) if not valid: valid = shift_point_closer_to_polygon( directions, start_difference, polygon, distance_away, pos) @@ -1604,17 +1672,14 @@ def get_resultant_vectors(normalized_horizontal_vector, def shift_point_closer_to_polygon( directions, start_difference, polygon, distance_away, pos): for direction in directions: - shift_amount = start_difference shift_amount = start_difference for _ in range(MAX_TRIES): - reverse_x = -direction.x * shift_amount - reverse_z = -direction.z * shift_amount - new_pos_x = pos.x + reverse_x - new_pos_z = pos.z + reverse_z + new_pos_x = pos.x + (direction.x * shift_amount) + new_pos_z = pos.z + (direction.z * shift_amount) (valid, difference_after_shift) = \ - distance_between_point_and_bounding_box_is_valid( # noqa - new_pos_x, new_pos_z, polygon, distance_away) - shift_amount += 0.01 + distance_between_point_and_bounding_box_is_valid( + new_pos_x, new_pos_z, polygon, distance_away) + shift_amount += difference_after_shift # the shift is in the wrong direction if difference_after_shift > start_difference: break @@ -1652,7 +1717,7 @@ def get_normalized_vector_from_two_points( return normalized_vector -def is_above(above: Dict[str, Any], below: Dict[str, Any]) -> bool: +def is_above(above: SceneObject, below: SceneObject) -> bool: """Returns whether the first given object instance is at least partially above the second.""" above_bounds = above['shows'][0]['boundingBox'] @@ -1673,3 +1738,112 @@ def rotate_point_around_origin(origin_x, origin_z, point_x, point_z, rotation): result_z = origin_z + math.sin(rotation) * (point_x - origin_x) + \ math.cos(rotation) * (point_z - origin_z) return result_x, result_z + + +def _nearby_equidistant_locations_helper( + x_1: float, + z_1: float, + start_x: float, + start_z: float, + x_min: float, + x_max: float, + z_min: float, + z_max: float +) -> Tuple[float, float]: + # Calculate the distance to this position. + d_1 = math.hypot(abs(x_1 - start_x), abs(z_1 - start_z)) + # The second position should be far enough away from the first so that the + # performer agent cannot reach both objects from a single location. + circle = geometry.Point((x_1, z_1)).buffer(MAX_REACH_DISTANCE + 0.5) + # Use the points in this circle as options for the second position. + points = [ + (round(x, 4), round(z, 4)) for x, z in list(circle.exterior.coords) + if x_min <= x <= x_max and z_min <= z <= z_max + ] + random.shuffle(points) + # Calculate the distances to all the other positions. + data = list(sorted([( + x, + z, + round(abs(d_1 - math.hypot(abs(x - start_x), abs(z - start_z))), 4) + ) for x, z in points], key=lambda i: i[2])) + # Choose the shortest distance. + nearest_point = data[0] if data else (None, None, None) + x_2, z_2, distance_diff = nearest_point + # Scale how near the two distances must be based on the distance. + near_enough = max(d_1 / 20.0, 0.5) + # If it's near enough to the first distance, then use this position. + if distance_diff is not None and distance_diff <= near_enough: + return x_2, z_2 + return None, None + + +def nearby_equidistant_locations( + start_x: float, + start_z: float, + x_min: float, + x_max: float, + z_min: float, + z_max: float, + iterator: int = 0 +) -> Tuple[float, float, float, float]: + """Returns two pairs of X/Z positions within the given min/max ranges that + are both "near" to each other (but far enough away so the performer agent + cannot reach both objects from a single location) and roughly "equidistant" + from the given start position (the two distances are within 0.5).""" + # Choose a random X/Z location. + x_1 = round(random.uniform(x_min, x_max), 4) + z_1 = round(random.uniform(z_min, z_max), 4) + # Find a corresponding nearby equidistant location. + x_2, z_2 = _nearby_equidistant_locations_helper( + x_1, + z_1, + start_x, + start_z, + x_min, + x_max, + z_min, + z_max + ) + # If a second valid location exists, return both of them. + if x_2 is not None and z_2 is not None: + return x_1, z_1, x_2, z_2 + # Otherwise try choosing a different location. + if iterator >= MAX_TRIES: + raise Exception( + f'Cannot find any nearby equidistant locations: start=({start_x}, ' + f'{start_z}), x_range=({x_min}, {x_max}), z_range=({z_min}, ' + f'{z_max}), point_1=({x_1}, {z_1})' + ) + return nearby_equidistant_locations( + start_x, start_z, x_min, x_max, z_min, z_max, iterator + 1 + ) + + +def calculate_aligned_position( + position_x: float, + position_z: float, + rotation_y: float, + size_1: float, + size_2: float, + separation: float, + axis: str = 'z', + offset_x: float = 0, + offset_z: float = 0 +) -> Tuple[float, float]: + """Calculate and return a position aligned with the given position and + rotation for two objects with the given sizes at the given separation + distance.""" + distance = (size_1 + size_2) / 2.0 + separation + line = geometry.LineString([[0, 0], [distance, 0]]) + rotation = -(rotation_y + (90 if axis == 'z' else 180)) + line = affinity.rotate(line, rotation, origin=(0, 0)) + line = affinity.translate(line, position_x, position_z) + if offset_x or offset_z: + offset = geometry.LineString([[0, 0], [offset_x, offset_z]]) + offset = affinity.rotate(offset, -rotation_y, origin=(0, 0)) + endpoint = offset.coords[1] + line = affinity.translate(line, endpoint[0], endpoint[1]) + x = round(line.coords[1][0], 4) + z = round(line.coords[1][1], 4) + return x, z diff --git a/generator/imitation.py b/generator/imitation.py new file mode 100644 index 0000000..45062db --- /dev/null +++ b/generator/imitation.py @@ -0,0 +1,635 @@ +import random +from enum import Enum +from typing import Any, Dict, List + +from machine_common_sense.config_manager import Vector3d + +from generator import MAX_TRIES, Scene, geometry, materials +from generator.agents import ( + AGENT_TYPES, + add_agent_movement, + create_agent, + get_random_agent_settings +) +from generator.base_objects import create_specific_definition_from_base +from generator.instances import instantiate_object +from generator.mechanisms import create_placer +from generator.objects import SceneObject + +IMITATION_TASK_CONTAINER_SCALE = 1 +IMITATION_TASK_TARGET_SEPARATION = 0.6 +IMITATION_AGENT_START_X = 0.3 +IMITATION_AGENT_END_X = 0.4 +IMITATION_CONTAINER_START_X = 0.9 +IMITATION_CONTAINER_SEPARATION = 1 + +""" +The rotations here work by referencing the world global rotation +and relative clockwise rotations. +Global means that the world rotation in unity will be the exact value. +LEFT_SIDE and RIGHT_SIDE is the relative clockwise from start shift. + +LEFT_SIDE: +Start End +[3]-> [3] +[2] Rotate 45 [2] ↘ +[1]-> [1] ↘ + +RIGHT_SIDE: +End Start + ↖ [3] <-[3] +↖ [2] Rotate 45 [2] +[1] <-[1] + +GLOBAL: +Start End + ↖ [3] [1] ↗ + ↖ [2] Rotate 45 [2] ↗ +[1] [3] +""" +IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL = [45, 135, 180, 225, 315] +IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE = [45, 90, 135, 225, 315] +IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE = [45, 135, 225, 270, 315] + +IMITATION_CONTAINER_TELEPORT_X_POS_RANGE = [ + (-0.25, 0.85), (-1, 1.5), (-1.5, -0.5), (-2.5, 0), (0, 2.5)] +IMITATION_CONTAINER_TELEPORT_MIN_Z_POS = 1 + + +class ImitationTriggerOrder(str, Enum): + LEFT = "left" + MIDDLE = "middle" + RIGHT = "right" + LEFT_MIDDLE = "left_middle" + LEFT_RIGHT = "left_right" + MIDDLE_LEFT = "middle_left" + MIDDLE_RIGHT = "middle_right" + RIGHT_MIDDLE = "right_middle" + RIGHT_LEFT = "right_left" + + +class ImitationKidnapOptions(str, Enum): + AGENT_ONLY = "agent_only" + CONTAINERS = "containers" + PERFORMER = "performer" + + +def _create_imitation_container( + position: Vector3d, rotation_y, material) -> SceneObject: + + container_definition = create_specific_definition_from_base( + type="chest_1", + color=material[1], + materials=[material[0]], + salient_materials=["wood"], + scale=IMITATION_TASK_CONTAINER_SCALE + ) + location = { + 'position': vars(position), + 'rotation': {'x': 0, 'y': rotation_y, 'z': 0} + } + container = instantiate_object(container_definition, location) + return container + + +def get_three_unique_materials(): + container_colors_used = [] + material_list = [] + material_choices = materials.CUSTOM_WOOD_MATERIALS + for i in range(3): + for _ in range(MAX_TRIES): + try_new_color = False + material = random.choice(material_choices) + colors = material[1] + for color in colors: + if color in container_colors_used: + try_new_color = True + break + if try_new_color: + continue + for color in colors: + container_colors_used.append(color) + material_list.append(material) + break + return material_list + + +def _setup_three_containers_for_imitation_task( + scene: Scene, containers_on_right_side, color_override) -> List: + containers = [] + left_container_start_pos_z = \ + 1 if containers_on_right_side else -1 + separation_between_containers = IMITATION_CONTAINER_SEPARATION * ( + -1 if containers_on_right_side else 1) + # Positive to negative z axis, positive is left, negative is right + container_range = range(left_container_start_pos_z, + -left_container_start_pos_z * 2, + separation_between_containers) + three_materials = color_override if color_override else \ + get_three_unique_materials() + material_index = 0 + for container_index in container_range: + pos_z = container_index + rotation_y = -90 if containers_on_right_side else 90 + pos = Vector3d( + x=(IMITATION_CONTAINER_START_X if + containers_on_right_side else -IMITATION_CONTAINER_START_X), + y=0, z=pos_z) + chest = _create_imitation_container( + pos, rotation_y, three_materials[material_index]) + scene.objects.append(chest) + container_index = scene.objects[-1] + containers.append(container_index) + material_index += 1 + return containers, separation_between_containers + + +def _setup_target_and_placer_imitation_task( + scene: Scene, containers_on_right_side, containers): + # move target to end of container + scene.objects.append(scene.objects.pop(0)) + target = scene.objects[-1] + target['shows'][0]['position']['y'] = \ + scene.room_dimensions.y + target['debug']['dimensions']['y'] * 2 + placer = create_placer( + target['shows'][0]['position'], target['debug']['dimensions'], + target['debug']['positionY'], 0, 0, scene.room_dimensions.y + ) + placer['triggeredBy'] = True + scene.objects.append(placer) + + target['triggeredBy'] = True + target['kinematic'] = True + target['moves'] = [placer['moves'][0]] + target['togglePhysics'] = [ + {'stepBegin': placer['changeMaterials'][0]['stepBegin']}] + target['shows'][0]['position']['x'] = \ + containers[0]['shows'][0]['position']['x'] + placer['shows'][0]['position']['x'] = \ + target['shows'][0]['position']['x'] + + # position in front of the left containers if containers on left + # position in front of the right containers if containers on right + target_separation = IMITATION_TASK_TARGET_SEPARATION + container_to_put_in_front_of_index = \ + -1 if containers_on_right_side else 0 + target['shows'][0]['position']['z'] = ( + containers[container_to_put_in_front_of_index + ]['shows'][0]['position']['z'] - target_separation) + placer['shows'][0]['position']['z'] = \ + target['shows'][0]['position']['z'] + return target, placer + + +def _setup_trigger_order_for_imitation_task(trigger_order, containers): + trigger_order_ids = [] + solo_options = ['left', 'middle', 'right'] + left_options = ['left_middle', 'left_right'] + middle_options = ['middle_left', 'middle_right'] + right_options = ['right_middle', 'right_left'] + containers_to_open_indexes = [] + if trigger_order in solo_options: + container_index = solo_options.index(trigger_order) + trigger_order_ids.append(containers[container_index]['id']) + containers_to_open_indexes.append(container_index) + elif trigger_order in left_options: + trigger_order_ids.append(containers[0]['id']) + container_index = left_options.index(trigger_order) + trigger_order_ids.append( + containers[1 if container_index == 0 else 2]['id']) + containers_to_open_indexes.append(0) + containers_to_open_indexes.append(1 if container_index == 0 else 2) + elif trigger_order in middle_options: + trigger_order_ids.append(containers[1]['id']) + container_index = middle_options.index(trigger_order) + trigger_order_ids.append( + containers[0 if container_index == 0 else 2]['id']) + containers_to_open_indexes.append(1) + containers_to_open_indexes.append(0 if container_index == 0 else 2) + elif trigger_order in right_options: + trigger_order_ids.append(containers[2]['id']) + container_index = right_options.index(trigger_order) + trigger_order_ids.append( + containers[1 if container_index == 0 else 0]['id']) + containers_to_open_indexes.append(2) + containers_to_open_indexes.append(1 if container_index == 0 else 0) + return trigger_order_ids, containers_to_open_indexes + + +def _setup_agent_for_imitation_task( + scene: Scene, containers_on_right_side, containers, + containers_to_open_indexes, agent_settings_override, + agent_type_override): + """ + Agent Setup + 1. Enter in front of starting chest + 2. Walk to chest + 3. Open (if only open one chest then end here and face performer) + 4. Walk to other chest + 5. Rotate to face chest + 6. Open the chest + 7. Face performer + """ + step_begin_open_first_chest = 18 + step_end_open_first_chest = 28 + open_animation = "TPE_jump" + turn_left_animation = "TPM_turnL45" + turn_right_animation = "TPM_turnR45" + + movement_points = [] + number_of_containers = 0 + start_turning_step = None + rotates = None + for container_index in containers_to_open_indexes: + end_point_x = IMITATION_AGENT_END_X if containers_on_right_side else \ + -IMITATION_AGENT_END_X + end_point_z = containers[container_index]['shows'][0]['position']['z'] + movement_points.append((end_point_x, end_point_z)) + number_of_containers += 1 + if number_of_containers > 1: + """ + Example of chest on the right: + Rotate left because the agent walks toward the performer. + Start + | c opened + | c + Agent > c open this + + Performer + """ + containers_on_right_side_agent_moving_toward_performer = ( + containers_on_right_side and + container_index > containers_to_open_indexes[0]) + containers_on_left_side_agent_moving_away_from_performer = ( + not containers_on_right_side and + container_index > containers_to_open_indexes[0]) + # negative is left turn, positive is right turn + direction = ( + -1 if + containers_on_right_side_agent_moving_toward_performer or + containers_on_left_side_agent_moving_away_from_performer + else 1) + is_adjacent_container = ( + container_index == containers_to_open_indexes[0] + 1 or + container_index == containers_to_open_indexes[0] - 1) + # for some reason the left side needs one extra step + extra_step = ( + 1 if + containers_on_left_side_agent_moving_away_from_performer + else 0) + # With an origin point of the start container z position, + # 57 and 82 are the number of steps required to reach the + # adjacent or far container z position and be centered in + # front of it + start_turning_step = 57 + extra_step if \ + is_adjacent_container else 82 + extra_step + rotation_per_step = 9 * direction + rotates = { + "stepBegin": start_turning_step, + "stepEnd": start_turning_step + 10, + "vector": { + "x": 0, + "y": rotation_per_step, + "z": 0 + } + } + + # End position facing the performer + end_point_x = IMITATION_AGENT_END_X if containers_on_right_side else \ + -IMITATION_AGENT_END_X + end_point_z = movement_points[-1][1] - 0.15 + movement_points.append((end_point_x, end_point_z)) + + # Animations + actions = [] + # The steps that each container is opened + open_steps = [] + first_open = { + 'stepBegin': step_begin_open_first_chest, + 'stepEnd': step_end_open_first_chest, + 'isLoopAnimation': False, + 'id': open_animation + } + actions.append(first_open) + open_steps.append(step_begin_open_first_chest) + + # Check if we are opening more than one chest + # A turning animation is required to face the second chest + if start_turning_step is not None: + turn = { + 'stepBegin': start_turning_step, + 'stepEnd': start_turning_step + 10, + 'isLoopAnimation': False, + 'id': (turn_left_animation if rotates['vector']['y'] < 1 else + turn_right_animation) + } + second_open = { + 'stepBegin': start_turning_step + 10, + 'stepEnd': start_turning_step + 20, + 'isLoopAnimation': False, + 'id': open_animation + } + actions.append(turn) + actions.append(second_open) + open_steps.append(start_turning_step + 10) + + # Config the agent in front of the first chest to open + start_position = Vector3d( + x=(-IMITATION_AGENT_START_X if + containers_on_right_side else + IMITATION_AGENT_START_X), y=0, + z=(containers[containers_to_open_indexes[0]] + ['shows'][0]['position']['z'])) + rotation_y = 90 if containers_on_right_side else -90 + + type = agent_type_override if agent_type_override else \ + random.choice(AGENT_TYPES) + settings = agent_settings_override if agent_settings_override else \ + get_random_agent_settings(type) + agent = create_agent( + type, start_position.x, start_position.z, rotation_y, settings) + agent['actions'] = actions + + if rotates: + agent['rotates'] = [rotates] + add_agent_movement(agent=agent, step_begin=1, points=movement_points) + + # Open containers with animation timing + i = 0 + for container_index in containers_to_open_indexes: + containers[container_index]['openClose'] = [{ + 'step': open_steps[i] + 4, + 'open': True + }] + i += 1 + + scene.objects.append(agent) + return agent, open_steps + + +def _kidnap_performer_for_imitation_task( + scene: Scene, kidnap_option, containers_on_right_side, target, placer, + agent, last_open_step, containers, containers_to_open_indexes, + separation_between_containers, containers_teleport_rotation=None, + relative_rotation=True): + kidnap_step = last_open_step + placer['moves'][-1]['stepEnd'] + 10 + freezes = [["Pass"]] * (kidnap_step - 1) + scene.goal.action_list = freezes + + placer_first_position = placer['shows'][0]['position'] + placer['shows'].append({ + 'stepBegin': kidnap_step, + 'position': { + 'x': placer_first_position['x'], + 'y': placer_first_position['y'], + 'z': placer_first_position['z'] + } + }) + target_first_position = target['shows'][0]['position'] + target['shows'].append({ + 'stepBegin': kidnap_step, + 'position': { + 'x': target_first_position['x'], + 'y': target_first_position['y'], + 'z': target_first_position['z'] + } + }) + + # Close containers + for container_index in containers_to_open_indexes: + containers[container_index]['openClose'].append({ + 'step': kidnap_step, + 'open': False + }) + + x = scene.performer_start.position.x + z = scene.performer_start.position.z + y = scene.performer_start.rotation.y + scene.debug['endHabituationStep'] = kidnap_step + scene.debug['endHabituationTeleportPositionX'] = x + scene.debug['endHabituationTeleportPositionZ'] = z + scene.debug['endHabituationTeleportRotationY'] = y + # If we do NOT need to teleport anything, teleport the agent only + if kidnap_option == ImitationKidnapOptions.AGENT_ONLY: + agent['shows'].append({ + 'stepBegin': kidnap_step, + 'position': { + 'x': random.uniform(-2, 2), + 'y': 0, + 'z': 2 + }, + 'rotation': { + 'y': 180 + } + }) + scene.goal.action_list.append([ + f"EndHabituation,xPosition={x},zPosition={z},yRotation={y}" + ]) + # If we need to teleport the containers + elif kidnap_option == ImitationKidnapOptions.CONTAINERS: + _change_container_and_agent_positions_during_kidnap( + scene, kidnap_option, containers_on_right_side, + containers, separation_between_containers, + kidnap_step, target, placer, agent, containers_teleport_rotation, + relative_rotation) + scene.goal.action_list.append([ + f"EndHabituation,xPosition={x},zPosition={z},yRotation={y}" + ]) + # If we need to teleport the performer + else: + _teleport_performer_for_imitation_task( + scene, agent, kidnap_step) + + +def _change_container_and_agent_positions_during_kidnap( + scene: Scene, kidnap_option, containers_on_right_side, containers, + separation_between_containers, kidnap_step, target, + placer, agent, containers_teleport_rotation=None, + relative_rotation=True): + # Need to slightly shift depending on the start side since + # the performer is offset to stay in the performers view + buffer_for_all_containers_to_fit = 2 + buffer_for_agent_to_stand_behind = 1 + + rotation = ( + containers_teleport_rotation if containers_teleport_rotation else + random.choice(IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL)) + if relative_rotation and containers_teleport_rotation: + rotation = (containers_teleport_rotation + + (270 if containers_on_right_side else 90)) + rotation %= 360 + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + abs(rotation)) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + + start_x = round(random.uniform(min_max[0], min_max[1]), 2) + start_z = round( + random.uniform(IMITATION_CONTAINER_TELEPORT_MIN_Z_POS, + scene.room_dimensions.z / 2 - + buffer_for_all_containers_to_fit - + buffer_for_agent_to_stand_behind), 2) + separation = separation_between_containers * (1 if not containers else -1) + for container in containers: + container['shows'].append({ + 'stepBegin': kidnap_step, + 'position': { + 'x': start_x, + 'y': 0, + 'z': start_z + }, + 'rotation': { + 'y': rotation + } + }) + x, z = \ + geometry.get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + rotation, separation * (-1 if containers_on_right_side else 1), + 0) + start_x += x + start_z += z + + # target and placer need to shift too + x, z = \ + geometry.get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + rotation, + IMITATION_TASK_TARGET_SEPARATION * + (-1 if containers_on_right_side else 1), + 0) + end_container = -1 if containers_on_right_side else 0 + target['shows'][1]['position']['x'] = \ + containers[end_container]['shows'][1]['position']['x'] + x + target['shows'][1]['position']['z'] = \ + containers[end_container]['shows'][1]['position']['z'] + z + placer['shows'][1]['position']['x'] = \ + target['shows'][1]['position']['x'] + placer['shows'][1]['position']['z'] = \ + target['shows'][1]['position']['z'] + + separation = 1 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z']) + agent_z = max_container_z['shows'][1]['position']['z'] + separation + agent_x = random.choice([min_max[0] - separation, min_max[1] + separation]) + agent['shows'].append({ + 'stepBegin': kidnap_step, + 'position': { + 'x': agent_x, + 'y': 0, + 'z': agent_z + }, + 'rotation': { + 'y': 180 + } + }) + + +def _teleport_performer_for_imitation_task( + scene: Scene, agent, + kidnap_step): + agent_z = random.choice([-2, 2]) + agent['shows'].append({ + 'stepBegin': kidnap_step, + # Put the agent still close the containers + 'position': { + 'x': random.uniform(-2, 2), + 'y': 0, + 'z': agent_z + }, + 'rotation': { + 'y': 180 if agent_z > 0 else 0 + } + }) + + # pick an x and z not in the center x of the room + # so the teleport is substantial + shift = 2.5 + x1 = round(random.uniform( + -scene.room_dimensions.x / 2 + geometry.PERFORMER_WIDTH, + -shift), 2) + x2 = round(random.uniform( + shift, scene.room_dimensions.x / 2 - geometry.PERFORMER_WIDTH), + 2) + x = random.choice([x1, x2]) + z1 = round(random.uniform( + -scene.room_dimensions.z / 2 + geometry.PERFORMER_WIDTH, + -shift), 2) + z2 = round(random.uniform( + shift, scene.room_dimensions.z / 2 - geometry.PERFORMER_WIDTH), + 2) + z = random.choice([z1, z2]) + _, y = geometry.calculate_rotations( + Vector3d(x=x, y=0, z=z), Vector3d(x=0, y=0, z=0)) + + scene.debug['endHabituationStep'] = kidnap_step + scene.debug['endHabituationTeleportPositionX'] = x + scene.debug['endHabituationTeleportPositionZ'] = z + scene.debug['endHabituationTeleportRotationY'] = y + scene.goal.action_list.append([ + f"EndHabituation,xPosition={x},zPosition={z},yRotation={y}" + ]) + + +def add_imitation_task(scene: Scene, + trigger_order: str, + containers_on_right_side: bool, + kidnap_option: str, + use_scene_room_dimensions: bool = False, + agent_settings_override: Dict[str, Any] = None, + agent_type_override: bool = False, + color_override: List = None, + containers_teleport_rotation: int = None, + relative_rotation: bool = True): + """ + Creates an imitation task scene. + Note: The target should already be added + to the scene as the first and only object in the object list + before calling this function. + """ + + # Performer start + scene.set_performer_start( + Vector3d(x=0, y=0, z=-3.75), Vector3d(x=0, y=0, z=0)) + + # Make a rectangular room + scene.room_dimensions.y = 2 + + if not use_scene_room_dimensions: + base_dimension = random.randint(8, 10) + rectangle_dimension = base_dimension * 2 + rectangle_direction = random.randint(0, 1) + scene.room_dimensions.x = \ + base_dimension if rectangle_direction == 0 else rectangle_dimension + scene.room_dimensions.z = \ + rectangle_dimension if rectangle_direction == 0 else base_dimension + + # Containers + containers, separation_between_containers = \ + _setup_three_containers_for_imitation_task( + scene, containers_on_right_side, color_override) + + # Target with Placer + target, placer = _setup_target_and_placer_imitation_task( + scene, containers_on_right_side, containers) + + # Trigger Order + trigger_order_ids, containers_to_open_indexes = \ + _setup_trigger_order_for_imitation_task(trigger_order, containers) + scene.goal.triggered_by_target_sequence = trigger_order_ids + + # Agent + agent, open_steps = _setup_agent_for_imitation_task( + scene, containers_on_right_side, containers, + containers_to_open_indexes, agent_settings_override, + agent_type_override) + + # Now Kidnap the performer!!! ヽ(°o°)ノ + _kidnap_performer_for_imitation_task( + scene, kidnap_option, containers_on_right_side, target, placer, agent, + open_steps[-1], containers, containers_to_open_indexes, + separation_between_containers, containers_teleport_rotation, + relative_rotation) + + # Raise the ceiling so the target and placer are visible after + # we generated the scene + scene.room_dimensions.y = 3 + return scene diff --git a/generator/instances.py b/generator/instances.py index 1ffb422..47f0d08 100644 --- a/generator/instances.py +++ b/generator/instances.py @@ -5,12 +5,13 @@ from . import exceptions, tags from .definitions import ObjectDefinition from .geometry import create_bounds +from .objects import SceneObject def instantiate_object( definition: ObjectDefinition, object_location: Dict[str, Any] -) -> Dict[str, Any]: +) -> SceneObject: """Create a new object from an object definition (as from the objects.json file). object_location will be modified by this function.""" if definition is None or object_location is None: @@ -28,7 +29,7 @@ def instantiate_object( ) # TODO MCS-697 Define and use an ObjectInstance class here. - instance = { + instance = SceneObject({ 'id': str(uuid.uuid4()), 'type': definition.type, 'mass': definition.mass * definition.massMultiplier, @@ -41,7 +42,7 @@ def instantiate_object( 'shape': definition.shape, 'size': definition.size } - } + }) if not definition.dimensions: raise exceptions.SceneException( @@ -179,7 +180,7 @@ def instantiate_object( return instance -def get_earliest_active_step(instance: Dict[str, Any]) -> int: +def get_earliest_active_step(instance: SceneObject) -> int: """Return the last step on which the given instance is scripted to move, rotate, or have a force applied to it. Return -1 if the given instance is never scripted to move, rotate, or has a force applied to it.""" @@ -192,7 +193,7 @@ def get_earliest_active_step(instance: Dict[str, Any]) -> int: return min([item['stepBegin'] for item in actions]) -def get_last_move_or_rotate_step(instance: Dict[str, Any]) -> int: +def get_last_move_or_rotate_step(instance: SceneObject) -> int: """Return the last step on which the given instance is scripted to move or rotate, or -1 if the given instance never moves or rotates. Does not work with scripted forces.""" diff --git a/generator/interactive_goals.py b/generator/interactive_goals.py index f0aa42a..2d146e9 100644 --- a/generator/interactive_goals.py +++ b/generator/interactive_goals.py @@ -5,6 +5,8 @@ from enum import Enum from typing import Any, AnyStr, Dict, List, Tuple, Union +from machine_common_sense.config_manager import Goal + from . import exceptions, tags from .definitions import ObjectDefinition from .geometry import ( @@ -13,6 +15,7 @@ calc_obj_pos, position_distance ) +from .objects import SceneObject from .specific_objects import ( get_interactable_definition_dataset, get_pickupable_definition_dataset, @@ -23,7 +26,7 @@ NO_TARGET_IMAGES = True -def generate_image_file_name(target: Dict[str, Any]) -> str: +def generate_image_file_name(target: SceneObject) -> str: if 'materials' not in target or not target['materials']: return target['type'] @@ -33,12 +36,12 @@ def generate_image_file_name(target: Dict[str, Any]) -> str: 0 else '') + ('_'.join(material_name_list)) -def find_image_for_object(object_def: Dict[str, Any]) -> AnyStr: +def find_image_for_object(target: SceneObject) -> AnyStr: image_file_name = "" try: image_file_name = '../images/' + \ - generate_image_file_name(object_def) + '.txt' + generate_image_file_name(target) + '.txt' with open(image_file_name, 'r') as image_file: target_image = image_file.read() @@ -50,7 +53,7 @@ def find_image_for_object(object_def: Dict[str, Any]) -> AnyStr: ' the image: ' + image_file_name) -def find_image_name(target: Dict[str, Any]) -> str: +def find_image_name(target: SceneObject) -> str: return generate_image_file_name(target) + '.png' @@ -72,9 +75,9 @@ def get_target_count(self) -> int: @abstractmethod def update_goal_template( self, - goal_template: Dict[str, Any], - target_list: List[Dict[str, Any]] - ) -> Dict[str, Any]: + goal_template: Goal, + target_list: List[SceneObject] + ) -> Goal: """Update and return the given goal config for a scene.""" pass @@ -83,7 +86,7 @@ def validate_target_location( self, target_number: int, target_location: Dict[str, Any], - previously_made_target_list: List[Dict[str, Any]], + previously_made_target_list: List[SceneObject], performer_start: Dict[str, Dict[str, float]] ) -> bool: """Return if a target can be positioned at the given location based on @@ -102,7 +105,7 @@ def choose_definition( def choose_location( self, - definition_or_instance: Union[ObjectDefinition, Dict[str, Any]], + definition_or_instance: Union[ObjectDefinition, SceneObject], performer_start: Dict[str, Dict[str, float]], bounds_list: List[ObjectBounds], is_target=False, @@ -150,23 +153,84 @@ def get_target_count(self) -> int: # Override def update_goal_template( self, - goal_template: Dict[str, Any], - target_list: List[Dict[str, Any]] - ) -> Dict[str, Any]: - goal_template['metadata'] = { + goal_template: Goal, + target_list: List[SceneObject] + ) -> Goal: + goal_template.metadata = { 'target': { 'id': target_list[0]['id'], 'info': target_list[0]['debug']['info'] } } - goal_template['description'] = f'Find and pick up the ' \ + goal_template.description = f'Find and pick up the ' \ f'{target_list[0]["debug"]["goalString"]}.' if not NO_TARGET_IMAGES: image = find_image_for_object(target_list[0]) image_name = find_image_name(target_list[0]) - goal_template['metadata']['target']['image'] = image - goal_template['metadata']['target']['image_name'] = image_name - goal_template['metadata']['target']['match_image'] = True + goal_template.metadata['target']['image'] = image + goal_template.metadata['target']['image_name'] = image_name + goal_template.metadata['target']['match_image'] = True + return goal_template + + # Override + def validate_target_location( + self, + target_number: int, + target_location: Dict[str, Any], + previously_made_target_list: List[SceneObject], + performer_start: Dict[str, Dict[str, float]] + ) -> bool: + return True + + +class MultiRetrievalGoal(InteractiveGoal): + def __init__(self, hypercube_type: str): + super().__init__(tags.SCENE.MULTI_RETRIEVAL, hypercube_type) + + # Override + def choose_target_definition(self, target_number: int) -> ObjectDefinition: + return self.choose_definition(must_be_pickupable=True) + + # Override + def get_target_count(self, target_list) -> int: + return len(target_list) + + # Override + def update_goal_template( + self, + goal_template: Goal, + target_list: List[SceneObject] + ) -> Goal: + goal_template.metadata = {'targets': []} + + for target_item in target_list: + self._append_target(goal_template, target_item) + + goal_strings = [i['debug']['goalString'] for i in target_list] + goal_strings = sorted(set(goal_strings)) + goal_string = goal_strings[0] if len(goal_strings) == 1 else ( + '; '.join(goal_strings[:-1]) + '; and ' + goal_strings[-1] + ) + goal_template.description = (f'Find and pick up as many objects as' + f' possible of type: {goal_string}.') + + return goal_template + + def _append_target(self, goal_template: Goal, target_item: Any): + target_to_add = { + 'id': target_item['id'], + 'info': target_item['debug']['info'] + } + + if not NO_TARGET_IMAGES: + image = find_image_for_object(target_item) + image_name = find_image_name(target_item) + target_to_add['image'] = image + target_to_add['image_name'] = image_name + target_to_add['match_image'] = True + + goal_template.metadata['targets'].append(target_to_add) + return goal_template # Override @@ -174,7 +238,7 @@ def validate_target_location( self, target_number: int, target_location: Dict[str, Any], - previously_made_target_list: List[Dict[str, Any]], + previously_made_target_list: List[SceneObject], performer_start: Dict[str, Dict[str, float]] ) -> bool: return True @@ -208,11 +272,11 @@ def get_target_count(self) -> int: # Override def update_goal_template( self, - goal_template: Dict[str, Any], - target_list: List[Dict[str, Any]] - ) -> Dict[str, Any]: + goal_template: Goal, + target_list: List[SceneObject] + ) -> Goal: relationship = random.choice(list(self.RelationshipType)) - goal_template['metadata'] = { + goal_template.metadata = { 'target_1': { 'id': target_list[0]['id'], 'info': target_list[0]['debug']['info'] @@ -223,7 +287,7 @@ def update_goal_template( }, 'relationship': ['target_1', relationship.value, 'target_2'] } - goal_template['description'] = f'Find and pick up the ' \ + goal_template.description = f'Find and pick up the ' \ f'{target_list[0]["debug"]["goalString"]} and move it ' \ f'{relationship.value} the ' \ f'{target_list[1]["debug"]["goalString"]}.' @@ -232,12 +296,12 @@ def update_goal_template( image_2 = find_image_for_object(target_list[1]) image_name_1 = find_image_name(target_list[0]) image_name_2 = find_image_name(target_list[1]) - goal_template['metadata']['target_1']['image'] = image_1 - goal_template['metadata']['target_1']['image_name'] = image_name_1 - goal_template['metadata']['target_1']['match_image'] = True - goal_template['metadata']['target_2']['image'] = image_2 - goal_template['metadata']['target_2']['image_name'] = image_name_2 - goal_template['metadata']['target_2']['match_image'] = True + goal_template.metadata['target_1']['image'] = image_1 + goal_template.metadata['target_1']['image_name'] = image_name_1 + goal_template.metadata['target_1']['match_image'] = True + goal_template.metadata['target_2']['image'] = image_2 + goal_template.metadata['target_2']['image_name'] = image_name_2 + goal_template.metadata['target_2']['match_image'] = True return goal_template # Override @@ -245,7 +309,7 @@ def validate_target_location( self, target_number: int, target_location: Dict[str, Any], - previously_made_target_list: List[Dict[str, Any]], + previously_made_target_list: List[SceneObject], performer_start: Dict[str, Dict[str, float]] ) -> bool: if target_number == 0: @@ -283,23 +347,23 @@ def get_target_count(self) -> int: # Override def update_goal_template( self, - goal_template: Dict[str, Any], - target_list: List[Dict[str, Any]] - ) -> Dict[str, Any]: - goal_template['metadata'] = { + goal_template: Goal, + target_list: List[SceneObject] + ) -> Goal: + goal_template.metadata = { 'target': { 'id': target_list[0]['id'], 'info': target_list[0]['debug']['info'] } } - goal_template['description'] = f'Find the ' \ + goal_template.description = f'Find the ' \ f'{target_list[0]["debug"]["goalString"]} and move near it.' if not NO_TARGET_IMAGES: image = find_image_for_object(target_list[0]) image_name = find_image_name(target_list[0]) - goal_template['metadata']['target']['image'] = image - goal_template['metadata']['target']['image_name'] = image_name - goal_template['metadata']['target']['match_image'] = True + goal_template.metadata['target']['image'] = image + goal_template.metadata['target']['image_name'] = image_name + goal_template.metadata['target']['match_image'] = True return goal_template # Override @@ -307,7 +371,7 @@ def validate_target_location( self, target_number: int, target_location: Dict[str, Any], - previously_made_target_list: List[Dict[str, Any]], + previously_made_target_list: List[SceneObject], performer_start: Dict[str, Dict[str, float]] ) -> bool: distance = position_distance( diff --git a/generator/intuitive_physics_objects.py b/generator/intuitive_physics_objects.py index e4de9c9..80abaa5 100644 --- a/generator/intuitive_physics_objects.py +++ b/generator/intuitive_physics_objects.py @@ -55,24 +55,32 @@ def generate_size_multiplier_list( ('car_1', base_objects._TOY_SEDAN_SIZE), ('car_2', base_objects._TOY_CAR_2_SIZE), ('car_3', base_objects._TOY_CAR_3_SIZE), + ('car_4', base_objects._TOY_CAR_4_SIZE), + ('car_5', base_objects._TOY_CAR_5_SIZE), ('cart_2', base_objects._CART_2_SIZE), ('dog_on_wheels', base_objects._DOG_ON_WHEELS_SIZE), ('dog_on_wheels_2', base_objects._DOG_ON_WHEELS_2_SIZE), ('duck_on_wheels', base_objects._DUCK_ON_WHEELS_SIZE), ('duck_on_wheels_2', base_objects._DUCK_ON_WHEELS_2_SIZE), ('jeep', base_objects._TOY_JEEP_SIZE), + ('power_shovel', base_objects._TOY_POWER_SHOVEL_SIZE), ('racecar_red', base_objects._TOY_RACECAR_SIZE), + ('road_scraper', base_objects._TOY_ROAD_SCRAPER_SIZE), ('roller', base_objects._TOY_ROLLER_SIZE), ('skateboard', base_objects._SKATEBOARD_SIZE), ('tank_1', base_objects._TOY_TANK_1_SIZE), ('tank_2', base_objects._TOY_TANK_2_SIZE), + ('tank_3', base_objects._TOY_TANK_3_SIZE), ('train_1', base_objects._TOY_TRAIN_1_SIZE), ('train_2', base_objects._TOY_TRAIN_2_SIZE), + ('train_3', base_objects._TOY_TRAIN_3_SIZE), + ('trike', base_objects._TOY_TRIKE_SIZE), ('trolley_1', base_objects._TOY_TROLLEY_SIZE), ('truck_1', base_objects._TOY_TRUCK_1_SIZE), ('truck_2', base_objects._TOY_TRUCK_2_SIZE), ('truck_3', base_objects._TOY_TRUCK_3_SIZE), ('truck_4', base_objects._TOY_TRUCK_4_SIZE), + ('truck_5', base_objects._TOY_TRUCK_5_SIZE), ('turtle_on_wheels', base_objects._TURTLE_ON_WHEELS_SIZE), ] _COMPLEX_TYPES_TO_SIZES = dict([ @@ -448,7 +456,6 @@ def generate_size_multiplier_list( size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_ROLLABLE_1.untrainedShape = True _ROLLABLE_1_NOVEL_SIZE = create_variable_definition_from_base( type='rollable_1', size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), @@ -467,7 +474,6 @@ def generate_size_multiplier_list( size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_ROLLABLE_2.untrainedShape = True _ROLLABLE_2_NOVEL_SIZE = create_variable_definition_from_base( type='rollable_2', size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), @@ -486,7 +492,6 @@ def generate_size_multiplier_list( size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_ROLLABLE_3.untrainedShape = True _ROLLABLE_3_NOVEL_SIZE = create_variable_definition_from_base( type='rollable_3', size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), @@ -505,7 +510,6 @@ def generate_size_multiplier_list( size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_ROLLABLE_4.untrainedShape = True _ROLLABLE_4_NOVEL_SIZE = create_variable_definition_from_base( type='rollable_4', size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), @@ -524,7 +528,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['bobcat'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_BOBCAT.untrainedShape = True _BOBCAT_NOVEL_SIZE = create_variable_definition_from_base( type='bobcat', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['bobcat'], @@ -538,7 +541,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['car_3'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_CAR_3.untrainedShape = True _CAR_3_NOVEL_SIZE = create_variable_definition_from_base( type='car_3', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['car_3'], @@ -552,7 +554,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['dog_on_wheels_2'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_DOG_2.untrainedShape = True _DOG_2_NOVEL_SIZE = create_variable_definition_from_base( type='dog_on_wheels_2', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['dog_on_wheels_2'], @@ -566,7 +567,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['duck_on_wheels_2'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_DUCK_2.untrainedShape = True _DUCK_2_NOVEL_SIZE = create_variable_definition_from_base( type='duck_on_wheels_2', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['duck_on_wheels_2'], @@ -580,7 +580,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['jeep'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_JEEP.untrainedShape = True _JEEP_NOVEL_SIZE = create_variable_definition_from_base( type='jeep', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['jeep'], @@ -594,7 +593,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['roller'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_ROLLER.untrainedShape = True _ROLLER_NOVEL_SIZE = create_variable_definition_from_base( type='roller', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['roller'], @@ -608,7 +606,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['skateboard'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_SKATEBOARD.untrainedShape = True _SKATEBOARD_NOVEL_SIZE = create_variable_definition_from_base( type='skateboard', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['skateboard'], @@ -622,7 +619,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['tank_1'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_TANK_1.untrainedShape = True _TANK_1_NOVEL_SIZE = create_variable_definition_from_base( type='tank_1', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['tank_1'], @@ -636,7 +632,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['tank_2'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_TANK_2.untrainedShape = True _TANK_2_NOVEL_SIZE = create_variable_definition_from_base( type='tank_2', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['tank_2'], @@ -650,7 +645,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['train_2'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_TRAIN_2.untrainedShape = True _TRAIN_2_NOVEL_SIZE = create_variable_definition_from_base( type='train_2', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['train_2'], @@ -664,7 +658,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['truck_3'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_TRUCK_3.untrainedShape = True _TRUCK_3_NOVEL_SIZE = create_variable_definition_from_base( type='truck_3', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['truck_3'], @@ -678,7 +671,6 @@ def generate_size_multiplier_list( size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['truck_4'], chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST ) -_TRUCK_4.untrainedShape = True _TRUCK_4_NOVEL_SIZE = create_variable_definition_from_base( type='truck_4', size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['truck_4'], @@ -687,6 +679,211 @@ def generate_size_multiplier_list( _TRUCK_4_NOVEL_SIZE.untrainedSize = True +# EVAL 6 NOVEL OBJECTS + + +_ROLLABLE_5 = create_variable_definition_from_base( + type='rollable_5', + size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_5.untrainedShape = True +_ROLLABLE_5_NOVEL_SIZE = create_variable_definition_from_base( + type='rollable_5', + size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_5_NOVEL_SIZE.untrainedSize = True +# Rotate the object onto its curved side so it can roll sideways. +_ROLLABLE_5_SIDEWAYS = copy.deepcopy(_ROLLABLE_5) +_ROLLABLE_5_SIDEWAYS.rotation = Vector3d(x=90, y=0, z=0) +_ROLLABLE_5_SIDEWAYS_NOVEL_SIZE = copy.deepcopy(_ROLLABLE_5_NOVEL_SIZE) +_ROLLABLE_5_SIDEWAYS_NOVEL_SIZE.rotation = Vector3d(x=90, y=0, z=0) + + +_ROLLABLE_6 = create_variable_definition_from_base( + type='rollable_6', + size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_6.untrainedShape = True +_ROLLABLE_6_NOVEL_SIZE = create_variable_definition_from_base( + type='rollable_6', + size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_6_NOVEL_SIZE.untrainedSize = True +# Rotate the object onto its curved side so it can roll sideways. +_ROLLABLE_6_SIDEWAYS = copy.deepcopy(_ROLLABLE_6) +_ROLLABLE_6_SIDEWAYS.rotation = Vector3d(x=90, y=0, z=0) +_ROLLABLE_6_SIDEWAYS_NOVEL_SIZE = copy.deepcopy(_ROLLABLE_6_NOVEL_SIZE) +_ROLLABLE_6_SIDEWAYS_NOVEL_SIZE.rotation = Vector3d(x=90, y=0, z=0) + + +_ROLLABLE_7 = create_variable_definition_from_base( + type='rollable_7', + size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_7.untrainedShape = True +_ROLLABLE_7_NOVEL_SIZE = create_variable_definition_from_base( + type='rollable_7', + size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_7_NOVEL_SIZE.untrainedSize = True +# Rotate the object onto its curved side so it can roll sideways. +_ROLLABLE_7_SIDEWAYS = copy.deepcopy(_ROLLABLE_7) +_ROLLABLE_7_SIDEWAYS.rotation = Vector3d(x=90, y=0, z=0) +_ROLLABLE_7_SIDEWAYS_NOVEL_SIZE = copy.deepcopy(_ROLLABLE_7_NOVEL_SIZE) +_ROLLABLE_7_SIDEWAYS_NOVEL_SIZE.rotation = Vector3d(x=90, y=0, z=0) + + +_ROLLABLE_8 = create_variable_definition_from_base( + type='rollable_8', + size_multiplier_list=TRAINED_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_8.untrainedShape = True +_ROLLABLE_8_NOVEL_SIZE = create_variable_definition_from_base( + type='rollable_8', + size_multiplier_list=NOVEL_SIZE_MULTIPLIER_LIST.copy(), + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROLLABLE_8_NOVEL_SIZE.untrainedSize = True +# Rotate the object onto its curved side so it can roll sideways. +_ROLLABLE_8_SIDEWAYS = copy.deepcopy(_ROLLABLE_8) +_ROLLABLE_8_SIDEWAYS.rotation = Vector3d(x=90, y=0, z=0) +_ROLLABLE_8_SIDEWAYS_NOVEL_SIZE = copy.deepcopy(_ROLLABLE_8_NOVEL_SIZE) +_ROLLABLE_8_SIDEWAYS_NOVEL_SIZE.rotation = Vector3d(x=90, y=0, z=0) + + +_CAR_4 = create_variable_definition_from_base( + type='car_4', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['car_4'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_CAR_4.untrainedShape = True +_CAR_4_NOVEL_SIZE = create_variable_definition_from_base( + type='car_4', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['car_4'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_CAR_4_NOVEL_SIZE.untrainedSize = True + + +_CAR_5 = create_variable_definition_from_base( + type='car_5', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['car_5'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_CAR_5.untrainedShape = True +_CAR_5_NOVEL_SIZE = create_variable_definition_from_base( + type='car_5', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['car_5'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_CAR_5_NOVEL_SIZE.untrainedSize = True + + +_POWER_SHOVEL = create_variable_definition_from_base( + type='power_shovel', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['power_shovel'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_POWER_SHOVEL.untrainedShape = True +_POWER_SHOVEL_NOVEL_SIZE = create_variable_definition_from_base( + type='power_shovel', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['power_shovel'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_POWER_SHOVEL_NOVEL_SIZE.untrainedSize = True + + +_ROAD_SCRAPER = create_variable_definition_from_base( + type='road_scraper', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['road_scraper'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROAD_SCRAPER.untrainedShape = True +_ROAD_SCRAPER_NOVEL_SIZE = create_variable_definition_from_base( + type='road_scraper', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['road_scraper'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_ROAD_SCRAPER_NOVEL_SIZE.untrainedSize = True + + +_TANK_3 = create_variable_definition_from_base( + type='tank_3', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['tank_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TANK_3.untrainedShape = True +_TANK_3_NOVEL_SIZE = create_variable_definition_from_base( + type='tank_3', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['tank_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TANK_3_NOVEL_SIZE.untrainedSize = True + + +_TANK_3 = create_variable_definition_from_base( + type='tank_3', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['tank_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TANK_3.untrainedShape = True +_TANK_3_NOVEL_SIZE = create_variable_definition_from_base( + type='tank_3', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['tank_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TANK_3_NOVEL_SIZE.untrainedSize = True + + +_TRAIN_3 = create_variable_definition_from_base( + type='train_3', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['train_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRAIN_3.untrainedShape = True +_TRAIN_3_NOVEL_SIZE = create_variable_definition_from_base( + type='train_3', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['train_3'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRAIN_3_NOVEL_SIZE.untrainedSize = True + + +_TRIKE = create_variable_definition_from_base( + type='trike', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['trike'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRIKE.untrainedShape = True +_TRIKE_NOVEL_SIZE = create_variable_definition_from_base( + type='trike', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['trike'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRIKE_NOVEL_SIZE.untrainedSize = True + + +_TRUCK_5 = create_variable_definition_from_base( + type='truck_5', + size_multiplier_list=_COMPLEX_TYPES_TO_SIZES['truck_5'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRUCK_5.untrainedShape = True +_TRUCK_5_NOVEL_SIZE = create_variable_definition_from_base( + type='truck_5', + size_multiplier_list=_NOVEL_COMPLEX_TYPES_TO_SIZES['truck_5'], + chosen_material_list=INTUITIVE_PHYSICS_OBJECT_CHOSEN_MATERIAL_LIST +) +_TRUCK_5_NOVEL_SIZE.untrainedSize = True + + # Only use rollable objects in move-across setups. _MOVE_ACROSS_BASIC = [ _CYLINDER_SIDEWAYS, @@ -703,6 +900,14 @@ def generate_size_multiplier_list( _ROLLABLE_3_SIDEWAYS_NOVEL_SIZE, _ROLLABLE_4_SIDEWAYS, _ROLLABLE_4_SIDEWAYS_NOVEL_SIZE, + _ROLLABLE_5_SIDEWAYS, + _ROLLABLE_5_SIDEWAYS_NOVEL_SIZE, + _ROLLABLE_6_SIDEWAYS, + _ROLLABLE_6_SIDEWAYS_NOVEL_SIZE, + _ROLLABLE_7_SIDEWAYS, + _ROLLABLE_7_SIDEWAYS_NOVEL_SIZE, + _ROLLABLE_8_SIDEWAYS, + _ROLLABLE_8_SIDEWAYS_NOVEL_SIZE, _SPHERE, _SPHERE_NOVEL_SIZE, _TIE_FIGHTER_SIDEWAYS, @@ -735,6 +940,14 @@ def generate_size_multiplier_list( _ROLLABLE_3_NOVEL_SIZE, _ROLLABLE_4, _ROLLABLE_4_NOVEL_SIZE, + _ROLLABLE_5, + _ROLLABLE_5_NOVEL_SIZE, + _ROLLABLE_6, + _ROLLABLE_6_NOVEL_SIZE, + _ROLLABLE_7, + _ROLLABLE_7_NOVEL_SIZE, + _ROLLABLE_8, + _ROLLABLE_8_NOVEL_SIZE, _TIE_FIGHTER, _TIE_FIGHTER_NOVEL_SIZE, # Objects specific to _FALL_DOWN_BASIC @@ -762,6 +975,10 @@ def generate_size_multiplier_list( _CAR_2_NOVEL_SIZE, _CAR_3, _CAR_3_NOVEL_SIZE, + _CAR_4, + _CAR_4_NOVEL_SIZE, + _CAR_5, + _CAR_5_NOVEL_SIZE, _CART_2, _CART_2_NOVEL_SIZE, _DOG, @@ -774,8 +991,12 @@ def generate_size_multiplier_list( _DUCK_2_NOVEL_SIZE, _JEEP, _JEEP_NOVEL_SIZE, + _POWER_SHOVEL, + _POWER_SHOVEL_NOVEL_SIZE, _RACECAR, _RACECAR_NOVEL_SIZE, + _ROAD_SCRAPER, + _ROAD_SCRAPER_NOVEL_SIZE, _ROLLER, _ROLLER_NOVEL_SIZE, _SEDAN, @@ -786,10 +1007,16 @@ def generate_size_multiplier_list( _TANK_1_NOVEL_SIZE, _TANK_2, _TANK_2_NOVEL_SIZE, + _TANK_3, + _TANK_3_NOVEL_SIZE, _TRAIN_1, _TRAIN_1_NOVEL_SIZE, _TRAIN_2, _TRAIN_2_NOVEL_SIZE, + _TRAIN_3, + _TRAIN_3_NOVEL_SIZE, + _TRIKE, + _TRIKE_NOVEL_SIZE, _TROLLEY, _TROLLEY_NOVEL_SIZE, _TRUCK_2, @@ -798,6 +1025,8 @@ def generate_size_multiplier_list( _TRUCK_3_NOVEL_SIZE, _TRUCK_4, _TRUCK_4_NOVEL_SIZE, + _TRUCK_5, + _TRUCK_5_NOVEL_SIZE, _TURTLE, _TURTLE_NOVEL_SIZE, ] @@ -843,7 +1072,7 @@ def _create_opposite_colors_definition_list( for definition in definition_list: definition.assign_chosen_material(definition.chooseMaterialList[0]) materials_count = len(definition.materialCategory) - definition.materialCategory = (['opposite'] * materials_count) + definition.materialCategory = (['object_opposite'] * materials_count) return definition_list diff --git a/generator/lava.py b/generator/lava.py new file mode 100644 index 0000000..4def008 --- /dev/null +++ b/generator/lava.py @@ -0,0 +1,80 @@ +import random +from dataclasses import dataclass + +DEFAULT_LAVA_SEPARATION_FROM_WALL = 3 +# Max lava width should be 6 for rect tools, 3 for hooked. +# (determined based on MAX_LAVA_WITH_ISLAND_WIDTH values) +MIN_LAVA_WIDTH_HOOKED_TOOL = 1 +MAX_LAVA_WIDTH_HOOKED_TOOL = 3 +MIN_LAVA_WIDTH = 2 +MAX_LAVA_WIDTH = 6 +MIN_LAVA_ISLAND_SIZE = 1 +MAX_LAVA_ISLAND_SIZE = 5 +MAX_LAVA_AREA_TOTAL_WIDTH = 9 + +# Min lava with island width should be 5 +MAX_LAVA_WITH_ISLAND_WIDTH = 9 +MIN_LAVA_WITH_ISLAND_WIDTH_HOOKED_TOOL = 3 +MAX_LAVA_WITH_ISLAND_WIDTH_HOOKED_TOOL = 7 + +MIN_ISLAND_SIZE = 1 +MAX_ISLAND_SIZE = 5 + + +@dataclass +class LavaIslandSizes(): + """ + island_size is the safe area of in the center of the lava pool. + For example: island size of 3 is a safe area of 3 x 3. + The directional values are the widths of the lava pools + which can be varying widths creating asymmetric pools + """ + island_size: int = 0 + front: int = 0 # positive z axis + rear: int = 0 # negative z axis + left: int = 0 # negative x axis + right: int = 0 # positive x axis + + +def random_lava_island(dim_x, dim_z): + back_front_width = 0 + left_right_width = 0 + island_size = MIN_ISLAND_SIZE + back = MIN_LAVA_WIDTH + front = MIN_LAVA_WIDTH + left = MIN_LAVA_WIDTH + right = MIN_LAVA_WIDTH + lava_widths = [back, front, left, right] + + """ + Skewed lava island widths for smaller rooms. + Otherwise lava shifting will not work + """ + if dim_x < 20 or dim_z < 20: + return LavaIslandSizes(island_size, front, back, left, right) + if dim_x < 30 or dim_z < 30: + island_size = random.randint(MIN_ISLAND_SIZE, 3) + for i in range(len(lava_widths)): + lava_widths[i] = random.randint(MIN_LAVA_WIDTH, 3) + return LavaIslandSizes(island_size, front, back, left, right) + + island_size = random.randint(MIN_ISLAND_SIZE, MAX_ISLAND_SIZE) + back_front_width += island_size + left_right_width += island_size + back = random.randint( + back, + MAX_LAVA_AREA_TOTAL_WIDTH - + back_front_width - + front) + back_front_width += back + front = random.randint(front, MAX_LAVA_AREA_TOTAL_WIDTH - back_front_width) + + left = random.randint( + left, + MAX_LAVA_AREA_TOTAL_WIDTH - + left_right_width - + right) + left_right_width += left + right = random.randint(right, MAX_LAVA_AREA_TOTAL_WIDTH - left_right_width) + + return LavaIslandSizes(island_size, front, back, left, right) diff --git a/generator/materials.py b/generator/materials.py index 3641c2d..888c5c4 100644 --- a/generator/materials.py +++ b/generator/materials.py @@ -13,54 +13,97 @@ class MaterialTuple(NamedTuple): AZURE = MaterialTuple("Custom/Materials/Azure", ["azure", "blue"]) BLACK = MaterialTuple("Custom/Materials/Black", ["black"]) BLUE = MaterialTuple("Custom/Materials/Blue", ["blue"]) -BROWN = MaterialTuple("Custom/Materials/Brown", ["brown"]) +BROWN = MaterialTuple("Custom/Materials/Brown", ["brown", "orange"]) CHARTREUSE = MaterialTuple( - "Custom/Materials/Chartreuse", ["chartreuse", "green"]) + "Custom/Materials/Chartreuse", + ["chartreuse", "green", "yellow"] +) +CREAM = MaterialTuple("Custom/Materials/Cream", ["cream", "white"]) CYAN = MaterialTuple("Custom/Materials/Cyan", ["cyan", "blue", "green"]) +DARKGREY = MaterialTuple( + "Custom/Materials/DarkGrey", + ["darkslategrey", "grey"] +) GOLDENROD = MaterialTuple( - "Custom/Materials/Goldenrod", ["goldenrod", "yellow"]) + "Custom/Materials/Goldenrod", + ["goldenrod", "yellow", "orange"] +) GREEN = MaterialTuple("Custom/Materials/Green", ["green"]) GREY = MaterialTuple("Custom/Materials/Grey", ["grey"]) -INDIGO = MaterialTuple("Custom/Materials/Indigo", ["indigo", "blue"]) +INDIGO = MaterialTuple( + "Custom/Materials/Indigo", + ["indigo", "blue", "purple"] +) LIME = MaterialTuple("Custom/Materials/Lime", ["lime", "green"]) -MAGENTA = MaterialTuple("Custom/Materials/Magenta", ["magenta", "purple"]) +MAGENTA = MaterialTuple( + "Custom/Materials/Magenta", + ["magenta", "purple", "red"] +) MAROON = MaterialTuple("Custom/Materials/Maroon", ["maroon", "red"]) NAVY = MaterialTuple("Custom/Materials/Navy", ["navy", "blue"]) -OLIVE = MaterialTuple("Custom/Materials/Olive", ["olive", "green"]) +OLIVE = MaterialTuple("Custom/Materials/Olive", ["olive", "yellow", "green"]) ORANGE = MaterialTuple("Custom/Materials/Orange", ["orange"]) PINK = MaterialTuple("Custom/Materials/Pink", ["pink", "red"]) RED = MaterialTuple("Custom/Materials/Red", ["red"]) -ROSE = MaterialTuple("Custom/Materials/Rose", ["rose", "red"]) +ROSE = MaterialTuple("Custom/Materials/Rose", ["rose", "red", "purple"]) PURPLE = MaterialTuple("Custom/Materials/Purple", ["purple"]) SPRINGGREEN = MaterialTuple( - "Custom/Materials/SpringGreen", ["springgreen", "green"]) -TAN = MaterialTuple("Custom/Materials/Tan", ["brown"]) + "Custom/Materials/SpringGreen", + ["springgreen", "green"] +) +TAN = MaterialTuple("Custom/Materials/Tan", ["tan", "brown"]) TEAL = MaterialTuple("Custom/Materials/Teal", ["teal", "blue", "green"]) -VIOLET = MaterialTuple("Custom/Materials/Violet", ["violet", "purple"]) +VIOLET = MaterialTuple( + "Custom/Materials/Violet", + ["violet", "purple", "blue"] +) WHITE = MaterialTuple("Custom/Materials/White", ["white"]) YELLOW = MaterialTuple("Custom/Materials/Yellow", ["yellow"]) # Only colors that are exact opposites of one another (uses RGB color wheel). # Only use bright colors with max saturation/value in this specific list. -OPPOSITE_MATERIALS = [ +OBJECT_OPPOSITE_MATERIALS = [ + AZURE, + BLUE, + CHARTREUSE, + CYAN, + MAGENTA, + ORANGE, + ROSE, + SPRINGGREEN, + VIOLET, + YELLOW +] + +# Do not use any placer colors (cyan/magenta) in this specific list. +WALL_OPPOSITE_MATERIALS = [ AZURE, BLUE, CHARTREUSE, + GREEN, + MAROON, + NAVY, + OLIVE, ORANGE, + PURPLE, ROSE, SPRINGGREEN, + TEAL, VIOLET, YELLOW ] +# Colors that are exact opposites of one another using the RGB color wheel. OPPOSITE_SETS = { "Custom/Materials/Azure": ORANGE, "Custom/Materials/Black": WHITE, "Custom/Materials/Blue": YELLOW, "Custom/Materials/Chartreuse": VIOLET, + "Custom/Materials/Cyan": MAGENTA, "Custom/Materials/Goldenrod": INDIGO, # Not an official opposite "Custom/Materials/Green": PURPLE, "Custom/Materials/Indigo": GOLDENROD, # Not an official opposite + "Custom/Materials/Magenta": CYAN, "Custom/Materials/Maroon": TEAL, "Custom/Materials/Navy": OLIVE, "Custom/Materials/Olive": NAVY, @@ -156,7 +199,9 @@ class MaterialTuple(NamedTuple): BLUE, BROWN, CHARTREUSE, + CREAM, CYAN, + DARKGREY, GREEN, GREY, LIME, @@ -175,64 +220,100 @@ class MaterialTuple(NamedTuple): YELLOW ] -_CUSTOM_CARPET_MATERIALS = [ +CUSTOM_CARPET_MATERIALS = [ MaterialTuple(item.material + 'CarpetMCS', item.color) for item in _CUSTOM_MATERIALS ] -_CUSTOM_DRYWALL_MATERIALS = [ +CUSTOM_DRYWALL_MATERIALS = [ MaterialTuple(item.material + 'DrywallMCS', item.color) - for item in _CUSTOM_MATERIALS if item not in [CYAN, MAGENTA] + for item in _CUSTOM_MATERIALS ] -_CUSTOM_WOOD_MATERIALS = [ +CUSTOM_WOOD_MATERIALS = [ MaterialTuple(item.material + 'WoodMCS', item.color) - for item in _CUSTOM_MATERIALS if item not in [CYAN, MAGENTA] + for item in _CUSTOM_MATERIALS ] BLOCK_BLANK_MATERIALS = [ - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/blue_1x1", - ["blue"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/gray_1x1", - ["grey"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/green_1x1", - ["green"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/red_1x1", - ["red"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/wood_1x1", - ["brown"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/yellow_1x1", - ["yellow"]) + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/blue_1x1", + ["blue"] + ), + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/gray_1x1", + ["grey"] + ), + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/green_1x1", + ["green"] + ), + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/red_1x1", + ["red"] + ), + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/wood_1x1", + ["brown"] + ), + MaterialTuple( + "UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/yellow_1x1", + ["yellow"] + ) ] BLOCK_LETTER_MATERIALS = [ - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_A_Blue_1K/ToyBlockBlueA", - ["blue", "brown"]), - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_B_Blue_1K/ToyBlockBlueB", - ["blue", "brown"]), - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_C_Blue_1K/ToyBlockBlueC", - ["blue", "brown"]), - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_D_Blue_1K/ToyBlockBlueD", - ["blue", "brown"]), - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_M_Blue_1K/ToyBlockBlueM", - ["blue", "brown"]), - MaterialTuple("UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_S_Blue_1K/ToyBlockBlueS", - ["blue", "brown"]) + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_A_Blue_1K/ToyBlockBlueA", + ["blue", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_B_Blue_1K/ToyBlockBlueB", + ["blue", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_C_Blue_1K/ToyBlockBlueC", + ["blue", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_D_Blue_1K/ToyBlockBlueD", + ["blue", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_M_Blue_1K/ToyBlockBlueM", + ["blue", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_AlphabetBlocks/Assets/Textures/Blue/TOYBlocks_AlphabetBlock_S_Blue_1K/ToyBlockBlueS", + ["blue", "brown"] + ) ] BLOCK_NUMBER_MATERIALS = [ - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_1_Yellow_1K/NumberBlockYellow_1", - ["yellow", "brown"]), - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_2_Yellow_1K/NumberBlockYellow_2", - ["yellow", "brown"]), - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_3_Yellow_1K/NumberBlockYellow_3", - ["yellow", "brown"]), - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_4_Yellow_1K/NumberBlockYellow_4", - ["yellow", "brown"]), - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_5_Yellow_1K/NumberBlockYellow_5", - ["yellow", "brown"]), - MaterialTuple("UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_6_Yellow_1K/NumberBlockYellow_6", - ["yellow", "brown"]) + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_1_Yellow_1K/NumberBlockYellow_1", + ["yellow", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_2_Yellow_1K/NumberBlockYellow_2", + ["yellow", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_3_Yellow_1K/NumberBlockYellow_3", + ["yellow", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_4_Yellow_1K/NumberBlockYellow_4", + ["yellow", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_5_Yellow_1K/NumberBlockYellow_5", + ["yellow", "brown"] + ), + MaterialTuple( + "UnityAssetStore/KD_NumberBlocks/Assets/Textures/Yellow/TOYBlocks_NumberBlock_6_Yellow_1K/NumberBlockYellow_6", + ["yellow", "brown"] + ) ] CARDBOARD_MATERIALS = [ @@ -241,30 +322,95 @@ class MaterialTuple(NamedTuple): MaterialTuple("AI2-THOR/Materials/Misc/Cardboard_White", ["grey"]) ] +BROWN_MARBLE_FAKE_1 = MaterialTuple( + "AI2-THOR/Materials/Ceramics/BrownMarbleFake 1", + ["brown", "orange"] +) +CONCRETE_BOARDS_1 = MaterialTuple( + "AI2-THOR/Materials/Ceramics/ConcreteBoards1", + ["grey", "white"] +) +CONCRETE_FLOOR = MaterialTuple( + "AI2-THOR/Materials/Ceramics/ConcreteFloor", + ["grey", "black", "blue"] +) +GREY_GRANITE = MaterialTuple( + "AI2-THOR/Materials/Ceramics/GREYGRANITE", + ["grey"] +) +PINK_CONCRETE_BEDROOM_1 = MaterialTuple( + "AI2-THOR/Materials/Ceramics/PinkConcrete_Bedroom1", + ["red", "grey"] +) +WHITE_COUNTERTOP = MaterialTuple( + "AI2-THOR/Materials/Ceramics/WhiteCountertop", + ["grey", "white"] +) + CERAMIC_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Ceramics/BrownMarbleFake 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/ConcreteBoards1", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/ConcreteFloor", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/GREYGRANITE", ["grey"]), - MaterialTuple( - "AI2-THOR/Materials/Ceramics/PinkConcrete_Bedroom1", ["red"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/WhiteCountertop", ["grey"]) + BROWN_MARBLE_FAKE_1, + CONCRETE_BOARDS_1, + CONCRETE_FLOOR, + GREY_GRANITE, + PINK_CONCRETE_BEDROOM_1, + WHITE_COUNTERTOP # Don't use the brick or cobblestone textures. ] +CARPET_2 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/Carpet2", + ["brown", "grey"] +) +CARPET_3 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/Carpet3", + ["brown"] +) +CARPET_4 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/Carpet4", + ["blue", "black"] +) +CARPET_8 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/Carpet8", + ["black"] +) +CARPET_DARK = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetDark", + ["yellow", "brown"] +) +CARPET_DARK_1 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetDark 1", + ["brown"] +) +CARPET_DARK_GREEN = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetDarkGreen", + ["green"] +) +CARPET_GREEN = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetGreen", + ["green"] +) +CARPET_WHITE = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetWhite", + ["white"] +) +CARPET_WHITE_3 = MaterialTuple( + "AI2-THOR/Materials/Fabrics/CarpetWhite 3", + ["white", "grey"] +) + FABRIC_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet2", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet3", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet4", ["blue"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet8", ["black"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDark", ["yellow"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDark 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDarkGreen", ["green"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetGreen", ["green"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite", ["white"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite 3", ["white"]) + CARPET_2, + CARPET_3, + CARPET_4, + CARPET_8, + CARPET_DARK, + CARPET_DARK_1, + CARPET_DARK_GREEN, + CARPET_GREEN, + CARPET_WHITE, + CARPET_WHITE_3 # Don't use highly patterned multicolor carpet textures. -] + _CUSTOM_CARPET_MATERIALS +] + CUSTOM_CARPET_MATERIALS LEATHER_MATERIALS = [ MaterialTuple("AI2-THOR/Materials/Fabrics/Leather", ["brown"]), @@ -277,101 +423,270 @@ class MaterialTuple(NamedTuple): LEATHER_ARMCHAIR_MATERIALS = [ MaterialTuple( "UnityAssetStore/Leather_Chair/Assets/Materials/Leather_Chair_NEW_1", - ["brown"]), + ["brown"] + ), MaterialTuple( "UnityAssetStore/Leather_Chair/Assets/Materials/Leather_Chair_NEW_2", - ["grey"]), + ["grey"] + ), MaterialTuple( "UnityAssetStore/Leather_Chair/Assets/Materials/Leather_Chair_NEW_3", - ["brown"]), + ["brown"] + ), MaterialTuple( "UnityAssetStore/Leather_Chair/Assets/Materials/Leather_Chair_NEW_4", - ["grey"]), + ["grey"] + ), MaterialTuple( "UnityAssetStore/Leather_Chair/Assets/Materials/Leather_Chair_normal_OLD", - ["brown"]) + ["brown"] + ) ] +BLACK_SMOOTH_METAL = MaterialTuple( + "AI2-THOR/Materials/Metals/BlackSmoothMeta", + ["black"] +) +BRASS_1 = MaterialTuple( + "AI2-THOR/Materials/Metals/Brass 1", + ["yellow", "brown"] +) +BROWN_METAL_1 = MaterialTuple( + "AI2-THOR/Materials/Metals/BrownMetal 1", + ["brown"] +) +BRUSHED_ALUMINUM_BLUE = MaterialTuple( + "AI2-THOR/Materials/Metals/BrushedAluminum_Blue", + ["blue", "grey"] +) +BRUSHED_IRON_ALBEDO = MaterialTuple( + "AI2-THOR/Materials/Metals/BrushedIron_AlbedoTransparency", + ["black", "grey"] +) +GENERIC_STAINLESS_STEEL = MaterialTuple( + "AI2-THOR/Materials/Metals/GenericStainlessSteel", + ["grey"] +) +HAMMERED_METAL_ALBEDO = MaterialTuple( + "AI2-THOR/Materials/Metals/HammeredMetal_AlbedoTransparency 1", + ["brown"] +) +METAL = MaterialTuple("AI2-THOR/Materials/Metals/Metal", ["grey", "white"]) +WHITE_METAL = MaterialTuple("AI2-THOR/Materials/Metals/WhiteMetal", ["white"]) +NURSERY_CABINET_METAL = MaterialTuple( + "UnityAssetStore/Baby_Room/Models/Materials/cabinet metal", + ["grey"] +) + METAL_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Metals/BlackSmoothMeta", ["black"]), - MaterialTuple("AI2-THOR/Materials/Metals/Brass 1", ["yellow"]), - MaterialTuple("AI2-THOR/Materials/Metals/BrownMetal 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Metals/BrushedAluminum_Blue", ["blue"]), - MaterialTuple( - "AI2-THOR/Materials/Metals/BrushedIron_AlbedoTransparency", ["black"]), - MaterialTuple("AI2-THOR/Materials/Metals/GenericStainlessSteel", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Metals/HammeredMetal_AlbedoTransparency 1", - ["green"]), - MaterialTuple("AI2-THOR/Materials/Metals/Metal", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Metals/WhiteMetal", ["white"]), - MaterialTuple( - "UnityAssetStore/Baby_Room/Models/Materials/cabinet metal", ["grey"]) + BLACK_SMOOTH_METAL, + BRASS_1, + BROWN_METAL_1, + BRUSHED_ALUMINUM_BLUE, + BRUSHED_IRON_ALBEDO, + GENERIC_STAINLESS_STEEL, + HAMMERED_METAL_ALBEDO, + METAL, + WHITE_METAL, + NURSERY_CABINET_METAL ] +BLACK_PLASTIC = MaterialTuple( + "AI2-THOR/Materials/Plastics/BlackPlastic", + ["black"] +) +ORANGE_PLASTIC = MaterialTuple( + "AI2-THOR/Materials/Plastics/OrangePlastic", + ["orange"] +) +WHITE_PLASTIC = MaterialTuple( + "AI2-THOR/Materials/Plastics/WhitePlastic", + ["white"] +) +KINDERGARTEN_RED_PLASTIC = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color 1", + ["red"] +) +KINDERGARTEN_BLUE_PLASTIC = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color 2", + ["blue"] +) +KINDERGARTEN_GREEN_PLASTIC = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color 3", + ["green"] +) +KINDERGARTEN_YELLOW_PLASTIC = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color 4", + ["yellow"] +) + PLASTIC_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Plastics/BlackPlastic", ["black"]), - MaterialTuple("AI2-THOR/Materials/Plastics/OrangePlastic", ["orange"]), - MaterialTuple("AI2-THOR/Materials/Plastics/WhitePlastic", ["white"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 1", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 2", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 4", - ["yellow"]) + BLACK_PLASTIC, + ORANGE_PLASTIC, + WHITE_PLASTIC, + KINDERGARTEN_RED_PLASTIC, + KINDERGARTEN_BLUE_PLASTIC, + KINDERGARTEN_GREEN_PLASTIC, + KINDERGARTEN_YELLOW_PLASTIC ] +BLUE_RUBBER = MaterialTuple( + "AI2-THOR/Materials/Plastics/BlueRubber", + ["blue"] +) +LIGHT_BLUE_RUBBER = MaterialTuple( + "AI2-THOR/Materials/Plastics/LightBlueRubber", + ["blue"] +) + RUBBER_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Plastics/BlueRubber", ["blue"]), - MaterialTuple("AI2-THOR/Materials/Plastics/LightBlueRubber", ["blue"]) + BLUE_RUBBER, + LIGHT_BLUE_RUBBER ] +BROWN_DRYWALL = MaterialTuple( + "AI2-THOR/Materials/Walls/BrownDrywall", + ["brown"] +) +DRYWALL = MaterialTuple( + "AI2-THOR/Materials/Walls/Drywall", + ["white", "grey"] +) +DRYWALL_BEIGE = MaterialTuple( + "AI2-THOR/Materials/Walls/DrywallBeige", + ["white", "brown"] +) +DRYWALL_GREEN = MaterialTuple( + "AI2-THOR/Materials/Walls/DrywallGreen", + ["green"] +) +DRYWALL_ORANGE = MaterialTuple( + "AI2-THOR/Materials/Walls/DrywallOrange", + ["orange"] +) +DRYWALL_4_TILED = MaterialTuple( + "AI2-THOR/Materials/Walls/Drywall4Tiled", + ["white"] +) +EGGSHELL_DRYWALL = MaterialTuple( + "AI2-THOR/Materials/Walls/EggshellDrywall", + ["blue"] +) +RED_DRYWALL = MaterialTuple("AI2-THOR/Materials/Walls/RedDrywall", ["red"]) +WALL_DRYWALL_GREY = MaterialTuple( + "AI2-THOR/Materials/Walls/WallDrywallGrey", + ["grey", "white"] +) +YELLOW_DRYWALL = MaterialTuple( + "AI2-THOR/Materials/Walls/YellowDrywall", + ["yellow"] +) + CEILING_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Walls/Drywall4Tiled", ["white"]), - MaterialTuple("AI2-THOR/Materials/Walls/DrywallBeige", ["white"]), - MaterialTuple("AI2-THOR/Materials/Walls/Drywall", ["white"])] + DRYWALL_4_TILED, + DRYWALL_BEIGE, + DRYWALL, + WALL_DRYWALL_GREY +] WALL_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Walls/BrownDrywall", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Walls/Drywall", ["white"]), - MaterialTuple("AI2-THOR/Materials/Walls/DrywallBeige", ["white"]), - MaterialTuple("AI2-THOR/Materials/Walls/DrywallGreen", ["green"]), - MaterialTuple("AI2-THOR/Materials/Walls/DrywallOrange", ["orange"]), - MaterialTuple("AI2-THOR/Materials/Walls/Drywall4Tiled", ["white"]), - MaterialTuple("AI2-THOR/Materials/Walls/EggshellDrywall", ["blue"]), - MaterialTuple("AI2-THOR/Materials/Walls/RedDrywall", ["red"]), - MaterialTuple("AI2-THOR/Materials/Walls/WallDrywallGrey", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Walls/YellowDrywall", ["yellow"]) -] + _CUSTOM_DRYWALL_MATERIALS + BROWN_DRYWALL, + DRYWALL, + DRYWALL_BEIGE, + DRYWALL_GREEN, + DRYWALL_ORANGE, + DRYWALL_4_TILED, + EGGSHELL_DRYWALL, + RED_DRYWALL, + WALL_DRYWALL_GREY, + YELLOW_DRYWALL +] + CUSTOM_DRYWALL_MATERIALS + +BEDROOM_FLOOR_1 = MaterialTuple( + "AI2-THOR/Materials/Wood/BedroomFloor1", + ["brown", "orange"] +) +BLACK_WOOD = MaterialTuple("AI2-THOR/Materials/Wood/BlackWood", ["black"]) +DARK_WOOD_2 = MaterialTuple("AI2-THOR/Materials/Wood/DarkWood2", ["black"]) +DARK_WOOD_SMOOTH_2 = MaterialTuple( + "AI2-THOR/Materials/Wood/DarkWoodSmooth2", + ["black"] +) +LIGHT_WOOD_COUNTERS_1 = MaterialTuple( + "AI2-THOR/Materials/Wood/LightWoodCounters 1", + ["brown", "orange"] +) +LIGHT_WOOD_COUNTERS_3 = MaterialTuple( + "AI2-THOR/Materials/Wood/LightWoodCounters3", + ["brown", "red"] +) +LIGHT_WOOD_COUNTERS_4 = MaterialTuple( + "AI2-THOR/Materials/Wood/LightWoodCounters4", + ["brown"] +) +TEXTURES_COM_WOOD_FINE_50_1_SEEMLESS = MaterialTuple( + "AI2-THOR/Materials/Wood/TexturesCom_WoodFine0050_1_seamless_S", + ["brown", "red"] +) +WHITE_WOOD = MaterialTuple("AI2-THOR/Materials/Wood/WhiteWood", ["white"]) +WOOD_FLOORS_CROSS = MaterialTuple( + "AI2-THOR/Materials/Wood/WoodFloorsCross", + ["brown", "yellow"] +) +WOOD_GRAIN_BROWN = MaterialTuple( + "AI2-THOR/Materials/Wood/WoodGrain_Brown", + ["brown"] +) +WOOD_GRAIN_TAN = MaterialTuple( + "AI2-THOR/Materials/Wood/WoodGrain_Tan", + ["brown"] +) +WORN_WOOD = MaterialTuple( + "AI2-THOR/Materials/Wood/WornWood", + ["brown", "black"] +) +KINDERGARTEN_BLUE_WOOD = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 1", + ["blue"] +) +KINDERGARTEN_RED_WOOD = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 2", + ["red"] +) +KINDERGARTEN_GREEN_WOOD = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 3", + ["green"] +) +KINDERGARTEN_YELLOW_WOOD = MaterialTuple( + "UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 4", + ["yellow"] +) +NURSERY_BROWN_WOOD = MaterialTuple( + "UnityAssetStore/Baby_Room/Models/Materials/wood 1", + ["brown"] +) WOOD_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Wood/BedroomFloor1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/BlackWood", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/DarkWood2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/DarkWoodSmooth2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters3", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters4", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/TexturesCom_WoodFine0050_1_seamless_S", - ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WhiteWood", ["white"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodFloorsCross", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Brown", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Tan", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WornWood", ["brown"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 1", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 2", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 4", - ["yellow"]), - MaterialTuple( - "UnityAssetStore/Baby_Room/Models/Materials/wood 1", ["brown"]) -] + _CUSTOM_WOOD_MATERIALS + BEDROOM_FLOOR_1, + BLACK_WOOD, + DARK_WOOD_2, + DARK_WOOD_SMOOTH_2, + LIGHT_WOOD_COUNTERS_1, + LIGHT_WOOD_COUNTERS_3, + LIGHT_WOOD_COUNTERS_4, + TEXTURES_COM_WOOD_FINE_50_1_SEEMLESS, + WHITE_WOOD, + WOOD_FLOORS_CROSS, + WOOD_GRAIN_BROWN, + WOOD_GRAIN_TAN, + WORN_WOOD, + KINDERGARTEN_BLUE_WOOD, + KINDERGARTEN_RED_WOOD, + KINDERGARTEN_GREEN_WOOD, + KINDERGARTEN_YELLOW_WOOD, + NURSERY_BROWN_WOOD +] + CUSTOM_WOOD_MATERIALS # Only used with sofa_1 SOFA_1_MATERIALS = [ @@ -398,7 +713,9 @@ class MaterialTuple(NamedTuple): MaterialTuple("AI2-THOR/Materials/Fabrics/Sofa2_Grey", ["grey"]), MaterialTuple("AI2-THOR/Materials/Fabrics/Sofa2_White", ["white"]), MaterialTuple( - "AI2-THOR/Materials/Fabrics/SofaChair2_Fabric_AlbedoTransparency", ["grey"]) + "AI2-THOR/Materials/Fabrics/SofaChair2_Fabric_AlbedoTransparency", + ["grey"] + ) ] # Only used with sofa_3 and sofa_chair_3 @@ -416,173 +733,206 @@ class MaterialTuple(NamedTuple): # Only used with sofa_8 SOFA_8_MATERIALS = [ MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat_BaseColor", ["red"]), + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat_BaseColor", + ["red"] + ), MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3SeatDirt_BaseColor", ["red"]), + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3SeatDirt_BaseColor", + ["red"] + ), MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat2_BaseColor", ["brown"]), + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat2_BaseColor", + ["brown"] + ), MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat2D_BaseColor", ["brown"]) + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/3Seat2D_BaseColor", + ["brown"] + ) ] # Only used with sofa_chair_8 SOFA_CHAIR_8_MATERIALS = [ MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/fotel2_BaseColor", ["brown"]), + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/fotel2_BaseColor", + ["brown"] + ), MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/fotel2D_BaseColor", ["brown"]) + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/fotel2D_BaseColor", + ["brown"] + ) ] # Only used with sofa_9 and sofa_chair_9 SOFA_9_MATERIALS = [ MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/2Seat_BaseColor", ["red"]), + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/2Seat_BaseColor", + ["red"] + ), MaterialTuple( - "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/2SeatD_BaseColor", ["brown"]) + "Assets/Addressables/MCS/UnityAssetStore/Furniture/Source/Materials/2SeatD_BaseColor", + ["brown"] + ) ] # Only used with sofa_chair_4, sofa_chair_5, sofa_chair_6, and sofa_chair_7 ARMCHAIR_THORKEA_MATERIALS = [ MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Ekemas_Fabric_Mat", ["grey"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Ekemas_Fabric_Mat", + ["grey"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Ektorp_Fabric_Mat", ["brown"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Ektorp_Fabric_Mat", + ["brown"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Emmabo_Fabric_Mat", ["blue"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Emmabo_Fabric_Mat", + ["blue"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Karlstad_Fabric_Mat", ["blue"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Karlstad_Fabric_Mat", + ["blue"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Overalt_Fabric_Mat", ["brown"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Overalt_Fabric_Mat", + ["brown"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Tullsta_Fabric_Mat", ["white"]) + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Armchair/Materials/THORKEA_Armchair_Tullsta_Fabric_Mat", + ["white"] + ) ] # Only used with sofa_4, sofa_5, sofa_6, and sofa_7 SOFA_THORKEA_MATERIALS = [ MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Alrid_Fabric_Mat", ["grey"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Alrid_Fabric_Mat", + ["grey"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Ektorp_Fabric_Mat", ["white"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Ektorp_Fabric_Mat", + ["white"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Kramfors_Fabric_Mat", ["brown"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Kramfors_Fabric_Mat", + ["brown"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Solsta_Fabric_Mat", ["grey"]), + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Solsta_Fabric_Mat", + ["grey"] + ), MaterialTuple( - "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Vreta_Fabric_Mat", ["white"]) + "AI2-THOR/Objects/Physics/SimObjsPhysics/THORKEA Objects/THORKEA_Assets_Furniture/Sofa/Materials/THORKEA_Sofa_Vreta_Fabric_Mat", + ["white"] + ) ] +# For use with Rect and Hooked Tools +TOOL_MATERIALS = [ + MaterialTuple( + "UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_13", + ["grey"] + ), + MaterialTuple( + "UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_15", + ["brown"] + ), + MaterialTuple( + "UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_34", + ["pink"] + ), + MaterialTuple( + "UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_36", + ["green"] + ), + MaterialTuple( + "UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_40", + ["blue"] + ) +] + + # Choose only ceramic, fabric, metal, and wood materials that aren't too shiny # or have distracting patterns. FLOOR_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet2", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet3", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet4", ["blue"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet8", ["black"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDark", ["yellow"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDark 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetDarkGreen", ["green"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetGreen", ["green"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite", ["white"]), - MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite 3", ["white"]), - MaterialTuple("AI2-THOR/Materials/Wood/DarkWood2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/DarkWoodSmooth2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/TexturesCom_WoodFine0050_1_seamless_S", - ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WornWood", ["brown"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 1", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 2", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 4", - ["yellow"]), - MaterialTuple( - "UnityAssetStore/Baby_Room/Models/Materials/wood 1", ["brown"]) -] - -INTUITIVE_PHYSICS_BLOCK_MATERIALS = [ - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/blue_1x1", - ["blue"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/gray_1x1", - ["grey"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/green_1x1", - ["green"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/red_1x1", - ["red"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/wood_1x1", - ["brown"]), - MaterialTuple("UnityAssetStore/Wooden_Toys_Bundle/ToyBlocks/meshes/Materials/yellow_1x1", - ["yellow"]) + CARPET_2, + CARPET_3, + CARPET_4, + CARPET_8, + CARPET_DARK, + CARPET_DARK_1, + CARPET_DARK_GREEN, + CARPET_GREEN, + CARPET_WHITE, + CARPET_WHITE_3, + DARK_WOOD_2, + DARK_WOOD_SMOOTH_2, + LIGHT_WOOD_COUNTERS_1, + TEXTURES_COM_WOOD_FINE_50_1_SEEMLESS, + WORN_WOOD, + KINDERGARTEN_BLUE_WOOD, + KINDERGARTEN_RED_WOOD, + KINDERGARTEN_GREEN_WOOD, + KINDERGARTEN_YELLOW_WOOD, + NURSERY_BROWN_WOOD ] +INTUITIVE_PHYSICS_BLOCK_MATERIALS = BLOCK_BLANK_MATERIALS.copy() + INTUITIVE_PHYSICS_METAL_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Metals/Brass 1", ["yellow"]), - MaterialTuple("AI2-THOR/Materials/Metals/BrownMetal 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Metals/BrushedAluminum_Blue", ["blue"]), - MaterialTuple( - "AI2-THOR/Materials/Metals/BrushedIron_AlbedoTransparency", ["black"]), - MaterialTuple("AI2-THOR/Materials/Metals/GenericStainlessSteel", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Metals/HammeredMetal_AlbedoTransparency 1", - ["green"]), - MaterialTuple("AI2-THOR/Materials/Metals/Metal", ["grey"]), - MaterialTuple( - "UnityAssetStore/Baby_Room/Models/Materials/cabinet metal", ["grey"]) + BRASS_1, + BROWN_METAL_1, + BRUSHED_ALUMINUM_BLUE, + BRUSHED_IRON_ALBEDO, + GENERIC_STAINLESS_STEEL, + HAMMERED_METAL_ALBEDO, + METAL, + NURSERY_CABINET_METAL ] INTUITIVE_PHYSICS_PLASTIC_MATERIALS = [ - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 1", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 2", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color 4", - ["yellow"]) + KINDERGARTEN_RED_PLASTIC, + KINDERGARTEN_BLUE_PLASTIC, + KINDERGARTEN_GREEN_PLASTIC, + KINDERGARTEN_YELLOW_PLASTIC ] INTUITIVE_PHYSICS_WOOD_MATERIALS = [ - MaterialTuple("AI2-THOR/Materials/Wood/DarkWoodSmooth2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters3", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters4", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Brown", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Tan", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WornWood", ["brown"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 1", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 2", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 4", - ["yellow"]), - MaterialTuple( - "UnityAssetStore/Baby_Room/Models/Materials/wood 1", ["brown"]) + DARK_WOOD_SMOOTH_2, + LIGHT_WOOD_COUNTERS_1, + LIGHT_WOOD_COUNTERS_3, + LIGHT_WOOD_COUNTERS_4, + WOOD_GRAIN_BROWN, + WOOD_GRAIN_TAN, + WORN_WOOD, + KINDERGARTEN_BLUE_WOOD, + KINDERGARTEN_RED_WOOD, + KINDERGARTEN_GREEN_WOOD, + KINDERGARTEN_YELLOW_WOOD, + NURSERY_BROWN_WOOD ] # Room and occluder walls in intuitive physics scenes cannot use reflective # materials, like some ceramics, metals and woods, due to the glare. INTUITIVE_PHYSICS_WALL_GROUPINGS = [WALL_MATERIALS + [ - MaterialTuple("AI2-THOR/Materials/Ceramics/BrownMarbleFake 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/ConcreteFloor", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/GREYGRANITE", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/WhiteCountertop", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Wood/DarkWoodSmooth2", ["black"]), - MaterialTuple("AI2-THOR/Materials/Wood/WornWood", ["brown"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 1", - ["blue"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 2", - ["red"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 3", - ["green"]), - MaterialTuple("UnityAssetStore/Kindergarten_Interior/Models/Materials/color wood 4", - ["yellow"]), + BROWN_MARBLE_FAKE_1, + CONCRETE_FLOOR, + GREY_GRANITE, + WHITE_COUNTERTOP, + DARK_WOOD_SMOOTH_2, + WORN_WOOD, + KINDERGARTEN_BLUE_WOOD, + KINDERGARTEN_RED_WOOD, + KINDERGARTEN_GREEN_WOOD, + KINDERGARTEN_YELLOW_WOOD ]] ROOM_WALL_MATERIALS = CERAMIC_MATERIALS + METAL_MATERIALS + WALL_MATERIALS + \ WOOD_MATERIALS +# Remove wall materials matching placer colors. +ROOM_WALL_MATERIALS = [item for item in ROOM_WALL_MATERIALS if ( + not item.material.startswith('Custom/Materials/Cyan') and + not item.material.startswith('Custom/Materials/Magenta') +)] FLAT_MATERIALS = [ AZURE, @@ -590,7 +940,9 @@ class MaterialTuple(NamedTuple): BLUE, BROWN, CHARTREUSE, + CREAM, CYAN, + DARKGREY, GOLDENROD, GREEN, GREY, @@ -662,9 +1014,10 @@ class MaterialTuple(NamedTuple): 'FABRIC_MATERIALS', 'LEATHER_MATERIALS', 'METAL_MATERIALS', - 'OPPOSITE_MATERIALS', + 'OBJECT_OPPOSITE_MATERIALS', 'PLASTIC_MATERIALS', 'RUBBER_MATERIALS', + 'WALL_OPPOSITE_MATERIALS', 'WALL_MATERIALS', 'WOOD_MATERIALS', 'FLAT_MATERIALS' @@ -698,6 +1051,24 @@ class MaterialTuple(NamedTuple): ) +def filter_used_colors( + possible_material_list: List[MaterialTuple], + used_material_list: List[MaterialTuple] +) -> List[MaterialTuple]: + """Return a subset of the given possible materials, excluding all materials + that have the same colors as any of the used materials.""" + filtered_material_list = [] + for material in possible_material_list: + excluded = False + for used_material in used_material_list: + if any([(c in used_material.color) for c in material.color]): + excluded = True + break + if not excluded: + filtered_material_list.append(material) + return filtered_material_list + + def find_colors(material_name: str, default_value: str = None) -> List[str]: for item in ALL_CONFIGURABLE_MATERIAL_TUPLES: if item.material == material_name: diff --git a/generator/mechanisms.py b/generator/mechanisms.py index cae2037..6d9b0ea 100644 --- a/generator/mechanisms.py +++ b/generator/mechanisms.py @@ -1,11 +1,12 @@ import copy import math from enum import Enum, auto -from typing import Any, Dict, List +from typing import Dict, List from .definitions import ObjectDefinition from .geometry import create_bounds, move_to_location from .materials import MaterialTuple +from .objects import SceneObject from .structures import finalize_structural_object DEVICE_MATERIAL = MaterialTuple('Custom/Materials/Grey', ['grey']) @@ -119,6 +120,7 @@ CYLINDRICAL_SHAPES = [ 'cylinder', 'double_cone', 'dumbbell_1', 'dumbbell_2', 'rollable_1', 'rollable_2', 'rollable_3', 'rollable_4', + 'rollable_5', 'rollable_6', 'rollable_7', 'rollable_8', 'tie_fighter', 'tube_narrow', 'tube_wide' ] @@ -138,7 +140,7 @@ def _calculate_tube_scale( def _set_placer_or_placed_object_movement( - instance: Dict[str, Any], + instance: SceneObject, move_distance: float, activation_step: int, is_placer: bool = False, @@ -190,7 +192,7 @@ def _set_placer_or_placed_object_movement( def _set_placer_or_placed_object_shellgame_movement( - instance: Dict[str, Any], + instance: SceneObject, move_object_end_position: Dict[str, float], placed_object_dimensions: Dict[str, float], activation_step: int, @@ -198,8 +200,7 @@ def _set_placer_or_placed_object_shellgame_movement( is_placer: bool = False, move_distance: int = None, move_object_y: float = None, - move_object_z: float = None, - + move_object_z: float = None ) -> int: """Add the placer/placed object shellgame movement starting at the given activation step to the given object instance.""" @@ -391,10 +392,10 @@ def create_dropping_device( dropping_step: int = None, id_modifier: str = None, is_round: bool = False -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of a dropping device (tube) to drop the object with the given dimensions. The device will always face down.""" - device = copy.deepcopy(DEVICE_TEMPLATE) + device = SceneObject(copy.deepcopy(DEVICE_TEMPLATE)) device['id'] = ( f'dropping_device_{(id_modifier + "_") if id_modifier else ""}' ) @@ -451,6 +452,8 @@ def create_placer( ceiling at the given max height on the given activation step to place an object with the given position and dimensions at the given end height. + NOTE: Call place_object, pickup_object or move_object BEFORE this function! + - placed_object_position: Placed object's position (probably in the air). - placed_object_dimensions: Placed object's dimensions. - placed_object_offset_y: Placed object's positionY, indicating whether its @@ -583,11 +586,11 @@ def create_throwing_device( id_modifier: str = None, object_rotation_y: float = None, is_round: bool = False -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of a throwing device (tube) to throw the object with the given dimensions. The device will always face left by default, but can also face right, forward, or backward.""" - device = copy.deepcopy(DEVICE_TEMPLATE) + device = SceneObject(copy.deepcopy(DEVICE_TEMPLATE)) device['id'] = ( f'throwing_device_{(id_modifier + "_") if id_modifier else ""}' ) @@ -640,22 +643,22 @@ def create_throwing_device( def rotate_x_for_cylinders_in_droppers_throwers( - instance: Dict[str, Any]) -> float: + instance: SceneObject) -> float: """For cylinder shaped projectiles in droppers and throwers, rotate 90 degrees along x-axis.""" x_rot = 0 - if(instance['type'] in CYLINDRICAL_SHAPES): + if (instance['type'] in CYLINDRICAL_SHAPES): x_rot = 90 return x_rot def drop_object( - instance: Dict[str, Any], - dropping_device: Dict[str, Any], + instance: SceneObject, + dropping_device: SceneObject, dropping_step: int, rotation_y: int = 0 -) -> Dict[str, Any]: +) -> SceneObject: """Modify and return the given object instance that will be dropped by the given device at the given step.""" # Assign the object's location using its chosen corner. @@ -677,17 +680,19 @@ def drop_object( def place_object( - instance: Dict[str, Any], + instance: SceneObject, activation_step: int, start_height: float = None, end_height: float = 0, deactivation_step: int = None -) -> Dict[str, Any]: +) -> SceneObject: """Modify and return the given object instance so its top is positioned at the given height, will move downward with a placer (instantiated later) at the given step like they're attached together, and is then placed when its bottom is at the given height. + NOTE: Call create_placer AFTER this function! + - instance: Instantiated object to place. - activation_step: Step on which object should begin downward movement. - start_height: Y coordinate at which top of object should be positioned. @@ -745,7 +750,7 @@ def place_object( def move_object( - instance: Dict[str, Any], + instance: SceneObject, move_object_end_position: Dict[str, float], activation_step: int, start_height: float = None, @@ -753,13 +758,14 @@ def move_object( deactivation_step: int = None, move_object_y: float = None, move_object_z: float = None, - -) -> Dict[str, Any]: +) -> SceneObject: """Modify and return the given object instance so its top is positioned at the given height, will move downward with a placer (instantiated later) at the given step like they're attached together, and is then placed when its bottom is at the given height. + NOTE: Call create_placer AFTER this function! + - instance: Instantiated object to place. - activation_step: Step on which object should begin downward movement. - start_height: Y coordinate at which top of object should be positioned. @@ -817,17 +823,19 @@ def move_object( def pickup_object( - instance: Dict[str, Any], + instance: SceneObject, activation_step: int, start_height: float = 0, end_height: float = 0, deactivation_step: int = None -) -> Dict[str, Any]: +) -> SceneObject: """Modify and return the given object instance so its bottom is positioned at the top of the object height, will move upward with a placer (instantiated later) at the given step like they're attached together, and is then stops when its top is at the ceiling height. + NOTE: Call create_placer AFTER this function! + - instance: Instantiated object to place. - activation_step: Step on which object should begin upward movement. - start_height: Y coordinate at which top of object should be positioned. @@ -873,8 +881,8 @@ def pickup_object( def throw_object( - instance: Dict[str, Any], - throwing_device: Dict[str, Any], + instance: SceneObject, + throwing_device: SceneObject, throwing_force: int, throwing_step: int, position_x_modifier: float = 0, @@ -883,7 +891,7 @@ def throw_object( rotation_y: int = 0, rotation_z: int = 0, impulse: bool = True -) -> Dict[str, Any]: +) -> SceneObject: """Modify and return the given object instance that will be thrown by the given device with the given force at the given step. The rotation_y should be the rotation needed to turn the object to face toward the left.""" diff --git a/generator/objects.py b/generator/objects.py new file mode 100644 index 0000000..36f2311 --- /dev/null +++ b/generator/objects.py @@ -0,0 +1,5 @@ +from collections import UserDict + + +class SceneObject(UserDict): + pass diff --git a/generator/occluders.py b/generator/occluders.py index d9ba973..5cd15d7 100644 --- a/generator/occluders.py +++ b/generator/occluders.py @@ -1,6 +1,6 @@ import copy import uuid -from typing import Any, Dict, List, Tuple +from typing import Dict, List, Tuple from .geometry import ( MAX_TRIES, @@ -10,6 +10,7 @@ ) from .intuitive_physics_util import retrieve_off_screen_position_x from .materials import MaterialTuple +from .objects import SceneObject # Default occluder height of 1.8 enables seeing a falling object for 8+ frames. OCCLUDER_HEIGHT = 1.8 @@ -328,7 +329,7 @@ def adjust_movement_and_rotation_to_scale( - occluder: List[Dict[str, Any]], + occluder: List[SceneObject], sideways: bool, last_step: int, x_scale_override: float = None @@ -430,7 +431,7 @@ def create_occluder( y_rotation: int = 0, z_position: float = OCCLUDER_POSITION_Z, move_down_only: bool = False -) -> Tuple[Dict[str, Any], Dict[str, Any]]: +) -> Tuple[SceneObject, SceneObject]: """ Create and return a moving and rotating intuitive-physics-style occluder as a pair of wall and pole object instances. By default, the pole is @@ -472,7 +473,10 @@ def create_occluder( # Move down only applies just to sideways occluders for now if sideways_left or sideways_right: - occluder = copy.deepcopy(_OCCLUDER_INSTANCE_SIDEWAYS) + occluder = [ + SceneObject(part) for part in + copy.deepcopy(_OCCLUDER_INSTANCE_SIDEWAYS) + ] pole_x_position = generate_sideways_pole_position_x( x_position, occluder_width, @@ -493,7 +497,10 @@ def create_occluder( 180 if reverse_direction else 0 ) elif sideways_back or sideways_front: - occluder = copy.deepcopy(_OCCLUDER_INSTANCE_SIDEWAYS) + occluder = [ + SceneObject(part) for part in + copy.deepcopy(_OCCLUDER_INSTANCE_SIDEWAYS) + ] # Do the same as with sideways_left or sideways_right, but just swap # the X and Z positions, and add Y rotation. pole_z_position = generate_sideways_pole_position_x( @@ -517,7 +524,10 @@ def create_occluder( -90 if reverse_direction else 90 ) else: - occluder = copy.deepcopy(_OCCLUDER_INSTANCE_NORMAL) + occluder = [ + SceneObject(part) for part in + copy.deepcopy(_OCCLUDER_INSTANCE_NORMAL) + ] pole_length = round((room['y'] - occluder_height) / 2.0, 3) occluder[POLE]['shows'][0]['scale']['y'] = pole_length occluder[POLE]['shows'][0]['position']['x'] = x_position @@ -525,7 +535,7 @@ def create_occluder( pole_length + occluder_height, 3 ) - if(move_down_only): + if (move_down_only): occluder[POLE]['shows'][0]['position']['y'] += ( room['y'] - occluder_height) occluder[POLE]['shows'][0]['position']['z'] = z_position @@ -565,7 +575,7 @@ def create_occluder( occluder[WALL]['debug']['info'] = wall_material.color occluder[POLE]['debug']['info'] = pole_material.color - if(move_down_only): + if (move_down_only): # remove extraneous moves/rotates for move down # case del occluder[POLE]['moves'][2] @@ -624,7 +634,7 @@ def create_occluder( def _assign_occluder_bounds( - occluder_part: Dict[str, Any], + occluder_part: SceneObject, is_sideways: bool, is_pole: bool = False ) -> None: @@ -694,7 +704,7 @@ def generate_sideways_pole_position_x( def generate_occluder_position( x_scale: float, - occluder_list: List[Dict[str, Any]] + occluder_list: List[SceneObject] ) -> float: """Generate and return a random X position for a new occluder with the given X scale that isn't too close to an existing occluder from the @@ -724,8 +734,8 @@ def generate_occluder_position( def make_occluder_sideways( - wall: Dict[str, Any], - pole: Dict[str, Any], + wall: SceneObject, + pole: SceneObject, is_left: bool, room_x: int = DEFAULT_INTUITIVE_PHYSICS_ROOM_DIMENSIONS['x'] ) -> None: diff --git a/generator/optimal_path.py b/generator/optimal_path.py index f0a99db..a67f064 100644 --- a/generator/optimal_path.py +++ b/generator/optimal_path.py @@ -17,6 +17,7 @@ PERFORMER_CAMERA_Y, PERFORMER_HALF_WIDTH ) +from .objects import SceneObject plotting.EXPORT_SIZE_X = plotting.EXPORT_SIZE_Y @@ -74,9 +75,10 @@ def _dilate_and_unify_object_bounds( if isinstance(merged_poly_list, Polygon): merged_poly_list = [merged_poly_list] - poly_coords_list = [ - list(poly.exterior.coords) for poly in merged_poly_list - ] + poly_coords_list = [list(poly.exterior.coords) for poly in ( + merged_poly_list.geoms if hasattr(merged_poly_list, 'geoms') + else merged_poly_list + )] # The polys returned by unary_union have the same first and last point, # but the shortest path code doesn't want them to have the repeated point. for coords in poly_coords_list: @@ -109,9 +111,9 @@ def _dilate_target_bounds( def _find_target_or_parent_dict( - target_object: Dict[str, Any], - object_list: List[Dict[str, Any]] -) -> Dict[str, Any]: + target_object: SceneObject, + object_list: List[SceneObject] +) -> SceneObject: """Find and return the target object dict from the given object list.""" logging.debug('target {target_object}') if 'locationParent' in target_object: @@ -353,8 +355,8 @@ def _rotate_then_move( def find_possible_best_path_list( room_dimensions: Optional[Dict[str, float]], performer_start: Dict[str, Any], - target_dict: Dict[str, Any], - object_list: List[Dict[str, Any]], + target_dict: SceneObject, + object_list: List[SceneObject], save_path_plot_with_name: str = None ) -> Tuple[List[ShortestPath]]: """Find and return lists of MCS actions that each may be the shortest path @@ -453,7 +455,7 @@ def look_at_target( def open_container_and_pickup_target( path: ShortestPath, target_id: str, - container_dict: Dict[str, Any] + container_dict: SceneObject ) -> None: """Update the given path to open the container with the given data and pickup the target with the given ID.""" diff --git a/generator/scene.py b/generator/scene.py index e18b4b1..ec39098 100644 --- a/generator/scene.py +++ b/generator/scene.py @@ -1,15 +1,17 @@ from dataclasses import asdict, dataclass, field -from typing import Any, Dict, List, Optional +from typing import List, Optional from machine_common_sense.config_manager import ( FloorTexturesConfig, + Goal, PerformerStart, PhysicsConfig, RoomMaterials, + Vector2dInt, Vector3d ) -from generator.geometry import DEFAULT_ROOM_DIMENSIONS +from . import ObjectBounds, SceneObject, geometry # TODO MCS-1234 # Wanted to use Pydantic, but need MCS to use it and release it first. @@ -48,17 +50,13 @@ class Scene: floor_material: str = None floor_properties: PhysicsConfig = None floor_textures: List[FloorTexturesConfig] = field(default_factory=list) - # goal: Goal = None - goal: dict = None - # holes: List[Vector2dInt] = field(default_factory=list) - holes: List = field(default_factory=list) + goal: Goal = None + holes: List[Vector2dInt] = field(default_factory=list) intuitive_physics: bool = False isometric: bool = False - # lava: List[Vector2dInt] = field(default_factory=list) - lava: List = field(default_factory=list) + lava: List[Vector2dInt] = field(default_factory=list) name: str = "" - # objects: List[SceneObject] = field(default_factory=list) - objects: List = field(default_factory=list) + objects: List[SceneObject] = field(default_factory=list) partition_floor: PartitionFloor = None performer_start: PerformerStart = None restrict_open_doors: bool = None @@ -74,15 +72,13 @@ def __post_init__(self): # there is probably a better way to make sure each instance has unique # values for defaults. if self.goal is None: - self.goal = { - "metadata": {} - } + self.goal = Goal(metadata={}) if self.performer_start is None: self.performer_start = PerformerStart( position=Vector3d(), rotation=Vector3d()) if self.room_dimensions is None: - self.room_dimensions = Vector3d(**DEFAULT_ROOM_DIMENSIONS) + self.room_dimensions = Vector3d(**geometry.DEFAULT_ROOM_DIMENSIONS) def set_room_dimensions(self, x: int, y: int, z: int): """convenience method to set room dimensions via components""" @@ -102,12 +98,12 @@ def set_performer_start_position(self, x: float, y: float, z: float): def set_performer_start_rotation(self, x: int, y: int): self.performer_start.rotation = Vector3d(x=x, y=y, z=0) - def get_targets(self) -> List[Dict[str, Any]]: + def get_targets(self) -> List[SceneObject]: """Returns the list of all targets for this scene's goal, or an empty list if this scene has no goal or targets.""" targets = [] - goal = self.goal or {} - metadata = goal.get('metadata', {}) + goal = self.goal or Goal() + metadata = goal.metadata or {} target = metadata.get('target', {}) targets_info = [target] if target else metadata.get('targets', []) for target_info in targets_info: @@ -118,11 +114,37 @@ def get_targets(self) -> List[Dict[str, Any]]: break return targets - def get_object_by_id(self, object_id: str) -> Optional[Dict[str, Any]]: + def get_object_by_id(self, object_id: str) -> Optional[SceneObject]: """Returns the object in this scene with the given ID, or None if such an object does not currently exist.""" return next(filter(lambda x: x['id'] == object_id, self.objects), None) + def find_bounds( + self, + ignore_ground: bool = False, + ignore_ids: List[str] = None + ) -> List[ObjectBounds]: + """Calculate and return the bounds for all the given objects.""" + # Create a bounding box for each hole/lava and add it to the list. + bounds = [] if ignore_ground else [ + geometry.generate_floor_area_bounds(area.x, area.z) + for area in (self.holes + self.lava) + ] + + if self.partition_floor and not ignore_ground: + bounds += geometry.find_partition_floor_bounds( + self.room_dimensions, self.partition_floor) + + # Add each object's bounding box to the list. + for instance in self.objects: + if instance.get('id') in (ignore_ids or []): + continue + try: + bounds.append(instance['shows'][0]['boundingBox']) + except (KeyError): + ... + return bounds + # TODO MCS-1234 # Wanted to use Pydantic, but need MCS to use it and release it first. # Much of this is temporary. @@ -139,14 +161,38 @@ def to_dict(self): del data[key] # manual conversion of pydantic classes + for prop in ['holes', 'lava']: + if prop in data: + data[prop] = [area.dict() for area in data[prop]] + if 'floorTextures' in data: + data['floorTextures'] = [ + item.dict() for item in data['floorTextures'] + ] data['performerStart'] = data['performerStart'].dict() data['roomDimensions'] = data['roomDimensions'].dict() + if 'roomMaterials' in data: + data['roomMaterials'] = data['roomMaterials'].dict() + data['goal'] = data['goal'].dict() + for key, alias in [ + ('domains_info', 'domainsInfo'), + ('objects_info', 'objectsInfo'), + ('scene_info', 'sceneInfo'), + ('triggered_by_target_sequence', 'triggeredByTargetSequence') + ]: + if key in data['goal']: + data['goal'][alias] = data['goal'][key] + del data['goal'][key] + remove = [key for key in data['goal'] if data['goal'][key] is None] + for key in remove: + del data['goal'][key] + + data['objects'] = [dict(instance) for instance in data['objects']] return data def get_step_limit_from_dimensions(room_x: int, room_z: int) -> int: - room_x = room_x or DEFAULT_ROOM_DIMENSIONS['x'] - room_z = room_z or DEFAULT_ROOM_DIMENSIONS['z'] + room_x = room_x or geometry.DEFAULT_ROOM_DIMENSIONS['x'] + room_z = room_z or geometry.DEFAULT_ROOM_DIMENSIONS['z'] steps_moving_around_the_room = ((room_x * 10) + (room_z * 10)) * 2 steps_for_rotation = 100 return int((steps_moving_around_the_room + steps_for_rotation) * 5) diff --git a/generator/scene_saver.py b/generator/scene_saver.py index dfd5119..19eed6d 100644 --- a/generator/scene_saver.py +++ b/generator/scene_saver.py @@ -11,6 +11,7 @@ sys.path.insert(1, '../pretty_json') from pretty_json import PrettyJsonEncoder, PrettyJsonNoIndent +from .objects import SceneObject from .scene import Scene @@ -46,17 +47,19 @@ def _json_no_indent(data: Dict[str, Any], prop_list: List[str]) -> None: data[prop] = PrettyJsonNoIndent(data[prop]) -def _strip_debug_data(scene: Scene) -> None: +def _strip_debug_data(scene_dict: Dict[str, Any]) -> Dict[str, Any]: """Remove internal debug data that should only be in debug files.""" - scene.debug = None - for instance in scene.objects: + del scene_dict['debug'] + for instance in scene_dict['objects']: _strip_debug_object_data(instance) for goal_key in ('answer', 'domainsInfo', 'objectsInfo', 'sceneInfo'): - scene.goal.pop(goal_key, None) - if 'metadata' in scene.goal: + if goal_key in scene_dict['goal']: + del scene_dict['goal'][goal_key] + if scene_dict['goal']['metadata']: for target_key in ['target', 'target_1', 'target_2']: - if scene.goal['metadata'].get(target_key, None): - scene.goal['metadata'][target_key].pop('info', None) + if scene_dict['goal']['metadata'].get(target_key, None): + scene_dict['goal']['metadata'][target_key].pop('info', None) + return scene_dict def _strip_debug_misleading_data(scene: Scene) -> None: @@ -76,7 +79,7 @@ def _strip_debug_misleading_data(scene: Scene) -> None: ) -def _strip_debug_object_data(instance: Dict[str, Any]) -> None: +def _strip_debug_object_data(instance: SceneObject) -> None: """Remove internal debug data from the given object.""" instance.pop('debug', None) if 'shows' in instance: @@ -106,10 +109,10 @@ def _truncate_floats_in_list(data: List[Any]) -> None: _truncate_floats_in_dict(data[i]) -def _ready_scene_for_writing(scene: Scene) -> None: +def _ready_scene_for_writing(scene: Scene) -> Dict[str, Any]: _strip_debug_misleading_data(scene) _convert_non_serializable_data(scene) - scene_dict = vars(scene) + scene_dict = scene.to_dict() _truncate_floats_in_dict(scene_dict) # Use PrettyJsonNoIndent on some of the lists and dicts in the @@ -129,8 +132,10 @@ def _ready_scene_for_writing(scene: Scene) -> None: if 'debug' in instance: _json_no_indent(instance['debug'], 'info') + return scene_dict + -def _write_scene_file(filename: str, scene: Scene) -> None: +def _write_scene_file(filename: str, scene_dict: Dict[str, Any]) -> None: # If the filename contains a directory, ensure that directory exists. path = Path(filename) path.parents[0].mkdir(parents=True, exist_ok=True) @@ -140,11 +145,11 @@ def _write_scene_file(filename: str, scene: Scene) -> None: try: out.write( json.dumps( - scene.to_dict(), + scene_dict, cls=PrettyJsonEncoder, indent=2)) except Exception as e: - logging.error(scene, e) + logging.error(scene_dict, e) raise e from e @@ -175,7 +180,7 @@ def save_scene_files( """Save the given scene as a normal JSON file and a debug JSON file.""" # The debug scene filename has the scene ID for debugging. - scene_id = scene.goal.get('sceneInfo', {}).get('id', [None])[0] + scene_id = (scene.goal.scene_info or {}).get('id', [None])[0] debug_filename = ( scene_filename if (no_scene_id or not scene_id) else f'{scene_filename}_{scene_id}' @@ -184,11 +189,11 @@ def save_scene_files( # Ensure that the scene's 'name' property doesn't have a directory. scene_copy = copy.deepcopy(scene) scene_copy.name = Path(scene_filename).name - _ready_scene_for_writing(scene_copy) + scene_dict = _ready_scene_for_writing(scene_copy) # Save the scene as both normal and debug JSON files. if not no_debug_file: - _write_scene_file(debug_filename + '_debug.json', scene_copy) - _strip_debug_data(scene_copy) + _write_scene_file(debug_filename + '_debug.json', scene_dict) + scene_dict = _strip_debug_data(scene_dict) if not only_debug_file: - _write_scene_file(scene_filename + '.json', scene_copy) + _write_scene_file(scene_filename + '.json', scene_dict) diff --git a/generator/specific_objects.py b/generator/specific_objects.py index d059d6f..877880e 100644 --- a/generator/specific_objects.py +++ b/generator/specific_objects.py @@ -352,11 +352,27 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: type='antique_armchair_1', size_multiplier_list=[2] ) +_ANTIQUE_ARMCHAIR_2 = create_variable_definition_from_base( + type='antique_armchair_2', + size_multiplier_list=[1] +) +_ANTIQUE_ARMCHAIR_3 = create_variable_definition_from_base( + type='antique_armchair_3', + size_multiplier_list=[1] +) _ANTIQUE_CHAIR_1 = create_variable_definition_from_base( type='antique_chair_1', size_multiplier_list=[1], chosen_material_list=[ChosenMaterial.WOOD] ) +_ANTIQUE_CHAIR_2 = create_variable_definition_from_base( + type='antique_chair_2', + size_multiplier_list=[1] +) +_ANTIQUE_CHAIR_3 = create_variable_definition_from_base( + type='antique_chair_3', + size_multiplier_list=[1] +) _ANTIQUE_SOFA_1 = create_variable_definition_from_base( type='antique_sofa_1', size_multiplier_list=[1], @@ -439,6 +455,20 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_BED_11_OCCLUDER = create_variable_definition_from_base( + type='bed_11', + size_multiplier_list=[1], + chosen_material_list=[ChosenMaterial.WOOD] +) + + +_BED_12_OCCLUDER = create_variable_definition_from_base( + type='bed_12', + size_multiplier_list=[1], + chosen_material_list=[ChosenMaterial.WOOD] +) + + _BOOKCASE = ObjectDefinition( chooseTypeList=[ create_variable_definition_from_base( @@ -498,6 +528,49 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_BOOKCASE_DOUBLE = ObjectDefinition( + chooseTypeList=[ + create_variable_definition_from_base( + type='double_bookcase_1_shelf', + size_multiplier_list=[1], + ), + create_variable_definition_from_base( + type='double_bookcase_1_shelf', + size_multiplier_list=[Vector3d(x=0.5, y=1, z=1)] + ), + create_variable_definition_from_base( + type='double_bookcase_2_shelf', + size_multiplier_list=[1] + ), + create_variable_definition_from_base( + type='double_bookcase_2_shelf', + size_multiplier_list=[Vector3d(x=0.5, y=1, z=1)] + ), + create_variable_definition_from_base( + type='double_bookcase_3_shelf', + size_multiplier_list=[1] + ), + create_variable_definition_from_base( + type='double_bookcase_3_shelf', + size_multiplier_list=[Vector3d(x=0.5, y=1, z=1)] + ), + create_variable_definition_from_base( + type='double_bookcase_4_shelf', + size_multiplier_list=[1] + ), + create_variable_definition_from_base( + type='double_bookcase_4_shelf', + size_multiplier_list=[Vector3d(x=0.5, y=1, z=1)] + ) + ], + chooseMaterialList=[ + ChosenMaterial.METAL.copy(), + ChosenMaterial.PLASTIC.copy(), + ChosenMaterial.WOOD.copy() + ] +) + + _BOOKCASE_SIDELESS = ObjectDefinition( chooseTypeList=[ create_variable_definition_from_base( @@ -738,6 +811,13 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _CHAIR_14 = create_variable_definition_from_base( type='chair_14', size_multiplier_list=[1], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + +_CHAIR_15 = create_variable_definition_from_base( + type='chair_15', + size_multiplier_list=[1], chosen_material_list=[ ChosenMaterial.METAL, ChosenMaterial.PLASTIC, @@ -746,6 +826,13 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_CHAIR_16 = create_variable_definition_from_base( + type='chair_16', + size_multiplier_list=[1], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + _BLOCK_BLANK_CUBE_NOT_PICKUPABLE = create_variable_definition_from_base( type='block_blank_wood_cube', attributes_overrides=['moveable'], @@ -1119,6 +1206,63 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_TABLE_21_RECT = create_variable_definition_from_base( + type='table_21', + size_multiplier_list=[ + 0.75, + Vector3d(x=1, y=0.75, z=1), + 1, + Vector3d(x=1.25, y=1, z=1.25), + 1.25, + Vector3d(x=1, y=1.25, z=1) + ], + chosen_material_list=[ + ChosenMaterial.METAL, + ChosenMaterial.PLASTIC, + ChosenMaterial.WOOD + ] +) + + +_TABLE_22_OVAL = create_variable_definition_from_base( + type='table_22', + size_multiplier_list=[ + 1, + Vector3d(x=0.5, y=1, z=1), + Vector3d(x=0.75, y=1, z=1.5), + Vector3d(x=1.5, y=1, z=1.5), + 1.5, + Vector3d(x=0.75, y=1.5, z=1.5), + Vector3d(x=1, y=1.5, z=2), + 2, + Vector3d(x=1, y=2, z=2) + ], + chosen_material_list=[ + ChosenMaterial.METAL, + ChosenMaterial.PLASTIC, + ChosenMaterial.WOOD + ] +) + + +_TABLE_25_CIRCLE = create_variable_definition_from_base( + type='table_25', + size_multiplier_list=[ + 0.75, + Vector3d(x=1, y=0.75, z=1), + 1, + Vector3d(x=1.25, y=1, z=1.25), + 1.25, + Vector3d(x=1, y=1.25, z=1) + ], + chosen_material_list=[ + ChosenMaterial.METAL, + ChosenMaterial.PLASTIC, + ChosenMaterial.WOOD + ] +) + + _TABLE_26_SQUARE = create_variable_definition_from_base( type='table_26', size_multiplier_list=[ @@ -1151,6 +1295,18 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_TABLE_28_CIRCLE_WOOD = create_variable_definition_from_base( + type='table_28', + size_multiplier_list=[ + Vector3d(x=0.5, y=0.75, z=0.5), + 0.75, + Vector3d(x=0.75, y=1, z=0.75), + 1 + ], + chosen_material_list=[ChosenMaterial.WOOD] +) + + _TV = create_variable_definition_from_base( type='tv_2', size_multiplier_list=[0.5, 1, 1.5, 2] @@ -1289,6 +1445,18 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_SOFA_11 = create_variable_definition_from_base( + type='sofa_11', + size_multiplier_list=[1] +) + + +_SOFA_12 = create_variable_definition_from_base( + type='sofa_12', + size_multiplier_list=[1.5] +) + + _SOFA_CHAIR_BABY_SCALED = ObjectDefinition( chooseTypeList=[ create_variable_definition_from_base( @@ -1395,6 +1563,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_BARREL_3 = create_variable_definition_from_base( + type='barrel_3', + # Big enough to fit a soccer ball inside + size_multiplier_list=[0.6], + chosen_material_list=[ChosenMaterial.WOOD] +) + + +_BARREL_4 = create_variable_definition_from_base( + type='barrel_4', + # Big enough to fit a soccer ball inside + size_multiplier_list=[0.6], + chosen_material_list=[ChosenMaterial.WOOD] +) + + _CASE_1_SUITCASE = create_variable_definition_from_base( type='case_1', size_multiplier_list=[ @@ -1447,6 +1631,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_CASE_6 = create_variable_definition_from_base( + type='case_6', + # Big enough to fit a soccer ball inside + size_multiplier_list=[9, 11], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + +_CASE_7 = create_variable_definition_from_base( + type='case_7', + # Big enough to fit a soccer ball inside + size_multiplier_list=[9, 11], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + _CRATE_1 = create_variable_definition_from_base( type='crate_1', # Big enough to fit a soccer ball inside @@ -1463,6 +1663,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_CRATE_3 = create_variable_definition_from_base( + type='crate_3', + # Big enough to fit a soccer ball inside + size_multiplier_list=[0.4, 0.6], + chosen_material_list=[ChosenMaterial.WOOD] +) + + +_CRATE_4 = create_variable_definition_from_base( + type='crate_4', + # Big enough to fit a soccer ball inside + size_multiplier_list=[0.6], + chosen_material_list=[ChosenMaterial.WOOD] +) + + _CHEST_1_CUBOID = create_variable_definition_from_base( type='chest_1', size_multiplier_list=[ @@ -1623,6 +1839,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_MILITARY_CASE_3 = create_variable_definition_from_base( + type='military_case_3', + # Big enough to fit a soccer ball inside + size_multiplier_list=[0.5], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + +_MILITARY_CASE_4 = create_variable_definition_from_base( + type='military_case_4', + # Big enough to fit a soccer ball inside + size_multiplier_list=[1], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + _TOOLBOX_1 = create_variable_definition_from_base( type='toolbox_1', size_multiplier_list=[ @@ -1663,6 +1895,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ) +_TOOLBOX_5 = create_variable_definition_from_base( + type='toolbox_5', + # Big enough to fit a soccer ball inside + size_multiplier_list=[4, 6], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + +_TOOLBOX_6 = create_variable_definition_from_base( + type='toolbox_6', + # Big enough to fit a soccer ball inside + size_multiplier_list=[4, 6], + chosen_material_list=[ChosenMaterial.METAL, ChosenMaterial.PLASTIC] +) + + # TODO Update this to use ObjectDefinition if needed in the future. # _POTTED_PLANT_LARGE = { # "shape": ["potted plant"], @@ -1858,7 +2106,16 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: # Arbitrary division: antique furniture [_ANTIQUE_ARMCHAIR_1, _ANTIQUE_CHAIR_1, _ANTIQUE_SOFA_1, _ANTIQUE_TABLE_1], # Arbitrary division: bed occluders - [_BED_1_OCCLUDER, _BED_2_OCCLUDER, _BED_3_OCCLUDER, _BED_4_OCCLUDER], + [ + _BED_1_OCCLUDER, + _BED_2_OCCLUDER, + _BED_3_OCCLUDER, + _BED_4_OCCLUDER, + _BED_7_OCCLUDER, + _BED_8_OCCLUDER, + _BED_9_OCCLUDER, + _BED_10_OCCLUDER + ], # Arbitrary division: bed obstacles [_BED_5_OBSTACLE, _BED_5_OBSTACLE], # Arbitrary division: shelf occluders @@ -1866,8 +2123,24 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: # Arbitrary division: shelf obstacles [_SHELF_1_CUBBY, _SHELF_2_TABLE_SQUARE, _SHELF_2_TABLE_RECT], # Arbitrary division: chairs - [_CHAIR_1, _CHAIR_2_STOOL_CIRCLE, _CHAIR_3_STOOL_RECT, _CHAIR_4_OFFICE], - [_CHAIR_5, _CHAIR_6, _CHAIR_7, _CHAIR_8], + [ + _CHAIR_1, + _CHAIR_2_STOOL_CIRCLE, + _CHAIR_3_STOOL_RECT, + _CHAIR_4_OFFICE, + _CHAIR_5, + _CHAIR_6, + _CHAIR_7, + _CHAIR_8, + _CHAIR_9, + _CHAIR_10, + _CHAIR_11, + _CHAIR_12_PLASTIC, + _CHAIR_13, + _CHAIR_14 + ] * 2, + # Arbitrary division: desks + [_DESK_1, _DESK_2, _DESK_3, _DESK_4], # Arbitrary division: sofas [ _SOFA_1, @@ -1877,6 +2150,8 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _SOFA_5, _SOFA_6, _SOFA_7, + _SOFA_8, + _SOFA_9, _SOFA_BABY_SCALED ], # Arbitrary division: sofa chairs @@ -1888,6 +2163,8 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _SOFA_CHAIR_5, _SOFA_CHAIR_6, _SOFA_CHAIR_7, + _SOFA_CHAIR_8, + _SOFA_CHAIR_9, _SOFA_CHAIR_BABY_SCALED ], # Arbitrary division: rectangular obstacle tables @@ -1896,7 +2173,11 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _TABLE_7_RECT_ACCESSIBLE, _TABLE_8_RECT_ACCESSIBLE, _TABLE_14_SMALL_RECT, - _TABLE_15_RECT + _TABLE_15_RECT, + _TABLE_17_RECT, + _TABLE_18_RECT, + _TABLE_19_RECT, + _TABLE_26_SQUARE ], # Arbitrary division: (semi)circular obstacle tables [ @@ -1904,7 +2185,9 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _TABLE_3_CIRCLE_ACCESSIBLE, _TABLE_4_SEMICIRCLE_ACCESSIBLE, _TABLE_13_SMALL_CIRCLE, - _TABLE_16_CIRCLE + _TABLE_16_CIRCLE, + _TABLE_20_CIRCLE, + _TABLE_27_CIRCLE_PLASTIC ], # Arbitrary division: occluder tables [_TABLE_5_RECT_ACCESSIBLE, _TABLE_11_T_LEGS, _TABLE_12_X_LEGS], @@ -1917,19 +2200,14 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _CRIB, _TV ], - # Eval 5 novel objects - [_BED_7_OCCLUDER, _BED_8_OCCLUDER, _BED_9_OCCLUDER, _BED_10_OCCLUDER], - [_CHAIR_9, _CHAIR_10, _CHAIR_11, _CHAIR_12_PLASTIC, _CHAIR_13, _CHAIR_14], - [_DESK_1, _DESK_2, _DESK_3, _DESK_4], - [_SOFA_8, _SOFA_9, _SOFA_CHAIR_8, _SOFA_CHAIR_9], - [ - _TABLE_17_RECT, - _TABLE_18_RECT, - _TABLE_19_RECT, - _TABLE_20_CIRCLE, - _TABLE_26_SQUARE, - _TABLE_27_CIRCLE_PLASTIC - ], + # Eval 5 novel obstacles and occluders. + [_ANTIQUE_ARMCHAIR_2, _ANTIQUE_ARMCHAIR_3], + [_ANTIQUE_CHAIR_2, _ANTIQUE_CHAIR_3], + [_BED_11_OCCLUDER, _BED_12_OCCLUDER], + [_CHAIR_15, _CHAIR_16], + [_BOOKCASE_DOUBLE], + [_SOFA_11, _SOFA_12], + [_TABLE_21_RECT, _TABLE_22_OVAL, _TABLE_25_CIRCLE, _TABLE_28_CIRCLE_WOOD] # Don't use containers here as possible occluders or context objects ] @@ -1946,29 +2224,45 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _CHEST_9_TRAPEZOID_LID ] _CONTAINERS_OPENABLE = [ - [_CASE_1_SUITCASE, _CASE_2, _CASE_3], - [_TOOLBOX_1, _TOOLBOX_2], - # Twice as likely to randomly choose one of the chests. - _CONTAINERS_CHESTS, - _CONTAINERS_CHESTS, - # Eval 5 novel containers [_BARREL_1, _BARREL_2], - [_CASE_4, _CASE_5], [_CRATE_1, _CRATE_2], - # TODO - # [_CRATE_1, _CRATE_2, _CRATE_3, _CRATE_4], [_MILITARY_CASE_1, _MILITARY_CASE_2], - [_TOOLBOX_3, _TOOLBOX_4] + # Twice as likely to randomly choose one of these lists. + [_CASE_1_SUITCASE, _CASE_2, _CASE_3, _CASE_4, _CASE_5] * 2, + [_TOOLBOX_1, _TOOLBOX_2, _TOOLBOX_3, _TOOLBOX_4] * 2, + _CONTAINERS_CHESTS * 2, + # Eval 6 novel containers. + [_BARREL_3, _BARREL_4], + [_CASE_6, _CASE_7], + [_CRATE_3, _CRATE_4], + [_MILITARY_CASE_1, _MILITARY_CASE_2], + [_TOOLBOX_5, _TOOLBOX_6] ] _CONTAINERS_BINS = [ # Each definition has multiple available sizes: the first is the smallest # size that can fit the soccer ball, and the rest are bigger sizes. + create_variable_definition_from_base( + type='bin_1', + size_multiplier_list=[ + Vector3d(x=0.8, y=0.8, z=1.25), + Vector3d(x=1, y=1, z=1.25) + ], + chosen_material_list=[ChosenMaterial.PLASTIC] + ), + create_variable_definition_from_base( + type='bin_3', + size_multiplier_list=[ + Vector3d(x=1, y=0.7, z=1.5), + Vector3d(x=1.25, y=0.8, z=1.5) + ], + chosen_material_list=[ChosenMaterial.PLASTIC] + ), create_variable_definition_from_base( type='bowl_3_static', size_multiplier_list=[ - Vector3d(x=3, y=3, z=3), Vector3d(x=3.5, y=3.5, z=3.5)], + Vector3d(x=3.25, y=3.25, z=3.25), Vector3d(x=3.5, y=3.5, z=3.5)], chosen_material_list=[ ChosenMaterial.METAL, ChosenMaterial.PLASTIC, @@ -1995,6 +2289,22 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: ChosenMaterial.WOOD ] ), + create_variable_definition_from_base( + type='crate_open_topped_1', + size_multiplier_list=[ + Vector3d(x=0.45, y=0.45, z=0.45), + Vector3d(x=0.55, y=0.55, z=0.55) + ], + chosen_material_list=[ChosenMaterial.WOOD] + ), + create_variable_definition_from_base( + type='crate_open_topped_2', + size_multiplier_list=[ + Vector3d(x=0.65, y=0.65, z=0.65), + Vector3d(x=0.75, y=0.75, z=0.75) + ], + chosen_material_list=[ChosenMaterial.WOOD] + ), create_variable_definition_from_base( type='cup_2_static', size_multiplier_list=[ @@ -2018,7 +2328,7 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: create_variable_definition_from_base( type='cup_6_static', size_multiplier_list=[ - Vector3d(x=4, y=3, z=4), Vector3d(x=4, y=3.5, z=4)], + Vector3d(x=4, y=3.25, z=4), Vector3d(x=4, y=3.5, z=4)], chosen_material_list=[ ChosenMaterial.METAL, ChosenMaterial.PLASTIC, @@ -2031,14 +2341,15 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _CONTAINERS_ASYMMETRIC = [ create_variable_definition_from_base( type=f'container_asymmetric_{suffix}', - size_multiplier_list=[1], + size_multiplier_list=[0.75], chosen_material_list=[ ChosenMaterial.METAL, ChosenMaterial.PLASTIC, ChosenMaterial.WOOD ] ) for suffix in [ - '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12' + # Intentionally ignore asymmetric container number 10. + '01', '02', '03', '04', '05', '06', '07', '08', '09', '11', '12' ] ] @@ -2046,7 +2357,7 @@ def multiply(one: Vector3d, two: Vector3d) -> Vector3d: _CONTAINERS_SYMMETRIC = [ create_variable_definition_from_base( type=f'container_symmetric_{suffix}', - size_multiplier_list=[1], + size_multiplier_list=[0.75], chosen_material_list=[ ChosenMaterial.METAL, ChosenMaterial.PLASTIC, diff --git a/generator/structures.py b/generator/structures.py index 6a614e3..b54267a 100644 --- a/generator/structures.py +++ b/generator/structures.py @@ -1,24 +1,14 @@ import copy import math -import random import uuid -from typing import Any, Dict, List, Tuple +from typing import Dict, List, Tuple from machine_common_sense.config_manager import Vector3d from shapely import affinity -from .base_objects import ( - ALL_LARGE_BLOCK_TOOLS, - LARGE_BLOCK_TOOLS_TO_DIMENSIONS -) -from .geometry import ( - PERFORMER_HALF_WIDTH, - PERFORMER_HEIGHT, - ObjectBounds, - create_bounds, - rotate_point_around_origin -) +from .geometry import PERFORMER_HEIGHT, ObjectBounds, create_bounds from .materials import MaterialTuple +from .objects import SceneObject ANGLE_BRACE_TEMPLATE = { 'id': 'angle_brace_', @@ -286,64 +276,70 @@ }] } -TOOL_HEIGHT = 0.3 -TOOL_TEMPLATE = { - 'id': 'tool_', - 'type': None, +GUIDE_RAIL_DEFAULT_POSITION_Y = 0.15 +GUIDE_RAIL_TEMPLATE = { + 'id': 'guide_rail_', + 'type': 'cube', 'debug': { - 'color': ['grey', 'black'], + 'color': [], 'info': [] }, + 'mass': None, # Set in finalize_structural_object + 'materials': [], # Set to randomly chosen material + 'kinematic': True, + 'structure': True, 'shows': [{ 'stepBegin': 0, 'position': { - 'x': 0, - 'y': TOOL_HEIGHT / 2.0, - 'z': 0 + 'x': 0, # Set to necessary position + 'y': GUIDE_RAIL_DEFAULT_POSITION_Y, + 'z': 0 # Set to necessary position }, 'rotation': { 'x': 0, - 'y': 0, + 'y': 0, # Set to necessary rotation 'z': 0 }, 'scale': { - 'x': 1, - 'y': 1, - 'z': 1 + 'x': 0.2, + 'y': 0.3, + 'z': 1 # Set to necessary length } }] } -GUIDE_RAIL_DEFAULT_POSITION_Y = 0.15 -GUIDE_RAIL_TEMPLATE = { - 'id': 'guide_rail_', - 'type': 'cube', +TUBE_OCCLUDER_TEMPLATE = { + 'id': 'tube_occluder_', + 'type': 'tube_wide', 'debug': { 'color': [], - 'info': [] + 'info': [], + 'shape': ['tube_occluder'], + 'size': 'huge' }, - 'mass': None, # Set in finalize_structural_object - 'materials': [], # Set to randomly chosen material 'kinematic': True, 'structure': True, + 'mass': None, + 'materials': [], 'shows': [{ 'stepBegin': 0, 'position': { - 'x': 0, # Set to necessary position - 'y': GUIDE_RAIL_DEFAULT_POSITION_Y, - 'z': 0 # Set to necessary position + 'x': 0, + 'y': 0, + 'z': 0 }, 'rotation': { 'x': 0, - 'y': 0, # Set to necessary rotation + 'y': 0, 'z': 0 }, 'scale': { - 'x': 0.2, - 'y': 0.3, - 'z': 1 # Set to necessary length + 'x': 1, + 'y': 1, + 'z': 1 } - }] + }], + 'moves': [] } # door constants @@ -356,16 +352,6 @@ # guide rail constants RAIL_CENTER_TO_EDGE = 0.2 -# broken tool -BROKEN_TOOL_VERTICAL_SEPARATION = 1.35 -BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN = 0.75 -BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX = 1.5 - -# inaccessible tool -INACCESSIBLE_TOOL_BLOCKING_WALL_HEIGHT = 0.25 -INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH = 0.1 -INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION = 0.5 - def _calculate_info(colors: List[str], labels: List[str]) -> List[str]: combos = [] @@ -398,11 +384,11 @@ def create_angle_brace( rotation_x: int = 0, rotation_z: int = 0, bounds: ObjectBounds = None -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of an L-shaped angle brace. Please note that its side is twice as long as its bottom, and that it's facing to the right (positive X axis) by default.""" - angle_brace = copy.deepcopy(ANGLE_BRACE_TEMPLATE) + angle_brace = SceneObject(copy.deepcopy(ANGLE_BRACE_TEMPLATE)) angle_brace['id'] += str(uuid.uuid4()) angle_brace['shows'][0]['position'] = { 'x': position_x, @@ -437,9 +423,9 @@ def create_interior_wall( position_y_modifier: float = 0, thickness: float = 0.1, bounds: ObjectBounds = None -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of an interior room wall.""" - wall = copy.deepcopy(INTERIOR_WALL_TEMPLATE) + wall = SceneObject(copy.deepcopy(INTERIOR_WALL_TEMPLATE)) wall['id'] += str(uuid.uuid4()) wall['shows'][0]['position'] = { 'x': position_x, @@ -476,10 +462,12 @@ def create_l_occluder( material_tuple: MaterialTuple, position_y_modifier: float = 0, flip: bool = False -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return an instance of an L-shaped occluder. If flip is True, the side part of the L will be on the left side (like a backwards L).""" - occluder = copy.deepcopy(L_OCCLUDER_TEMPLATE) + occluder = [ + SceneObject(part) for part in copy.deepcopy(L_OCCLUDER_TEMPLATE) + ] front = occluder[0] side = occluder[1] @@ -559,9 +547,9 @@ def create_platform( position_y_modifier: float = 0, bounds: ObjectBounds = None, auto_adjust_platform: bool = False -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of a platform.""" - platform = copy.deepcopy(PLATFORM_TEMPLATE) + platform = SceneObject(copy.deepcopy(PLATFORM_TEMPLATE)) buffer = PERFORMER_HEIGHT if auto_adjust_platform: @@ -619,12 +607,12 @@ def create_ramp( material_tuple: MaterialTuple, position_y_modifier: float = 0, bounds: ObjectBounds = None -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of a ramp. Its height is derived from the given angle and length. Length is the length along the ground in a single dimension. The angle must be in degress up from the ground.""" height = length * (math.tan(math.radians(angle))) - ramp = copy.deepcopy(RAMP_TEMPLATE) + ramp = SceneObject(copy.deepcopy(RAMP_TEMPLATE)) ramp['shows'][0]['position'] = { 'x': position_x, 'y': position_y_modifier + (height / 2.0), @@ -664,7 +652,7 @@ def create_door( wall_scale_y: float, bounds: ObjectBounds = None, add_walls: bool = True, -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return an instance of an interior door.""" if rotation_y not in [0, 90, 180, 270]: raise Exception("Doors rotation must be either 0, 90, 180, or 270") @@ -711,7 +699,7 @@ def create_door( top_wall_scale_y=top_wall_scale_y, side_wall_scale_x=side_wall_scale_x) - door = copy.deepcopy(DOOR_TEMPLATE) + door = SceneObject(copy.deepcopy(DOOR_TEMPLATE)) door['shows'][0]['position'] = { 'x': position_x, 'y': position_y, @@ -753,14 +741,32 @@ def create_door( return objs + walls -def create_door_occluder(room_dimensions: Vector3d, - door_start_drop_step: int, - door_mat: MaterialTuple, - wall_mat: MaterialTuple, - middle_height: float, - middle_width: float, - position_z: float = 0): - door_gap = room_dimensions.y - middle_height - 2.25 +def create_door_occluder( + room_dimensions: Vector3d, + door_start_drop_step: int, + door_mat: MaterialTuple, + wall_mat: MaterialTuple, + middle_height: float, + middle_width: float, + position_z: float = 0, + wall_height: float = 0 +) -> Tuple[ + List[SceneObject], + int, + SceneObject, + SceneObject, + SceneObject +]: + """ + Creates and returns a triple or double door occluder using the two given + materials that starts positioned above the ceiling and begins to descend + at the given step. Accommodates an existing platform in the middle of the + room with the given middle_height and middle_width; set both to 0 to + create a double door occluder. Set wall_height to a non-zero value to + make the occluder wall shorter than the height of the room. Returns all + object instances, the step at which the door occluder has finished + descending, the middle door, the left door, and the right door. + """ side_wall_x = (room_dimensions.x - middle_width) / 2 side_pos_x = room_dimensions.x / 2 - side_wall_x / 2 add_y = room_dimensions.y @@ -769,11 +775,11 @@ def create_door_occluder(room_dimensions: Vector3d, door_y = [middle_height, 0, 0] wall_scale_x = [middle_width, side_wall_x, side_wall_x] wall_scale_y = [ - room_dimensions.y - - middle_height - door_gap, - room_dimensions.y - door_gap, - room_dimensions.y - door_gap] - door_end_drop_step = door_start_drop_step + add_y * 4 - 1 + (wall_height or room_dimensions.y) - middle_height, + (wall_height or room_dimensions.y), + (wall_height or room_dimensions.y) + ] + door_end_drop_step = int(door_start_drop_step + add_y * 4 - 1) for i in range(3): new_objs = create_door( position_x=door_x[i], @@ -798,8 +804,10 @@ def create_door_occluder(room_dimensions: Vector3d, center_door = doors_objs[0] left_door = doors_objs[4] right_door = doors_objs[8] - return (doors_objs, int(door_end_drop_step), - center_door, left_door, right_door) + if not middle_width: + doors_objs = doors_objs[4:] + center_door = None + return (doors_objs, door_end_drop_step, center_door, left_door, right_door) def create_turntable( @@ -814,13 +822,13 @@ def create_turntable( movement_rotation: float, material_tuple: MaterialTuple, bounds: ObjectBounds = None -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an instance of a turntable.""" # Need to reconcile issues with y-scale value since the # default cog base object is very thin. y_scale_multiplier = 50.0 - turntable = copy.deepcopy(TURNTABLE_TEMPLATE) + turntable = SceneObject(copy.deepcopy(TURNTABLE_TEMPLATE)) turntable['shows'][0]['position'] = { 'x': position_x, 'y': position_y_modifier + height / 2.0, @@ -837,10 +845,11 @@ def create_turntable( 'z': radius * 2.0 } - turntable['rotates'] = [copy.deepcopy(TURNTABLE_MOVEMENT_TEMPLATE)] - turntable['rotates'][0]['stepBegin'] = step_begin - turntable['rotates'][0]['stepEnd'] = step_end - turntable['rotates'][0]['vector']['y'] = movement_rotation + if step_begin > 0 and step_end > 0 and step_end >= step_begin: + turntable['rotates'] = [copy.deepcopy(TURNTABLE_MOVEMENT_TEMPLATE)] + turntable['rotates'][0]['stepBegin'] = step_begin + turntable['rotates'][0]['stepEnd'] = step_end + turntable['rotates'][0]['vector']['y'] = movement_rotation # Use height instead of y-scale for override return finalize_structural_object( @@ -853,7 +862,7 @@ def create_turntable( 'y': height, 'z': turntable['shows'][0]['scale']['z'] } - ) + )[0] def _get_door_wall_objects( @@ -868,12 +877,12 @@ def _get_door_wall_objects( top_wall_scale_y: float, side_wall_scale_x: float, wall_material_str: str = "Custom/Materials/GreyDrywallMCS", -): +) -> List[SceneObject]: # Create the wall sections and the door object objs = [] # only add top wall if the scale is greater than 0 if top_wall_scale_x > 0 and top_wall_scale_y > 0: - objs.append({ + objs.append(SceneObject({ "id": "wall_top", "type": "cube", "mass": 10, @@ -904,9 +913,9 @@ def _get_door_wall_objects( } } ] - }) + })) if side_wall_scale_x > 0: - objs += [{ + objs += [SceneObject({ "id": "wall_left", "type": "cube", "mass": 10, @@ -937,8 +946,7 @@ def _get_door_wall_objects( } } ] - }, - { + }), SceneObject({ "id": "wall_right", "type": "cube", "mass": 10, @@ -969,8 +977,7 @@ def _get_door_wall_objects( } } ] - } - ] + })] return objs @@ -982,7 +989,7 @@ def create_guide_rails_around( width: float, material_tuple: MaterialTuple, position_y: float = GUIDE_RAIL_DEFAULT_POSITION_Y, - bounds: ObjectBounds = None): + bounds: ObjectBounds = None) -> List[SceneObject]: """Provide a rectangle to put rails on the sides. The rectangle will be centered on the position, rotated by the rotation_y and have size of length and width.""" @@ -1025,8 +1032,8 @@ def create_guide_rail( length: float, material_tuple: MaterialTuple, position_y: float = GUIDE_RAIL_DEFAULT_POSITION_Y, - bounds: ObjectBounds = None): - rail = copy.deepcopy(GUIDE_RAIL_TEMPLATE) + bounds: ObjectBounds = None) -> SceneObject: + rail = SceneObject(copy.deepcopy(GUIDE_RAIL_TEMPLATE)) rail['shows'][0]['position']['x'] = position_x rail['shows'][0]['position']['y'] = position_y rail['shows'][0]['position']['z'] = position_z @@ -1041,50 +1048,68 @@ def create_guide_rail( return rail -def create_tool( - # TODO MCS-1206 Move into a separate file for interactable objects - object_type: str, +def create_tube_occluder( position_x: float, position_z: float, - rotation_y: float, - bounds: ObjectBounds = None -) -> Dict[str, Any]: - """Create and return an instance of a tool.""" - tool = copy.deepcopy(TOOL_TEMPLATE) - tool['type'] = object_type - tool['shows'][0]['position']['x'] = position_x - tool['shows'][0]['position']['z'] = position_z - tool['shows'][0]['rotation']['y'] = rotation_y - dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(object_type) - if not dimensions: - raise Exception(f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') - tool = finalize_structural_object( - [tool], - # Use the tool's default materials set by the Unity prefab. - None, - ['tool'], - bounds, - override_scale={ - 'x': dimensions[0], - 'y': TOOL_HEIGHT, - 'z': dimensions[1] - }, - override_standing_y=(TOOL_HEIGHT / 2.0) + radius: float, + room_height: int, + material_tuple: MaterialTuple, + down_step: int, + up_step: int = 0 +) -> SceneObject: + """Create and return a tube occluder using the given parameters.""" + tube = SceneObject(copy.deepcopy(TUBE_OCCLUDER_TEMPLATE)) + tube['shows'][0]['position']['x'] = position_x + tube['shows'][0]['position']['y'] = round(room_height * 1.5, 2) - 0.25 + tube['shows'][0]['position']['z'] = position_z + tube['shows'][0]['scale']['x'] = radius + tube['shows'][0]['scale']['y'] = room_height + tube['shows'][0]['scale']['z'] = radius + down_end = down_step + (room_height * 4) - 2 + if up_step > 0: + down_end = min(down_end, up_step - 1) + tube['moves'].append({ + 'stepBegin': down_step, + 'stepEnd': down_end, + 'vector': { + 'x': 0, + 'y': -0.25, + 'z': 0 + } + }) + if up_step > 0: + tube['moves'].append({ + 'stepBegin': up_step, + 'stepEnd': up_step + (room_height * 4) - 2, + 'vector': { + 'x': 0, + 'y': 0.25, + 'z': 0 + } + }) + tube = finalize_structural_object( + [tube], + material_tuple, + ['tube_occluder'], )[0] - # Use the tool's default mass set in the Unity object registry file. - del tool['mass'] - return tool + # Other objects can safely be positioned inside the tube occluder, but the + # scene generator cannot handle that use case properly, so just move the + # bounding box out-of-the-way, to avoid triggering the collision detection. + # Yes, this may cause problems if used outside Hidden Set Rotation scenes. + tube['shows'][0]['boundingBox'].min_y = -room_height + tube['shows'][0]['boundingBox'].max_y = -0.25 + return tube def finalize_structural_object( - instance_list: List[Dict[str, Any]], + instance_list: List[SceneObject], material_tuple: MaterialTuple = None, name_list: List[str] = None, bounds: ObjectBounds = None, override_scale: Dict[str, float] = None, override_offset: Dict[str, float] = None, override_standing_y: float = None -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Finalize and return each of the instances of structural objects.""" common_id = str(uuid.uuid4()) for instance in instance_list: @@ -1110,266 +1135,3 @@ def finalize_structural_object( name_list or [] ) return instance_list - - -def create_broken_tool( - object_type: str, - direction: str, - width_position: float, - max_broken_tool_length_pos: float, - min_broken_tool_length_pos: float, - rotation_for_entire_tool: float, - length: int -) -> Dict[str, Any]: - """Create and return an instance of a broken tool.""" - tools_before_rotation = [] - final_tools = [] - current_pos = max_broken_tool_length_pos # start at lava edge - for _ in range(length): - tool = copy.deepcopy(TOOL_TEMPLATE) - tool['type'] = object_type - tool['shows'][0]['position']['x'] = \ - (width_position + round(random.uniform( - BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN, - BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX), 2) * - random.choice([-1, 1]) if direction == 'z' else current_pos) - tool['shows'][0]['position']['z'] = \ - (width_position + round(random.uniform( - BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN, - BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX), 2) * - random.choice([-1, 1]) if direction == 'x' else current_pos) - tool['shows'][0]['rotation']['y'] = random.randint(0, 359) - dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(object_type) - if not dimensions: - raise Exception( - f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') - temp_tool = copy.deepcopy(tool) - tools_before_rotation.append(temp_tool) - current_pos -= BROKEN_TOOL_VERTICAL_SEPARATION - center_of_tool = (max_broken_tool_length_pos + - min_broken_tool_length_pos) / 2 - for tool in tools_before_rotation: - tool_pos = tool['shows'][0]['position'] - (x, z) = rotate_point_around_origin( - origin_x=width_position if direction == 'z' else - center_of_tool, - origin_z=width_position if direction == 'x' else - center_of_tool, - point_x=tool_pos['x'], - point_z=tool_pos['z'], - rotation=rotation_for_entire_tool) - tool_pos['x'] = round(x, 2) - tool_pos['z'] = round(z, 2) - tool = finalize_structural_object( - instance_list=[tool], - material_tuple=None, - name_list=['tool'], - bounds=None, - override_scale={ - 'x': dimensions[0], - 'y': TOOL_HEIGHT, - 'z': dimensions[1] - }, - override_standing_y=(TOOL_HEIGHT / 2.0) - )[0] - # Use the tool's default mass set in the Unity object registry file. - del tool['mass'] - final_tools.append(tool) - return final_tools - - -def create_inaccessible_tool( - tool_type: str, - long_direction: str, - short_direction: str, - original_short_position: float, - original_long_position: float, - tool_horizontal_offset: float, - tool_offset_backward_from_lava: float, - blocking_wall_horizontal_offset: float, - tool_rotation_y: int, - room_dimension_x: float, - room_dimension_z: float, - blocking_wall_material: MaterialTuple, - bounds: ObjectBounds = None -) -> Tuple: - """ - Create and return an instance of an inaccessible tool and its blocking wall - - `tool_type` (string): The type of tool - - `long_direction` (string): The direction to tool extends to reach - the target. - - `short_direction` (string): The direction of the tool width - - `original_short_position` (float): The original position of the tool - that is centered with the target. - - `original_long_position` (float): The original position of the tool that - is shifted to the edge of the lava pool. - - `tool_horizontal_offset` (float): The left and right offset the tool - should be moved from the blocking wall position. - - `tool_offset_backwards_from_lava` (float): The offset away from the - lava pool the tool should move from its original position. - - `blocking_wall_separation` (float): The separation from the tool the - blocking wall should have. Negative numbers will be on left side, positive - will be on right. The minimum separation should always be greater than or - equal to 0.5 or less than or equal to -0.5. - - `tool_rotation_y` (float): The rotation of the tool. - - `room_dimension_x` (float): X Room dimension. - - `room_dimension_z` (float): Z Room dimension. - - `blocking_wall_material` (MaterialTyple): Material for the blocking wall. - - `bounds` (ObjectBounds): Object Bounds. Default: None - """ - # Make sure the blocking wall separation is valid - if abs(blocking_wall_horizontal_offset) < \ - INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION: - raise Exception( - "The minimum separation of the inaccessible tool blocking " - "wall should always be greater than or equal to " - f"{INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION} or " - f"less than or equal to " - f"-{INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION}") - - """ - short direction is the axis of width when the tool is - aligned with the target. Left right directions: - For 'x' short direction when the tool long direction is on the z axis - ------------------------ - | (z+) | - | front | - |(x-) left * right (x+)| - | back | - | (z-) | - ------------------------ - - For 'z' short direction the tool long direction is on the x axis - everything is rotated clockwise ⟳ 90 - ------------------------ - | (z+) | - | left | - |(x-) back * front (x+)| - | right | - | (z-) | - ------------------------ - """ - # Wall creation - # Create a straight wall positioned vertically across the room - # If z short direction reverse separation direction because left is now - # z 'positive' axis instead of x 'negative' axis - original_offset = blocking_wall_horizontal_offset - if short_direction == 'z': - blocking_wall_horizontal_offset *= -1 - blocking_wall_horizontal_offset = \ - original_short_position + blocking_wall_horizontal_offset - length = room_dimension_z if short_direction == 'x' else room_dimension_x - wall_rotation = 0 if short_direction == 'x' else 90 - x = blocking_wall_horizontal_offset if short_direction == 'x' else 0 - z = blocking_wall_horizontal_offset if short_direction == 'z' else 0 - horizontal_pos = x if short_direction == 'x' else z - wall = create_interior_wall( - position_x=x, - position_z=z, - rotation_y=wall_rotation, - width=INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH, - height=INACCESSIBLE_TOOL_BLOCKING_WALL_HEIGHT, - material_tuple=blocking_wall_material, - position_y_modifier=0, - thickness=length - ) - blocking_wall_pos_cutoff = ( - horizontal_pos + - (INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH + PERFORMER_HALF_WIDTH) * - (-1 if blocking_wall_horizontal_offset > 0 else 1)) - room_wall_pos_cutoff = ( - (room_dimension_x / 2 - PERFORMER_HALF_WIDTH) * - (-1 if original_offset > 0 else 1) * - (-1 if long_direction == 'x' else 1)) - - adjusted_offset_x = 0 - adjusted_offset_z = 0 - base_x = wall['shows'][0]['position']['x'] - base_z = wall['shows'][0]['position']['z'] - # Make the tool twice, the first time figure out where its bounds are - # after its been rotated. - # Calculate the difference from its maximum or minimum bounds to the edge - # of the wall and then shift the tool so its aligned perfectly to wall edge - # Then apply the tool horizontal offset after so that the closet edge - # of the tool is the correct horizontal offset away rather than the - # the tool center. - for _ in range(2): - # Tool creation - tool = copy.deepcopy(TOOL_TEMPLATE) - tool['type'] = tool_type - x = ( - ((base_x + tool_horizontal_offset) if short_direction == 'x' else - (original_long_position - tool_offset_backward_from_lava)) + - adjusted_offset_x - ) - z = ( - ((base_z - tool_horizontal_offset) if short_direction == 'z' else - (original_long_position - tool_offset_backward_from_lava)) - - adjusted_offset_z - ) - tool['shows'][0]['position']['x'] = x - tool['shows'][0]['position']['z'] = z - tool['shows'][0]['rotation']['y'] = tool_rotation_y - dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(tool_type) - if not dimensions: - raise Exception( - f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') - # Create the tool - tool = finalize_structural_object( - [tool], - None, - ['tool'], - bounds, - override_scale={ - 'x': dimensions[0], - 'y': TOOL_HEIGHT, - 'z': dimensions[1] - }, - override_standing_y=(TOOL_HEIGHT / 2.0) - )[0] - del tool['mass'] # Use default mass in unity - tool_bounds = tool['shows'][0]['boundingBox'].box_xz - # Maximum bounds and minimum bounds of tool in short direction - maximum_bound = round(max(getattr(pos, short_direction) - for pos in tool_bounds), 2) - minimum_bound = round(min(getattr(pos, short_direction) - for pos in tool_bounds), 2) - cutoff = (wall['shows'][0][ - 'position']['x' if short_direction == 'x' else 'z'] - - (INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH * - (1 if original_offset > 0 else -1) * - (1 if long_direction == 'x' else -1))) - if short_direction == 'x': - difference = cutoff - \ - (maximum_bound if original_offset < 0 else minimum_bound) - adjusted_offset_x += difference + tool_horizontal_offset - adjusted_offset_z = 0 - else: - difference = cutoff - \ - (maximum_bound if original_offset > 0 else minimum_bound) - adjusted_offset_z -= difference - tool_horizontal_offset - adjusted_offset_x = 0 - - """ - Simple example of what we should have now. - This example is with a negative blocking wall separation and - tool long direction on the z axis, short direction on x axis. - P = Performer - T = Target - * = Lava - C1 = (cutoff 1) blocking_wall_pos_cutoff - C2 = (cutoff 2) room_wall_pos_cutoff - x = x wall - z = z wall - |--------(z+)-------| - | |B| *** | - | |l| *T* | - (x-) |t| |o| *** (x+) - | |o| |c| | - | |o| |k| P | - | |l| |k| | - |--------(z-)-------| - [C1---C2] - """ - return (tool, wall, short_direction, blocking_wall_pos_cutoff, - room_wall_pos_cutoff) diff --git a/generator/tags.py b/generator/tags.py index f90d981..d126084 100644 --- a/generator/tags.py +++ b/generator/tags.py @@ -2,6 +2,8 @@ from types import SimpleNamespace from typing import Any, Dict, List +from .objects import SceneObject + ALL = 'all' ANY = 'any' @@ -17,12 +19,14 @@ ROLE_DICT = { 'AGENT': 'agent', 'BLOB': 'blob', + 'COLLIDER': 'collider', 'CONFUSOR': 'confusor', 'CONTAINER': 'container', 'CONTEXT': 'context', 'HOME': 'home', 'KEY': 'key', 'INTUITIVE_PHYSICS_OCCLUDER': 'intuitive physics occluder', + 'LID': 'lid', 'NON_TARGET': 'non target', 'OBSTACLE': 'obstacle', 'OCCLUDER': 'occluder', @@ -41,19 +45,41 @@ } SCENE_OPTIONAL_TAGS_DICT = { + # Multiple hypercubes + 'AGENT_SKINTONE': 'skintone', 'AMBIGUOUS': 'ambiguous', 'ANGLE': 'angle', 'CORNER': 'corner', 'CORRECT_DOOR': 'correctDoor', 'DIRECTION': 'direction', - 'EXTENSION_SIDE': 'extensionSide', 'FALL_DOWN': 'fallDown', 'MOVE_ACROSS': 'moveAcross', 'NUM_FLOOR_FEATURES': 'numFloorFeatures', 'PATH_RATIO': 'pathRatio', + 'ROTATION': 'rotation', 'SETUP': 'sceneSetup', 'TARGET_SIDE': 'targetSide', - 'WALL': 'wall' + 'TIPSY': 'tipsy', + # Arithmetic, Number Comparison + 'REFERENCE_SIDE': 'referenceSide', + # Imitation + 'CONTAINERS_SIDE': 'containersSide', + 'CONTAINERS_KIDNAP_ROTATION': 'containersKidnapRotation', + 'CONTAINER_TRIGGER_ORDER_SIDES': 'containerTriggerOrderSides', + # Seeing Leads to Knowing + 'AGENT_FACING_DIRECTION': 'agentFacingDirection', + 'TARGET_BIN_GLOBAL_LOCATION': 'targetBinGlobalLocation', + # Set Rotation + 'TURNTABLE_CONTAINER_TARGET_SIDE': 'turntableContainerTargetSide', + # Shell Game + 'SHELL_GAME_MOVEMENT_DIRECTION': 'shellGameMovementDirection', + 'SHELL_GAME_CONTAINER_PICKED': 'shellGameContainerPicked', + # Solidity + 'EXTENSION_SIDE': 'extensionSide', + # Spatial Reorientation + 'STABLE_LANDMARK': 'stableLandmarkWall', + 'UNSTABLE_LANDMARK_SIDE': 'unstableLandmarkStartingSide', + 'UNSTABLE_LANDMARK_TYPE': 'unstableLandmarkType' } SCENE_ROLE_TAGS_DICT = { @@ -95,6 +121,7 @@ SECONDARY='secondaryType', TERTIARY='tertiaryType', QUATERNARY='quaternaryType', + DOMAIN_TYPE='domainType', ID='id', NAME='name', @@ -105,6 +132,12 @@ ACTION_VARIABLE='action variable', ACTION_FULL='action full', + INTERACTIVE_AGENTS='interactive agents', + INTERACTIVE_OBJECTS='interactive objects', + INTERACTIVE_PLACES='interactive places', + PASSIVE_AGENTS='passive agents', + PASSIVE_OBJECT='passive objects', + INTUITIVE_PHYSICS='intuitive physics', COLLISIONS='collisions', GRAVITY_SUPPORT='gravity support', @@ -113,15 +146,28 @@ SPATIO_TEMPORAL_CONTINUITY='spatio temporal continuity', AGENT_IDENTIFICATION='agent identification', + ARITHMETIC='arithmetic', + CONTAINER='container', HOLES='holes', + IMITATION_TASK='imitation task', + INTERACTIVE_COLLISION='interactive collision', INTERACTIVE_OBJECT_PERMANENCE='interactive object permanence', LAVA='lava', MOVING_TARGET_PREDICTION='moving target prediction', + NUMBER_COMPARISON='number comparison', + OCCLUDER='occluder', + OBSTACLE='obstacle', RAMP='ramp', - REORIENTATION='reorientation', + REFERENCE='spatial reference', + REORIENTATION='spatial reorientation', + SEEING_LEADS_TO_KNOWING='seeing leads to knowing', + SET_ROTATION='set rotation', + SHELL_GAME='shell game', + SOCIAL_REFERENCING='social referencing', SOLIDITY='solidity', SPATIAL_ELIMINATION='spatial elimination', SUPPORT_RELATIONS='support relations', + TRAJECTORY='trajectory', TOOL_USE='tool use', # For passive agent tasks: @@ -174,23 +220,44 @@ INTERACTIVE_TARGET_HIDDEN='targetHidden', INTERACTIVE_TARGET_INSIDE='targetInside', + AGENT_BACKGROUND_AGENT_ONE_GOAL='agents agent one goal', + AGENT_BACKGROUND_AGENT_PREFERENCE='agents agent preference', + AGENT_BACKGROUND_COLLECT='agents collect', AGENT_BACKGROUND_INSTRUMENTAL_ACTION='agents instrumental action', + AGENT_BACKGROUND_INSTRUMENTAL_APPROACH='agents instrumental approach', + AGENT_BACKGROUND_INSTRUMENTAL_IMITATION='agents instrumental imitation', AGENT_BACKGROUND_MULTIPLE_AGENTS='agents multiple agents', + AGENT_BACKGROUND_NON_AGENT_ONE_GOAL='agents nonagent one goal', + AGENT_BACKGROUND_NON_AGENT_PREFERENCE='agents nonagent preference', AGENT_BACKGROUND_OBJECT_PREFERENCE='agents object preference', AGENT_BACKGROUND_SINGLE_OBJECT='agents single object', + AGENT_BACKGROUND_SOCIAL_APPROACH='agents social approach', + AGENT_BACKGROUND_SOCIAL_IMITATION='agents social imitation', + AGENT_EVALUATION_AGENT_NON_AGENT='agents agent nonagent', + AGENT_EVALUATION_APPROACH='agents approach', AGENT_EVALUATION_EFFICIENT_IRRATIONAL='agents efficient action irrational', AGENT_EVALUATION_EFFICIENT_PATH='agents efficient action path lure', AGENT_EVALUATION_EFFICIENT_TIME='agents efficient action time control', + AGENT_EVALUATION_IMITATION='agents imitation', AGENT_EVALUATION_INACCESSIBLE_GOAL='agents inaccessible goal', AGENT_EVALUATION_INSTRUMENTAL_BLOCKING_BARRIERS='agents instrumental action blocking barriers', # noqa: E501 AGENT_EVALUATION_INSTRUMENTAL_INCONSEQUENTIAL_BARRIERS='agents instrumental action inconsequential barriers', # noqa: E501 AGENT_EVALUATION_INSTRUMENTAL_NO_BARRIERS='agents instrumental action no barriers', # noqa: E501 AGENT_EVALUATION_MULTIPLE_AGENTS='agents multiple agents', - AGENT_EVALUATION_OBJECT_PREFERENCE='agents object preference' + AGENT_EVALUATION_OBJECT_PREFERENCE='agents object preference', + + AGENT_EXAMPLE='agents examples' ) + CELLS = SimpleNamespace( + AGENT_SKINTONE=SimpleNamespace( + LIGHT='light', + MEDIUM='medium', + DARK='dark' + ), + COLLISIONS_MOVES=SimpleNamespace( ONE='first object', TWO='second object' @@ -349,9 +416,56 @@ YES='yes', NO='no' ), - TARGET_SIDE=SimpleNamespace( + REFERENCE_SIDE=SimpleNamespace( + LEFT='left', + RIGHT='right' + ), + + TURNTABLE_CONTAINER_TARGET_SIDE=SimpleNamespace( + LEFT='left', + MIDDLE='middle', + RIGHT='right' + ), + ROTATION=SimpleNamespace( + CLOCKWISE='clockwise', + COUNTER_CLOCKWISE='counterClockwise' + ), + SIDE=SimpleNamespace( + LEFT='left', + RIGHT='right', + EITHER='either' + ), + + CONTAINERS_SIDE=SimpleNamespace( + LEFT='left', + RIGHT='right' + ), + CONTAINERS_KIDNAP_ROTATION=SimpleNamespace( + FORTY_FIVE=45, + NINETY=90, + ONE_THIRTY_FIVE=135, + TWO_TWENTY_FIVE=225, + TWO_SEVENTY=270, + THREE_FIFTEEN=315 + ), + SHELL_GAME_MOVEMENT_DIRECTION=SimpleNamespace( LEFT='left', RIGHT='right' + ), + SHELL_GAME_CONTAINER_PICKED=SimpleNamespace( + LEFT='left', + MIDDLE='middle', + RIGHT='right' + ), + TARGET_BIN_GLOBAL_LOCATION=SimpleNamespace( + FRONT_RIGHT='front_right', + BACK_RIGHT='back_right', + BACK_LEFT='back_left', + FRONT_LEFT='front_left' + ), + AGENT_FACING_DIRECTION=SimpleNamespace( + LEFT='left', + RIGHT='right', ) ) @@ -380,26 +494,35 @@ SPATIO_TEMPORAL_CONTINUITY='stc', AGENT_IDENTIFICATION='agident', + ARITHMETIC='arith', HOLES='holes', + IMITATION_TASK='imit', + INTERACTIVE_COLLISION='intcoll', INTERACTIVE_CONTAINER='intcon', INTERACTIVE_OBJECT_PERMANENCE='intobjp', INTERACTIVE_OBSTACLE='intobs', INTERACTIVE_OCCLUDER='intocc', LAVA='lava', MOVING_TARGET_PREDICTION='movtar', + NUMBER_COMPARISON='numcomp', RAMP='ramp', + REFERENCE='ref', REORIENTATION='reor', + SEEING_LEADS_TO_KNOWING='seeknow', + SET_ROTATION='setrot', + SHELL_GAME='shell', SOLIDITY='solidity', SPATIAL_ELIMINATION='spateli', SUPPORT_RELATIONS='suprel', - TOOL_USE='tool' + TRAJECTORY='traj', + TOOL_USE='tool', ) def append_object_tags( scene_info: Dict[str, Any], objects_info: Dict[str, Any], - role_to_object_list: Dict[str, List[Dict[str, Any]]] + role_to_object_list: Dict[str, List[SceneObject]] ) -> None: """Appends object-specific tags from the given role_to_object_list onto the given scene_info and objects_info collections.""" @@ -418,7 +541,7 @@ def append_object_tags( def append_object_tags_of_type( scene_info: Dict[str, Any], objects_info: Dict[str, Any], - role_to_object_list: Dict[str, List[Dict[str, Any]]], + role_to_object_list: Dict[str, List[SceneObject]], role: str ) -> None: """Appends object-specific tags from the object_list in the given @@ -459,7 +582,7 @@ def append_object_tags_of_type( def append_familiarity_object_tags( scene_info: Dict[str, Any], objects_info: Dict[str, Any], - instance: Dict[str, Any], + instance: SceneObject, role: str, trained_tag: str, untrained_tag: str @@ -497,18 +620,53 @@ def tag_to_label(tag: str) -> str: return re.sub(r'(? str: + if (is_interactive_agents_task(task)): + return SCENE.INTERACTIVE_AGENTS + if (is_interactive_objects_task(task)): + return SCENE.INTERACTIVE_OBJECTS + if (is_interactive_places_task(task)): + return SCENE.INTERACTIVE_PLACES + if (is_passive_agent_task(task)): + return SCENE.PASSIVE_AGENTS + if (is_passive_physics_task(task)): + return SCENE.PASSIVE_OBJECT + + def is_passive_agent_task(task: str) -> bool: """Returns whether the given task is a passive agent task.""" return task in [ + TYPES.AGENT_BACKGROUND_AGENT_ONE_GOAL, + TYPES.AGENT_BACKGROUND_AGENT_PREFERENCE, + TYPES.AGENT_BACKGROUND_COLLECT, + TYPES.AGENT_BACKGROUND_INSTRUMENTAL_ACTION, + TYPES.AGENT_BACKGROUND_INSTRUMENTAL_APPROACH, + TYPES.AGENT_BACKGROUND_INSTRUMENTAL_IMITATION, + TYPES.AGENT_BACKGROUND_MULTIPLE_AGENTS, + TYPES.AGENT_BACKGROUND_NON_AGENT_ONE_GOAL, + TYPES.AGENT_BACKGROUND_NON_AGENT_PREFERENCE, + TYPES.AGENT_BACKGROUND_OBJECT_PREFERENCE, + TYPES.AGENT_BACKGROUND_SINGLE_OBJECT, + TYPES.AGENT_BACKGROUND_SOCIAL_APPROACH, + TYPES.AGENT_BACKGROUND_SOCIAL_IMITATION, + TYPES.AGENT_EVALUATION_AGENT_NON_AGENT, + TYPES.AGENT_EVALUATION_APPROACH, TYPES.AGENT_EVALUATION_EFFICIENT_IRRATIONAL, TYPES.AGENT_EVALUATION_EFFICIENT_PATH, TYPES.AGENT_EVALUATION_EFFICIENT_TIME, + TYPES.AGENT_EVALUATION_IMITATION, TYPES.AGENT_EVALUATION_INACCESSIBLE_GOAL, TYPES.AGENT_EVALUATION_INSTRUMENTAL_BLOCKING_BARRIERS, TYPES.AGENT_EVALUATION_INSTRUMENTAL_INCONSEQUENTIAL_BARRIERS, TYPES.AGENT_EVALUATION_INSTRUMENTAL_NO_BARRIERS, TYPES.AGENT_EVALUATION_MULTIPLE_AGENTS, - TYPES.AGENT_EVALUATION_OBJECT_PREFERENCE + TYPES.AGENT_EVALUATION_OBJECT_PREFERENCE, + TYPES.AGENT_EXAMPLE, + # While the Seeing Leads to Knowing hypercube will set its secondary + # type and goal category as "passive" (not "agents"), to differentiate + # it from the NYU passive agent tasks, its domain type should always + # be "passive agents", so keep it in this list. + SCENE.SEEING_LEADS_TO_KNOWING ] @@ -518,6 +676,56 @@ def is_passive_physics_task(task: str) -> bool: SCENE.COLLISIONS, SCENE.GRAVITY_SUPPORT, SCENE.OBJECT_PERMANENCE, + SCENE.SEEING_LEADS_TO_KNOWING, SCENE.SHAPE_CONSTANCY, SCENE.SPATIO_TEMPORAL_CONTINUITY ] + + +def is_multi_retrieval(task: str) -> bool: + """Returns whether the given task is a multi-retrieval task.""" + return task in [ + SCENE.ARITHMETIC, + SCENE.NUMBER_COMPARISON + ] + + +def is_interactive_places_task(task: str) -> bool: + """Returns whether the given task is a interactive places task.""" + return task in [ + SCENE.CONTAINER, + SCENE.HOLES, + SCENE.LAVA, + SCENE.OCCLUDER, + SCENE.OBSTACLE, + SCENE.RAMP, + SCENE.REORIENTATION, + SCENE.SET_ROTATION, + SCENE.SHELL_GAME, + SCENE.SPATIAL_ELIMINATION + ] + + +def is_interactive_agents_task(task: str) -> bool: + """Returns whether the given task is a interactive agents task.""" + return task in [ + SCENE.AGENT_IDENTIFICATION, + SCENE.IMITATION_TASK, + SCENE.SOCIAL_REFERENCING, + SCENE.REFERENCE + ] + + +def is_interactive_objects_task(task: str) -> bool: + """Returns whether the given task is a interactive objects task.""" + return task in [ + SCENE.ARITHMETIC, + SCENE.INTERACTIVE_COLLISION, + SCENE.INTERACTIVE_OBJECT_PERMANENCE, + SCENE.MOVING_TARGET_PREDICTION, + SCENE.NUMBER_COMPARISON, + SCENE.SOLIDITY, + SCENE.SUPPORT_RELATIONS, + SCENE.TRAJECTORY, + SCENE.TOOL_USE + ] diff --git a/generator/tools.py b/generator/tools.py new file mode 100644 index 0000000..99c0d83 --- /dev/null +++ b/generator/tools.py @@ -0,0 +1,490 @@ +import copy +import random +import uuid +from enum import Enum +from types import SimpleNamespace +from typing import Tuple + +from generator import materials +from generator.base_objects import ( + ALL_LARGE_BLOCK_TOOLS, + LARGE_BLOCK_NOVEL_TOOLS_TO_DIMENSIONS, + LARGE_BLOCK_TOOLS_TO_DIMENSIONS +) +from generator.objects import SceneObject +from generator.structures import create_interior_wall + +from .geometry import ( + PERFORMER_HALF_WIDTH, + ObjectBounds, + create_bounds, + rotate_point_around_origin +) +from .materials import MaterialTuple + +MAX_TOOL_LENGTH = 9 +USEFUL_LENGTH_MIN = 4 + +# Hooked Tool +HOOKED_TOOL_BUFFER = 2 + +# Tool Choice +MIN_TOOL_CHOICE_X_DIMENSION = 20 +MIN_LAVA_ISLAND_LONG_ROOM_DIMENSION_LENGTH = 13 +MIN_LAVA_ISLAND_SHORT_ROOM_DIMENSION_LENGTH = 7 + +TOOL_HEIGHT = 0.3 +TOOL_TEMPLATE = { + 'id': 'tool_', + 'type': None, + 'materials': ['UnityAssetStore/YughuesFreeMetalMaterials/Materials/M_YFMM_13'], # noqa: E501 + 'debug': { + 'info': [] + }, + 'shows': [{ + 'stepBegin': 0, + 'position': { + 'x': 0, + 'y': TOOL_HEIGHT / 2.0, + 'z': 0 + }, + 'rotation': { + 'x': 0, + 'y': 0, + 'z': 0 + }, + 'scale': { + 'x': 1, + 'y': 1, + 'z': 1 + } + }] +} + + +# broken tool +BROKEN_TOOL_VERTICAL_SEPARATION = 1.35 +BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN = 0.75 +BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX = 1.5 + +# inaccessible tool +INACCESSIBLE_TOOL_BLOCKING_WALL_HEIGHT = 0.25 +INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH = 0.1 +INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION = 0.5 + + +class ImprobableToolOption(str, Enum): + NO_TOOL = 'no_tool' + TOO_SHORT_TOOL = 'too_short' + BROKEN_TOOL = 'broken' + INACCESSIBLE_TOOL = 'inaccessible' + INACCESSIBLE_DIAGONAL = 'inaccessible_diagonal' + INACCESSIBLE_ROTATED = 'inaccessible_rotated' + INACCESSIBLE_MISALIGNED = 'inaccessible_misaligned' + + +TOOL_TYPES = SimpleNamespace( + RECT="rectangular", + HOOKED="hooked", + BROKEN="broken", + SMALL="small", + INACCESSIBLE="inaccessible", + INACCESSIBLE_DIAGONAL="inaccessible_diagonal", + INACCESSIBLE_ROTATED="inaccessible_rotated", + INACCESSIBLE_MISALIGNED="inaccessible_misaligned", + ISOSCELES="isosceles", + NO_TOOL="no_tool" +) + + +def get_tool_shape( + tool_length: float, + tool_category: TOOL_TYPES, + tool_width: float = None +) -> str: + """Returns the tool shape (object type) matching the given arguments, or + None if no such tool shape exists. Both tool_length and/or tool_width can + be None to randomly choose from any matching tool shapes.""" + if tool_category == TOOL_TYPES.NO_TOOL: + return None + if tool_category in [TOOL_TYPES.SMALL, TOOL_TYPES.BROKEN]: + # Tool length must be 1 for SMALL and BROKEN tools. + tool_length = 1 + + tools = [] + for tool, (width, length) in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items(): + if tool_category == TOOL_TYPES.HOOKED: + if 'hooked' not in tool: + continue + elif tool_category == TOOL_TYPES.ISOSCELES: + if 'isosceles' not in tool: + continue + elif tool_category in [TOOL_TYPES.SMALL, TOOL_TYPES.BROKEN]: + if 'rect' not in tool or length != 1: + continue + else: + # Default to RECT tools (including for INACCESSIBLE type). + if 'rect' not in tool or length < USEFUL_LENGTH_MIN: + continue + if tool_length and tool_length != length: + continue + # For asymmetric tools, use the width from the tool string rather than + # from the actual dimensions. + if tool_category in [TOOL_TYPES.HOOKED, TOOL_TYPES.ISOSCELES]: + width = get_tool_width_from_type(tool) + if tool_width and tool_width != width: + continue + tools.append(tool) + + return random.choice(tools) if tools else None + + +def get_tool_width_from_type(tool_type: str) -> float: + # Assume tool type format: "tool_whatever_A_AA_x_B_BB" + index = len(tool_type) - 11 + width = float(tool_type[index:(index + 4)].replace('_', '.')) + return width + + +def finalize_tool( + tool: SceneObject, + width: float, + length: float, + bounds: ObjectBounds = None, + material_tuple: MaterialTuple = None +) -> SceneObject: + """Finalize and return the given tool.""" + tool['id'] += str(uuid.uuid4()) + tool['materials'] = [material_tuple.material] + tool["debug"]["color"] = material_tuple.color + tool['debug']['dimensions'] = { + 'x': width, + 'y': TOOL_HEIGHT, + 'z': length + } + colors = material_tuple.color + tool['shows'][0]['boundingBox'] = bounds or create_bounds( + dimensions=tool['debug']['dimensions'], + offset={'x': 0, 'y': 0, 'z': 0}, + position=tool['shows'][0]['position'], + rotation=tool['shows'][0]['rotation'], + standing_y=(TOOL_HEIGHT * 0.5) + ) + tool['debug']['info'] = colors + ['tool'] + [ + f'{color} tool' for color in colors + ] + [f'{" ".join(colors)} tool'] + return tool + + +def create_tool( + object_type: str, + position_x: float = 0, + position_z: float = 0, + rotation_y: float = 0, + bounds: ObjectBounds = None, + material_tuple: MaterialTuple = materials.TOOL_MATERIALS[0] +) -> SceneObject: + """Create and return an instance of a tool.""" + tool = SceneObject(copy.deepcopy(TOOL_TEMPLATE)) + tool['type'] = object_type + tool['shows'][0]['position']['x'] = position_x + tool['shows'][0]['position']['z'] = position_z + tool['shows'][0]['rotation']['y'] = rotation_y + dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(object_type) + if not dimensions: + raise Exception(f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') + tool = finalize_tool( + tool, + dimensions[0], + dimensions[1], + bounds, + material_tuple + ) + return tool + + +def get_novel_tool_shape(tool_length): + tools = [] + tools = [ + tool + for tool, (_, length) in LARGE_BLOCK_NOVEL_TOOLS_TO_DIMENSIONS.items() + if (length == tool_length) + ] + return random.choice(tools) + + +def get_scene_tool_length(scene): + for obj in scene.objects: + if 'tool' in obj['type']: + return float(obj['type'][-4:].replace("_", ".")) + + +def get_too_small_tool(): + tools = [ + tool + for tool, (_, length) in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items() + if tool.startswith('tool_rect') and length < USEFUL_LENGTH_MIN + ] + return random.choice(tools) + + +def get_tool_choice_tool_object(scene, is_valid): + for obj in scene.objects: + if 'tool' in obj['type'] and obj['shows'][0]['position']['x'] >= 0 \ + and is_valid: + return obj + if 'tool' in obj['type'] and obj['shows'][0]['position']['x'] < 0 \ + and not is_valid: + return obj + + +def create_broken_tool( + object_type: str, + direction: str, + width_position: float, + max_broken_tool_length_pos: float, + min_broken_tool_length_pos: float, + rotation_for_entire_tool: float, + length: int, + material_tuple: MaterialTuple = materials.TOOL_MATERIALS[0] +) -> SceneObject: + """Create and return an instance of a broken tool.""" + tools_before_rotation = [] + final_tools = [] + current_pos = max_broken_tool_length_pos # start at lava edge + for _ in range(length): + tool = SceneObject(copy.deepcopy(TOOL_TEMPLATE)) + tool['type'] = object_type + tool['shows'][0]['position']['x'] = \ + (width_position + round(random.uniform( + BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN, + BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX), 2) * + random.choice([-1, 1]) if direction == 'z' else current_pos) + tool['shows'][0]['position']['z'] = \ + (width_position + round(random.uniform( + BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN, + BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX), 2) * + random.choice([-1, 1]) if direction == 'x' else current_pos) + tool['shows'][0]['rotation']['y'] = random.randint(0, 359) + dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(object_type) + if not dimensions: + raise Exception( + f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') + temp_tool = copy.deepcopy(tool) + tools_before_rotation.append(temp_tool) + current_pos -= BROKEN_TOOL_VERTICAL_SEPARATION + center_of_tool = (max_broken_tool_length_pos + + min_broken_tool_length_pos) / 2 + for tool in tools_before_rotation: + tool_pos = tool['shows'][0]['position'] + (x, z) = rotate_point_around_origin( + origin_x=width_position if direction == 'z' else + center_of_tool, + origin_z=width_position if direction == 'x' else + center_of_tool, + point_x=tool_pos['x'], + point_z=tool_pos['z'], + rotation=rotation_for_entire_tool) + tool_pos['x'] = round(x, 2) + tool_pos['z'] = round(z, 2) + tool = finalize_tool( + tool, + dimensions[0], + dimensions[1], + bounds=None, + material_tuple=material_tuple + ) + final_tools.append(tool) + return final_tools + + +def create_inaccessible_tool( + tool_type: str, + long_direction: str, + short_direction: str, + original_short_position: float, + original_long_position: float, + tool_horizontal_offset: float, + tool_offset_backward_from_lava: float, + blocking_wall_horizontal_offset: float, + tool_rotation_y: int, + room_dimension_x: float, + room_dimension_z: float, + blocking_wall_material: MaterialTuple, + bounds: ObjectBounds = None, + material_tuple: MaterialTuple = materials.TOOL_MATERIALS[0] +) -> Tuple[SceneObject, SceneObject, str, float, float]: + """ + Create and return an instance of an inaccessible tool and its blocking wall + - `tool_type` (string): The type of tool + - `long_direction` (string): The direction to tool extends to reach + the target. + - `short_direction` (string): The direction of the tool width + - `original_short_position` (float): The original position of the tool + that is centered with the target. + - `original_long_position` (float): The original position of the tool that + is shifted to the edge of the lava pool. + - `tool_horizontal_offset` (float): The left and right offset the tool + should be moved from the blocking wall position. + - `tool_offset_backwards_from_lava` (float): The offset away from the + lava pool the tool should move from its original position. + - `blocking_wall_separation` (float): The separation from the tool the + blocking wall should have. Negative numbers will be on left side, positive + will be on right. The minimum separation should always be greater than or + equal to 0.5 or less than or equal to -0.5. + - `tool_rotation_y` (float): The rotation of the tool. + - `room_dimension_x` (float): X Room dimension. + - `room_dimension_z` (float): Z Room dimension. + - `blocking_wall_material` (MaterialTyple): Material for the blocking wall. + - `bounds` (ObjectBounds): Object Bounds. Default: None + """ + # Make sure the blocking wall separation is valid + if abs(blocking_wall_horizontal_offset) < \ + INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION: + raise Exception( + "The minimum separation of the inaccessible tool blocking " + "wall should always be greater than or equal to " + f"{INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION} or " + f"less than or equal to " + f"-{INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION}") + + """ + short direction is the axis of width when the tool is + aligned with the target. Left right directions: + For 'x' short direction when the tool long direction is on the z axis + ------------------------ + | (z+) | + | front | + |(x-) left * right (x+)| + | back | + | (z-) | + ------------------------ + + For 'z' short direction the tool long direction is on the x axis + everything is rotated clockwise ⟳ 90 + ------------------------ + | (z+) | + | left | + |(x-) back * front (x+)| + | right | + | (z-) | + ------------------------ + """ + # Wall creation + # Create a straight wall positioned vertically across the room + # If z short direction reverse separation direction because left is now + # z 'positive' axis instead of x 'negative' axis + original_offset = blocking_wall_horizontal_offset + if short_direction == 'z': + blocking_wall_horizontal_offset *= -1 + blocking_wall_horizontal_offset = \ + original_short_position + blocking_wall_horizontal_offset + length = room_dimension_z if short_direction == 'x' else room_dimension_x + wall_rotation = 0 if short_direction == 'x' else 90 + x = blocking_wall_horizontal_offset if short_direction == 'x' else 0 + z = blocking_wall_horizontal_offset if short_direction == 'z' else 0 + horizontal_pos = x if short_direction == 'x' else z + wall = create_interior_wall( + position_x=x, + position_z=z, + rotation_y=wall_rotation, + width=INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH, + height=INACCESSIBLE_TOOL_BLOCKING_WALL_HEIGHT, + material_tuple=blocking_wall_material, + position_y_modifier=0, + thickness=length + ) + blocking_wall_pos_cutoff = ( + horizontal_pos + + (INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH + PERFORMER_HALF_WIDTH) * + (-1 if blocking_wall_horizontal_offset > 0 else 1)) + room_wall_pos_cutoff = ( + (room_dimension_x / 2 - PERFORMER_HALF_WIDTH) * + (-1 if original_offset > 0 else 1) * + (-1 if long_direction == 'x' else 1)) + + adjusted_offset_x = 0 + adjusted_offset_z = 0 + base_x = wall['shows'][0]['position']['x'] + base_z = wall['shows'][0]['position']['z'] + # Make the tool twice, the first time figure out where its bounds are + # after its been rotated. + # Calculate the difference from its maximum or minimum bounds to the edge + # of the wall and then shift the tool so its aligned perfectly to wall edge + # Then apply the tool horizontal offset after so that the closet edge + # of the tool is the correct horizontal offset away rather than the + # the tool center. + for _ in range(2): + # Tool creation + tool = SceneObject(copy.deepcopy(TOOL_TEMPLATE)) + tool['type'] = tool_type + x = ( + ((base_x + tool_horizontal_offset) if short_direction == 'x' else + (original_long_position - tool_offset_backward_from_lava)) + + adjusted_offset_x + ) + z = ( + ((base_z - tool_horizontal_offset) if short_direction == 'z' else + (original_long_position - tool_offset_backward_from_lava)) - + adjusted_offset_z + ) + tool['shows'][0]['position']['x'] = x + tool['shows'][0]['position']['z'] = z + tool['shows'][0]['rotation']['y'] = tool_rotation_y + dimensions = LARGE_BLOCK_TOOLS_TO_DIMENSIONS.get(tool_type) + if not dimensions: + raise Exception( + f'Tool object type must be in {ALL_LARGE_BLOCK_TOOLS}') + # Create the tool + tool = finalize_tool( + tool, + dimensions[0], + dimensions[1], + bounds, + material_tuple + ) + tool_bounds = tool['shows'][0]['boundingBox'].box_xz + # Maximum bounds and minimum bounds of tool in short direction + maximum_bound = round(max(getattr(pos, short_direction) + for pos in tool_bounds), 2) + minimum_bound = round(min(getattr(pos, short_direction) + for pos in tool_bounds), 2) + cutoff = (wall['shows'][0][ + 'position']['x' if short_direction == 'x' else 'z'] - + (INACCESSIBLE_TOOL_BLOCKING_WALL_WIDTH * + (1 if original_offset > 0 else -1) * + (1 if long_direction == 'x' else -1))) + if short_direction == 'x': + difference = cutoff - \ + (maximum_bound if original_offset < 0 else minimum_bound) + adjusted_offset_x += difference + tool_horizontal_offset + adjusted_offset_z = 0 + else: + difference = cutoff - \ + (maximum_bound if original_offset > 0 else minimum_bound) + adjusted_offset_z -= difference - tool_horizontal_offset + adjusted_offset_x = 0 + + """ + Simple example of what we should have now. + This example is with a negative blocking wall separation and + tool long direction on the z axis, short direction on x axis. + P = Performer + T = Target + * = Lava + C1 = (cutoff 1) blocking_wall_pos_cutoff + C2 = (cutoff 2) room_wall_pos_cutoff + x = x wall + z = z wall + |--------(z+)-------| + | |B| *** | + | |l| *T* | + (x-) |t| |o| *** (x+) + | |o| |c| | + | |o| |k| P | + | |l| |k| | + |--------(z-)-------| + [C1---C2] + """ + return (tool, wall, short_direction, blocking_wall_pos_cutoff, + room_wall_pos_cutoff) diff --git a/hypercube/LEGACY_PASSIVE_AGENT_DATASETS.md b/hypercube/LEGACY_PASSIVE_AGENT_DATASETS.md index 53ac7b2..cd7224e 100644 --- a/hypercube/LEGACY_PASSIVE_AGENT_DATASETS.md +++ b/hypercube/LEGACY_PASSIVE_AGENT_DATASETS.md @@ -38,6 +38,26 @@ Winter 2020 Evaluation/Testing: - [agents_evaluation_multiple_agents.zip](https://nyu-datasets.s3.amazonaws.com/agents_evaluation_multiple_agents.zip) - [agents_evaluation_object_preference_eval_4.zip](https://nyu-datasets.s3.amazonaws.com/agents_evaluation_object_preference_eval_4.zip) +Spring 2023 Background/Training: + +* Please note: The format for some of these scene files has changed, so some datasets may no longer work properly. If you have trouble with them, use the evaluation/testing datasets instead, or ask NYU for more background/training data if necessary. * + +- [agents_background_agent_one_goal.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_agent_one_goal.zip) +- [agents_background_agent_preference.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_agent_preference.zip) +- [agents_background_collect.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_collect.zip) +- [agents_background_instrumental_approach.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_instrumental_approach.zip) +- [agents_background_instrumental_imitation.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_instrumental_imitation.zip) * Please note: Scene 000000iie.json is a bad file! Ignore it. * +- [agents_background_non_agent_one_goal.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_non_agent_one_goal.zip) +- [agents_background_non_agent_preference.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_non_agent_preference.zip) +- [agents_background_social_approach.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_social_approach.zip) +- [agents_background_social_imitation.zip](https://nyu-datasets.s3.amazonaws.com/agents_background_social_imitation.zip) + +Spring 2023 Evaluation/Testing: + +- [agents_evaluation_approach.zip](https://nyu-datasets.s3.amazonaws.com/agents_evaluation_approach.zip) +- [agents_evaluation_imitation.zip](https://nyu-datasets.s3.amazonaws.com/agents_evaluation_imitation.zip) +- [agents_evaluation_non_agent.zip](https://nyu-datasets.s3.amazonaws.com/agents_evaluation_non_agent.zip) + Unzipping each bundle should extract a corresponding folder (containing the JSON files) nested within this folder, with a name like "agents_background_object_preference". Now you can generate the MCS agent scene JSON files, converted from the NYU JSON files. diff --git a/hypercube/__init__.py b/hypercube/__init__.py index 29de350..6ded90a 100644 --- a/hypercube/__init__.py +++ b/hypercube/__init__.py @@ -1,26 +1,25 @@ from .agent_hypercubes import ( - AgentEfficientActionIrrationalEvaluationHypercubeFactory, - AgentEfficientActionPathEvaluationHypercubeFactory, - AgentEfficientActionTimeEvaluationHypercubeFactory, - AgentExamplesEvaluationHypercubeFactory, - AgentExamplesTrainingHypercubeFactory, AgentHypercube, AgentHypercubeFactory, - AgentInaccessibleGoalEvaluationHypercubeFactory, - AgentInstrumentalActionBlockingBarriersEvaluationHypercubeFactory, - AgentInstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory, - AgentInstrumentalActionNoBarriersEvaluationHypercubeFactory, - AgentInstrumentalActionTrainingHypercubeFactory, - AgentMultipleAgentsEvaluationHypercubeFactory, - AgentMultipleAgentsTrainingHypercubeFactory, - AgentObjectPreferenceEvaluationHypercubeFactory, - AgentObjectPreferenceTrainingHypercubeFactory, - AgentSingleObjectTrainingHypercubeFactory + EfficientActionIrrationalEvaluationHypercubeFactory, + EfficientActionPathEvaluationHypercubeFactory, + EfficientActionTimeEvaluationHypercubeFactory, + ExamplesEvaluationHypercubeFactory, + ExamplesTrainingHypercubeFactory, + InaccessibleGoalEvaluationHypercubeFactory, + InstrumentalActionBlockingBarriersEvaluationHypercubeFactory, + InstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory, + InstrumentalActionNoBarriersEvaluationHypercubeFactory, + InstrumentalActionTrainingHypercubeFactory, + MultipleAgentsEvaluationHypercubeFactory, + MultipleAgentsTrainingHypercubeFactory, + ObjectPreferenceEvaluationHypercubeFactory, + ObjectPreferenceTrainingHypercubeFactory, + SingleObjectTrainingHypercubeFactory ) from .hypercubes import ( Hypercube, HypercubeFactory, - get_skewed_bell_curve_for_room_size, initialize_goal, update_floor_and_walls, update_scene_objects, diff --git a/hypercube/agent_hypercubes.py b/hypercube/agent_hypercubes.py index d412dbc..2718321 100644 --- a/hypercube/agent_hypercubes.py +++ b/hypercube/agent_hypercubes.py @@ -3,11 +3,13 @@ import logging import os import random -from typing import Any, Callable, Dict, List +from typing import Callable, Dict, List -from generator import Scene, SceneException, tags +from machine_common_sense.config_manager import Goal -from .agent_scene_pair_json_converter import convert_scene_pair +from generator import Scene, tags + +from .agent_scene_pair_json_converter import OccluderMode, convert_scene_pair from .hypercubes import Hypercube, HypercubeFactory logger = logging.getLogger(__name__) @@ -21,11 +23,13 @@ def __init__( task_type: str, role_to_type: Dict[str, str], training=False, - untrained=False + untrained=False, + occluder_mode=None ) -> None: self._filename_prefix = filename_prefix self._role_to_type = role_to_type self._untrained = untrained + self._occluder_mode = occluder_mode super().__init__( task_type, starter_scene, @@ -37,7 +41,7 @@ def __init__( def _create_scenes( self, starter_scene: Scene, - goal_template: Dict[str, Any] + goal_template: Goal ) -> List[Scene]: # Each JSON filename will have a suffix of either 'e.json' or 'u.json' json_filename = { @@ -77,20 +81,26 @@ def _create_scenes( json_data['unexpected'], self._filename_prefix, self._role_to_type, - self._untrained + self._untrained, + occluder_mode=self._occluder_mode ) # Remember a training hypercube will only have its expected scene. - scenes[0].goal['sceneInfo'][tags.SCENE.ID] = [os.path.splitext( + scenes[0].goal.scene_info[tags.SCENE.ID] = [os.path.splitext( os.path.basename(json_filename['expected']) )[0]] if len(scenes) > 1: - scenes[1].goal['sceneInfo'][tags.SCENE.ID] = [os.path.splitext( + scenes[1].goal.scene_info[tags.SCENE.ID] = [os.path.splitext( os.path.basename(json_filename['unexpected']) )[0]] return scenes + # Override + def get_info(self) -> str: + """Return unique hypercube info. Can override as needed.""" + return self._filename_prefix + # Override def _get_slices(self) -> List[str]: """Return all of this hypercube's slices (string tags).""" @@ -114,6 +124,7 @@ def __init__( self._folder_name = folder_name self._task_type = task_type self._untrained = False + self._occluder_mode = OccluderMode.NONE # Override def _build(self, starter_scene: Scene) -> Hypercube: @@ -124,13 +135,15 @@ def _build(self, starter_scene: Scene) -> Hypercube: self.role_to_type, self.training, # Must use only untrained shapes in 50% of scenes. - untrained=self._untrained + untrained=self._untrained, + # For the agent/non-agent scenes. + occluder_mode=self._occluder_mode ) # Override - def build( + def generate_hypercubes( self, - total: str, + total: int, starter_scene_function: Callable[[], Scene], role_to_type: Dict[str, str], throw_error=False, @@ -143,6 +156,19 @@ def build( # in the folder associated with this factory, or up to the given total. hypercubes = [] + if self.training: + logger.info( + 'Agent hypercube factory set to generate training scenes; ' + 'only "expected" JSON scene files are required (ending with ' + '"e.json")' + ) + else: + logger.info( + 'Agent hypercube factory set to generate evaluation scenes; ' + 'both "expected" and "unexpected" JSON scene files are ' + 'required (ending with "e.json" or "u.json" respectively)' + ) + prefix_to_number = {} # Each JSON filename will have a suffix of either 'e.json' or 'u.json' for suffix in ['e.json', 'u.json']: @@ -154,7 +180,7 @@ def build( logger.info( f'Agent hypercube factory found {len(prefix_to_number.items())} ' - f'pairs of scene JSON files in {self._folder_name}' + f'sets of scene JSON files in {self._folder_name}' ) # Randomize (or sort, for testing) the order of the files. @@ -166,55 +192,46 @@ def build( # Generate one hypercube per valid pair of files. count = 0 - failed_filename_list = [] for prefix, number in randomized_prefix_to_number: if (not self.training) and (number < 2): logger.warn( - f'[NOTE] Agent hypercube factory found only one scene ' + f'Agent hypercube factory found only one scene ' f'JSON file in {self._folder_name} but expected two ' f'named {prefix + "e.json"} and {prefix + "u.json"}' ) continue count += 1 - logger.info(f'Generating agent hypercube {count} / {total}') self._filename_prefix = prefix - try: - hypercube = self._build(starter_scene_function()) - hypercubes.append(hypercube) - # Every other scene pair should have untrained objects. - self._untrained = (not self._untrained) - except ( - RuntimeError, - ZeroDivisionError, - TypeError, - SceneException, - ValueError - ) as e: - if throw_error: - raise - logger.warn(f'[ERROR] Failed to create {self.name} hypercube') - logger.exception(e) - failed_filename_list.append(prefix) - if count == total: - break + hypercube = self._build(starter_scene_function()) + hypercubes.append(hypercube) + # Every other scene pair should have untrained objects. + self._untrained = (not self._untrained) + if count % 100 == 0: + logger.info( + f'Finished initialization of {count} / ' + f'{len(randomized_prefix_to_number)} ' + f'{self._task_type} hypercubes...' + ) if count < total: - logger.debug( - f'[NOTE] Agent hypercube factory found only ' + logger.info( + f'Agent hypercube factory found only ' f'{count} valid pairs of scene JSON files in ' f'{self._folder_name} but {total} were required ' f'via command line argument.' ) - - if len(failed_filename_list) > 0: - logger.warn('[NOTE] The following agent scene files failed:') - for filename in failed_filename_list: - logger.warn(f'{filename}') + if count > total: + logger.info( + f'Agent hypercube factory found {count} valid pairs of scene ' + f'JSON files in {self._folder_name} but only {total} were ' + f'required via command line argument; extra scenes will only ' + f'be used if other scenes fail.' + ) return hypercubes -class AgentInstrumentalActionTrainingHypercubeFactory(AgentHypercubeFactory): +class InstrumentalActionTrainingHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentInstrumentalActionTraining', @@ -224,7 +241,7 @@ def __init__(self) -> None: ) -class AgentMultipleAgentsTrainingHypercubeFactory(AgentHypercubeFactory): +class MultipleAgentsTrainingHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentMultipleAgentsTraining', @@ -234,7 +251,7 @@ def __init__(self) -> None: ) -class AgentObjectPreferenceTrainingHypercubeFactory(AgentHypercubeFactory): +class ObjectPreferenceTrainingHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentObjectPreferenceTraining', @@ -244,7 +261,7 @@ def __init__(self) -> None: ) -class AgentSingleObjectTrainingHypercubeFactory(AgentHypercubeFactory): +class SingleObjectTrainingHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentSingleObjectTraining', @@ -254,7 +271,7 @@ def __init__(self) -> None: ) -class AgentEfficientActionIrrationalEvaluationHypercubeFactory( +class EfficientActionIrrationalEvaluationHypercubeFactory( AgentHypercubeFactory ): def __init__(self) -> None: @@ -266,9 +283,7 @@ def __init__(self) -> None: ) -class AgentEfficientActionPathEvaluationHypercubeFactory( - AgentHypercubeFactory -): +class EfficientActionPathEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentEfficientActionPath', @@ -278,9 +293,7 @@ def __init__(self) -> None: ) -class AgentEfficientActionTimeEvaluationHypercubeFactory( - AgentHypercubeFactory -): +class EfficientActionTimeEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentEfficientActionTime', @@ -290,7 +303,7 @@ def __init__(self) -> None: ) -class AgentInaccessibleGoalEvaluationHypercubeFactory(AgentHypercubeFactory): +class InaccessibleGoalEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentInaccessibleGoal', @@ -300,7 +313,7 @@ def __init__(self) -> None: ) -class AgentInstrumentalActionBlockingBarriersEvaluationHypercubeFactory( +class InstrumentalActionBlockingBarriersEvaluationHypercubeFactory( AgentHypercubeFactory ): def __init__(self) -> None: @@ -312,7 +325,7 @@ def __init__(self) -> None: ) -class AgentInstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory( +class InstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory( AgentHypercubeFactory ): def __init__(self) -> None: @@ -324,7 +337,7 @@ def __init__(self) -> None: ) -class AgentInstrumentalActionNoBarriersEvaluationHypercubeFactory( +class InstrumentalActionNoBarriersEvaluationHypercubeFactory( AgentHypercubeFactory ): def __init__(self) -> None: @@ -336,7 +349,7 @@ def __init__(self) -> None: ) -class AgentMultipleAgentsEvaluationHypercubeFactory(AgentHypercubeFactory): +class MultipleAgentsEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentMultipleAgents', @@ -346,7 +359,7 @@ def __init__(self) -> None: ) -class AgentObjectPreferenceEvaluationHypercubeFactory(AgentHypercubeFactory): +class ObjectPreferenceEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentObjectPreference', @@ -356,44 +369,181 @@ def __init__(self) -> None: ) -class AgentExamplesTrainingHypercubeFactory(AgentHypercubeFactory): +class ExamplesTrainingHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentExamplesTraining', 'agents_examples', - 'agents examples', + tags.TYPES.AGENT_EXAMPLE, training=True ) -class AgentExamplesEvaluationHypercubeFactory(AgentHypercubeFactory): +class ExamplesEvaluationHypercubeFactory(AgentHypercubeFactory): def __init__(self) -> None: super().__init__( 'AgentExamples', 'agents_examples', - 'agents examples', + tags.TYPES.AGENT_EXAMPLE, training=False ) +class AgentOneGoalTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentOneGoal', + 'agents_background_agent_one_goal', + tags.TYPES.AGENT_BACKGROUND_AGENT_ONE_GOAL, + training=True + ) + self._occluder_mode = OccluderMode.TRAINING + + +class AgentPreferenceTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentPreference', + 'agents_background_agent_preference', + tags.TYPES.AGENT_BACKGROUND_AGENT_PREFERENCE, + training=True + ) + self._occluder_mode = OccluderMode.TRAINING + + +class CollectTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentCollect', + 'agents_background_collect', + tags.TYPES.AGENT_BACKGROUND_COLLECT, + training=True + ) + + +class InstrumentalApproachTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentInstrumentalApproach', + 'agents_background_instrumental_approach', + tags.TYPES.AGENT_BACKGROUND_INSTRUMENTAL_APPROACH, + training=True + ) + + +class InstrumentalImitationTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentInstrumentalImitation', + 'agents_background_instrumental_imitation', + tags.TYPES.AGENT_BACKGROUND_INSTRUMENTAL_IMITATION, + training=True + ) + + +class NonAgentEvaluationHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentNonAgent', + 'agents_evaluation_non_agent', + tags.TYPES.AGENT_EVALUATION_AGENT_NON_AGENT, + training=False + ) + self._occluder_mode = OccluderMode.EVAL + + +class NonAgentOneGoalTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentNonAgentOneGoal', + 'agents_background_non_agent_one_goal', + tags.TYPES.AGENT_BACKGROUND_NON_AGENT_ONE_GOAL, + training=True + ) + self._occluder_mode = OccluderMode.TRAINING + + +class NonAgentPreferenceTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentNonAgentPreference', + 'agents_background_non_agent_preference', + tags.TYPES.AGENT_BACKGROUND_NON_AGENT_PREFERENCE, + training=True + ) + self._occluder_mode = OccluderMode.TRAINING + + +class SocialApproachEvaluationHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentApproach', + 'agents_evaluation_approach', + tags.TYPES.AGENT_EVALUATION_APPROACH, + training=False + ) + + +class SocialApproachTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentSocialApproach', + 'agents_background_social_approach', + tags.TYPES.AGENT_BACKGROUND_SOCIAL_APPROACH, + training=True + ) + + +class SocialImitationEvaluationHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentImitation', + 'agents_evaluation_imitation', + tags.TYPES.AGENT_EVALUATION_IMITATION, + training=False + ) + + +class SocialImitationTrainingHypercubeFactory(AgentHypercubeFactory): + def __init__(self) -> None: + super().__init__( + 'AgentSocialImitation', + 'agents_background_social_imitation', + tags.TYPES.AGENT_BACKGROUND_SOCIAL_IMITATION, + training=True + ) + + AGENT_TRAINING_HYPERCUBE_LIST = [ - AgentInstrumentalActionTrainingHypercubeFactory(), - AgentMultipleAgentsTrainingHypercubeFactory(), - AgentObjectPreferenceTrainingHypercubeFactory(), - AgentSingleObjectTrainingHypercubeFactory(), - AgentExamplesTrainingHypercubeFactory() + AgentOneGoalTrainingHypercubeFactory(), + AgentPreferenceTrainingHypercubeFactory(), + CollectTrainingHypercubeFactory(), + InstrumentalActionTrainingHypercubeFactory(), + InstrumentalApproachTrainingHypercubeFactory(), + InstrumentalImitationTrainingHypercubeFactory(), + MultipleAgentsTrainingHypercubeFactory(), + NonAgentOneGoalTrainingHypercubeFactory(), + NonAgentPreferenceTrainingHypercubeFactory(), + ObjectPreferenceTrainingHypercubeFactory(), + SingleObjectTrainingHypercubeFactory(), + SocialApproachTrainingHypercubeFactory(), + SocialImitationTrainingHypercubeFactory(), + ExamplesTrainingHypercubeFactory() ] AGENT_EVALUATION_HYPERCUBE_LIST = [ - AgentEfficientActionIrrationalEvaluationHypercubeFactory(), - AgentEfficientActionPathEvaluationHypercubeFactory(), - AgentEfficientActionTimeEvaluationHypercubeFactory(), - AgentInaccessibleGoalEvaluationHypercubeFactory(), - AgentInstrumentalActionBlockingBarriersEvaluationHypercubeFactory(), - AgentInstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory(), - AgentInstrumentalActionNoBarriersEvaluationHypercubeFactory(), - AgentMultipleAgentsEvaluationHypercubeFactory(), - AgentObjectPreferenceEvaluationHypercubeFactory(), - AgentExamplesEvaluationHypercubeFactory() + EfficientActionIrrationalEvaluationHypercubeFactory(), + EfficientActionPathEvaluationHypercubeFactory(), + EfficientActionTimeEvaluationHypercubeFactory(), + InaccessibleGoalEvaluationHypercubeFactory(), + InstrumentalActionBlockingBarriersEvaluationHypercubeFactory(), + InstrumentalActionInconsequentialBarriersEvaluationHypercubeFactory(), + InstrumentalActionNoBarriersEvaluationHypercubeFactory(), + MultipleAgentsEvaluationHypercubeFactory(), + NonAgentEvaluationHypercubeFactory(), + ObjectPreferenceEvaluationHypercubeFactory(), + SocialApproachEvaluationHypercubeFactory(), + SocialImitationEvaluationHypercubeFactory(), + ExamplesEvaluationHypercubeFactory() ] diff --git a/hypercube/agent_scene_pair_json_converter.py b/hypercube/agent_scene_pair_json_converter.py index 682ddb2..a119baa 100644 --- a/hypercube/agent_scene_pair_json_converter.py +++ b/hypercube/agent_scene_pair_json_converter.py @@ -3,16 +3,18 @@ import math import random import uuid +from enum import Enum, auto from typing import Any, Callable, Dict, List, Optional, Tuple import shapely -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d from generator import ( MaterialTuple, ObjectBounds, Scene, SceneException, + SceneObject, geometry, materials, structures, @@ -80,6 +82,13 @@ def __init__( self.z = z self.center_y = (y / 2.0) if center_y is None else center_y + def get_dict(self, scale: dict) -> dict: + return { + 'x': self.x * scale['x'], + 'y': self.y * scale['y'], + 'z': self.z * scale['z'] + } + class ObjectConfigWithMaterial(ObjectConfig): def __init__( @@ -124,7 +133,7 @@ def get_true_poly_points(self) -> List[Vector3d]: # Debug logging -SAVE_TRIALS_TO_FILE = True +SAVE_TRIALS_TO_FILE = False TRIALS_SUFFIX = '_trials.txt' EXPECTED = 'expected' @@ -135,12 +144,21 @@ def get_true_poly_points(self) -> List[Vector3d]: GRID_MAX_X = 2.5 GRID_MIN_Z = -2.5 GRID_MAX_Z = 2.5 +MAX_DISTANCE = math.dist([GRID_MIN_X, GRID_MIN_Z], [GRID_MAX_X, GRID_MAX_Z]) JSON_BORDER_WALL_MIN_X = 0 JSON_BORDER_WALL_MIN_Z = 0 JSON_BORDER_WALL_MAX_X = 180 JSON_BORDER_WALL_MAX_Z = 180 +# Grid used for positioning occluders in training scenes with occluders. +GRID = [] +for x in range(0, int(GRID_MAX_X - GRID_MIN_X) * 10 + 1): + for z in range(0, int(GRID_MAX_Z - GRID_MIN_Z) * 10 + 1): + # Exclude area for the static border wall. + if 5 < x < 40 and 5 < z < 40: + GRID.append((round(x / 10.0, 1), round(z / 10.0, 1))) + # The wait time in steps before and after the agent's movement in each trial. STARTING_STEP_WAIT_TIME = 3 PAUSED_STEP_WAIT_TIME = 2 @@ -148,16 +166,18 @@ def get_true_poly_points(self) -> List[Vector3d]: POST_DEFUSE_WAIT_TIME = 5 OBJECT_DIMENSIONS = { - 'blob_01': ObjectDimensions('blob_01', 0.26, 0.8, 0.36), + 'blob_01': ObjectDimensions('blob_01', 0.26, 0.8, 0.3), 'blob_02': ObjectDimensions('blob_02', 0.33, 0.78, 0.33), 'blob_03': ObjectDimensions('blob_03', 0.25, 0.69, 0.25), - 'blob_04': ObjectDimensions('blob_04', 0.3, 0.53, 0.3, 0.225), + 'blob_04': ObjectDimensions('blob_04', 0.3, 0.53, 0.29, 0.225), 'blob_05': ObjectDimensions('blob_05', 0.38, 0.56, 0.38, 0.24), - 'blob_06': ObjectDimensions('blob_06', 0.52, 0.5, 0.54), + 'blob_06': ObjectDimensions('blob_06', 0.5, 0.5, 0.52), 'blob_07': ObjectDimensions('blob_07', 0.25, 0.55, 0.25, 0.245), - 'blob_08': ObjectDimensions('blob_08', 0.27, 0.62, 0.15), - 'blob_09': ObjectDimensions('blob_09', 0.33, 0.78, 0.44), + 'blob_08': ObjectDimensions('blob_08', 0.24, 0.62, 0.15), + 'blob_09': ObjectDimensions('blob_09', 0.33, 0.78, 0.38), 'blob_10': ObjectDimensions('blob_10', 0.24, 0.5, 0.24), + 'blob_11': ObjectDimensions('blob_11', 0.35, 0.58, 0.35), + 'blob_12': ObjectDimensions('blob_12', 0.3, 0.48, 0.3), 'circle_frustum_with_base': ObjectDimensions( 'circle_frustum_with_base', 1, 2, 1), 'cone_with_base': ObjectDimensions('cone_with_base', 1, 2, 1), @@ -180,52 +200,55 @@ def get_true_poly_points(self) -> List[Vector3d]: } ROUND_TYPES = [ - 'blob_01', 'blob_02', 'blob_03', 'blob_04', 'blob_05', - 'blob_06', 'blob_07', 'blob_08', 'blob_09', 'blob_10', + 'blob_01', 'blob_02', 'blob_03', 'blob_04', 'blob_05', 'blob_06', + 'blob_07', 'blob_08', 'blob_09', 'blob_10', 'blob_11', 'blob_12', 'circle_frustum', 'circle_frustum_with_base', 'cone', 'cone_with_base', 'cylinder', 'semi_sphere', 'semi_sphere_with_base', 'sphere', 'tube_narrow', 'tube_wide' ] AGENT_OBJECT_CONFIG_LIST = [ - # Scaled to ensure that each agent's max X/Z dimension is 1 - AgentConfig('blob_01', 2.77), + # Scaled to ensure that each agent's max X/Z dimension is 1. + # Remove the C-shaped and S-shaped blobs for now. + # AgentConfig('blob_01', 3.33), AgentConfig('blob_02', 3.03), AgentConfig('blob_03', 4), AgentConfig('blob_04', 3.33), AgentConfig('blob_05', 2.63), - AgentConfig('blob_06', 1.85), + # AgentConfig('blob_06', 1.92), AgentConfig('blob_07', 4), - AgentConfig('blob_08', 3.7), - AgentConfig('blob_09', 2.27), + # AgentConfig('blob_08', 4.16), + # AgentConfig('blob_09', 2.63), AgentConfig('blob_10', 4.16), + AgentConfig('blob_11', 2.85), + AgentConfig('blob_12', 3.33), ] AGENT_OBJECT_MATERIAL_LIST = [ materials.BLUE, materials.GOLDENROD, materials.GREEN, materials.PURPLE - # Don't use red here because it looks too much like the maroon key. ] GOAL_OBJECT_CONFIG_LIST = [ # Scaled to ensure that each object's max X/Z dimension is 1 ObjectConfig('circle_frustum_with_base', 1, 0.5), ObjectConfig('cone_with_base', 1, 0.5), - ObjectConfig('cube', 1, 1), - ObjectConfig('cube_hollow_narrow', 1, 1), - ObjectConfig('cube_hollow_wide', 1, 1), + ObjectConfig('cube', 0.707, 1, rotation_y=45), + ObjectConfig('cube_hollow_narrow', 0.707, 1, rotation_y=45), + ObjectConfig('cube_hollow_wide', 0.707, 1, rotation_y=45), ObjectConfig('cylinder', 1, 0.5), # TODO MCS-1614 Replace these types with square or cylindrical versions. # ObjectConfig('hash', 1, 1), # ObjectConfig('letter_x', 1, 1), - ObjectConfig('pyramid_with_base', 1, 0.5), - # ObjectConfig('semi_sphere_with_base', 1, 0.5), + ObjectConfig('pyramid_with_base', 0.707, 0.5, rotation_y=45), + ObjectConfig('semi_sphere_with_base', 1, 0.5), ObjectConfig('sphere', 1, 1), - ObjectConfig('square_frustum_with_base', 1, 0.5), + ObjectConfig('square_frustum_with_base', 0.707, 0.5, rotation_y=45), ObjectConfig('tube_narrow', 1, 1), ObjectConfig('tube_wide', 1, 1), ] +GOAL_OBJECT_HIGHLIGHT_MATERIAL = materials.RED.material GOAL_OBJECT_MATERIAL_LIST = [ materials.AZURE, materials.BROWN, @@ -245,19 +268,19 @@ def get_true_poly_points(self) -> List[Vector3d]: # Make the home object as short as possible, without it looking weird in Unity. HOME_OBJECT_HEIGHT = [0.01, 0.02] -HOME_OBJECT_MATERIAL = MaterialTuple('Custom/Materials/Magenta', ['magenta']) +HOME_OBJECT_MATERIAL = materials.MAGENTA HOME_OBJECT_SIZE = [0.5, 0.5] WALL_OBJECT_HEIGHT = [0.0625, 0.125] -WALL_OBJECT_MATERIAL = MaterialTuple('Custom/Materials/Black', ['black']) +WALL_OBJECT_MATERIAL = materials.BLACK WALL_OBJECT_SIZE = [0.5, 0.5] FUSE_WALL_OBJECT_HEIGHT = [0.05, 0.1] -FUSE_WALL_OBJECT_MATERIAL = MaterialTuple('Custom/Materials/Lime', ['lime']) +FUSE_WALL_OBJECT_MATERIAL = materials.LIME FUSE_WALL_OBJECT_SIZE = [0.495, 0.495] KEY_OBJECT_HEIGHT = [FUSE_WALL_OBJECT_HEIGHT[0], 0.35] -KEY_OBJECT_MATERIAL = MaterialTuple('Custom/Materials/Maroon', ['maroon']) +KEY_OBJECT_MATERIAL = materials.MAROON KEY_OBJECT_SIZE = [FUSE_WALL_OBJECT_HEIGHT[1], 0.35] KEY_OBJECT_TYPE = 'triangle' KEY_OBJECT_ROTATION_X = 0 @@ -297,8 +320,13 @@ def get_true_poly_points(self) -> List[Vector3d]: LOCK_WALL_OBJECT_MATERIAL = FUSE_WALL_OBJECT_MATERIAL LOCK_WALL_OBJECT_SIZE = FUSE_WALL_OBJECT_SIZE +OCCLUDER_OBJECT_JSON_COORDS = [-35, 21] +OCCLUDER_OBJECT_JSON_SIZE = [10, 42] +OCCLUDER_OBJECT_MATERIAL = materials.WHITE +OCCLUDER_OBJECT_TYPE = 'cube' + PADDLE_OBJECT_HEIGHT = [0.25, 0.5] -PADDLE_OBJECT_MATERIAL = MaterialTuple('Custom/Materials/Black', ['black']) +PADDLE_OBJECT_MATERIAL = materials.BLACK PADDLE_OBJECT_SIZE = [0.25, 1] PADDLE_OBJECT_TYPE = 'cube' @@ -309,23 +337,28 @@ def get_true_poly_points(self) -> List[Vector3d]: MaterialTuple("AI2-THOR/Materials/Ceramics/ConcreteBoards1", ["grey"]), MaterialTuple("AI2-THOR/Materials/Ceramics/ConcreteFloor", ["grey"]), MaterialTuple("AI2-THOR/Materials/Ceramics/GREYGRANITE", ["grey"]), - MaterialTuple("AI2-THOR/Materials/Ceramics/PinkConcrete_Bedroom1", - ["red"]), MaterialTuple("AI2-THOR/Materials/Ceramics/WhiteCountertop", ["grey"]), MaterialTuple("AI2-THOR/Materials/Wood/BedroomFloor1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters 1", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters3", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters4", ["brown"]), + # Mark some brown textures as also orange because they look very similar. + MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters 1", + ["brown", "orange"]), + MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters3", + ["brown", "orange"]), + MaterialTuple("AI2-THOR/Materials/Wood/LightWoodCounters4", + ["brown", "orange"]), MaterialTuple( "AI2-THOR/Materials/Wood/TexturesCom_WoodFine0050_1_seamless_S", ["brown"]), MaterialTuple("AI2-THOR/Materials/Wood/WhiteWood", ["white"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodFloorsCross", ["brown"]), + MaterialTuple("AI2-THOR/Materials/Wood/WoodFloorsCross", + ["brown", "orange"]), MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Brown", ["brown"]), - MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Tan", ["brown"]) + MaterialTuple("AI2-THOR/Materials/Wood/WoodGrain_Tan", ["brown"]), + MaterialTuple("UnityAssetStore/Baby_Room/Models/Materials/wood 1", + ["brown"]) ] FLOOR_MATERIALS = FLOOR_OR_WALL_MATERIALS + [ - MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet2", ["brown"]), + MaterialTuple("AI2-THOR/Materials/Fabrics/Carpet2", ["grey"]), MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite", ["white"]), MaterialTuple("AI2-THOR/Materials/Fabrics/CarpetWhite 3", ["white"]) ] @@ -338,8 +371,14 @@ def get_true_poly_points(self) -> List[Vector3d]: ] +class OccluderMode(Enum): + EVAL = auto() + TRAINING = auto() + NONE = auto() + + def _append_each_show_to_object( - mcs_object: Dict[str, Any], + mcs_object: SceneObject, trial: List[Dict[str, Any]], trial_start_step: int, json_property: str, @@ -347,7 +386,7 @@ def _append_each_show_to_object( on_step_callback: Callable = None, rotation_y: int = 0, json_index: int = 0 -) -> Dict[str, Any]: +) -> SceneObject: """Append a "shows" array element to the given moving object for each step in the given trial list.""" @@ -423,15 +462,19 @@ def _choose_config_list( object_config_list = [] - # Retrieve the relevant data from the first frame of the first trial. - # Assume the number of objects will never change across trials, and - # objects will never change shape/color across trials/frames. - object_count = len(trial_list[0][0].get(json_property, [])) + # Minimum of 2 agent configs, for unexpected multiple agents scenes. + object_count = 2 if json_property == 'agent' else 0 + # Retrieve the relevant object list from the first frame of each trial. + # Use all trials since objects are sometimes added after the first trial. + # Assume objects will never change shape/color across trials/frames. + for trial in trial_list: + json_objects = trial[0].get(json_property, []) + if json_property == 'agent': + json_objects = [json_objects] + trial[0].get('other_agents', []) + object_count = max(object_count, len(json_objects)) + logger.info(f'Found {object_count} {json_property} in the JSON') - # Always return three agent configs, since doing so is trivial. We can just - # ignore ones that aren't needed. if json_property == 'agent': - object_count = 3 # Remove agent objects with pointy tops (e.g. cones, pyramids) from # scenes with keys (e.g. instrumental action) as requested by NYU. # Old prop: 'key' ... New prop: 'pin' @@ -479,11 +522,11 @@ def _choose_config_list( f'No more available {json_property} types: {used_type_list=}' ) - # Filter out used materials. - filtered_material_list = [ - material for material in material_list - if material[0] not in used_material_list - ] + # Filter out used materials and colors. + filtered_material_list = materials.filter_used_colors( + material_list, + used_material_list + ) if not filtered_material_list: raise SceneException( f'No more available {json_property} materials: ' @@ -493,7 +536,7 @@ def _choose_config_list( chosen_config = random.choice(filtered_config_list) # Choose a random Y rotation for each agent. - if json_property == 'agent' and chosen_config.rotation_y is None: + if json_property == 'agent': chosen_config.rotation_y = random.choice([0, 90, 180, 270]) # Choose a random object config (type/size) and material. @@ -505,7 +548,7 @@ def _choose_config_list( # Add the chosen type and material to the "used" lists. used_type_list.append(config_with_material.object_type) - used_material_list.append(config_with_material.material[0]) + used_material_list.append(config_with_material.material) return object_config_list @@ -519,10 +562,6 @@ def _create_action_list( for index in range(0, len(trial_list)): # Add 1 for the EndHabituation action step at the end of the trial. total_steps = len(trial_list[index]) + 1 - logger.info( - f'Trial={index+1} Frames={len(trial_list[index])} Steps=' - f'{total_steps}' - ) action_list.extend([['Pass']] * (total_steps - 1)) action_list.append(['EndHabituation']) # Remove the EndHabituation action from the last test trial. @@ -533,7 +572,7 @@ def _create_agent_object_list( trial_list: List[List[Dict[str, Any]]], agent_object_config_list: List[ObjectConfigWithMaterial], unit_size: Tuple[float, float] -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return the MCS scene's agent object list using the given trial list from the JSON file data.""" @@ -544,10 +583,10 @@ def _create_agent_object_list( # Assume each agent will never change shape/color. json_agents_unique = {} for trial_index, trial in enumerate(trial_list): - for json_property in ['agent', 'imitated_agent']: + for json_property in ['agent', 'other_agents']: json_agents = trial[0].get(json_property, []) # Assume a single 'agent' per trial. - # Assume multiple 'imitated_agent' per trial. + # Assume multiple 'other_agents' per trial. if json_property == 'agent': json_agents = [json_agents] for agent_index, json_agent in enumerate(json_agents): @@ -594,6 +633,7 @@ def _create_agent_object_list( agent_object['debug'][ tags.SCENE.UNTRAINED_SHAPE ] = config_with_material.untrained + agent_object['debug']['trialToSteps'] = {} # Remove the object's first appearance (we will override it later). agent_object['shows'] = [] @@ -609,8 +649,9 @@ def _create_agent_object_list( # Update each MCS agent object with its movement in each trials' frames. for trial_index, trial in enumerate(trial_list): step = _identify_trial_index_starting_step(trial_index, trial_list) + last_step = step + len(trial) found_in_trial = {} - for json_property in ['agent', 'imitated_agent']: + for json_property in ['agent', 'other_agents']: trial_json_agents = trial[0].get(json_property, []) if json_property == 'agent': trial_json_agents = [trial_json_agents] @@ -636,6 +677,7 @@ def _create_agent_object_list( json_index=agent_index ) for json_icon, mcs_agent in mcs_agents_unique.items(): + mcs_agent['debug']['trialToSteps'][trial_index] = (step, last_step) if found_in_trial.get(json_icon): continue # Else, hide the current agent during this trial. @@ -651,7 +693,7 @@ def _create_agent_object_list( def _create_fuse_wall_object_list( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float] -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return the MCS scene's green fuse wall object list (used in the instrumental action scenes) using the given trial list from the JSON file data.""" @@ -733,91 +775,120 @@ def _create_goal_object_list( agent_start_bounds: ObjectBounds, filename_prefix: str, unit_size: Tuple[float, float] -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return the MCS scene's goal object list using the given trial list from the JSON file data.""" goal_object_list = [] + icons_to_objects = {} + step = 0 + object_index = 0 - # Map the JSON icon of each goal object to an index in the MCS object list - # because sometimes they change positions in the JSON 'objects' list. - icon_to_index = {} - - # Retrieve the objects data from the first frame of the first trial. - # Assume the number of objects will never change, and the objects will - # never change shape/color. - for index, json_object in enumerate(trial_list[0][0].get('objects', [])): - json_coords = json_object[0] - json_radius = json_object[1] - json_icon = json_object[2] - json_size = [json_radius * 2, json_radius * 2] - icon_to_index[json_icon] = index - - # Create the MCS goal object. - config_with_material = goal_object_config_list[index] - dimensions = OBJECT_DIMENSIONS[config_with_material.object_type] - # Multiply the object's scale based on its JSON radius and unit size. - factor = json_radius * 2 * min(unit_size[0], unit_size[1]) - scale_xz = config_with_material.scale_xz * factor - scale_y = config_with_material.scale_y * factor - center_y = dimensions.center_y * config_with_material.scale_y * factor - goal_object = _create_object( - 'object_', - config_with_material.object_type, - config_with_material.material, - [center_y, scale_y], - [scale_xz, scale_xz], - json_coords, - json_size, - unit_size, - rotation_y=config_with_material.rotation_y - ) - # Set kinematic to avoid awkward shifting due to collision issues. - goal_object['kinematic'] = True - # Set physics so this object's info is returned in the oracle metadata. - goal_object['physics'] = True - goal_object['debug'][ - tags.SCENE.UNTRAINED_SHAPE - ] = config_with_material.untrained - goal_object_list.append(goal_object) - - # Add the object's bounds for each other frame of the first trial. - for _ in range(0, len(trial_list[0])): - goal_object['debug']['boundsAtStep'].append( - goal_object['shows'][-1]['boundingBox'] - ) - - # Find the step for the start of the second trial. - # Assume scenes will have more than one trial. - step = _identify_trial_index_starting_step(1, trial_list) - - # Add data for each object's new position to each trial's start step. - # Assume objects only change in position across trials (not frames). - for trial in trial_list[1:]: + # Retrieve the object data for each trial using the trial's first frame. + # Use all trials since objects are sometimes added after the first trial. + # Assume each object will never change shape/color. + for trial_index, trial in enumerate(trial_list): + icons_this_trial = {} for json_object in trial[0].get('objects', []): json_coords = json_object[0] json_radius = json_object[1] json_icon = json_object[2] json_size = [json_radius * 2, json_radius * 2] + icons_this_trial[json_icon] = True + + # Use the previously made MCS goal object, or create a new one. + goal_object = icons_to_objects.get(json_icon) + already_existed = (goal_object is not None) + if not already_existed: + config_with_material = goal_object_config_list[object_index] + object_index += 1 + dimensions = OBJECT_DIMENSIONS[ + config_with_material.object_type + ] + # Multiply the object's scale by JSON radius and unit size. + factor = json_radius * 2 * min(unit_size[0], unit_size[1]) + scale_xz = config_with_material.scale_xz * factor + scale_y = config_with_material.scale_y * factor + center_y = ( + dimensions.center_y * config_with_material.scale_y * factor + ) + # Initialize the goal object. + goal_object = _create_object( + 'object_', + config_with_material.object_type, + config_with_material.material, + [center_y, scale_y], + [scale_xz, scale_xz], + json_coords, + json_size, + unit_size, + rotation_y=config_with_material.rotation_y + ) + # The goal object should appear on this step. + goal_object['shows'][0]['stepBegin'] = step + # Set kinematic to avoid awkward shifting on collision. + goal_object['kinematic'] = True + # Set physics so object's info is returned in oracle metadata. + goal_object['physics'] = True + # Initialize other important properties. + goal_object['changeMaterials'] = [] + goal_object['hides'] = [] + goal_object['debug']['agentTouches'] = {} + for index in range(0, len(trial_list)): + goal_object['debug']['agentTouches'][index] = [] + for _ in range(0, step): + goal_object['debug']['boundsAtStep'].insert(0, None) + goal_object['debug'][ + tags.SCENE.UNTRAINED_SHAPE + ] = config_with_material.untrained + # Save the goal object. + goal_object_list.append(goal_object) + icons_to_objects[json_icon] = goal_object + + # If needed, add the object's new position in this trial. + # Assume objects only change in position across trials, not frames. + if already_existed: + goal_object['shows'].append(_create_show( + step, + goal_object['type'], + goal_object['debug']['configHeight'], + goal_object['debug']['configSize'], + json_coords, + json_size, + unit_size, + rotation_y=goal_object['debug']['configRotation'] + )) - # Find the MCS object corresponding to this JSON object's icon. - goal_object = goal_object_list[icon_to_index[json_icon]] - - # Move the object to its new position for the trial. - goal_object['shows'].append(_create_show( - step, - goal_object['type'], - goal_object['debug']['configHeight'], - goal_object['debug']['configSize'], - json_coords, - json_size, - unit_size - )) # Add the object's bounds for each frame of the trial. - for _ in range(0, len(trial) + 1): + for _ in range(0, len(trial) + (1 if already_existed else 0)): goal_object['debug']['boundsAtStep'].append( goal_object['shows'][-1]['boundingBox'] ) + + # Mark each time the object's color changes to signal an agent + # touches an object. We will change the object's color elsewhere. + previous_color = json_object[3] + for future_step, future_frame in enumerate(trial): + if future_step == 0: + continue + for future_object in future_frame.get('objects', []): + if future_object[2] != json_icon: + continue + if future_object[3] != previous_color: + previous_color = future_object[3] + agent_touches = goal_object['debug']['agentTouches'] + agent_touches[trial_index].append(step + future_step) + break + + for icon, goal_object in icons_to_objects.items(): + if icon in icons_this_trial: + continue + goal_object['hides'].append({ + 'stepBegin': step + }) + for _ in range(0, len(trial) + 1): + goal_object['debug']['boundsAtStep'].append(None) + # Add 1 for the EndHabituation action step at the end of the trial. step += len(trial) + 1 @@ -826,9 +897,11 @@ def _create_goal_object_list( # We can't have the object's position on top of the agent's start # position or the agent and object will collide. This can happen # if the 2D icons overlap themselves in the original data. - if _do_objects_intersect( + # (Only bother checking if a "home" is in the scene.) + if agent_start_bounds and _do_objects_overlap( agent_start_bounds, - show['boundingBox'] + show['boundingBox'], + show['stepBegin'] ): raise SceneException( f'Cannot convert {filename_prefix} because an object is ' @@ -844,7 +917,7 @@ def _create_goal_object_list( def _create_home_object( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float] -) -> Dict[str, Any]: +) -> SceneObject: """Create and return the MCS scene's home object using the given trial list from the JSON file data.""" @@ -877,7 +950,7 @@ def _create_key_object( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float], agent_height: float -) -> Optional[Dict[str, Any]]: +) -> Optional[SceneObject]: """Create and return the MCS scene's key object using the given trial list from the JSON file data.""" @@ -944,9 +1017,9 @@ def _create_key_object( def _create_lock_wall_object_list( trial_list: List[List[Dict[str, Any]]], - key_object: Dict[str, Any], + key_object: SceneObject, unit_size: Tuple[float, float] -) -> Dict[str, Any]: +) -> SceneObject: """Create and return the MCS scene's green lock wall object list (used in the instrumental action scenes) using the given trial list from the JSON file data.""" @@ -1058,9 +1131,10 @@ def _create_object( json_size: Tuple[int, int], unit_size: Tuple[float, float], rotation_y: int = 0 -) -> Dict[str, Any]: +) -> SceneObject: """Create and return an MCS object using the given data.""" - mcs_object = { + dimensions = OBJECT_DIMENSIONS[object_type] + mcs_object = SceneObject({ 'id': id_prefix + str(uuid.uuid4()), 'type': object_type, 'materials': [object_material.material], @@ -1082,14 +1156,9 @@ def _create_object( unit_size, rotation_y )] - } - dimensions = OBJECT_DIMENSIONS[object_type] + }) scale = mcs_object['shows'][0]['scale'] - mcs_object['debug']['dimensions'] = { - 'x': dimensions.x * scale['x'], - 'y': dimensions.y * scale['y'], - 'z': dimensions.z * scale['z'] - } + mcs_object['debug']['dimensions'] = dimensions.get_dict(scale) mcs_object['debug']['info'].append(' '.join(mcs_object['debug']['info'])) mcs_object['debug']['boundsAtStep'] = [ mcs_object['shows'][0]['boundingBox'] @@ -1097,10 +1166,299 @@ def _create_object( return mcs_object +def _create_occluder_object( + trial_list: List[List[Dict[str, Any]]], + agent_object: SceneObject, + paddle_object: SceneObject, + goal_object_list: List[SceneObject], + occluder_mode: OccluderMode, + unit_size: Tuple[float, float] +) -> Optional[SceneObject]: + """Create and return the MCS scene's occluder object using the given trial + list from the JSON file data.""" + + if occluder_mode == OccluderMode.NONE: + return None + + json_size = OCCLUDER_OBJECT_JSON_SIZE.copy() + + scale_x = json_size[0] * unit_size[0] + scale_z = json_size[1] * unit_size[1] + + # Ensure the occluder is always 1 taller than the agent. + agent_height = agent_object['debug']['dimensions']['y'] + occluder_height = max(0.5, round(agent_height * 10) / 10.0) + 1 + + # Create the MCS occluder object. + occluder_object = _create_object( + 'occluder_', + OCCLUDER_OBJECT_TYPE, + OCCLUDER_OBJECT_MATERIAL, + [occluder_height / 2.0, occluder_height], + [scale_x, scale_z], + OCCLUDER_OBJECT_JSON_COORDS.copy(), + json_size, + unit_size, + rotation_y=0 + ) + + # Add the occluder's bounds for each other frame of the first trial. + for _ in range(0, len(trial_list[0])): + occluder_object['debug']['boundsAtStep'].append( + occluder_object['shows'][-1]['boundingBox'] + ) + + # Find the step for the start of the second trial. + # Assume scenes will have more than one trial. + step = _identify_trial_index_starting_step(1, trial_list) + + # Add data for the occluder's new position to each trial's start step. + # Assume occluders only change in position across trials (not frames). + for trial_index, trial in enumerate(trial_list): + if trial_index == 0: + continue + + is_final_trial = (trial_index == (len(trial_list) - 1)) + + # For the final trial, in a training scene, position the occluder in a + # random location where it does not block the view of anything + # important in the scene. + if is_final_trial and occluder_mode == OccluderMode.TRAINING: + observer_center = [4, -4] + + # Gather the bounds for all the important objects. + agent_bounds_at_step = agent_object['debug']['boundsAtStep'] + paddle_bounds_at_step = paddle_object['debug']['boundsAtStep'] + unoccluded_data = [] + for unoccluded_bounds in agent_bounds_at_step[step:] + [ + instance['debug']['boundsAtStep'][step] + for instance in goal_object_list + ] + paddle_bounds_at_step[step:]: + poly = unoccluded_bounds.true_poly + center = list(poly.centroid.coords)[0] + view = shapely.geometry.LineString([observer_center, center]) + unoccluded_data.append((unoccluded_bounds, view)) + + # Generate a random grid. + grid = [(x / unit_size[0], z / unit_size[1]) for x, z in GRID] + random.shuffle(grid) + + # Try each location in the random grid to see if it's appropriate. + for x, z in grid: + json_coords = [x, z] + # Create the "show" (and bounds) for this position. + occluder_show = _create_show( + step, + occluder_object['type'], + occluder_object['debug']['configHeight'], + occluder_object['debug']['configSize'], + json_coords, + json_size, + unit_size, + # Perpendicular to the observer. + rotation_y=45 + ) + occluder_bounds = occluder_show['boundingBox'] + occluder_poly = occluder_bounds.true_poly + for bounds, view in unoccluded_data: + # Ensure the occluder at this position will NOT obstruct + # the observer's view of the other object. + if view.intersects(occluder_poly): + occluder_show = None + break + # Ensure the occluder at this position will NOT overlap the + # other object. + if _do_objects_overlap(bounds, occluder_bounds, -1): + occluder_show = None + break + if occluder_show: + logger.debug( + f'Occluder location {json_coords=} ' + f'position={occluder_show["position"]}' + ) + occluder_object['shows'].append(occluder_show) + occluder_object['debug']['jsonCoords'] = json_coords + occluder_object['debug']['jsonSize'] = json_size + break + + # For the final trial, in an evaluation scene, position the occluder + # where the agent/non-agent would be hit by the paddle (this is + # somewhere between the position of the paddle and the actual position + # of the agent/non-agent). + elif is_final_trial and occluder_mode == OccluderMode.EVAL: + # Find the starting position for the agent in this trial. + agent_bounds_at_step = agent_object['debug']['boundsAtStep'] + agent_bounds = agent_bounds_at_step[step] + agent_center = list(agent_bounds.true_poly.centroid.coords)[0] + + # Identify the step at which the agent begins to move. + for agent_show in agent_object['shows']: + if agent_show['stepBegin'] > step: + break + agent_show = None + + # The paddle object would have pushed the agent about 30 steps + # before it begins to move (from NYU). + push_step = agent_show['stepBegin'] - 29 + for paddle_show in paddle_object['shows']: + if paddle_show['stepBegin'] == push_step: + break + paddle_show = None + + # Find the position for the paddle object in this trial. + paddle_bounds = paddle_show['boundingBox'] + paddle_center = list(paddle_bounds.true_poly.centroid.coords)[0] + + # Find the position near the paddle at which the agent would be + # found if it was to be pushed by the paddle. + distance_over = PADDLE_OBJECT_SIZE[0] / 2.0 + distance_down = PADDLE_OBJECT_SIZE[1] / 2.0 + distance_diagonal = math.sqrt(distance_over**2 + distance_down**2) + # Multiply by -1 because the angle should point downward. + push_angle = math.radians(90) - math.acos(( + distance_diagonal**2 + distance_over**2 - distance_down**2 + ) / (2 * distance_diagonal * distance_over)) + # Subtract from 270 to covert the Unity rotation. + paddle_angle = math.radians(270 - paddle_show['rotation']['y']) + angle = push_angle + paddle_angle + contact_center = [ + paddle_center[0] + distance_diagonal * math.cos(angle), + paddle_center[1] + distance_diagonal * math.sin(angle) + ] + + # Try positioning the occluder directly between the moving agent + # and the performer (a.k.a. observer). + hide_coords = [ + round((agent_center[0] - GRID_MIN_X) / unit_size[0]), + round((agent_center[1] - GRID_MIN_Z) / unit_size[1]) + ] + + logger.debug( + f'Finding location for occluder: {agent_center=} ' + f'{paddle_center=} {push_step=} {distance_diagonal=} ' + f'push_angle={math.degrees(push_angle)} ' + f'paddle_angle={math.degrees(paddle_angle)} ' + f'{contact_center=} {hide_coords=}' + ) + + occluder_object['debug']['contactPoint'] = contact_center + occluder_object['debug']['hidePosition'] = agent_center + occluder_object['debug']['hideCoords'] = hide_coords + + observer_center = [4, -4] + view_to_contrast = shapely.geometry.LineString( + [observer_center, contact_center] + ) + view_to_agent = shapely.geometry.LineString( + [observer_center, agent_center] + ) + + for z in range(0, 11): + for i in range(20, 41): + # Occluder position to try. + json_coords = [ + hide_coords[0] + i - (json_size[0] / 2.0), + hide_coords[1] - i - z - (json_size[1] / 2.0) + ] + # Create the "show" (and bounds) for this position. + occluder_show = _create_show( + step, + occluder_object['type'], + occluder_object['debug']['configHeight'], + occluder_object['debug']['configSize'], + json_coords, + json_size, + unit_size, + # Perpendicular to the observer. + rotation_y=45 + ) + occluder_poly = occluder_show['boundingBox'].true_poly + + # Ensure the occluder at this position obstructs the view + # of both the agent and the paddle contact point. + if not view_to_contrast.intersects(occluder_poly): + logger.debug( + f'Move back: can see contact {json_coords=} ' + f'occluder_position={occluder_show["position"]}' + ) + occluder_show = None + if not view_to_agent.intersects(occluder_poly): + logger.debug( + f'Move back: can see agent {json_coords=} ' + f'occluder_position={occluder_show["position"]}' + ) + occluder_show = None + + # Ensure the occluder does not intersect with the agent's + # movement. + if occluder_show: + for next_step, next_agent_bounds in enumerate( + agent_bounds_at_step[step:] + ): + if _do_objects_overlap( + next_agent_bounds, + occluder_show['boundingBox'], + step + next_step + ): + logger.debug( + f'Move back: in the way {json_coords=} ' + f'occluder_position=' + f'{occluder_show["position"]}' + ) + occluder_show = None + break + + if occluder_show: + logger.debug( + f'Occluder location {json_coords=} ' + f'position={occluder_show["position"]}' + ) + occluder_object['shows'].append(occluder_show) + occluder_object['debug']['jsonCoords'] = json_coords + occluder_object['debug']['jsonSize'] = json_size + break + if occluder_show: + break + if not occluder_show: + raise SceneException('Cannot find valid location for occluder') + else: + # Move the object to its new position for the trial. + occluder_object['shows'].append(_create_show( + step, + occluder_object['type'], + occluder_object['debug']['configHeight'], + occluder_object['debug']['configSize'], + OCCLUDER_OBJECT_JSON_COORDS.copy(), + json_size, + unit_size, + rotation_y=0 + )) + + # Remove the scale from each element in 'shows' except for the + # first, or it will really mess up the simulation. + del occluder_object['shows'][-1]['scale'] + # Add the occluder's bounds for each frame of the trial. + for _ in range(0, len(trial) + 1): + occluder_object['debug']['boundsAtStep'].append( + occluder_object['shows'][-1]['boundingBox'] + ) + + # Add 1 for the EndHabituation action step at the end of the trial. + step += len(trial) + 1 + + occluder_object['structure'] = True + # Set kinematic to avoid awkward shifting due to collision issues. + occluder_object['kinematic'] = True + # Set physics so this object's info is returned in the oracle metadata. + occluder_object['physics'] = True + + return occluder_object + + def _create_paddle_object( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float] -) -> Optional[Dict[str, Any]]: +) -> Optional[SceneObject]: """Create and return the MCS scene's paddle object using the given trial list from the JSON file data.""" @@ -1162,13 +1520,14 @@ def _create_paddle_object( def _create_scene( starter_scene: Scene, - goal_template: Dict[str, Any], + goal_template: Goal, agent_object_config_list: List[ObjectConfigWithMaterial], goal_object_config_list: List[ObjectConfigWithMaterial], trial_list: List[List[Dict[str, Any]]], filename_prefix: str, platform_material: MaterialTuple, - is_expected: bool + is_expected: bool, + occluder_mode: OccluderMode = OccluderMode.NONE ) -> Scene: """Create and return the MCS scene using the given templates, trial list, and expectedness answer from the JSON file data.""" @@ -1178,16 +1537,20 @@ def _create_scene( scene.isometric = True scene.goal = copy.deepcopy(goal_template) - scene.goal['action_list'] = _create_action_list(trial_list) - scene.goal['habituation_total'] = len(trial_list) - 1 - scene.goal['last_step'] = len(scene.goal['action_list']) - scene.goal['metadata'] = {'target': {}} - scene.goal['answer'] = { + scene.goal.action_list = _create_action_list(trial_list) + scene.goal.category = 'agents' + scene.goal.habituation_total = len(trial_list) - 1 + scene.goal.last_step = len(scene.goal.action_list) + scene.goal.metadata = {} + scene.goal.answer = { 'choice': EXPECTED if is_expected else UNEXPECTED } unit_size = _retrieve_unit_size(trial_list) wall_object_list = _create_wall_object_list(trial_list, unit_size) + # IIRC the only tasks with multiple agents are the Multiple Agents task, + # which has a different "agent" in the final trial, and the Imitation / + # Social Approach tasks, which have an "agent" and multiple "other_agents". agent_object_list = _create_agent_object_list( trial_list, agent_object_config_list, @@ -1197,14 +1560,14 @@ def _create_scene( agent_object = agent_object_list[0] # Assume the primary agent is the only one moving around. agent_start_bounds = agent_object['shows'][0]['boundingBox'] + home_object = _create_home_object(trial_list, unit_size) goal_object_list = _create_goal_object_list( trial_list, goal_object_config_list, - agent_start_bounds, + agent_start_bounds if home_object else None, filename_prefix, unit_size ) - home_object = _create_home_object(trial_list, unit_size) # Assume the primary agent is the only one that can hold the key. agent_height = agent_start_bounds.max_y key_object = _create_key_object(trial_list, unit_size, agent_height) @@ -1219,18 +1582,40 @@ def _create_scene( _remove_intersecting_agent_steps( agent_object_list, - goal_object_list + lock_wall_list + goal_object_list + lock_wall_list + wall_object_list + + (agent_object_list[1:] if 'other_agents' in trial_list[0][0] else []) + ) + _reposition_agents_away_from_paddle( + agent_object_list, + paddle_object ) _remove_extraneous_object_show( - agent_object_list + [key_object] if key_object else [], + agent_object_list + ([key_object] if key_object else []), trial_list ) _move_agent_past_lock_location(agent_object_list, lock_wall_list) - _move_agent_adjacent_to_goal( + _move_agents_adjacent_to_goal( + [agent_object] if 'other_agents' in trial_list[0][0] else agent_object_list, - goal_object_list, + goal_object_list + + (agent_object_list[1:] if 'other_agents' in trial_list[0][0] else []), trial_list ) + # Extra check for the agent colliding with the paddle at any step. + if paddle_object: + for step, agent_bounds in enumerate( + agent_object['debug']['boundsAtStep'] + ): + paddle_bounds = paddle_object['debug']['boundsAtStep'][step] + poly = agent_bounds.true_poly.intersection(paddle_bounds.true_poly) + agent_area = round(agent_bounds.true_poly.area, 4) + collision_area = 0 if poly.is_empty else round(poly.area, 4) + area = round(collision_area / agent_area, 4) + if area >= 0.05: + raise SceneException( + f'Cannot convert {filename_prefix} because the paddle ' + f'intersects too much with the agent: {step=} {area=}' + ) # If the agent is carrying the key on this step, move the key to be # centered directly above the agent. @@ -1282,20 +1667,28 @@ def _create_scene( for color in mcs_object['debug']['color'] ] scene.ceiling_material = CEILING_MATERIAL.material - floor_choices = [material_tuple for material_tuple in FLOOR_MATERIALS if ( - material_tuple.color[0] not in excluded_colors - )] + floor_choices = [choice for choice in FLOOR_MATERIALS if all([ + color not in excluded_colors for color in choice.color + ])] floor_choice = random.choice(floor_choices) scene.floor_material = floor_choice.material scene.debug['floorColors'] = floor_choice.color - wall_choices = [material_tuple for material_tuple in WALL_MATERIALS if ( - material_tuple.color[0] not in excluded_colors and - material_tuple.material != floor_choice.material - )] + wall_choices = [choice for choice in WALL_MATERIALS if all([ + color not in excluded_colors for color in choice.color + ]) and choice.material != floor_choice.material] wall_choice = random.choice(wall_choices) scene.wall_material = wall_choice.material scene.debug['wallColors'] = wall_choice.color + occluder_object = _create_occluder_object( + trial_list, + agent_object, + paddle_object, + goal_object_list, + occluder_mode, + unit_size + ) if occluder_mode != OccluderMode.NONE else None + role_to_object_list = {} role_to_object_list[tags.ROLES.AGENT] = agent_object_list role_to_object_list[tags.ROLES.HOME] = [home_object] if home_object else [] @@ -1304,7 +1697,9 @@ def _create_scene( [paddle_object] if paddle_object else [] ) role_to_object_list[tags.ROLES.NON_TARGET] = non_target_list - role_to_object_list[tags.ROLES.STRUCTURAL] = [platform] + role_to_object_list[tags.ROLES.STRUCTURAL] = [platform] + ( + [occluder_object] if occluder_object else [] + ) role_to_object_list[tags.ROLES.TARGET] = target_list role_to_object_list[tags.ROLES.WALL] = wall_object_list + lock_wall_list @@ -1324,32 +1719,28 @@ def _create_show( ) -> Dict[str, Any]: """Create and return an MCS object's 'shows' element using the given data.""" + dimensions = OBJECT_DIMENSIONS[object_type] mcs_show = { 'stepBegin': begin_frame, 'position': { - 'x': GRID_MIN_X + ( + 'x': round(GRID_MIN_X + ( (json_coords[0] + (json_size[0] / 2)) * unit_size[0] - ), - 'y': object_height[0], - 'z': GRID_MIN_Z + ( + ), 4), + 'y': round(object_height[0], 4), + 'z': round(GRID_MIN_Z + ( (json_coords[1] + (json_size[1] / 2)) * unit_size[1] - ) + ), 4) }, 'rotation': {'x': 0, 'y': rotation_y, 'z': 0}, 'scale': { - 'x': object_size[0], - 'y': object_height[1], - 'z': object_size[1] + 'x': round(object_size[0], 4), + 'y': round(object_height[1], 4), + 'z': round(object_size[1], 4) } } - dimensions = OBJECT_DIMENSIONS[object_type] mcs_show['boundingBox'] = _make_true_bounds( object_type=object_type, - dimensions={ - 'x': mcs_show['scale']['x'] * dimensions.x, - 'y': mcs_show['scale']['y'] * dimensions.y, - 'z': mcs_show['scale']['z'] * dimensions.z - }, + dimensions=dimensions.get_dict(mcs_show['scale']), offset={'x': 0, 'y': 0, 'z': 0}, position=mcs_show['position'], rotation=mcs_show['rotation'], @@ -1361,7 +1752,7 @@ def _create_show( def _create_static_wall_object_list( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float] -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return the MCS scene's black static wall object list using the given trial list from the JSON file data.""" @@ -1383,15 +1774,16 @@ def _create_static_wall_object_list( wall_object['hides'] = [{ 'stepBegin': step }] + # Add the wall's bounds for each frame of the trial. + for _ in range(0, len(trial) + 1): + wall_object['debug']['boundsAtStep'].append(None) for json_wall in json_wall_list: json_coords = json_wall[0] json_size = json_wall[1] # Ignore each part of border wall (we make it automatically)... - # EXCEPT in agent/non-agent scenes with paddles, since part of - # a border wall is removed in the final trial. - if (trial[0].get('paddle') is None) and ( + if ( json_coords[0] == JSON_BORDER_WALL_MIN_X or json_coords[0] == JSON_BORDER_WALL_MAX_X or json_coords[1] == JSON_BORDER_WALL_MIN_Z or @@ -1414,7 +1806,22 @@ def _create_static_wall_object_list( wall_object['structure'] = True # Adjust the show step to sync with the trial step. wall_object['shows'][0]['stepBegin'] = step + # Add the wall's bounds for each frame before the trial. + for _ in range(0, step): + wall_object['debug']['boundsAtStep'].append(None) + # Add the wall's bounds for each frame of the trial. + for _ in range(0, len(trial) + 1): + wall_object['debug']['boundsAtStep'].append( + wall_object['shows'][-1]['boundingBox'] + ) static_wall_object_list.append(wall_object) + else: + for wall_object in static_wall_object_list: + # Add the wall's bounds for each frame of the trial. + for _ in range(0, len(trial) + 1): + wall_object['debug']['boundsAtStep'].append( + wall_object['shows'][-1]['boundingBox'] + ) # Add 1 for the EndHabituation action step at the end of the trial. step += len(trial) + 1 @@ -1423,7 +1830,8 @@ def _create_static_wall_object_list( def _create_trial_frame_list( - trial: List[Dict[str, Any]] + trial: List[Dict[str, Any]], + trial_index: int ) -> List[Dict[str, Any]]: """Return all the frames in the given trial that we want to keep in the final MCS scene using the agent's movement. Skip about half of the frames @@ -1433,7 +1841,8 @@ def _create_trial_frame_list( starting_coords = {} previous_coords = {} json_property_list = [ - 'agent', 'fuse_walls', 'imitated_agent', 'key', 'lock', 'paddle', 'pin' + 'agent', 'fuse_walls', 'other_agents', 'key', 'lock', 'occluder', + 'paddle', 'pin' ] for json_property in json_property_list: starting_coords[json_property] = trial[0].get(json_property) @@ -1451,19 +1860,26 @@ def _create_trial_frame_list( # Only keep a specific number of the trial's starting frames. if all([ coords[json_property] == starting_coords[json_property] - for json_property in ['agent', 'imitated_agent', 'paddle'] + for json_property in ['agent', 'other_agents', 'paddle'] ]): if starting_frame_count > 0: frame_list.append(frame) starting_frame_count -= 1 continue + # Reset in case another pause happens in the middle of the trial. + starting_frame_count = STARTING_STEP_WAIT_TIME + # Remove the last frames of any trial with a paddle because the paddle + # will just keep spinning for an excessively long time, and then it + # will stop as the 2D version of the scene "fades out". + if coords['paddle'] and index >= (len(trial) - 50): + continue # Only keep a specific number of the trial's agent-is-paused frames. if coords['agent'] == previous_coords['agent']: is_repeated = True for json_property in json_property_list: if coords[json_property] != previous_coords[json_property]: is_repeated = False - if json_property not in ['imitated_agent', 'paddle']: + if json_property not in ['other_agents', 'paddle']: skip_next = False # If a separate object is changing, don't skip this frame. if is_repeated: @@ -1496,6 +1912,9 @@ def _create_trial_frame_list( for _ in range(POST_DEFUSE_WAIT_TIME): frame_list.append(frame) defuse_frame_count = DEFUSE_STEP_SKIP_TIME + # Record this frame for future comparison. + for json_property in json_property_list: + previous_coords[json_property] = coords[json_property] # Skip this frame if we used the previous frame. # Keep it if it's the last frame of the trial. if skip_next and index < (len(trial) - 1): @@ -1503,87 +1922,102 @@ def _create_trial_frame_list( continue # Else keep this frame. frame_list.append(frame) - for json_property in json_property_list: - previous_coords[json_property] = coords[json_property] skip_next = True + logger.info( + f'Trial={trial_index + 1} Frames={len(trial)} Steps={len(frame_list)}' + ) return frame_list def _create_wall_object_list( trial_list: List[List[Dict[str, Any]]], unit_size: Tuple[float, float] -) -> List[Dict[str, Any]]: +) -> List[SceneObject]: """Create and return the MCS scene's wall object list using the given trial list from the JSON file data.""" fuse_wall_list = _create_fuse_wall_object_list(trial_list, unit_size) static_wall_list = _create_static_wall_object_list(trial_list, unit_size) - # Create four static black border walls... EXCEPT in agent/non-agent scenes - # with paddles, because they will be generated individually. - if trial_list[0][0].get('paddle') is None: - for name, position, size in [ - ('wall_front', (0, 2.25), (5, WALL_OBJECT_SIZE[1])), - ('wall_back', (0, -2.25), (5, WALL_OBJECT_SIZE[1])), - ('wall_left', (-2.25, 0), (WALL_OBJECT_SIZE[0], 4)), - ('wall_right', (2.25, 0), (WALL_OBJECT_SIZE[0], 4)) - ]: - wall_object = { - 'id': name, - 'type': 'cube', - 'materials': [WALL_OBJECT_MATERIAL[0]], - 'shows': [{ - 'stepBegin': 0, - 'position': { - 'x': position[0], - 'y': WALL_OBJECT_HEIGHT[0], - 'z': position[1] - }, - 'rotation': { - 'x': 0, - 'y': 0, - 'z': 0 - }, - 'scale': { - 'x': size[0], - 'y': WALL_OBJECT_HEIGHT[1], - 'z': size[1] - } - }], - 'kinematic': True, - 'structure': True, - 'debug': { - 'info': WALL_OBJECT_MATERIAL[1] + ['cube'], + # Create four static black border walls... + for name, position, size in [ + ('wall_grid_front', (0, 2.25), (5, WALL_OBJECT_SIZE[1])), + ('wall_grid_back', (0, -2.25), (5, WALL_OBJECT_SIZE[1])), + ('wall_grid_left', (-2.25, 0), (WALL_OBJECT_SIZE[0], 4)), + ('wall_grid_right', (2.25, 0), (WALL_OBJECT_SIZE[0], 4)) + ]: + wall_object = SceneObject({ + 'id': name, + 'type': 'cube', + 'materials': [WALL_OBJECT_MATERIAL[0]], + 'shows': [{ + 'stepBegin': 0, + 'position': { + 'x': position[0], + 'y': WALL_OBJECT_HEIGHT[0], + 'z': position[1] + }, + 'rotation': { + 'x': 0, + 'y': 0, + 'z': 0 + }, + 'scale': { + 'x': size[0], + 'y': WALL_OBJECT_HEIGHT[1], + 'z': size[1] } + }], + 'kinematic': True, + 'structure': True, + 'debug': { + 'info': WALL_OBJECT_MATERIAL[1] + ['cube'], } - wall_object['debug']['info'].append( - ' '.join(wall_object['debug']['info']) - ) - wall_object['shows'][0]['boundingBox'] = _make_true_bounds( - object_type='cube', - dimensions=wall_object['shows'][0]['scale'], - offset={'x': 0, 'y': 0, 'z': 0}, - position=wall_object['shows'][0]['position'], - rotation=wall_object['shows'][0]['rotation'], - standing_y=(wall_object['shows'][0]['scale']['y'] / 2.0) - ) - static_wall_list.append(wall_object) + }) + wall_object['debug']['info'].append( + ' '.join(wall_object['debug']['info']) + ) + wall_object['shows'][0]['boundingBox'] = _make_true_bounds( + object_type='cube', + dimensions=wall_object['shows'][0]['scale'], + offset={'x': 0, 'y': 0, 'z': 0}, + position=wall_object['shows'][0]['position'], + rotation=wall_object['shows'][0]['rotation'], + standing_y=(wall_object['shows'][0]['scale']['y'] / 2.0) + ) + wall_object['debug']['boundsAtStep'] = [] + for trial in trial_list: + # Add the wall's bounds for each frame of the trial. + for _ in range(0, len(trial) + 1): + wall_object['debug']['boundsAtStep'].append( + wall_object['shows'][-1]['boundingBox'] + ) + static_wall_list.append(wall_object) return static_wall_list + fuse_wall_list -def _do_objects_intersect( +def _do_objects_overlap( bounds_1: TrueObjectBounds, - bounds_2: TrueObjectBounds + bounds_2: TrueObjectBounds, + step: int ) -> bool: """Returns whether the two objects represented by the given "true bounds" - intersect using their corresponding "true_poly" properties.""" - return bounds_1.true_poly.intersects(bounds_2.true_poly) + overlap using their corresponding "true_poly" properties.""" + # Using "overlaps" instead of "intersects" appears to work better since it + # still returns False if a single point in the perimeters touch, which is + # especially important for agents potentially bumping into walls. + # Also call on "within" since "overlaps" apparently doesn't do that. + return ( + bounds_1.true_poly.overlaps(bounds_2.true_poly) or + bounds_1.true_poly.within(bounds_2.true_poly) or + bounds_2.true_poly.within(bounds_1.true_poly) + ) def _fix_key_location( trial_start_step: int, json_key: Dict[str, Any], - key_object: Dict[str, Any] -) -> Dict[str, Any]: + key_object: SceneObject +) -> SceneObject: """Update the given key object's location on a specific step (frame) in the current trial using the given JSON key data. Used as the on_step_callback parameter to _append_each_show_to_object on key objects.""" @@ -1719,8 +2153,8 @@ def _make_true_bounds( def _move_agent_past_lock_location( - agent_object_list: List[Dict[str, Any]], - lock_wall_list: Dict[str, Any] + agent_object_list: List[SceneObject], + lock_wall_list: List[SceneObject] ) -> None: """Adjust the agent's movement onto and away from the lock space before and after inserting the key and removing the fuse walls.""" @@ -1745,9 +2179,10 @@ def _move_agent_past_lock_location( lock_object['hides'][0]['stepBegin'] - 1 ] for index_2, target_show in enumerate(agent_object['shows'][(index):]): - if _do_objects_intersect( + if _do_objects_overlap( target_show['boundingBox'], - lock_bounds + lock_bounds, + target_show['stepBegin'] ): remove_list.append(index + index_2) else: @@ -1791,12 +2226,11 @@ def _move_agent_past_lock_location( def _move_agent_adjacent_to_goal( - agent_object_list: List[Dict[str, Any]], - goal_object_list: List[Dict[str, Any]], + agent_object: SceneObject, + goal_object_list: List[SceneObject], trial_list: List[List[Dict[str, Any]]] ) -> None: """Ensure the agent is directly adjacent to its goal in each trial.""" - # Record the starting step of each trial for future use. trial_index_to_step = {} for trial_index in range(len(trial_list) + 1): step = _identify_trial_index_starting_step(trial_index, trial_list) @@ -1806,61 +2240,125 @@ def _move_agent_adjacent_to_goal( first_step = trial_index_to_step[trial_index] final_step = trial_index_to_step[trial_index + 1] - 1 - for agent_object in agent_object_list: - # Identify the agent's final "show" of this trial. - agent_show = None - for show in agent_object['shows']: + # Identify each of the agent object's "shows" in this trial. + agent_shows = [ + agent_show for agent_show in agent_object['shows'] + if first_step <= agent_show['stepBegin'] <= final_step + ] + + # Skip this trial if the agent does not ever appear. + if not agent_shows: + continue + + # Calculate the distance on each step from the agent to the closest + # goal object. + closest_object_distances = [(MAX_DISTANCE, None, None, None)] + + for goal_object in goal_object_list: + # Identify the goal object's "show" in this trial. + # Assume each object will only have one "show" per trial. + goal_show = None + for show in goal_object['shows']: if first_step <= show['stepBegin'] <= final_step: - agent_show = show + goal_show = show if final_step < show['stepBegin']: break - # If the agent is hidden in this trial, skip it. - if not agent_show: + # Skip this object if it does not ever appear in this trial. + if not goal_show: continue - agent_poly = agent_show['boundingBox'].true_poly + agent_shows_measure_distances = [] - # Calculate the distance from the agent to each goal object. - goals_with_distances = [] - for goal_object in goal_object_list: - # Identify the goal object's "show" in this trial. - goal_show = None - for show in goal_object['shows']: - if first_step <= show['stepBegin'] <= final_step: - goal_show = show - if final_step < show['stepBegin']: - break + agent_touches = goal_object['debug'].get('agentTouches', {}) + has_touches = any( + [len(touches) > 0 for touches in agent_touches.values()] + ) + if has_touches: + # If we know an agent touches an object in this trial because + # the object's color changes, only calculate the distances for + # the step(s) on which an agent touches an object. + # Assume an agent only "touches" ONE object in each trial. + for touch_step in agent_touches[trial_index]: + touch_agent_show = agent_shows[0] + for agent_show in agent_shows[1:]: + if agent_show['stepBegin'] > touch_step: + break + touch_agent_show = agent_show + agent_shows_measure_distances.append(touch_agent_show) + else: + # Otherwise calculate the distances for ALL the steps. + agent_shows_measure_distances = agent_shows - # If the goal object does not show in this trial, skip it. - if not goal_show: - continue + goal_poly = goal_show['boundingBox'].true_poly - # If the agent's too far away from this goal object, skip it. - poly = goal_show['boundingBox'].true_poly - distance = agent_poly.distance(poly) - if distance > 0.5: - continue - goals_with_distances.append((distance, goal_show)) + # Calculate the distance on each step from the agent to the object. + distance_tuples = [] + for agent_show in agent_shows_measure_distances: + agent_poly = agent_show['boundingBox'].true_poly + distance = round(agent_poly.distance(goal_poly), 4) + distance_tuples.append( + (distance, agent_show, goal_show, goal_object) + ) - # If the agent's not near any goal in this trial, skip it. - if not goals_with_distances: + # Sort the distances between the agent and the object on each step. + distance_tuples = list(sorted( + distance_tuples, + # Later steps should be ordered before earlier steps with + # the same distance. + key=lambda x: (x[0], -x[1]['stepBegin']) + )) + + # Skip this object if the agent never gets very close to it. + if not distance_tuples or distance_tuples[0][0] > 0.15: continue - goal_show = sorted(goals_with_distances, key=lambda x: x[0])[0][1] - goal_poly = goal_show['boundingBox'].true_poly + # See if the object is closer to the agent than any other object at + # any step; the agent is probably touching the closest object. + if distance_tuples[0][0] < closest_object_distances[0][0]: + closest_object_distances = distance_tuples + + # Skip if no agents and/or goal objects are in this trial. + if not closest_object_distances[0][1]: + continue + + goal_object = closest_object_distances[0][3] + highlight = True + + for data in closest_object_distances: + logger.debug( + f'distance={data[0]} step={data[1]["stepBegin"]}' + ) + + # Determine how many times the agent should touch the object. In + # scenes with no color change (agentTouches is 0), use 1. + agent_touches = goal_object['debug'].get('agentTouches', {}) + touch_count = len(agent_touches.get(trial_index) or [{}]) + # If the agent touches the object more than once, resort the list so + # the touches are ordered by step rather than distance. + if touch_count > 1: + closest_object_distances = list(sorted( + closest_object_distances, + key=lambda x: x[1]['stepBegin'] + )) + for index in range(touch_count): + distance = closest_object_distances[index][0] + agent_show = closest_object_distances[index][1] + goal_show = closest_object_distances[index][2] + old_position = agent_show['position'].copy() # Find the nearest distance from the agent to the goal. agent_point, goal_point = shapely.ops.nearest_points( - agent_poly, - goal_poly + agent_show['boundingBox'].true_poly, + goal_show['boundingBox'].true_poly ) diff_x = goal_point.coords[0][0] - agent_point.coords[0][0] diff_z = goal_point.coords[0][1] - agent_point.coords[0][1] # Move the agent directly adjacent to the goal. - agent_show['position']['x'] += diff_x - agent_show['position']['z'] += diff_z + new_position = agent_show['position'] + new_position['x'] = round(new_position['x'] + diff_x, 4) + new_position['z'] = round(new_position['z'] + diff_z, 4) agent_dimensions = agent_object['debug']['dimensions'] agent_show['boundingBox'] = _make_true_bounds( object_type=agent_object['type'], @@ -1870,19 +2368,76 @@ def _move_agent_adjacent_to_goal( 'z': agent_dimensions['z'] }, offset={'x': 0, 'y': 0, 'z': 0}, - position=agent_show['position'], + position=new_position, rotation=agent_show['rotation'], standing_y=(agent_dimensions['y'] / 2.0) ) bounds_at_step = agent_object['debug']['boundsAtStep'] + old_bounds = bounds_at_step[agent_show['stepBegin']] for bounds_index in range(agent_show['stepBegin'], final_step): if bounds_index >= len(bounds_at_step): break - bounds_at_step[bounds_index] = agent_show['boundingBox'] + if bounds_at_step[bounds_index] == old_bounds: + bounds_at_step[bounds_index] = agent_show['boundingBox'] + + logger.debug( + f'Moving agent next to goal object: trial={trial_index + 1} ' + f'step={agent_show["stepBegin"]} ' + f'agent={agent_object["type"]} ' + f'object={goal_object["type"]} ' + f'old_position={(old_position["x"], old_position["z"])} ' + f'new_position={(new_position["x"], new_position["z"])} ' + f'distance={distance}' + ) + + # Toggle the color for ALL goal objects on the step the agent + # touches one of them. + for goal_object in goal_object_list: + if goal_object['type'].startswith('blob_'): + continue + goal_object['changeMaterials'].append({ + 'stepBegin': agent_show['stepBegin'], + 'materials': ( + [GOAL_OBJECT_HIGHLIGHT_MATERIAL] if highlight + else goal_object['materials'] + ) + }) + + # If the agent touches the object multiple times, toggle the + # highlight color. + highlight = (not highlight) + + # Reset materials for ALL goal objects before the next trial. + for goal_object in goal_object_list: + if goal_object['type'].startswith('blob_'): + continue + goal_object['changeMaterials'].append({ + 'stepBegin': final_step + 1, + 'materials': goal_object['materials'] + }) + # Ensure the changeMaterials list is sorted by start step! + goal_object['changeMaterials'] = list(sorted( + goal_object['changeMaterials'], + key=lambda x: x['stepBegin'] + )) + + +def _move_agents_adjacent_to_goal( + agent_object_list: List[SceneObject], + goal_object_list: List[SceneObject], + trial_list: List[List[Dict[str, Any]]] +) -> None: + """Ensure the agents are directly adjacent to its goal in each trial.""" + for agent_object in agent_object_list: + _move_agent_adjacent_to_goal( + agent_object, + goal_object_list, + trial_list + ) def _remove_extraneous_object_show( - object_list: List[Dict[str, Any]], + object_list: List[SceneObject], trial_list: List[List[Dict[str, Any]]] ) -> None: """Remove each moving object's 'shows' array element that is the same as @@ -1917,32 +2472,163 @@ def _remove_extraneous_object_show( def _remove_intersecting_agent_steps( - agent_object_list: List[Dict[str, Any]], - other_object_list: List[Dict[str, Any]] + agent_object_list: List[SceneObject], + other_object_list: List[SceneObject] ) -> None: """Remove each agent object's step that intersects with any goal object's location at that step, since sometimes the agent moves a little too close to the goal object.""" for agent_object in agent_object_list: - remove_step_list = [] - for step, agent_bounds in enumerate( - agent_object['debug']['boundsAtStep'] - ): - if not agent_bounds: + trial_to_steps = agent_object['debug']['trialToSteps'] + agent_object['debug']['intersectingSteps'] = [] + + for trial_index in sorted(list(trial_to_steps.keys())): + remove_step_list = [] + # Identify the steps of the trial. + trial_start, trial_end = trial_to_steps[trial_index] + # Loop over each step, starting from the final step in the trial. + for step in range(trial_end, trial_start - 1, -1): + agent_bounds = agent_object['debug']['boundsAtStep'][step] # If the agent is hidden on this step, skip it. + if not agent_bounds: + continue + agent_poly = agent_bounds.true_poly + # Look for intersections with other objects. + for other_object in other_object_list: + if other_object['id'] == agent_object['id']: + continue + object_bounds = other_object['debug']['boundsAtStep'][step] + # If the object is hidden on this step, skip it. + if not object_bounds: + continue + intersection = _do_objects_overlap( + agent_bounds, + object_bounds, + step + ) + if not intersection: + continue + # If the agent intersects an object, remove this movement + # step, UNLESS it intersects with a wall in the middle of + # its movement, when it has one or more non-intersecting + # steps later (since this would cause the agent to jump). + should_remove_this_step = True + if other_object['id'].startswith('wall_'): + for next_step in range(step + 1, trial_end + 1): + if next_step not in remove_step_list: + should_remove_this_step = False + break + if should_remove_this_step: + agent_center = list(agent_poly.centroid.coords)[0] + agent_center = ( + round(agent_center[0], 4), + round(agent_center[1], 4) + ) + logger.debug( + f'Removing {agent_center} ' + f'at step {step} due to agent intersecting with ' + f'{other_object["id"]}' + ) + remove_step_list.append(step) + break + # Remove all the intersecting steps for this trial. + agent_object['shows'] = [ + show for show in agent_object['shows'] + if show['stepBegin'] not in remove_step_list + ] + agent_object['debug']['intersectingSteps'].extend(remove_step_list) + # Update the agent's boundsAtStep. + last_step = len(agent_object['debug']['boundsAtStep']) + for agent_show in list(reversed(agent_object['shows'])): + show_step = agent_show['stepBegin'] + bounds = agent_show['boundingBox'] + for step in range(show_step, last_step): + agent_object['debug']['boundsAtStep'][step] = bounds + last_step = show_step + + agent_object['debug']['intersectingSteps'].sort() + + +def _reposition_agents_away_from_paddle( + agent_object_list: List[SceneObject], + paddle_object: SceneObject +) -> None: + if not paddle_object: + return + # Map each step to the corresponding "show" for the paddle. + step_to_paddle_show = {} + for paddle_show in paddle_object['shows']: + step = paddle_show['stepBegin'] + step_to_paddle_show[step] = paddle_show + for agent_object in agent_object_list: + agent_bounds_at_step = agent_object['debug']['boundsAtStep'] + trial_to_steps = agent_object['debug']['trialToSteps'] + # Loop over all the trials in REVERSE order. + for trial_index in list(range(len(trial_to_steps) - 1, -1, -1)): + # Identify the start and end steps for the current trial. + trial_start, trial_end = trial_to_steps[trial_index] + # Identify the agent's "shows" for the current trial. + agent_shows = [ + show for show in agent_object['shows'] + if trial_start <= show['stepBegin'] <= trial_end + ] + if not agent_shows: continue - for other_object in other_object_list: - object_bounds = other_object['debug']['boundsAtStep'][step] - if object_bounds and _do_objects_intersect( + # See if the spinning paddle would ever intersect the agent's + # starting position of the current trial. If not, the agent does + # not begin near the paddle, so skip checking the current trial. + agent_bounds = agent_bounds_at_step[agent_shows[0]['stepBegin']] + skip = True + for step in range(trial_start, trial_end + 1): + if not step_to_paddle_show.get(step): + continue + paddle_bounds = paddle_object['debug']['boundsAtStep'][step] + if _do_objects_overlap( agent_bounds, - object_bounds + paddle_bounds, + step ): - remove_step_list.append(step) - agent_object['shows'] = [ - show for show in agent_object['shows'] - if show['stepBegin'] not in remove_step_list - ] - agent_object['debug']['intersectingSteps'] = remove_step_list + skip = False + break + if skip: + continue + # Keep record of the most recent valid "show" location. + valid_show = None + previously_intersects = False + # Loop over all the "shows" in REVERSE order. + for agent_show in list(reversed(agent_shows)): + step = agent_show['stepBegin'] + # See if the paddle and agent would intersect. + if not step_to_paddle_show.get(step): + continue + agent_bounds = agent_bounds_at_step[step] + paddle_bounds = paddle_object['debug']['boundsAtStep'][step] + intersects = _do_objects_overlap( + agent_bounds, + paddle_bounds, + step + ) + if intersects and not previously_intersects: + # Allow exactly one step of intersection; it is probably + # minor and ensures the paddle and agent actually "touch". + valid_show = agent_show + previously_intersects = True + elif intersects or previously_intersects: + logger.debug( + f'Replacing {agent_show["position"]} with ' + f'{valid_show["position"]} at step {step} ' + f'due to agent intersecting with paddle' + ) + # Move the agent to the most recent valid "show" location. + agent_show['position'] = valid_show['position'].copy() + agent_show['rotation'] = valid_show['rotation'].copy() + agent_bounds_at_step[step] = agent_bounds_at_step[ + valid_show['stepBegin'] + ] + previously_intersects = True + else: + # Otherwise keep the agent at its current location. + valid_show = agent_show def _retrieve_unit_size( @@ -1961,30 +2647,81 @@ def _save_trials(trial_list: List[List[Dict[str, Any]]], filename_prefix: str): with open(f'{filename_prefix}{TRIALS_SUFFIX}', 'w') as output_file: for trial_index, trial in enumerate(trial_list): step = _identify_trial_index_starting_step(trial_index, trial_list) - output_file.write(f'TRIAL {trial_index + 1} STEP {step}\n\n') + output_file.write(f'TRIAL {trial_index + 1} (STEP {step})\n\n') for frame_index, frame in enumerate(trial): - output_file.write(f'FRAME {frame_index + 1}\n') + output_file.write( + f'FRAME {frame_index + 1} (STEP {step + frame_index})\n' + ) output_file.write(f'AGENT {frame.get("agent")}\n') - output_file.write(f'FUSE_WALLS {frame.get("fuse_walls")}\n') output_file.write( - f'IMITATED AGENT {frame.get("imitated_agent")}\n' + f'OTHER AGENTS {frame.get("other_agents")}\n' ) + output_file.write(f'FUSE_WALLS {frame.get("fuse_walls")}\n') output_file.write(f'KEY {frame.get("key")}\n') output_file.write(f'LOCK {frame.get("lock")}\n') + output_file.write(f'OCCLUDER {frame.get("occluder")}\n') output_file.write(f'PADDLE {frame.get("paddle")}\n') output_file.write(f'PIN {frame.get("pin")}\n') output_file.write(f'OBJECTS {frame.get("objects")}\n') output_file.write('\n') +def _validate_trials( + trial_list: List[List[Dict[str, Any]]], + filename_prefix: str, + suffix: str +) -> List[List[Dict[str, Any]]]: + """Fix some formatting issues in the NYU JSON scene files.""" + + logger.info(f'Found {len(trial_list)} trials...') + + if len(trial_list) > 9: + if all([isinstance(trial, list) for trial in trial_list[:8]]): + final_trial = trial_list[8:] + trial_list = trial_list[:8] + [final_trial] + logger.info( + f'Collapsed {len(final_trial)} frames to use as final trial.' + ) + + if len(trial_list) > 9: + raise Exception(f'Too many trials in {filename_prefix}{suffix}') + + # Sometimes frames (which are supposed to be dicts) are accidentally lists + # of frames instead, so just add all the nested frames to the trial. + for trial_index, trial in enumerate(trial_list): + new_trial = [] + for frame_index, frame in enumerate(trial): + if isinstance(frame, dict): + new_trial.append(frame) + elif isinstance(frame, list): + logger.warn( + f'FRAME IS LIST {filename_prefix}{suffix} ' + f'trial {trial_index + 1} frame {frame_index + 1} / ' + f'{len(trial)}' + ) + new_trial.extend(frame) + else: + # If it's not a dict or a list, something's wrong... + raise Exception( + f'FRAME IS {type(frame).__name__.upper()} ' + f'{filename_prefix}{suffix} ' + f'trial {trial_index + 1} frame {frame_index + 1} / ' + f'{len(trial)}' + ) + trial_list[trial_index] = new_trial + + return trial_list + + def convert_scene_pair( starter_scene: Dict[str, Any], - goal_template: Dict[str, Any], + goal_template: Goal, trial_list_expected: List[List[Dict[str, Any]]], trial_list_unexpected: List[List[Dict[str, Any]]], filename_prefix: str, role_to_type: Dict[str, str], - untrained: bool + untrained: bool, + occluder_mode: OccluderMode = OccluderMode.NONE ) -> List[Dict[str, Any]]: """Create and return the pair of MCS scenes using the given templates and trial lists from the JSON file data.""" @@ -1992,37 +2729,22 @@ def convert_scene_pair( # Ignore untrained for now. untrained = False - # Sometimes frames (which are supposed to be dicts) are accidentally lists - # of frames instead, so just add all the nested frames to the trial. - for trial_list, suffix in [(trial_list_expected, 'e')] + ( - [(trial_list_unexpected, 'u')] if trial_list_unexpected else [] - ): - for trial_index, trial in enumerate(trial_list): - new_trial = [] - for frame_index, frame in enumerate(trial): - if isinstance(frame, dict): - new_trial.append(frame) - elif isinstance(frame, list): - logger.warn( - f'FRAME IS LIST {filename_prefix}{suffix} ' - f'trial={trial_index} frame={frame_index}/' - f'{len(trial)}' - ) - new_trial.extend(frame) - else: - # If it's not a dict or a list, something's wrong... - raise Exception( - f'FRAME IS {type(frame).__name__.upper()} ' - f'{filename_prefix}{suffix} ' - f'trial={trial_index} frame={frame_index}/' - f'{len(trial)}' - ) - trial_list[trial_index] = new_trial + trial_list_expected = _validate_trials( + trial_list_expected, + filename_prefix, + 'e' + ) + trial_list_unexpected = _validate_trials( + trial_list_unexpected, + filename_prefix, + 'u' + ) if trial_list_unexpected else None # Create the converted trial lists for both of the scenes. This will # remove extraneous frames from all of the trials. converted_trial_list_expected = [ - _create_trial_frame_list(trial) for trial in trial_list_expected + _create_trial_frame_list(trial, index) for index, trial + in enumerate(trial_list_expected) ] if SAVE_TRIALS_TO_FILE: @@ -2055,11 +2777,15 @@ def convert_scene_pair( config for config in GOAL_OBJECT_CONFIG_LIST if config.untrained == untrained ], - [role_to_type[tags.ROLES.TARGET], role_to_type[tags.ROLES.NON_TARGET]], + [ + role_to_type[tags.ROLES.TARGET], + role_to_type[tags.ROLES.NON_TARGET], + None + ], GOAL_OBJECT_MATERIAL_LIST, 'objects', [item.object_type for item in agent_object_config_list], - [item.material[0] for item in agent_object_config_list] + [item.material for item in agent_object_config_list] ) # Ensure the two scenes have exactly the same platform material. @@ -2074,7 +2800,8 @@ def convert_scene_pair( converted_trial_list_expected, filename_prefix, platform_material, - is_expected=True + is_expected=True, + occluder_mode=occluder_mode ) scenes = [scene_expected] @@ -2082,8 +2809,14 @@ def convert_scene_pair( if trial_list_unexpected: logger.info('Generating unexpected MCS agent scene from JSON data') converted_trial_list_unexpected = [ - _create_trial_frame_list(trial) for trial in trial_list_unexpected + _create_trial_frame_list(trial, index) for index, trial + in enumerate(trial_list_unexpected) ] + if SAVE_TRIALS_TO_FILE: + _save_trials( + converted_trial_list_unexpected, + f'{filename_prefix[(filename_prefix.rfind("/") + 1):]}u' + ) scene_unexpected = _create_scene( starter_scene, goal_template, @@ -2092,7 +2825,8 @@ def convert_scene_pair( converted_trial_list_unexpected, filename_prefix, platform_material, - is_expected=False + is_expected=False, + occluder_mode=occluder_mode ) # Ensure the two scenes have exactly the same room materials. for prop in ['ceiling_material', 'floor_material', 'wall_material']: diff --git a/hypercube/hypercubes.py b/hypercube/hypercubes.py index c0a5482..53a32f1 100644 --- a/hypercube/hypercubes.py +++ b/hypercube/hypercubes.py @@ -1,29 +1,25 @@ import copy -import logging import random import uuid from abc import ABC, abstractmethod from typing import Any, Callable, Dict, List -from generator import Scene, SceneException, materials, tags +from machine_common_sense.config_manager import Goal -logger = logging.getLogger(__name__) +from generator import Scene, SceneException, SceneObject, materials, tags -def initialize_goal(goal: Dict[str, Any]) -> Dict[str, Any]: +def initialize_goal(goal_template: Goal) -> Goal: """Initialize and return the properties in the given goal template.""" - goal_copy = copy.deepcopy(goal) - for prop in ['category', 'domainsInfo', 'sceneInfo']: - if prop not in goal_copy: - raise ValueError(f'Hypercube goal template must have {prop}') + goal_copy = copy.deepcopy(goal_template) - scene_info = goal_copy['sceneInfo'] + scene_info = goal_copy.scene_info scene_info[tags.ALL] = scene_info.get(tags.ALL, []) scene_info[tags.SCENE.SLICES] = scene_info.get(tags.SCENE.SLICES, []) - goal_copy['objectsInfo'] = goal_copy.get('objectsInfo', {}) - goal_copy['objectsInfo'][tags.ALL] = [] + goal_copy.objects_info = goal_copy.objects_info or {} + goal_copy.objects_info[tags.ALL] = [] # Add scene tags like 'fallDown', 'moveAcross', 'targetLocation', etc. for tag in tags.SCENE_OPTIONAL_TAGS_DICT.values(): @@ -44,15 +40,15 @@ def initialize_goal(goal: Dict[str, Any]) -> Dict[str, Any]: 0 if tag == tags.SCENE.COUNT else False ) - goal_copy['objectsInfo'][tags.role_to_key(role)] = [] + goal_copy.objects_info[tags.role_to_key(role)] = [] return goal_copy def update_floor_and_walls( scene: Scene, - role_to_object_data_list: Dict[str, Any], - retrieve_object_list_from_data: Callable[[], List[Dict[str, Any]]], + role_to_object_data_list: Dict[str, List[Any]], + retrieve_object_list_from_data: Callable[[Any], List[SceneObject]], scenes: List[Scene], floor_material_list=materials.FLOOR_MATERIALS, wall_material_list=materials.WALL_MATERIALS @@ -99,7 +95,7 @@ def update_floor_and_walls( def update_scene_objects( scene: Scene, - role_to_object_list: Dict[str, Dict[str, Any]] + role_to_object_list: Dict[str, List[SceneObject]] ) -> Scene: """Update and return the given scene with the given objects.""" @@ -124,24 +120,24 @@ def update_scene_objects( # The intuitive physics occluders are always two objects. count = (int)(count / 2) - scene.goal['sceneInfo'][tags.SCENE.COUNT][ + scene.goal.scene_info[tags.SCENE.COUNT][ tags.role_to_key(role) ] = count - scene.goal['sceneInfo'][tags.SCENE.PRESENT][ + scene.goal.scene_info[tags.SCENE.PRESENT][ tags.role_to_key(role) ] = (count > 0) - scene.goal['sceneInfo'][tags.SCENE.COUNT][tags.ALL] += count + scene.goal.scene_info[tags.SCENE.COUNT][tags.ALL] += count tags.append_object_tags( - scene.goal['sceneInfo'], - scene.goal['objectsInfo'], + scene.goal.scene_info, + scene.goal.objects_info, role_to_object_list ) for role, object_list in role_to_object_list.items(): for instance in object_list: # First add this object's info to the scene's objects tags. - scene.goal['objectsInfo'][tags.role_to_key(role)].extend( + scene.goal.objects_info[tags.role_to_key(role)].extend( instance['debug']['info'] ) @@ -159,40 +155,40 @@ def update_scene_objects_tag_lists( for role in tags.ROLE_DICT.values(): # Ensure the objects tags have only unique values. - scene.goal['objectsInfo'][tags.role_to_key(role)] = list( - set(scene.goal['objectsInfo'][tags.role_to_key(role)]) + scene.goal.objects_info[tags.role_to_key(role)] = list( + set(scene.goal.objects_info[tags.role_to_key(role)]) ) - if role in scene.goal['objectsInfo'][tags.role_to_key(role)]: - scene.goal['objectsInfo'][tags.role_to_key(role)].remove( + if role in scene.goal.objects_info[tags.role_to_key(role)]: + scene.goal.objects_info[tags.role_to_key(role)].remove( role ) # Add the list of object tags by role to the list of all tags. - if len(scene.goal['objectsInfo'][tags.role_to_key(role)]) > 0: - scene.goal['objectsInfo'][tags.ALL].extend( + if len(scene.goal.objects_info[tags.role_to_key(role)]) > 0: + scene.goal.objects_info[tags.ALL].extend( [role] + - scene.goal['objectsInfo'][tags.role_to_key(role)] + scene.goal.objects_info[tags.role_to_key(role)] ) # Ensure the objects tags have only unique values. - scene.goal['objectsInfo'][tags.ALL] = list( - set(scene.goal['objectsInfo'][tags.ALL]) + scene.goal.objects_info[tags.ALL] = list( + set(scene.goal.objects_info[tags.ALL]) ) # Add all domains tags to the 'all' list. - scene.goal['domainsInfo'][tags.ALL] = ( - scene.goal['domainsInfo'].get(tags.DOMAINS.OBJECTS, []) + - scene.goal['domainsInfo'].get(tags.DOMAINS.PLACES, []) + - scene.goal['domainsInfo'].get(tags.DOMAINS.AGENTS, []) + scene.goal.domains_info[tags.ALL] = ( + scene.goal.domains_info.get(tags.DOMAINS.OBJECTS, []) + + scene.goal.domains_info.get(tags.DOMAINS.PLACES, []) + + scene.goal.domains_info.get(tags.DOMAINS.AGENTS, []) ) # Add all scene tags to the 'all' list. - scene.goal['sceneInfo'][tags.ALL] = list(filter(None, [ - scene.goal['sceneInfo'].get(tags.SCENE.PRIMARY), - scene.goal['sceneInfo'].get(tags.SCENE.SECONDARY), - scene.goal['sceneInfo'].get(tags.SCENE.TERTIARY), - scene.goal['sceneInfo'].get(tags.SCENE.QUATERNARY) + scene.goal.scene_info[tags.ALL] = list(filter(None, [ + scene.goal.scene_info.get(tags.SCENE.PRIMARY), + scene.goal.scene_info.get(tags.SCENE.SECONDARY), + scene.goal.scene_info.get(tags.SCENE.TERTIARY), + scene.goal.scene_info.get(tags.SCENE.QUATERNARY) ])) for tag in tags.SCENE_OPTIONAL_TAGS_DICT.values(): @@ -201,19 +197,19 @@ def update_scene_objects_tag_lists( tag_label = ( '' if tag == tags.SCENE.SETUP else tags.tag_to_label(tag) ) - tag_value = scene.goal['sceneInfo'][tag] + tag_value = scene.goal.scene_info[tag] if tag_value: - scene.goal['sceneInfo'][tags.ALL].append( - ((tag_label + ' ') if tag_label else '') + tag_value + scene.goal.scene_info[tags.ALL].append( + f"{((tag_label + ' ') if tag_label else '')}{tag_value}" ) for tag in tags.SCENE_ROLE_TAGS_DICT.values(): tag_label = tags.tag_to_label(tag) for role in tags.ROLE_DICT.values(): - if scene.goal['sceneInfo'][tag][tags.role_to_key(role)]: - scene.goal['sceneInfo'][tags.ALL].append(role + ' ' + ( + if scene.goal.scene_info[tag][tags.role_to_key(role)]: + scene.goal.scene_info[tags.ALL].append(role + ' ' + ( tag_label if tag != 'count' - else str(scene.goal['sceneInfo'][tag][ + else str(scene.goal.scene_info[tag][ tags.role_to_key(role) ]) )) @@ -221,31 +217,6 @@ def update_scene_objects_tag_lists( return scene -def get_skewed_bell_curve_for_room_size(minimum=10, maximum=50): - val = random.uniform(0, 100) - if minimum > 19: - raise Exception( - f'Minimum room size {minimum} must be less than or equal to 10' - ) - if maximum < 40: - raise Exception( - f'Maximum room size {maximum} must be greater than or equal to 40' - ) - if val <= 80: - # most scenes should be medium sized 20 - 39 - low = 20 - high = 39 - elif val <= 90: - # size small 10 medium 19 - low = minimum - high = 19 - elif val <= 100: - # size large 40 to 50 - low = 40 - high = maximum - return low, high - - class Hypercube(ABC): """Creates a unique hypercube of one or more scenes that each have the same goals, objects, and variables, except for specific differences.""" @@ -259,33 +230,38 @@ def __init__( ) -> None: self._uuid = str(uuid.uuid4()).upper() self._name = name + self._scenes = [] + self._starter_scene = starter_scene + self._task_type = task_type self._training = training + def generate_scenes(self) -> List[Scene]: + """Generate and return the scenes for this hypercube.""" # Create all the scenes using the starter scene and the goal template. - goal_template = self._create_goal_template(task_type) - self._scenes = self._create_scenes(starter_scene, goal_template) + goal_template = self._create_goal_template(self._task_type) + self._scenes = self._create_scenes(self._starter_scene, goal_template) # Finalize the slice tags for each scene in this hypercube. for scene in self._scenes: for tag in self._get_slices(): - scene.goal['sceneInfo'][tags.SCENE.SLICES].append( + scene.goal.scene_info[tags.SCENE.SLICES].append( tags.tag_to_label(tag) + ' ' + - str(scene.goal['sceneInfo'][tag]) + str(scene.goal.scene_info[tag]) ) # Sort the scenes alphabetically by ID. self._scenes = sorted( self._scenes, - key=lambda x: x.goal['sceneInfo'].get(tags.SCENE.ID, [''])[0] + key=lambda x: x.goal.scene_info.get(tags.SCENE.ID, [''])[0] ) - is_passive_agent = tags.is_passive_agent_task(task_type) - is_passive_physics = tags.is_passive_physics_task(task_type) + is_passive_agent = tags.is_passive_agent_task(self._task_type) + is_passive_physics = tags.is_passive_physics_task(self._task_type) # Update specific tags in each scene. prefix = ((self._name + ' ') if self._name else '').replace(' ', '_') for index, scene in enumerate(self._scenes): - scene_info = scene.goal['sceneInfo'] + scene_info = scene.goal.scene_info if tags.SCENE.ID not in scene_info: scene_info[tags.SCENE.ID] = [f'{(index + 1):02}'] if tags.SCENE.NAME not in scene_info: @@ -298,26 +274,31 @@ def __init__( scene_info[tags.ALL].extend( [scene_info[tags.SCENE.NAME]] + scene_info[tags.SCENE.ID] ) + scene_info[tags.SCENE.DOMAIN_TYPE] = ( + tags.get_domain_type(self._task_type)) if not (is_passive_agent or is_passive_physics): scene_info[tags.SCENE.QUATERNARY] = tags.tag_to_label( tags.SCENE.ACTION_FULL - if not len(scene.goal.get('action_list', [])) else + if not len(scene.goal.action_list or []) else tags.SCENE.ACTION_VARIABLE ) - def _create_goal_template(self, task_type: str) -> Dict[str, Any]: + return self.get_scenes() + + def _create_goal_template(self, task_type: str) -> Goal: """Create and return this hypercube's template for a goal object.""" - goal_template = { - 'category': '', - 'sceneInfo': {}, + goal_template = Goal( + category='', + scene_info={}, # No longer used but kept here to maintain backwards compatibility. - 'domainsInfo': {'objects': [], 'places': [], 'agents': []} - } - scene_info = goal_template['sceneInfo'] + domains_info={'objects': [], 'places': [], 'agents': []} + ) + scene_info = goal_template.scene_info is_passive_agent = tags.is_passive_agent_task(task_type) is_passive_physics = tags.is_passive_physics_task(task_type) + is_multi_retrieval = tags.is_multi_retrieval(task_type) if is_passive_agent: - goal_template['category'] = tags.tag_to_label(tags.SCENE.AGENTS) + goal_template.category = tags.tag_to_label(tags.SCENE.AGENTS) scene_info[tags.SCENE.PRIMARY] = tags.tag_to_label( tags.SCENE.PASSIVE ) @@ -328,7 +309,7 @@ def _create_goal_template(self, task_type: str) -> Dict[str, Any]: tags.SCENE.ACTION_NONE ) elif is_passive_physics: - goal_template['category'] = tags.tag_to_label( + goal_template.category = tags.tag_to_label( tags.SCENE.INTUITIVE_PHYSICS ) scene_info[tags.SCENE.PRIMARY] = tags.tag_to_label( @@ -340,8 +321,17 @@ def _create_goal_template(self, task_type: str) -> Dict[str, Any]: scene_info[tags.SCENE.QUATERNARY] = tags.tag_to_label( tags.SCENE.ACTION_NONE ) + elif is_multi_retrieval: + goal_template.category = tags.tag_to_label( + tags.SCENE.MULTI_RETRIEVAL) + scene_info[tags.SCENE.PRIMARY] = tags.tag_to_label( + tags.SCENE.INTERACTIVE + ) + scene_info[tags.SCENE.SECONDARY] = tags.tag_to_label( + tags.SCENE.MULTI_RETRIEVAL + ) else: - goal_template['category'] = tags.tag_to_label(tags.SCENE.RETRIEVAL) + goal_template.category = tags.tag_to_label(tags.SCENE.RETRIEVAL) scene_info[tags.SCENE.PRIMARY] = tags.tag_to_label( tags.SCENE.INTERACTIVE ) @@ -358,7 +348,7 @@ def _create_goal_template(self, task_type: str) -> Dict[str, Any]: def _create_scenes( self, starter_scene: Scene, - goal_template: Dict[str, Any] + goal_template: Goal ) -> List[Scene]: """Create and return this hypercube's scenes.""" pass @@ -373,6 +363,10 @@ def _get_training_scenes(self) -> List[Scene]: By default, returns [] (no training data from this hypercube).""" return [] + def get_info(self) -> str: + """Return unique hypercube info. Can override as needed.""" + return '' + def get_name(self) -> str: """Return this hypercube's name.""" return self._name @@ -381,11 +375,7 @@ def get_scenes(self) -> List[Scene]: """Return this hypercube's list of scenes.""" if self._training: scenes = self._get_training_scenes() - logger.info(f'{self.get_name()} hypercube made ' - f'{len(scenes)} training scenes') return scenes - logger.info(f'{self.get_name()} hypercube made ' - f'{len(self._scenes)} non-training scenes') return self._scenes @@ -402,7 +392,7 @@ def _build(self, starter_scene: Scene) -> Hypercube: """Create and return a new hypercube built by this factory.""" pass - def build( + def generate_hypercubes( self, total: str, starter_scene_function: Callable[[], Scene], @@ -410,30 +400,12 @@ def build( throw_error=False, sort_data=False ) -> List[Hypercube]: - """Create and return a new list of scenes built by this factory.""" + """Create and return a new list of hypercubes built by this factory.""" # Save this now in case it's used by a hypercube factory subclass. self.role_to_type = role_to_type hypercubes = [] for count in range(1, total + 1): - logger.info(f'Generating hypercube {count} / {total}') - tries = 0 - while tries < 100: - tries += 1 - try: - # Build the hypercube and all of its scenes. - hypercube = self._build(starter_scene_function()) - hypercubes.append(hypercube) - break - except ( - SceneException, - RuntimeError, - TypeError, - ValueError, - ZeroDivisionError - ): - logging.exception(f'Fail to create {self.name} hypercube') - if throw_error or tries >= 100: - raise - + hypercube = self._build(starter_scene_function()) + hypercubes.append(hypercube) return hypercubes diff --git a/hypercube/interactive_hypercubes.py b/hypercube/interactive_hypercubes.py index 4e7d317..852907f 100644 --- a/hypercube/interactive_hypercubes.py +++ b/hypercube/interactive_hypercubes.py @@ -3,7 +3,7 @@ import random from typing import Any, Callable, Dict, List, Optional, Tuple -from machine_common_sense.config_manager import PerformerStart, Vector3d +from machine_common_sense.config_manager import Goal, PerformerStart, Vector3d from generator import ( MAX_TRIES, @@ -14,6 +14,7 @@ RetrievalGoal, Scene, SceneException, + SceneObject, base_objects, containers, definitions, @@ -48,9 +49,11 @@ logger = logging.getLogger(__name__) -ROOM_SIZE_X = list(range(10, 16)) +ROOM_SIZE_XZ_MIN = 10 +ROOM_SIZE_XZ_MAX = 20 +ROOM_SIZE_X = list(range(ROOM_SIZE_XZ_MIN, ROOM_SIZE_XZ_MAX + 1)) ROOM_SIZE_Y = list(range(3, 6)) -ROOM_SIZE_Z = list(range(10, 16)) +ROOM_SIZE_Z = list(range(ROOM_SIZE_XZ_MIN, ROOM_SIZE_XZ_MAX + 1)) SMALL_CONTEXT_OBJECT_CHOICES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] SMALL_CONTEXT_OBJECT_WEIGHTS = [5, 5, 10, 10, 12.5, 15, 12.5, 10, 10, 5, 5] @@ -64,7 +67,7 @@ WALL_SEPARATION = 1 -def retrieve_template_list(object_data: ObjectData) -> List[Dict[str, Any]]: +def retrieve_template_list(object_data: ObjectData) -> List[SceneObject]: return [object_data.trained_template, object_data.untrained_template] @@ -196,7 +199,7 @@ def _validate_object_plan(self) -> None: def _create_scenes( self, starter_scene: Scene, - goal_template: Dict[str, Any] + goal_template: Goal ) -> List[Scene]: tries = 0 @@ -808,7 +811,7 @@ def _assign_confusor_definition( def _choose_small_context_definition( self, target_confusor_data_list: List[ObjectData] - ) -> Dict[str, Any]: + ) -> ObjectDefinition: """Choose and return a small context object definition for the given target and confusor objects from the given definition list.""" return specific_objects.choose_distractor_definition([ @@ -865,7 +868,7 @@ def _choose_obstacle_or_occluder_definition( target_definition: ObjectDefinition, definition_dataset: DefinitionDataset, is_occluder: bool - ) -> Dict[str, Any]: + ) -> ObjectDefinition: """Choose and return an obstacle or occluder definition for the given target object from the given definition list.""" @@ -959,7 +962,7 @@ def _choose_container_definition( confusor_definition: Optional[ObjectDefinition], definition_dataset: DefinitionDataset, find_invalid_container: bool = False, - ) -> Tuple[Dict[str, Any], int, containers.Orientation, float, float]: + ) -> Tuple[ObjectDefinition, int, containers.Orientation, float, float]: """Choose and return a valid or an invalid container definition for the given target and confusor objects from the given definition list.""" @@ -1117,7 +1120,7 @@ def _create_target_list( target_validation_list: List[Dict[str, float]], start_index: int = None, end_index: int = None - ) -> Tuple[List[Dict[str, Any]], List[ObjectBounds]]: + ) -> Tuple[List[SceneObject], List[ObjectBounds]]: """Create and return each of the goal's targets between the start_index and the end_index. Used if the goal needs more targets than are defined by the hypercube's plan. Changes the bounds_list.""" @@ -1697,10 +1700,10 @@ def _log_debug_object_data(self, object_data: ObjectData) -> None: def _move_distractor_into_receptacle( self, - object_instance: Dict[str, Any], + object_instance: SceneObject, performer_start: Dict[str, Dict[str, float]], bounds_list: List[ObjectBounds] - ) -> Dict[str, Any]: + ) -> SceneObject: """Create and return a receptacle object, moving the given object into the new receptacle. Changes the bounds_list.""" # Only a pickupable object can be positioned inside a receptacle. @@ -1740,10 +1743,10 @@ def _move_distractor_into_receptacle( def _move_distractor_onto_receptacle( self, - object_instance: Dict[str, Any], + object_instance: SceneObject, performer_start: Dict[str, Dict[str, float]], bounds_list: List[ObjectBounds] - ) -> Dict[str, Any]: + ) -> SceneObject: """Create and return a receptacle object, moving the given object onto the new receptacle. Changes the bounds_list.""" # TODO MCS-146 Position objects on top of receptacles. @@ -1753,7 +1756,7 @@ def _update_scene_at_index( self, scene: Scene, scene_index: int, - goal_template: Dict[str, Any] + goal_template: Goal ) -> None: """Update the given scene with its metadata like all of its objects.""" scene_plan = self._plan_list[scene_index] @@ -1786,7 +1789,7 @@ def _update_scene_at_index( [self._target_data.instance_list[scene_index]] ) - scene.goal['last_step'] = get_step_limit_from_dimensions( + scene.goal.last_step = get_step_limit_from_dimensions( scene.room_dimensions.x, scene.room_dimensions.z ) @@ -1818,13 +1821,13 @@ def _update_scene_at_index( ] update_scene_objects(scene, role_to_object_list) - scene.goal['sceneInfo'][tags.SCENE.ID] = [ + scene.goal.scene_info[tags.SCENE.ID] = [ scene_plan.scene_id.upper() ] - scene.goal['sceneInfo'][tags.SCENE.SLICES] = [] + scene.goal.scene_info[tags.SCENE.SLICES] = [] for tag, value in scene_plan.slice_tags.items(): - scene.goal['sceneInfo'][tag] = value - scene.goal['sceneInfo'][tags.SCENE.SLICES].append( + scene.goal.scene_info[tag] = value + scene.goal.scene_info[tags.SCENE.SLICES].append( tags.tag_to_label(tag) + ' ' + str(value) ) diff --git a/hypercube/intuitive_physics_hypercubes.py b/hypercube/intuitive_physics_hypercubes.py index 7092ace..2027ae2 100644 --- a/hypercube/intuitive_physics_hypercubes.py +++ b/hypercube/intuitive_physics_hypercubes.py @@ -9,7 +9,7 @@ from types import SimpleNamespace from typing import Any, Dict, List, Optional, Tuple -from machine_common_sense.config_manager import PerformerStart, Vector3d +from machine_common_sense.config_manager import Goal, PerformerStart, Vector3d from shapely import affinity from generator import ( @@ -21,6 +21,7 @@ ObjectDefinition, Scene, SceneException, + SceneObject, geometry, gravity_support_objects, instances, @@ -42,7 +43,6 @@ ) from generator.intuitive_physics_util import ( COLLISION_SPEEDS, - MAX_TARGET_Y, MAX_TARGET_Z, MIN_TARGET_Z, choose_position_z, @@ -75,6 +75,11 @@ BACKGROUND_MIN_Z = 3.25 BACKGROUND_MAX_Z = 4.95 +TIPSY_OBJECT_TYPES = [ + 'rollable_4', + 'rollable_6' +] + PERFORMER_START = PerformerStart( position=Vector3d(x=0, y=0, z=-4.5), rotation=Vector3d(x=0, y=0, z=0) @@ -175,7 +180,7 @@ def adjust_movement_to_position( def choose_move_across_object_position( left_side: bool, - object_list: List[Dict[str, Any]], + object_list: List[SceneObject], min_z: float = MIN_TARGET_Z, max_z: float = MAX_TARGET_Z ) -> Optional[Dict[str, float]]: @@ -266,14 +271,14 @@ class ObjectVariations(): hypercube may use a variation of the object. Variations may be different in size, shape, position, color, etc.""" - def __init__(self, name_to_instance: Dict[str, Dict[str, Any]]) -> None: + def __init__(self, name_to_instance: Dict[str, SceneObject]) -> None: self._instances = name_to_instance - def all(self) -> List[Dict[str, Any]]: + def all(self) -> List[SceneObject]: """Return a list of instances for all variations.""" return list(self._instances.values()) - def get(self, name) -> Dict[str, Any]: + def get(self, name) -> SceneObject: """Return an instance for the variation with the given name.""" return self._instances.get(name) @@ -347,7 +352,7 @@ def _adjust_location( return location_copy -def retrieve_as_list(data: Dict[str, Any]) -> List[Dict[str, Any]]: +def retrieve_as_list(data: Any) -> List[Any]: return [data] @@ -356,7 +361,7 @@ def __init__( self, name: str, starter_scene: Scene, - goal_template: Dict[str, Any], + goal_template: Goal, role_to_type: Dict[str, str], is_fall_down=False, is_move_across=False, @@ -403,7 +408,7 @@ def _create_intuitive_physics_scenes( def _create_scenes( self, starter_scene: Scene, - goal_template: Dict[str, Any] + goal_template: Goal ) -> List[Scene]: default_scene = self._create_default_scene( starter_scene, @@ -412,16 +417,16 @@ def _create_scenes( scenes = self._create_intuitive_physics_scenes(default_scene) for scene in scenes.values(): # Update the scene info tags for the evaluation UI. - scene.goal['sceneInfo'][tags.SCENE.ID] = [ + scene.goal.scene_info[tags.SCENE.ID] = [ scene_id.upper() for scene_id - in scene.goal['sceneInfo'][tags.SCENE.ID] + in scene.goal.scene_info[tags.SCENE.ID] ] return list(scenes.values()) # Override def _get_training_scenes(self) -> List[Scene]: return [scene for scene in self._scenes if ( - scene.goal['answer']['choice'] == PLAUSIBLE and + scene.goal.answer['choice'] == PLAUSIBLE and (not scene.debug['evaluationOnly']) )] @@ -791,7 +796,8 @@ def _choose_object_variations( # Sometimes object variations must have colors that are completely # the same as or opposite to the trained variation's colors. - if 'opposite' in definitions[VARIATIONS.TRAINED].materialCategory: + material_categories = definitions[VARIATIONS.TRAINED].materialCategory + if 'object_opposite' in material_categories: base_material_list = [] for material in definitions[VARIATIONS.TRAINED].materials: opposite = materials.OPPOSITE_SETS[material][0] @@ -947,7 +953,7 @@ def _callback(definition: ImmutableObjectDefinition) -> bool: def _create_default_scene( self, starter_scene: Scene, - goal_template: Dict[str, Any] + goal_template: Goal ) -> Scene: """Create and return this hypercube's default scene JSON using the given templates that will be shared by each scene in this hypercube.""" @@ -965,21 +971,21 @@ def _create_default_scene( scene.debug['wallColors'] = room_wall_material_choice.color scene.goal = copy.deepcopy(goal_template) - scene.goal['answer'] = { + scene.goal.answer = { 'choice': PLAUSIBLE } - scene.goal['sceneInfo'][tags.SCENE.FALL_DOWN] = self.is_fall_down() - scene.goal['sceneInfo'][tags.SCENE.MOVE_ACROSS] = ( + scene.goal.scene_info[tags.SCENE.FALL_DOWN] = self.is_fall_down() + scene.goal.scene_info[tags.SCENE.MOVE_ACROSS] = ( self.is_move_across() ) - scene.goal['sceneInfo'][tags.SCENE.SETUP] = ( + scene.goal.scene_info[tags.SCENE.SETUP] = ( tags.tag_to_label(tags.SCENE.FALL_DOWN) if self.is_fall_down() else tags.tag_to_label(tags.SCENE.MOVE_ACROSS) ) - scene.goal['last_step'] = self._last_step - scene.goal['action_list'] = [['Pass']] * scene.goal['last_step'] - scene.goal['description'] = '' - scene.goal['metadata'] = {} + scene.goal.last_step = self._last_step + scene.goal.action_list = [['Pass']] * scene.goal.last_step + scene.goal.description = '' + scene.goal.metadata = {} role_to_object_list = self._create_default_objects( room_wall_material_choice @@ -1106,7 +1112,7 @@ def _find_structural_object_material_list( structural_object_material_list.append(filtered_material_list) return structural_object_material_list - def _generate_background_object_list(self) -> List[Dict[str, Any]]: + def _generate_background_object_list(self) -> List[SceneObject]: """Generate and return the list of background (a.k.a. context) objects, behind the moving objects, positioned near the room's back wall.""" @@ -1157,7 +1163,7 @@ def random_z(room_dimensions: Dict[str, float]) -> float: def _generate_fall_down( self, occluder_wall_material_list: List[List[Tuple]] - ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + ) -> Tuple[List[SceneObject], List[SceneObject]]: """Generate and return fall-down objects and occluders.""" object_list = None occluder_list = None @@ -1179,10 +1185,10 @@ def _generate_fall_down( except SceneException as e: latest_exception = e if not object_list or not occluder_list: - raise latest_exception + raise latest_exception from latest_exception return object_list, occluder_list - def _generate_fall_down_object_list(self) -> List[Dict[str, Any]]: + def _generate_fall_down_object_list(self) -> List[SceneObject]: """Generate and return fall-down objects.""" object_list = [] self._variations_list = [] @@ -1260,9 +1266,9 @@ def _generate_fall_down_object_list(self) -> List[Dict[str, Any]]: def _generate_fall_down_paired_occluder( self, paired_variations: TargetVariations, - occluder_list: List[Dict[str, Any]], + occluder_list: List[SceneObject], occluder_wall_material_list: List[List[Tuple]] - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return one fall-down paired occluder that must be positioned underneath the paired object.""" paired_object = paired_variations.get(VARIATIONS.TRAINED) @@ -1362,7 +1368,7 @@ def _generate_fall_down_paired_occluder( def _generate_fall_down_paired_occluder_list( self, occluder_wall_material_list: List[List[Tuple]] - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return needed fall-down paired occluders.""" paired_list = self._find_fall_down_paired_list() occluder_list = [] @@ -1383,7 +1389,7 @@ def _generate_fall_down_paired_occluder_list( def _generate_move_across( self, occluder_wall_material_list: List[List[Tuple]] - ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + ) -> Tuple[List[SceneObject], List[SceneObject]]: """Generate and return move-across objects and occluders.""" object_list = None occluder_list = None @@ -1410,13 +1416,13 @@ def _generate_move_across( except SceneException as e: latest_exception = e if not object_list or not occluder_list: - raise latest_exception + raise latest_exception from latest_exception return object_list, occluder_list def _generate_move_across_object_list( self, last_action_step: int - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return move-across objects.""" object_count = self._get_move_across_object_count() object_list = [] @@ -1528,10 +1534,10 @@ def _generate_move_across_object_list( def _generate_move_across_paired_occluder( self, paired_variations: TargetVariations, - occluder_list: List[Dict[str, Any]], + occluder_list: List[SceneObject], occluder_wall_material_list: List[List[Tuple]], index: int - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return one move-across paired occluder that must be positioned at one of the paired object's distance_by_step so that it will properly hide the paired object during the implausible event.""" @@ -1553,9 +1559,9 @@ def _generate_move_across_paired_occluder( def _generate_move_across_paired_occluder_list( self, - object_list: List[Dict[str, Any]], + object_list: List[SceneObject], occluder_wall_material_list: List[List[Tuple]] - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return needed move-across paired occluders.""" paired_list = self._find_move_across_paired_list( self._variations_list[0] @@ -1578,10 +1584,10 @@ def _generate_move_across_paired_occluder_list( def _generate_occluder( self, - occluder_list: List[Dict[str, Any]], + occluder_list: List[SceneObject], occluder_wall_material_list: List[List[Tuple]], sideways: bool - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Generate and return a single occluder.""" successful = False for _ in range(MAX_TRIES): @@ -1618,7 +1624,7 @@ def _generate_occluder( def _generate_occluder_list( self, number: int, - occluder_list: List[Dict[str, Any]], + occluder_list: List[SceneObject], occluder_wall_material_list: List[List[Tuple]], sideways: bool ) -> None: @@ -1685,8 +1691,8 @@ def _init_each_object_definition_list( def _identify_targets_and_non_targets( self, - moving_object_list: List[Dict[str, Any]] - ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + moving_object_list: List[SceneObject] + ) -> Tuple[List[SceneObject], List[SceneObject]]: """Return the two separate target and non-target lists using the given moving object list.""" return moving_object_list, [] @@ -1712,7 +1718,7 @@ def _retrieve_definition_data( def _update_object_tags( self, scene: Scene, - object_list: List[Dict[str, Any]], + object_list: List[SceneObject], role: str ) -> Scene: """Update and return the given scene with info from targets in the @@ -1727,21 +1733,21 @@ def _update_object_tags( ] for tag in important_tag_list: - scene.goal['sceneInfo'][tag][tags.role_to_key(role)] = False + scene.goal.scene_info[tag][tags.role_to_key(role)] = False - scene.goal['objectsInfo']['all'] = [] - scene.goal['objectsInfo'][tags.role_to_key(role)] = [] + scene.goal.objects_info['all'] = [] + scene.goal.objects_info[tags.role_to_key(role)] = [] for instance in object_list: role_to_object_list = {} role_to_object_list[role] = object_list tags.append_object_tags_of_type( - scene.goal['sceneInfo'], - scene.goal['objectsInfo'], + scene.goal.scene_info, + scene.goal.objects_info, role_to_object_list, role ) - scene.goal['objectsInfo'][tags.role_to_key(role)].extend( + scene.goal.objects_info[tags.role_to_key(role)].extend( instance['debug']['info'] ) instance['debug']['info'].append(role) @@ -1767,7 +1773,11 @@ def _update_hypercube_scene_info_tags( if instance['debug']['role'] == role ], role) - scene.goal['sceneInfo'][tags.SCENE.ID] = [scene_id] + scene.goal.scene_info[tags.SCENE.ID] = [scene_id] + scene.goal.scene_info[tags.SCENE.TIPSY] = False + for obj in scene.objects: + if obj['type'] in TIPSY_OBJECT_TYPES: + scene.goal.scene_info[tags.SCENE.TIPSY] = True def _validate_in_view( self, @@ -1819,8 +1829,8 @@ def __init__( def _adjust_impact_position( self, - target: Dict[str, Any], - non_target: Dict[str, Any] + target: SceneObject, + non_target: SceneObject ) -> Dict[str, Any]: """Find and return an X position for the given non-target object so that the target object will impact the non-target object at the exact @@ -1859,16 +1869,16 @@ def _create_intuitive_physics_scenes( # Initialize default collision tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_MOVES ] = tags.CELLS.COLLISIONS_MOVES.ONE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_OCCLUDERS ] = tags.CELLS.COLLISIONS_OCCLUDERS.NO - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_TRAINED ] = tags.CELLS.COLLISIONS_TRAINED.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_REVEALS ] = tags.CELLS.COLLISIONS_REVEALS.EMPTY # Remove the occluder from each scene. @@ -1882,7 +1892,7 @@ def _create_intuitive_physics_scenes( # Remove the non-target object from the scene. scene = scenes['a2'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_REVEALS ] = tags.CELLS.COLLISIONS_REVEALS.EMPTY objects = scene.objects @@ -1893,10 +1903,10 @@ def _create_intuitive_physics_scenes( # Reposition the non-target object to the target object's Z position. scene = scenes['h2'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_MOVES ] = tags.CELLS.COLLISIONS_MOVES.TWO - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.COLLISIONS_REVEALS ] = tags.CELLS.COLLISIONS_REVEALS.ON_PATH for instance in scene.objects: @@ -1961,8 +1971,8 @@ def _get_object_min_z(self) -> int: # Override def _identify_targets_and_non_targets( self, - target_object_list: List[Dict[str, Any]] - ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + target_object_list: List[SceneObject] + ) -> Tuple[List[SceneObject], List[SceneObject]]: """Return the two separate target and non-target lists using the given moving object list.""" target_variations = self._variations_list[0] @@ -2138,7 +2148,7 @@ def __init__( def _create_default_objects( self, room_wall_material: MaterialTuple - ) -> Dict[str, Any]: + ) -> SceneObject: """Generate and return this hypercube's objects in a dict of roles with their corresponding object lists.""" @@ -2150,14 +2160,20 @@ def _create_default_objects( self._target, structural_object_material_list ) - # Adjust the target's starting Y position by the visible support's size - # so the target ends its movement directly above the visible support. - start_height = ( - MAX_TARGET_Y - 0.5 + - self._visible_support['debug']['dimensions']['y'] - ) for instance in self._target.all(): + # The bottom of the target object must be off-screen. + min_height = retrieve_off_screen_position_y( + instance['shows'][0]['position']['z'] + ) + # Adjust the target's starting Y position by the visible support's + # size so the target ends its movement directly above the visible + # support. (Subtract 0.5 because that's the minimum support size.) + start_height = ( + min_height + instance['debug']['dimensions']['y'] + + self._visible_support['debug']['dimensions']['y'] - 0.5 + ) + # Add downward movement and other properties to target. mechanisms.place_object( instance=instance, @@ -2270,7 +2286,7 @@ def _create_gravity_support_target(self) -> TargetVariations: def _create_pole( self, target_variations: TargetVariations, - visible_support: Dict[str, Any] + visible_support: SceneObject ) -> ObjectVariations: target_symmetric = target_variations.get(VARIATIONS.SYMMETRIC) target_asymmetric_left = target_variations.get( @@ -2298,7 +2314,9 @@ def _create_pole( placed_object_offset_y=target['debug']['positionY'], activation_step=target['shows'][0]['stepBegin'], end_height=visible_support['debug']['dimensions']['y'], - max_height=MAX_TARGET_Y, + # This just informs the length of the placer, which isn't + # really important, so just make it a large enough value. + max_height=10, placed_object_placer_offset_y=placer_offset_y ) # Each pole variation should have the same ID. @@ -2323,7 +2341,7 @@ def _create_visible_support_object( self, target_variations: TargetVariations, structural_object_material_list: List[Tuple[str, List[str]]] - ) -> Dict[str, Any]: + ) -> SceneObject: target_symmetric = target_variations.get(VARIATIONS.SYMMETRIC) # Retrieve the finalized definition for the visible support. # Restrict the possible sizes to the same or bigger than the target. @@ -2420,19 +2438,19 @@ def _create_intuitive_physics_scenes( scenes = {} for i in self._get_scene_ids(): scenes[i + '1'] = copy.deepcopy(default_scene) - scenes[i + '1'].goal['sceneInfo'][tags.SCENE.DIRECTION] = ( + scenes[i + '1'].goal.scene_info[tags.SCENE.DIRECTION] = ( 'right' if is_positive else 'left' ) # Initialize gravity support tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_PLAUSIBLE ] = tags.CELLS.GRAVITY_SUPPORT_PLAUSIBLE.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.FULL - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_TYPE ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_TYPE.SYMMETRIC @@ -2447,31 +2465,31 @@ def _create_intuitive_physics_scenes( x_position_multiplier = 0 # Move target to its no-support X position. if i in self._get_scene_ids_target_support_none(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.NONE x_position_multiplier = no_support_multiplier # Move target to its minimal-support (5%) X position. if i in self._get_scene_ids_target_support_minimal(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.MINIMAL x_position_multiplier = 0.45 # Move target to its 25%-supported X position. if i in self._get_scene_ids_target_support_25(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.TWENTY_FIVE x_position_multiplier = 0.25 # Move target to its 49%-supported X position. if i in self._get_scene_ids_target_support_49(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.FORTY_NINE x_position_multiplier = 0.01 # Move target to its 75%-supported X position. if i in self._get_scene_ids_target_support_75(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_POSITION ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_POSITION.SEVENTY_FIVE x_position_multiplier = -0.25 @@ -2496,8 +2514,8 @@ def _create_intuitive_physics_scenes( continue scene = scenes[i + '1'] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_PLAUSIBLE ] = tags.CELLS.GRAVITY_SUPPORT_PLAUSIBLE.NO # Add an invisible support on the floor next to the visible one. @@ -2574,7 +2592,7 @@ def _update_asymmetric_target_slice_scene( if (i + '1') not in scenes: continue scene = scenes[i + '1'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.GRAVITY_SUPPORT_TARGET_TYPE ] = tags.CELLS.GRAVITY_SUPPORT_TARGET_TYPE.ASYMMETRIC # Assume any asymmetric object is right-to-left (like an L). @@ -2594,7 +2612,7 @@ def _update_asymmetric_target_slice_scene( def _change_object_center_of_mass( self, - target: Dict[str, Any], + target: SceneObject, visible_support_height: float, is_positive: bool ) -> None: @@ -2650,9 +2668,9 @@ def _find_center_of_mass_x( def _position_on_center_of_mass( self, target_asymmetric_definition: ObjectDefinition, - target_asymmetric: Dict[str, Any], - pole_asymmetric: Dict[str, Any], - target_symmetric: Dict[str, Any], + target_asymmetric: SceneObject, + pole_asymmetric: SceneObject, + target_symmetric: SceneObject, flip: bool ) -> None: asymmetric_center = self._find_center_of_mass_x( @@ -2677,7 +2695,7 @@ def _position_on_center_of_mass( def _place_invisible_support_on_floor( self, scene: Scene, - target: Dict[str, Any], + target: SceneObject, is_positive: bool ) -> None: invisible_support = copy.deepcopy(self._visible_support) @@ -2705,7 +2723,7 @@ def _place_invisible_support_on_floor( def _shove_target_off_its_support( self, - instance: Dict[str, Any], + instance: SceneObject, is_positive: bool ) -> None: wind_step = instance['togglePhysics'][0]['stepBegin'] + 1 + ( @@ -2727,8 +2745,8 @@ def _shove_target_off_its_support( def _stack_invisible_support_on_support( self, scene: Scene, - target: Dict[str, Any], - pole: Dict[str, Any], + target: SceneObject, + pole: SceneObject, implausible_support_y: float ) -> None: invisible_support = copy.deepcopy(self._visible_support) @@ -2793,7 +2811,7 @@ def _appear_behind_occluder( def _appear_behind_occluder_fall_down( self, - target: Dict[str, Any] + target: SceneObject ) -> None: # Implausible event happens after target falls behind occluder. implausible_event_step = ( @@ -2809,7 +2827,7 @@ def _appear_behind_occluder_fall_down( def _appear_behind_occluder_move_across( self, - target: Dict[str, Any] + target: SceneObject ) -> None: # Held back in Eval 4 (see override in secret file). pass @@ -2833,7 +2851,7 @@ def _disappear_behind_occluder( def _disappear_behind_occluder_fall_down( self, - target: Dict[str, Any] + target: SceneObject ) -> None: # Implausible event happens after target falls behind occluder. implausible_event_step = ( @@ -2846,7 +2864,7 @@ def _disappear_behind_occluder_fall_down( def _disappear_behind_occluder_move_across( self, - target: Dict[str, Any] + target: SceneObject ) -> None: # Held back in Eval 4 (see override in secret file). pass @@ -2884,23 +2902,23 @@ def _create_intuitive_physics_scenes( # Initialize object permanence tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_ONE ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_ONE.NO_CHANGE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_TWO ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_TWO.NO_CHANGE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_ONE ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_ONE.NONE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_TWO ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_TWO.NONE # Remove object two completely. for i in ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']: scene = scenes[i + '1'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_TWO ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_TWO.NONE for index in range(len(scene.objects)): @@ -2915,7 +2933,7 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_ONE ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_ONE.SIZE for index in range(len(scene.objects)): @@ -2932,7 +2950,7 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_ONE ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_ONE.SHAPE for index in range(len(scene.objects)): @@ -2947,7 +2965,7 @@ def _create_intuitive_physics_scenes( for j in [i + '2', i + '3', i + '4']: scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_TWO ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_TWO.SIZE for index in range(len(scene.objects)): @@ -2962,7 +2980,7 @@ def _create_intuitive_physics_scenes( for j in [i + '2', i + '3', i + '4']: scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_TWO ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_TWO.SHAPE for index in range(len(scene.objects)): @@ -2979,8 +2997,8 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_ONE ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_ONE.DISAPPEAR self._disappear_behind_occluder(scene, target_id_1) @@ -2992,8 +3010,8 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_ONE ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_ONE.APPEAR self._appear_behind_occluder(scene, target_id_1) @@ -3006,8 +3024,8 @@ def _create_intuitive_physics_scenes( # Make object two disappear. scene = scenes[i + '4'] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_TWO ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_TWO.DISAPPEAR self._disappear_behind_occluder(scene, target_id_2) @@ -3015,8 +3033,8 @@ def _create_intuitive_physics_scenes( # Make object two appear. scene = scenes[i + '3'] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_TWO ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_TWO.APPEAR self._appear_behind_occluder(scene, target_id_2) @@ -3070,7 +3088,7 @@ def _turn_a_into_b( self, scene: Scene, target_id: str, - template_b: Dict[str, Any] + template_b: SceneObject ) -> None: target_a = [ instance for instance in scene.objects @@ -3152,16 +3170,16 @@ def _create_intuitive_physics_scenes( # Initialize shape constancy tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_ONE ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_ONE.NO_CHANGE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_TWO ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_TWO.NO_CHANGE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_TRAINED_ONE ] = tags.CELLS.SHAPE_CONSTANCY_TRAINED_ONE.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_TRAINED_TWO ] = tags.CELLS.SHAPE_CONSTANCY_TRAINED_TWO.YES @@ -3171,7 +3189,7 @@ def _create_intuitive_physics_scenes( if j not in scenes: continue scene = scenes[i + '1'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_TWO ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_TWO.NONE for index in range(len(scene.objects)): @@ -3186,7 +3204,7 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_TRAINED_ONE ] = tags.CELLS.SHAPE_CONSTANCY_TRAINED_ONE.NO for index in range(len(scene.objects)): @@ -3203,7 +3221,7 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_TRAINED_TWO ] = tags.CELLS.SHAPE_CONSTANCY_TRAINED_TWO.NO for index in range(len(scene.objects)): @@ -3220,8 +3238,8 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_ONE ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_ONE.TRAINED_SHAPE self._turn_a_into_b(scene, target_id_1, trained_variation_1_b) @@ -3233,8 +3251,8 @@ def _create_intuitive_physics_scenes( continue scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_ONE ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_ONE.UNTRAINED_SHAPE self._turn_a_into_b( @@ -3250,8 +3268,8 @@ def _create_intuitive_physics_scenes( # Object two transforms into a different trained shape. scene = scenes['e3'] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_TWO ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_TWO.TRAINED_SHAPE self._turn_a_into_b(scene, target_id_2, trained_variation_2_b) @@ -3259,8 +3277,8 @@ def _create_intuitive_physics_scenes( # Object two transforms into a different untrained shape. scene = scenes['l4'] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.SHAPE_CONSTANCY_OBJECT_TWO ] = tags.CELLS.SHAPE_CONSTANCY_OBJECT_TWO.UNTRAINED_SHAPE self._turn_a_into_b( @@ -3382,19 +3400,19 @@ def _create_intuitive_physics_scenes( # Initialize spatio temporal continuity tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OBJECTS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OBJECTS.TWO - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS.THREE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_NON_TARGET_TRAINED ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_NON_TARGET_TRAINED.YES @@ -3402,7 +3420,7 @@ def _create_intuitive_physics_scenes( for i in ['a', 'd', 'g', 'j', 'm', 'p', 'b', 'e', 'h', 'k', 'n', 'q']: for j in [i + '1', i + '2', i + '3', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OBJECTS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OBJECTS.ONE for index in range(len(scene.objects)): @@ -3414,7 +3432,7 @@ def _create_intuitive_physics_scenes( for i in ['a', 'd', 'g', 'j', 'm', 'p']: for j in [i + '1', i + '2', i + '3', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OBJECTS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OBJECTS.ZERO for index in range(len(scene.objects)): @@ -3426,7 +3444,7 @@ def _create_intuitive_physics_scenes( for i in ['g', 'h', 'i', 'j', 'k', 'l']: for j in [i + '1', i + '2', i + '3', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS.TWO remove_id_list = [ @@ -3442,7 +3460,7 @@ def _create_intuitive_physics_scenes( for i in ['m', 'n', 'o', 'p', 'q', 'r']: for j in [i + '1', i + '2', i + '3', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS.ZERO remove_id_list = [ @@ -3460,7 +3478,7 @@ def _create_intuitive_physics_scenes( # Switch the target with its untrained variation. for j in [i + '2', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED.NO scene.debug['evaluationOnly'] = True @@ -3474,7 +3492,7 @@ def _create_intuitive_physics_scenes( # Switch the non-target with its untrained variation. for j in [i + '3', i + '4']: scene = scenes[j] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_NON_TARGET_TRAINED ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_NON_TARGET_TRAINED.NO scene.debug['evaluationOnly'] = True @@ -3491,8 +3509,8 @@ def _create_intuitive_physics_scenes( for j in [i + '1', i + '2', i + '3', i + '4']: scene = scenes[j] scene.debug['evaluationOnly'] = True - scene.goal['answer']['choice'] = IMPLAUSIBLE - scene.goal['sceneInfo'][ + scene.goal.answer['choice'] = IMPLAUSIBLE + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE.NO self._shroud_object(scene, target_id) @@ -3508,12 +3526,12 @@ def _create_intuitive_physics_scenes( # Consolidate specific redundant scenes (STC only). for i in ['a', 'd', 'g', 'j', 'm', 'p']: for j in [i + '2', i + '3', i + '4']: - scenes[i + '1'].goal['sceneInfo'][tags.SCENE.ID].append(j) + scenes[i + '1'].goal.scene_info[tags.SCENE.ID].append(j) del scenes[j] for i in ['b', 'e', 'h', 'k', 'n', 'q']: - scenes[i + '1'].goal['sceneInfo'][tags.SCENE.ID].append(i + '3') + scenes[i + '1'].goal.scene_info[tags.SCENE.ID].append(i + '3') del scenes[i + '3'] - scenes[i + '2'].goal['sceneInfo'][tags.SCENE.ID].append(i + '4') + scenes[i + '2'].goal.scene_info[tags.SCENE.ID].append(i + '4') del scenes[i + '4'] return scenes @@ -3547,8 +3565,8 @@ def _find_move_across_paired_list( # Override def _identify_targets_and_non_targets( self, - moving_object_list: List[Dict[str, Any]] - ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]: + moving_object_list: List[SceneObject] + ) -> Tuple[List[SceneObject], List[SceneObject]]: """Return the two separate target and non-target lists using the given moving object list.""" # STC scenes will always have one target and one non-target in Eval 3. @@ -3738,16 +3756,16 @@ def _create_intuitive_physics_scenes( # Initialize object permanence tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_SETUP ] = tags.CELLS.OBJECT_PERMANENCE_SETUP.EXIT - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_MOVEMENT ] = tags.CELLS.OBJECT_PERMANENCE_MOVEMENT.LINEAR - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_OBJECT_ONE ] = tags.CELLS.OBJECT_PERMANENCE_OBJECT_ONE.NO_CHANGE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_NOVELTY_ONE ] = tags.CELLS.OBJECT_PERMANENCE_NOVELTY_ONE.NONE @@ -3780,7 +3798,7 @@ def _design_object_permanence_eval_4_scenes( # Switch the "exit" movement with the "stop" movement. scene = scenes['j1'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.OBJECT_PERMANENCE_SETUP ] = tags.CELLS.OBJECT_PERMANENCE_SETUP.STOP for instance in scene.objects: @@ -3909,19 +3927,19 @@ def _create_intuitive_physics_scenes( # Initialize spatio temporal continuity tags in scenes. for scene in scenes.values(): - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_MOVEMENT ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_MOVEMENT.LINEAR - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OBJECTS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OBJECTS.ONE - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS.TWO - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_PLAUSIBLE.YES - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_TARGET_TRAINED.YES @@ -3948,7 +3966,7 @@ def _design_spatio_temporal_continuity_eval_4_scenes( ) -> Dict[str, Scene]: # Remove every occluder. scene = scenes['e1'] - scene.goal['sceneInfo'][ + scene.goal.scene_info[ tags.TYPES.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS ] = tags.CELLS.SPATIO_TEMPORAL_CONTINUITY_OCCLUDERS.ZERO remove_id_list = [ diff --git a/hypercube/intuitive_physics_test_util.py b/hypercube/intuitive_physics_test_util.py index 56df9d4..1248d0a 100644 --- a/hypercube/intuitive_physics_test_util.py +++ b/hypercube/intuitive_physics_test_util.py @@ -1,5 +1,7 @@ import math +from machine_common_sense.config_manager import Goal + from generator import ( Scene, definitions, @@ -12,7 +14,7 @@ from . import intuitive_physics_hypercubes OPPOSITE_MATERIAL_STRING_LIST = [ - item[0] for item in materials.OPPOSITE_MATERIALS + item.material for item in materials.OBJECT_OPPOSITE_MATERIALS ] @@ -26,28 +28,28 @@ def verify_scene( assert scene.intuitive_physics assert scene.version == 3 assert scene.debug['evaluationOnly'] == (eval_only or implausible) - assert scene.goal['answer']['choice'] == ( + assert scene.goal.answer['choice'] == ( 'implausible' if implausible else 'plausible' ) if is_move_across: - assert scene.goal['last_step'] == (last_step if last_step else 200) + assert scene.goal.last_step == (last_step if last_step else 200) else: - assert scene.goal['last_step'] == (last_step if last_step else 160) - assert scene.goal['action_list'] == ( - [['Pass']] * scene.goal['last_step'] + assert scene.goal.last_step == (last_step if last_step else 160) + assert scene.goal.action_list == ( + [['Pass']] * scene.goal.last_step ) - assert scene.goal['category'] == 'intuitive physics' - assert scene.goal['sceneInfo']['primaryType'] == 'passive' - assert scene.goal['sceneInfo']['secondaryType'] == 'intuitive physics' - assert scene.goal['sceneInfo']['quaternaryType'] == 'action none' + assert scene.goal.category == 'intuitive physics' + assert scene.goal.scene_info['primaryType'] == 'passive' + assert scene.goal.scene_info['secondaryType'] == 'intuitive physics' + assert scene.goal.scene_info['quaternaryType'] == 'action none' if is_move_across: - assert scene.goal['sceneInfo']['moveAcross'] - assert 'move across' == scene.goal['sceneInfo']['sceneSetup'] + assert scene.goal.scene_info['moveAcross'] + assert 'move across' == scene.goal.scene_info['sceneSetup'] else: - assert scene.goal['sceneInfo']['fallDown'] - assert 'fall down' == scene.goal['sceneInfo']['sceneSetup'] + assert scene.goal.scene_info['fallDown'] + assert 'fall down' == scene.goal.scene_info['sceneSetup'] def verify_hypercube(object_dict, room_wall_material): @@ -93,9 +95,9 @@ def verify_hypercube(object_dict, room_wall_material): def verify_object_tags(scene, object_list, role_info, role_prop): for instance in object_list: for info in instance['debug']['info']: - assert info in scene.goal['objectsInfo']['all'] + assert info in scene.goal.objects_info['all'] if info != role_info: - assert info in scene.goal['objectsInfo'][role_prop] + assert info in scene.goal.objects_info[role_prop] return True @@ -459,9 +461,9 @@ def verify_object_fall_down_position(instance, name, bigger=False): intuitive_physics_hypercubes.retrieve_off_screen_position_y( z_position ) - ) - # Y position may be adjusted by up to 0.5 by placer function. - if y_position < (y_expected - 0.5): + ) + instance['debug']['positionY'] + # Y position may be adjusted by up to 0.25 by placer function. + if y_position < (y_expected - 0.25): print(f'[ERROR] {name} Y POSITION SHOULD BE GREATER THAN ' f'{y_expected} BUT WAS {y_position}\n{name}={instance}') return False @@ -950,8 +952,8 @@ def create_goal_template(task_type): scene_info[tags.SCENE.QUATERNARY] = tags.tag_to_label( tags.SCENE.ACTION_NONE ) - return { - 'category': tags.tag_to_label(tags.SCENE.INTUITIVE_PHYSICS), - 'domainsInfo': {}, - 'sceneInfo': scene_info - } + return Goal( + category=tags.tag_to_label(tags.SCENE.INTUITIVE_PHYSICS), + domains_info={}, + scene_info=scene_info + ) diff --git a/hypercube/object_data.py b/hypercube/object_data.py index f42d448..456bfd6 100644 --- a/hypercube/object_data.py +++ b/hypercube/object_data.py @@ -10,7 +10,7 @@ def identify_larger_definition( one: ObjectDefinition, two: ObjectDefinition -) -> Dict[str, Any]: +) -> ObjectDefinition: """Return the larger (in dimensions) of the two given definitions.""" if not one: return two @@ -226,7 +226,7 @@ def is_random(self) -> bool: """Return whether this object ever uses location plan RANDOM.""" return self._uses_location_plan(ObjectLocationPlan.RANDOM) - def larger_definition(self) -> Dict[str, Any]: + def larger_definition(self) -> ObjectDefinition: """Return the larger (in dimensions) of this object's trained or untrained definition.""" return identify_larger_definition( @@ -238,7 +238,7 @@ def larger_definition_of( self, container_data_list: List[Any], second_object_data: Optional[Any] = None - ) -> Dict[str, Any]: + ) -> ObjectDefinition: """Return the larger (in dimensions) of this object's, the given second object's, or this object's container's trained or untrained definition. Useful if you need to reserve space for the object, since we assume the diff --git a/hypercube/scene_generator.py b/hypercube/scene_generator.py index e1b8eb1..33ca38f 100644 --- a/hypercube/scene_generator.py +++ b/hypercube/scene_generator.py @@ -10,7 +10,7 @@ from machine_common_sense.logging_config import LoggingConfig -from generator import Scene, materials, tags +from generator import MAX_TRIES, Scene, SceneException, materials, tags from generator.scene_saver import find_next_filename, save_scene_files STARTER_SCENE = Scene( @@ -69,6 +69,8 @@ def generate_scenes( stop_on_error: bool, role_to_type: Dict[str, str] ) -> None: + logger = logging.getLogger(__name__) + # If the file prefix is in a folder, ensure that folder exists. folder_name = os.path.dirname(prefix) if folder_name != '': @@ -82,8 +84,8 @@ def generate_scenes( if not hypercube_factory: raise ValueError(f'Failed to find {type_name} hypercube factory') - # Generate all of the needed hypercubes. - hypercubes = hypercube_factory.build( + # Create all of the needed hypercubes. + hypercubes = hypercube_factory.generate_hypercubes( total, self.generate_starter_scene, role_to_type, @@ -91,8 +93,12 @@ def generate_scenes( sort_hypercube ) + logger.info(f'Generating {len(hypercubes)} {type_name} hypercubes') + index = -1 hypercube_index = 1 - for hypercube in hypercubes: + failed_info = [] + count = 0 + for index, hypercube in enumerate(hypercubes): # Identify the next available file name index. base_filename, hypercube_index = find_next_filename( f'{prefix}_', @@ -101,8 +107,29 @@ def generate_scenes( suffix='_01.json' ) - # Retrieve all of the scenes from this hypercubes. - scenes = hypercube.get_scenes() + # Create and retrieve all of the scenes from this hypercubes. + scenes = None + for try_index in range(MAX_TRIES + 1): + try: + scenes = hypercube.generate_scenes() + break + except ( + SceneException, + RuntimeError, + TypeError, + ValueError, + ZeroDivisionError + ) as e: + if stop_on_error: + raise e from e + logger.exception(f'Failed to make a {type_name} hypercube') + info = hypercube.get_info() + if info and info not in failed_info: + failed_info.append(info) + + if not scenes: + logger.warn('Skipping hypercube...') + continue # Randomly shuffle the scenes. if not sort_hypercube: @@ -126,6 +153,21 @@ def generate_scenes( no_scene_id=bool(eval_name) ) + count += 1 + logger.info( + f'Saved {type_name} hypercube {count} / {total} ' + f'({len(scenes)} scenes): {base_filename}' + ) + if count == total: + break + + logger.info(f'Finished {count} {type_name} hypercubes') + + if failed_info: + logger.warn('The following hypercubes failed:') + for info in failed_info: + logger.warn(f'{info}') + def generate_scenes_from_args(self, argv) -> None: parser = argparse.ArgumentParser( description='Generate MCS scene configuration JSON files.' diff --git a/ideal_learning_env/__init__.py b/ideal_learning_env/__init__.py index 80a4cfa..e110785 100644 --- a/ideal_learning_env/__init__.py +++ b/ideal_learning_env/__init__.py @@ -27,7 +27,13 @@ ) from .global_settings_component import GlobalSettingsComponent from .goal_services import GoalConfig -from .interactable_object_service import InteractableObjectConfig +from .interactable_object_service import ( + InteractableObjectConfig, + InteractableObjectCreationService, + TargetCreationService, + ToolConfig, + ToolCreationService +) from .interactable_objects_component import ( KeywordObjectsConfig, RandomInteractableObjectsComponent, diff --git a/ideal_learning_env/action_service.py b/ideal_learning_env/action_service.py index 691c3b1..cea2022 100644 --- a/ideal_learning_env/action_service.py +++ b/ideal_learning_env/action_service.py @@ -2,19 +2,16 @@ import math import random from dataclasses import dataclass -from typing import List, Union +from typing import List import matplotlib.pyplot as plt -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d from generator import Scene, geometry -from ideal_learning_env.defs import ( - ILEConfigurationException, - ILEException, - RandomizableString -) -from ideal_learning_env.numerics import MinMaxFloat, MinMaxInt, RandomizableInt -from ideal_learning_env.object_services import ObjectRepository + +from .defs import ILEConfigurationException, ILEException, RandomizableString +from .numerics import RandomizableFloat, RandomizableInt +from .object_services import ObjectRepository logger = logging.getLogger(__name__) @@ -35,8 +32,8 @@ class StepBeginEnd(): The step where the performer agent ends being frozen and can resume using actions besides `"Pass"`. Therefore, this is an exclusive limit. """ - begin: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - end: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None + begin: RandomizableInt = None + end: RandomizableInt = None @dataclass @@ -67,13 +64,10 @@ class TeleportConfig(): is teleported. This field is required for teleport action restrictions if `position_x` and `position_z` are not both set. """ - step: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - position_x: Union[float, MinMaxFloat, - List[Union[float, MinMaxFloat]]] = None - position_z: Union[float, MinMaxFloat, - List[Union[float, MinMaxFloat]]] = None - rotation_y: Union[float, MinMaxFloat, - List[Union[float, MinMaxFloat]]] = None + step: RandomizableInt = None + position_x: RandomizableFloat = None + position_z: RandomizableFloat = None + rotation_y: RandomizableFloat = None look_at_center: bool = False @@ -82,15 +76,17 @@ class SidestepsConfig(): """ Contains data to describe the performer's sidesteps. - - `begin` ([RandomizableInt](#RandomizableInt)): + - `begin` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints and/or + MinMaxInt dicts): The step where the performer agent starts being frozen and can only use the `"Pass"` action. For example, if 1, the performer agent must pass immediately at the start of the scene. This is an inclusive limit. - - `object_label` ([RandomizableString](#RandomizableString)): + - `object_label` (string, or list of strings): The label of the object the performer will sidestep around. The performer must be 3 distance away from the object's center for sidesteps to work. - - `degrees` ([RandomizableInt](#RandomizableInt)): + - `degrees` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints and/or + MinMaxInt dicts): The positive or negative degrees the performer will sidestep around the object. Positive forces the performer to sidestep right while negative sidesteps left. The degree value must always be in 90 or -90 degree @@ -104,14 +100,14 @@ class SidestepsConfig(): class ActionService(): @staticmethod - def add_circles(goal: dict, circles: List): + def add_circles(goal: Goal, circles: List): """Adds restrictions to the steps in the goal portion of a scene such that the performer can only rotate clockwise for 36 consecutive steps. The intervals provided by 'circles' should not overlap.""" circle_action_list = ['RotateRight'] circle_length = 36 - goal['action_list'] = goal.get('action_list', []) - action_list = goal['action_list'] + goal.action_list = goal.action_list or [] + action_list = goal.action_list for circle in circles: no_actions = len(action_list) == 0 @@ -142,24 +138,24 @@ def add_circles(goal: dict, circles: List): step = step + 1 @staticmethod - def add_freezes(goal: dict, freezes: List[StepBeginEnd]): + def add_freezes(goal: Goal, freezes: List[StepBeginEnd]): """Adds freezes to the goal portion of the scene. The freezes occur over ranges provided by `freezes` and should not overlap. All random choices in any StepBeginEnd instances should be determined prior to calling this method.""" - goal['action_list'] = goal.get('action_list', []) - al = goal['action_list'] + goal.action_list = goal.action_list or [] + al = goal.action_list limit = 1 for f in freezes: f.begin = 1 if f.begin is None else f.begin if f.end is None: - if goal['last_step'] is None: + if goal.last_step is None: raise ILEConfigurationException( "Configuration error. A freeze without an 'end' " "requires 'last_step' to be set.") else: # Add one so we include the last step. End is exclusive. - f.end = goal['last_step'] + 1 + f.end = goal.last_step + 1 if (limit > f.begin): raise ILEException(f"Freezes overlapped at {limit}") if f.begin >= f.end: @@ -172,10 +168,10 @@ def add_freezes(goal: dict, freezes: List[StepBeginEnd]): limit = f.end @staticmethod - def add_freeze_while_moving(goal: dict, freeze_while_moving): + def add_freeze_while_moving(goal: Goal, freeze_while_moving): """Adds freeze while moving""" - goal['action_list'] = goal.get('action_list', []) - al = goal['action_list'] + goal.action_list = goal.action_list or [] + al = goal.action_list # Must be the first action if len(al) > 0: raise ILEException( @@ -184,6 +180,7 @@ def add_freeze_while_moving(goal: dict, freeze_while_moving): "must be after.") last_move = 0 last_rotate = 0 + last_action = 0 if isinstance(freeze_while_moving, str): freeze_while_moving = [freeze_while_moving] for label in freeze_while_moving: @@ -198,15 +195,20 @@ def add_freeze_while_moving(goal: dict, freeze_while_moving): for object in objects: moves = object.get("moves") rotates = object.get("rotates") + actions = object.get("actions") if moves: last_move = max(moves[-1]['stepEnd'], last_move) if rotates: last_rotate = max(rotates[-1]['stepEnd'], last_rotate) - al += ([['Pass']] * (max(last_move, last_rotate))) + if actions: + last_action = max(max( + item['stepEnd'] for item in actions if not item.get( + 'isLoopAnimation', False)), last_action) + al += ([['Pass']] * (max(last_move, last_rotate, last_action))) @staticmethod def add_teleports( - goal: dict, + goal: Goal, teleports: List[TeleportConfig], passive: bool ): @@ -214,8 +216,8 @@ def add_teleports( performer will be teleported to a new position and/or rotation. All random choices in any TeleportConfig instances should be determined prior to calling this method.""" - goal['action_list'] = goal.get('action_list', []) - al = goal['action_list'] + goal.action_list = goal.action_list or [] + al = goal.action_list for t in teleports: rotation_y = t.rotation_y # See TeleportConfig docs for information and assumptions. @@ -245,15 +247,15 @@ def add_teleports( al[step - 1] = [cmd] @staticmethod - def add_swivels(goal: dict, swivels: List[StepBeginEnd]): + def add_swivels(goal: Goal, swivels: List[StepBeginEnd]): """Adds restrictions to steps the goal portion of a scene such that the performer can only rotate its view (LookDown, LookUp, RotateLeft, or RotateRight). The intervals provided by 'swivels' should not overlap. All random choices in any StepBeginEnd instances should be determined prior to calling this method.""" swivel_actions = ['LookDown', 'LookUp', 'RotateLeft', 'RotateRight'] - goal['action_list'] = goal.get('action_list', []) - al = goal['action_list'] + goal.action_list = goal.action_list or [] + al = goal.action_list # check if actions already exist in action list # at the start @@ -264,20 +266,20 @@ def add_swivels(goal: dict, swivels: List[StepBeginEnd]): for s in swivels: s.begin = 1 if s.begin is None else s.begin if s.end is None: - if goal['last_step'] is None: + if goal.last_step is None: raise ILEConfigurationException( "Configuration error. A swivel without an 'end' " "requires 'last_step' to be set.") else: # Add one so we include the last step. End is exclusive. - s.end = goal['last_step'] + 1 + s.end = goal.last_step + 1 if (limit > s.begin): raise ILEException(f"Swivels overlapped at {limit}") if s.begin >= s.end: raise ILEException( f"Swivels has begin >= end ({s.begin} >= {s.end})") - if(no_actions or s.begin > al_length): + if (no_actions or s.begin > al_length): num_free = s.begin - \ limit if no_actions else (s.begin - al_length - 1) num_limited = s.end - s.begin @@ -287,9 +289,9 @@ def add_swivels(goal: dict, swivels: List[StepBeginEnd]): else: step = s.begin - while(step < s.end): - if(len(al) >= step): - if(al[step - 1] != []): + while (step < s.end): + if (len(al) >= step): + if (al[step - 1] != []): raise ILEException( f"Swivels with begin {s.begin} and end " f"{s.end} overlap with existing action " @@ -301,7 +303,7 @@ def add_swivels(goal: dict, swivels: List[StepBeginEnd]): limit = s.end @staticmethod - def add_sidesteps(goal: dict, + def add_sidesteps(goal: Goal, sidesteps: List[SidestepsConfig], scene: Scene): """Adds sidesteps to the goal portion of the scene. The sidesteps occur @@ -309,8 +311,8 @@ def add_sidesteps(goal: dict, choices in any SidestepsConfig instances should be determined prior to calling this method.""" # For a 90 degree increment - goal['action_list'] = goal.get('action_list', []) - al = goal['action_list'] + goal.action_list = goal.action_list or [] + al = goal.action_list object = None label = None start_x = scene.performer_start.position.x diff --git a/ideal_learning_env/actions_component.py b/ideal_learning_env/actions_component.py index c7f8bf9..2232f66 100644 --- a/ideal_learning_env/actions_component.py +++ b/ideal_learning_env/actions_component.py @@ -1,11 +1,9 @@ import logging -from typing import Any, Dict, List, Union +from typing import Any, List, Union -from generator import tags -from ideal_learning_env.defs import ( - ILEConfigurationException, - RandomizableString -) +from machine_common_sense.config_manager import Goal + +from generator import Scene, tags from .action_service import ( ActionService, @@ -16,7 +14,8 @@ from .choosers import choose_random from .components import ILEComponent from .decorators import ile_config_setter -from .numerics import MinMaxInt +from .defs import ILEConfigurationException, RandomizableString +from .numerics import RandomizableInt from .validators import ValidateNumber, ValidateOptions logger = logging.getLogger(__name__) @@ -33,7 +32,7 @@ class ActionRestrictionsComponent(ILEComponent): Default: `false` """ - circles: List[Union[int, MinMaxInt, List[Union[int, MinMaxInt]]]] = None + circles: List[RandomizableInt] = None """ (list of either ints, or lists of ints, or [MinMaxInt](#MinMaxInt) dicts, or lists of MinMaxInt dicts): When the AI should be forced to rotate in a @@ -262,11 +261,11 @@ def get_sidesteps(self) -> List[SidestepsConfig]: return [choose_random(s) for s in (self.sidesteps or [])] # Override - def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def update_ile_scene(self, scene: Scene) -> Scene: logger.info('Configuring action restrictions for the scene...') - goal = scene.goal or {} - total_steps = goal.get('last_step') + goal = scene.goal or Goal() + total_steps = goal.last_step circles = sorted(self.get_circles()) freezes = sorted(self.get_freezes(), key=lambda x: x.begin) freeze_while_moving = self.get_freeze_while_moving() @@ -286,9 +285,9 @@ def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: self._delayed_actions = 0 self._delayed_sidesteps = None if passive: - goal['category'] = tags.tag_to_label( + goal.category = tags.tag_to_label( tags.SCENE.INTUITIVE_PHYSICS) - goal['action_list'] = [['Pass']] * total_steps + goal.action_list = [['Pass']] * total_steps logger.trace('Setting whole scene as passive') if circles: ActionService.add_circles(goal, circles) @@ -317,7 +316,10 @@ def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: def get_num_delayed_actions(self) -> int: return self._delayed_actions - def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def get_delayed_action_error_strings(self) -> List[str]: + return ['Configured sidesteps'] if self._delayed_actions > 0 else [] + + def run_delayed_actions(self, scene: Scene) -> Scene: if self._delayed_sidesteps: ActionService.add_sidesteps( scene.goal, self._delayed_sidesteps, scene) @@ -325,7 +327,7 @@ def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: return scene def run_actions_at_end_of_scene_generation( - self, scene: Dict[str, Any]) -> Dict[str, Any]: + self, scene: Scene) -> Scene: if self.freeze_while_moving: ActionService.add_freeze_while_moving( scene.goal, self.freeze_while_moving) diff --git a/ideal_learning_env/agent_component.py b/ideal_learning_env/agent_component.py index 5c7e6fb..ea87a93 100644 --- a/ideal_learning_env/agent_component.py +++ b/ideal_learning_env/agent_component.py @@ -7,20 +7,14 @@ AGENT_MOVEMENT_ANIMATIONS, AGENT_TYPES ) -from ideal_learning_env.agent_service import ( - DEFAULT_TEMPLATE_AGENT_MOVEMENT, - AgentConfig -) -from ideal_learning_env.defs import ILEDelayException, find_bounds, return_list -from ideal_learning_env.feature_creation_service import ( - FeatureCreationService, - FeatureTypes -) -from ideal_learning_env.numerics import MinMaxInt +from .agent_service import DEFAULT_TEMPLATE_AGENT_MOVEMENT, AgentConfig from .choosers import choose_counts, choose_random from .components import ILEComponent from .decorators import ile_config_setter +from .defs import ILEDelayException, find_bounds, return_list +from .feature_creation_service import FeatureCreationService, FeatureTypes +from .numerics import RandomizableInt from .validators import ValidateNumber, ValidateOptions logger = logging.getLogger(__name__) @@ -138,8 +132,7 @@ def get_delayed_action_error_strings(self) -> List[str]: class RandomAgentComponent(ILEComponent): - num_random_agents: Union[int, MinMaxInt, - List[Union[int, MinMaxInt]]] = False + num_random_agents: RandomizableInt = False """ (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints or diff --git a/ideal_learning_env/agent_service.py b/ideal_learning_env/agent_service.py index 53d2143..1060912 100644 --- a/ideal_learning_env/agent_service.py +++ b/ideal_learning_env/agent_service.py @@ -8,6 +8,7 @@ from machine_common_sense.config_manager import Vector3d from shapely.geometry import Point, Polygon +from generator import SceneObject from generator.agents import ( AGENT_DIMENSIONS, AGENT_TYPES, @@ -15,7 +16,8 @@ add_agent_movement, add_agent_pointing, create_agent, - estimate_move_step_length + estimate_move_step_length, + get_random_agent_settings ) from generator.geometry import ( MAX_TRIES, @@ -25,41 +27,37 @@ validate_location_rect ) from generator.scene import Scene -from ideal_learning_env.defs import ( + +from .choosers import choose_position, choose_random +from .defs import ( + ILEConfigurationException, ILEDelayException, ILEException, + RandomizableBool, + RandomizableString, find_bounds ) -from ideal_learning_env.feature_creation_service import ( +from .feature_creation_service import ( BaseFeatureConfig, BaseObjectCreationService, FeatureCreationService, FeatureTypes ) -from ideal_learning_env.interactable_object_service import ( - KeywordLocationConfig +from .interactable_object_service import KeywordLocationConfig +from .numerics import ( + MinMaxFloat, + MinMaxInt, + RandomizableFloat, + RandomizableInt, + RandomizableVectorFloat3d ) -from ideal_learning_env.object_services import ( +from .object_services import ( KeywordLocation, ObjectRepository, add_random_placement_tag, reconcile_template ) -from .choosers import choose_position, choose_random -from .defs import ( - ILEConfigurationException, - RandomizableBool, - RandomizableString -) -from .numerics import ( - MinMaxInt, - RandomizableFloat, - RandomizableInt, - RandomizableVectorFloat3d, - VectorFloatConfig -) - logger = logging.getLogger(__name__) @@ -173,28 +171,29 @@ class AgentSettings(): - `tie` (int, or list of ints) - `tieMaterial` (int, or list of ints) """ - chest: RandomizableInt = 0 - chestMaterial: RandomizableInt = 0 - eyes: RandomizableInt = 0 - feet: RandomizableInt = 0 - feetMaterial: RandomizableInt = 0 - glasses: RandomizableInt = 0 - hair: RandomizableInt = 0 - hairMaterial: RandomizableInt = 0 - hatMaterial: RandomizableInt = 0 - hideHair: RandomizableBool = False - isElder: RandomizableBool = False - jacket: RandomizableInt = 0 - jacketMaterial: RandomizableInt = 0 - legs: RandomizableInt = 0 - legsMaterial: RandomizableInt = 0 - showBeard: RandomizableBool = False - showGlasses: RandomizableBool = False - showJacket: RandomizableBool = False - showTie: RandomizableBool = False - skin: RandomizableInt = 0 - tie: RandomizableInt = 0 - tieMaterial: RandomizableInt = 0 + # All default values should be None! + chest: RandomizableInt = None + chestMaterial: RandomizableInt = None + eyes: RandomizableInt = None + feet: RandomizableInt = None + feetMaterial: RandomizableInt = None + glasses: RandomizableInt = None + hair: RandomizableInt = None + hairMaterial: RandomizableInt = None + hatMaterial: RandomizableInt = None + hideHair: RandomizableBool = None + isElder: RandomizableBool = None + jacket: RandomizableInt = None + jacketMaterial: RandomizableInt = None + legs: RandomizableInt = None + legsMaterial: RandomizableInt = None + showBeard: RandomizableBool = None + showGlasses: RandomizableBool = None + showJacket: RandomizableBool = None + showTie: RandomizableBool = None + skin: RandomizableInt = None + tie: RandomizableInt = None + tieMaterial: RandomizableInt = None @dataclass @@ -231,9 +230,9 @@ class AgentConfig(BaseFeatureConfig): VectorFloatConfig dicts): The position of this agent in each scene. If configured as a list, a new position will be randomly chosen for each scene. Default: random - - `rotation_y` (int, or [MinMaxInt](#MinMaxInt) dict, or list of ints - and/or MinMaxInt dicts): The rotation of this agent in each scene. If - configured as a list, a new rotation will be randomly chosen for each + - `rotation_y` (float, or [MinMaxFloat](#MinMaxFloat) dict, or list of + floats and/or MinMaxFloat dicts): The rotation of this agent in each scene. + If configured as a list, a new rotation will be randomly chosen for each scene. Default: random - `type` (string, or list of strings) The model ("type") of the agent. Please see the list in our [schema doc]( @@ -260,10 +259,10 @@ class AgentConfig(BaseFeatureConfig): is_loop_animation: [True, False] ``` """ - type: Union[str, List[str]] = None + type: RandomizableString = None agent_settings: Union[AgentSettings, List[AgentSettings]] = None - position: Union[VectorFloatConfig, List[VectorFloatConfig]] = None - rotation_y: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None + position: RandomizableVectorFloat3d = None + rotation_y: RandomizableFloat = None actions: List[AgentActionConfig] = None movement: Union[ bool, @@ -272,38 +271,13 @@ class AgentConfig(BaseFeatureConfig): ] = None keyword_location: KeywordLocationConfig = None pointing: Union[AgentPointingConfig, List[AgentPointingConfig]] = None - - -def get_default_agent_settings(): - return AgentSettings( - chest=MinMaxInt(0, 8), - chestMaterial=MinMaxInt(0, 14), - eyes=MinMaxInt(0, 3), - feet=MinMaxInt(0, 2), - feetMaterial=MinMaxInt(0, 11), - glasses=MinMaxInt(0, 10), - hair=MinMaxInt(0, 9), - hairMaterial=MinMaxInt(0, 9), - hatMaterial=MinMaxInt(0, 11), - hideHair=[True, False, False, False, False], - isElder=[True, False, False, False, False], - jacket=0, - jacketMaterial=0, - legs=MinMaxInt(0, 3), - legsMaterial=MinMaxInt(0, 14), - showBeard=False, - showGlasses=False, - showJacket=False, - showTie=[True, False], - skin=MinMaxInt(0, 3), - tie=MinMaxInt(0, 2), - tieMaterial=MinMaxInt(0, 9) - ) + labels: RandomizableString = None DEFAULT_TEMPLATE_AGENT = AgentConfig( - num=0, type=AGENT_TYPES, agent_settings=get_default_agent_settings(), - position=None, actions=None, rotation_y=MinMaxInt(0, 359), pointing=None) + num=0, type=AGENT_TYPES, agent_settings=None, + position=None, actions=None, rotation_y=MinMaxFloat(0, 359), + pointing=None, labels=None) DEFAULT_TEMPLATE_AGENT_MOVEMENT = AgentMovementConfig( animation=['TPM_walk', 'TPM_run'], step_begin=MinMaxInt(0, 20), @@ -365,13 +339,21 @@ def create_feature_from_specific_values( source_template: AgentConfig): logger.trace(f"Creating agent:\n{source_template=}\n{reconciled=}") + reconciled_settings = reconciled.agent_settings or AgentSettings() + full_settings = get_random_agent_settings( + reconciled.type, + settings=vars(reconciled_settings) + ) + logger.trace(f"Agent settings: {full_settings}") + agent = create_agent( type=reconciled.type, position_x=reconciled.position.x, position_z=reconciled.position.z, rotation_y=reconciled.rotation_y, - settings=vars(reconciled.agent_settings), - position_y_modifier=reconciled.position.y) + settings=full_settings, + position_y_modifier=reconciled.position.y + ) if reconciled.keyword_location: if source_template.position: @@ -508,7 +490,7 @@ def add_pointing( }, 'rotation': { 'x': 0, - 'y': (agent['shows'][0]['rotation']['y'] + 180) % 360, + 'y': round(agent['shows'][0]['rotation']['y'] + 180, 2) % 360, 'z': 0 } }) @@ -553,7 +535,7 @@ def add_pointing( @staticmethod def add_random_agent_movement( - scene: Scene, agent: dict, config: AgentMovementConfig): + scene: Scene, agent: SceneObject, config: AgentMovementConfig): logger.trace(f"Adding random agent movement:\n{config=}") def_temp: AgentMovementConfig = choose_random( DEFAULT_TEMPLATE_AGENT_MOVEMENT) diff --git a/ideal_learning_env/choosers.py b/ideal_learning_env/choosers.py index 73948ac..8082aaf 100644 --- a/ideal_learning_env/choosers.py +++ b/ideal_learning_env/choosers.py @@ -1,5 +1,5 @@ import random -from typing import Any, List, Optional, Tuple, Type, Union +from typing import Any, List, Optional, Tuple, Type from machine_common_sense.config_manager import Vector3d @@ -11,10 +11,16 @@ materials ) -from .defs import ILEException, ILESharedConfiguration +from .defs import ILEException, ILESharedConfiguration, RandomizableString from .defs import choose_random as _choose_random from .defs import return_list -from .numerics import MinMaxFloat, VectorFloatConfig, VectorIntConfig +from .numerics import ( + MinMaxFloat, + RandomizableFloat, + RandomizableVectorFloat3d, + RandomizableVectorFloat3dOrFloat, + VectorFloatConfig +) SOCCER_BALL_SCALE_MAX = 3 SOCCER_BALL_SCALE_MIN = 1 @@ -47,7 +53,7 @@ def choose_counts( def choose_position( - position: Union[VectorFloatConfig, List[VectorFloatConfig]], + position: RandomizableVectorFloat3d, object_x: float = None, object_z: float = None, room_x: float = None, @@ -85,7 +91,7 @@ def _get_min_max_room_dimensions(room_dim, object_dim): def _constrain_position_x_z( - position: Union[float, MinMaxFloat, List[float]] = None, + position: RandomizableFloat = None, object_dim: float = None, room_dim: float = None): constrained_position = position @@ -126,7 +132,7 @@ def _constrain_position_x_z( def _constrain_position_y( - position: Union[float, MinMaxFloat, List[float]] = None, + position: RandomizableFloat = None, room_dim: float = None, is_placer_obj: bool = False): max_y = room_dim - geometry.PERFORMER_HEIGHT @@ -205,13 +211,11 @@ def _constrain_min_max_pos_to_room_dimensions_x_z( return MinMaxFloat(constrained_min, constrained_max) -def choose_rotation( - rotation: Union[VectorIntConfig, List[VectorIntConfig]] -) -> Vector3d: +def choose_rotation(rotation: RandomizableFloat) -> Vector3d: """Choose and return a random rotation for the given rotation config or, if it is null, a random object rotation.""" if rotation is None: - rotation = VectorIntConfig() + rotation = VectorFloatConfig() rotation = rotation if isinstance(rotation, list) else [rotation] for rot in rotation: if rot.x is None: @@ -224,7 +228,7 @@ def choose_rotation( def choose_material_tuple_from_material( - material_or_category: Union[str, List[str]], + material_or_category: RandomizableString, prohibited_material: str = None ) -> MaterialTuple: """Return a MaterialTuple chosen randomly from the given materials that can @@ -283,9 +287,7 @@ def choose_material_tuple_from_material( return random.choice(random.choice(unprohibited_material_list)) -def _filter_scale_soccer_ball( - scale: Union[float, MinMaxFloat, VectorFloatConfig] -) -> bool: +def _filter_scale_soccer_ball(scale: RandomizableFloat) -> bool: if not scale: return False # Soccer balls are restricted to only specific scales. @@ -310,13 +312,7 @@ def _filter_scale_soccer_ball( return False -def choose_scale( - scale: Union[ - float, MinMaxFloat, VectorFloatConfig, - List[Union[float, MinMaxFloat, VectorFloatConfig]] - ], - shape: str -) -> float: +def choose_scale(scale: RandomizableVectorFloat3dOrFloat, shape: str) -> float: """Return a randomly chosen scale for the given shape using the given scale options.""" # Default scale is 1 if scale is None or 0. @@ -333,8 +329,8 @@ def choose_scale( def choose_shape_material( - shape_list: Union[str, List[str]] = None, - material_or_category: Union[str, List[str]] = None, + shape_list: RandomizableString = None, + material_or_category: RandomizableString = None, prohibited_material: str = None ) -> Optional[Tuple[str, MaterialTuple]]: """Takes choices for shape and material_or_category and returns a valid diff --git a/ideal_learning_env/defs.py b/ideal_learning_env/defs.py index 8ecc6b4..4fdcc2c 100644 --- a/ideal_learning_env/defs.py +++ b/ideal_learning_env/defs.py @@ -12,13 +12,7 @@ get_type_hints ) -from generator import ( - MAX_TRIES, - MaterialTuple, - ObjectBounds, - ObjectDefinition, - geometry -) +from generator import MAX_TRIES, MaterialTuple, ObjectBounds, ObjectDefinition from generator.materials import find_colors from generator.scene import Scene @@ -190,25 +184,7 @@ def find_bounds( ignore_ids: List[str] = None ) -> List[ObjectBounds]: """Calculate and return the bounds for all the given objects.""" - # Create a bounding box for each hole and lava area and add it to the list. - bounds = [] if ignore_ground else [ - geometry.generate_floor_area_bounds(area['x'], area['z']) - for area in (scene.holes + scene.lava) - ] - - if scene.partition_floor and not ignore_ground: - bounds += geometry.find_partition_floor_bounds( - scene.room_dimensions, scene.partition_floor) - - # Add each object's bounding box to the list. - for instance in scene.objects: - if instance.get('id') in (ignore_ids or []): - continue - try: - bounds.append(instance['shows'][0]['boundingBox']) - except(KeyError): - ... - return bounds + return scene.find_bounds(ignore_ground, ignore_ids) def return_list(data: Any, default_value: Any = None) -> List[Any]: diff --git a/ideal_learning_env/feature_creation_service.py b/ideal_learning_env/feature_creation_service.py index 159e79f..74194af 100644 --- a/ideal_learning_env/feature_creation_service.py +++ b/ideal_learning_env/feature_creation_service.py @@ -2,31 +2,32 @@ import copy import logging +import math import random from abc import ABC, abstractmethod from dataclasses import dataclass from enum import Enum, auto from typing import Any, Dict, List, Optional, Tuple, Union -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Vector2dInt, Vector3d -from generator import MAX_TRIES, geometry -from generator.scene import Scene -from ideal_learning_env.numerics import MinMaxInt -from ideal_learning_env.object_services import ( - InstanceDefinitionLocationTuple, - ObjectRepository, - RelativePositionConfig, - reconcile_template -) +from generator import MAX_TRIES, Scene, SceneObject, geometry from .choosers import choose_random from .defs import ( ILEConfigurationException, ILEDelayException, ILEException, + RandomizableString, return_list ) +from .numerics import RandomizableInt +from .object_services import ( + InstanceDefinitionLocationTuple, + ObjectRepository, + RelativePositionConfig, + reconcile_template +) logger = logging.getLogger(__name__) @@ -49,6 +50,7 @@ class FeatureTypes(Enum): TOOLS = auto() INTERACTABLE = auto() AGENT = auto() + TUBE_OCCLUDERS = auto() TURNTABLES = auto() # Should be the same as the TARGET_LABEL TARGET = auto() @@ -57,8 +59,8 @@ class FeatureTypes(Enum): @dataclass class BaseFeatureConfig(): """Base class that should used for all structural objects.""" - num: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = 1 - labels: Union[str, List[str]] = None + num: RandomizableInt = 1 + labels: RandomizableString = None randomize_once: Dict = None @@ -164,7 +166,7 @@ def _handle_failure(self, retries: int): def _on_valid_instances( self, scene: Scene, reconciled_template: BaseFeatureConfig, - new_obj: dict, key: str = 'objects'): + new_obj: SceneObject, key: str = 'objects'): if new_obj is not None: for obj in new_obj: key_list = getattr(scene, key) @@ -230,7 +232,7 @@ def is_valid(self, scene, new_obj, bounds, try_num, retries): } for single_bounds in bounds] logger.trace( f'Failed validating location of {self._get_type()} on' - f' try {try_num + 1} of {retries}.' + f' try {try_num} of {retries}.' f'\nEXISTING BOUNDS = {debug_bounds}' f'\nFAILED OBJECT = {new_obj}' f'\nROOM DIMENSIONS = {scene.room_dimensions}' @@ -238,14 +240,14 @@ def is_valid(self, scene, new_obj, bounds, try_num, retries): else: logger.debug( f'Failed validating location of {self._get_type()} on' - f' try {try_num + 1} of {retries}.' + f' try {try_num} of {retries}.' ) new_obj = None return valid def save_to_object_repository( - obj_or_idl: Union[Dict[str, Any], InstanceDefinitionLocationTuple], + obj_or_idl: Union[SceneObject, InstanceDefinitionLocationTuple], structural_type: FeatureTypes, labels: List[str] ) -> None: @@ -280,7 +282,7 @@ def save_to_object_repository( def validate_all_locations_and_update_bounds( - objects: Union[list, dict], + objects: Union[SceneObject, List[SceneObject]], scene: Scene, bounds: List[geometry.ObjectBounds] ) -> bool: @@ -392,3 +394,32 @@ def position_relative_to( ) return position_x, position_z + + +def validate_floor_position( + scene: Scene, floor_pos: Vector2dInt, key, restrict_under_user, + bounds): + room_dim = scene.room_dimensions + x = floor_pos.x + z = floor_pos.z + perf_x = round(scene.performer_start.position.x) + perf_z = round(scene.performer_start.position.z) + xmax = math.floor(room_dim.x / 2) + zmax = math.floor(room_dim.z / 2) + valid = not (x < -xmax or x > xmax or z < -zmax or z > zmax) + bb = geometry.generate_floor_area_bounds(x, z) + # It is expected that some holes/lava will extend beyond the walls, so we + # extend the room bounds. + room_dim_extended = Vector3d( + x=scene.room_dimensions.x + 1, + y=scene.room_dimensions.y, + z=scene.room_dimensions.z + 1) + valid = valid and geometry.validate_location_rect( + bb, + vars(scene.performer_start.position), + bounds, + vars(room_dim_extended)) + valid = valid and floor_pos not in getattr(scene, key, '') + restricted = restrict_under_user and x == perf_x and z == perf_z + valid = valid and not restricted + return valid, bb diff --git a/ideal_learning_env/global_settings_component.py b/ideal_learning_env/global_settings_component.py index 643b4cd..79b9be8 100644 --- a/ideal_learning_env/global_settings_component.py +++ b/ideal_learning_env/global_settings_component.py @@ -5,17 +5,10 @@ from dataclasses import dataclass from typing import Any, Dict, List, Union -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import RoomMaterials, Vector3d from generator import MaterialTuple, geometry, materials, tags from generator.scene import Scene, get_step_limit_from_dimensions -from ideal_learning_env.numerics import ( - MinMaxFloat, - MinMaxInt, - RandomizableFloat, - VectorFloatConfig, - VectorIntConfig -) from .choosers import ( choose_material_tuple_from_material, @@ -35,10 +28,20 @@ ILEException, ILESharedConfiguration, RandomizableBool, + RandomizableString, find_bounds, return_list ) from .goal_services import GoalConfig, GoalServices +from .numerics import ( + MinMaxFloat, + MinMaxInt, + RandomizableFloat, + RandomizableVectorFloat3d, + RandomizableVectorInt3d, + VectorFloatConfig, + VectorIntConfig +) from .object_services import ( KeywordLocation, KeywordLocationConfig, @@ -126,7 +129,7 @@ class PerformerStartsNearConfig(): distance: 0.1 ``` """ - label: Union[str, List[str]] = None + label: RandomizableString = None distance: RandomizableFloat = 0.1 @@ -153,7 +156,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - ceiling_material: Union[str, List[str]] = None + ceiling_material: RandomizableString = None """ (string, or list of strings): A single material for the ceiling, or a list of materials for the ceiling, from which one is chosen at random for @@ -170,7 +173,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - excluded_colors: Union[str, List[str]] = None + excluded_colors: RandomizableString = None """ (string, or list of strings): Zero or more color words to exclude from being randomly generated as object or room materials. Materials with the @@ -191,7 +194,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - excluded_shapes: Union[str, List[str]] = None + excluded_shapes: RandomizableString = None """ (string, or list of strings): Zero or more object shapes (types) to exclude from being randomly generated. Objects with the listed shapes can still be @@ -211,7 +214,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - floor_material: Union[str, List[str]] = None + floor_material: RandomizableString = None """ (string, or list of strings): A single material for the floor, or a list of materials for the floor, from which one is chosen at random for @@ -393,7 +396,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - performer_look_at: Union[str, List[str]] = None + performer_look_at: RandomizableString = None """ (string or list of strings): If set, configures the performer to start looking at an object found by the label matching the string given. @@ -487,12 +490,9 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - performer_start_rotation: Union[ - VectorIntConfig, - List[VectorIntConfig] - ] = None + performer_start_rotation: RandomizableVectorFloat3d = None """ - ([VectorIntConfig](#VectorIntConfig) dict, or list of VectorIntConfig + ([VectorFloatConfig](#VectorFloatConfig) dict, or list of VectorFloatConfig dicts): The starting rotation of the performer agent, or a list of rotations, from which one is chosen at random for each scene. The (required) `y` is left/right and (optional) `x` is up/down. Default: random @@ -548,7 +548,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - room_dimensions: Union[VectorIntConfig, List[VectorIntConfig]] = None + room_dimensions: RandomizableVectorInt3d = None """ ([VectorIntConfig](#VectorIntConfig) dict, or list of VectorIntConfig dicts): The total dimensions for the room, or list of dimensions, from @@ -594,7 +594,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - side_wall_opposite_colors: Union[bool, List[bool]] = None + side_wall_opposite_colors: RandomizableBool = None """ (bool, or list of bools): Makes three of the room's walls the same color, and the other wall (either the left or the right) an opposite color. @@ -611,7 +611,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - trapezoidal_room: Union[bool, List[bool]] = None + trapezoidal_room: RandomizableBool = None """ (bool, or list of bools): Makes the room trapezoidal, so the left and right walls will be angled inward. Currently only supported for room_dimensions @@ -628,7 +628,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - wall_back_material: Union[str, List[str]] = None + wall_back_material: RandomizableString = None """ (string, or list of strings): The material for the back wall, or list of materials, from which one is chosen for each scene. Default: random @@ -644,7 +644,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - wall_front_material: Union[str, List[str]] = None + wall_front_material: RandomizableString = None """ (string, or list of strings): The material for the front wall, or list of materials, from which one is chosen for each scene. Default: random @@ -660,7 +660,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - wall_left_material: Union[str, List[str]] = None + wall_left_material: RandomizableString = None """ (string, or list of strings): The material for the left wall, or list of materials, from which one is chosen for each scene. Default: random @@ -676,7 +676,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - wall_material: Union[str, List[str]] = None + wall_material: RandomizableString = None """ (string, or list of strings): The material for all room walls, or list of materials, from which one is chosen for each scene. Is overridden by the @@ -693,7 +693,7 @@ class GlobalSettingsComponent(ILEComponent): ``` """ - wall_right_material: Union[str, List[str]] = None + wall_right_material: RandomizableString = None """ (string, or list of strings): The material for the right wall, or list of materials, from which one is chosen for each scene. Default: random @@ -730,7 +730,7 @@ def update_ile_scene(self, scene: Scene) -> Scene: # may be needed for other parts of the ILE scene generator to work. self.set_room_dimensions(VectorIntConfig(20, 10, 20)) self.set_performer_start_position(VectorFloatConfig(0, 0, -4.5)) - self.set_performer_start_rotation(VectorIntConfig(0, 0, 0)) + self.set_performer_start_rotation(VectorFloatConfig(0, 0, 0)) # Don't use set_goal() here because of the category validator. self.goal = GoalConfig(category=tags.SCENE.INTUITIVE_PHYSICS) @@ -764,7 +764,8 @@ def update_ile_scene(self, scene: Scene) -> Scene: if excluded_shapes: logger.trace(f'Setting excluded shapes = {excluded_shapes}') - scene.room_dimensions = self.get_room_dimensions() + room_size = self.get_room_dimensions() + scene.room_dimensions = room_size logger.trace(f'Setting room dimensions = {scene.room_dimensions}') self._delayed_position_label = None @@ -776,9 +777,14 @@ def update_ile_scene(self, scene: Scene) -> Scene: random_perf_starts_near.distance ) self._set_position_by_distance(scene) + # Temporarily set the performer start location outside of the room + # to avoid issues with collision checking in other ILE components. + scene.set_performer_start( + Vector3d(x=(room_size.x + 1), y=0, z=(room_size.z + 1)), + Vector3d(x=0, y=0, z=0) + ) else: - start_pos = self.get_performer_start_position( - scene.room_dimensions) + start_pos = self.get_performer_start_position(room_size) scene.set_performer_start( start_pos, self.get_performer_start_rotation()) @@ -794,9 +800,12 @@ def update_ile_scene(self, scene: Scene) -> Scene: scene.floor_material = floor_material_tuple.material scene.debug['floorColors'] = floor_material_tuple.color wall_material_data = self.get_wall_material_data() - scene.room_materials = dict([ - (key, value.material) for key, value in wall_material_data.items() - ]) + scene.room_materials = RoomMaterials( + front=wall_material_data['front'].material, + left=wall_material_data['left'].material, + right=wall_material_data['right'].material, + back=wall_material_data['back'].material + ) scene.restrict_open_doors = self.get_restrict_open_doors() scene.restrict_open_objects = self.get_restrict_open_objects() scene.debug['wallColors'] = list(set([ @@ -828,8 +837,8 @@ def update_ile_scene(self, scene: Scene) -> Scene: if reverse_angle: wall_left['shows'][0]['rotation']['y'] *= -1 wall_right['shows'][0]['rotation']['y'] *= -1 - wall_left['materials'] = [scene.room_materials['left']] - wall_right['materials'] = [scene.room_materials['right']] + wall_left['materials'] = [scene.room_materials.left] + wall_right['materials'] = [scene.room_materials.right] scene.objects.extend([wall_left, wall_right]) last_step = self.get_last_step() @@ -838,7 +847,7 @@ def update_ile_scene(self, scene: Scene) -> Scene: room_x=scene.room_dimensions.x, room_z=scene.room_dimensions.z) if last_step: - scene.goal['last_step'] = last_step + scene.goal.last_step = last_step logger.trace(f'Setting last step = {last_step}') self._attempt_goal(scene) @@ -1049,7 +1058,7 @@ def get_performer_start_rotation(self) -> Vector3d: # If not null, the Y property is required. @ile_config_setter(validator=ValidateNoNullProp(props=['y'])) def set_performer_start_rotation(self, data: Any) -> None: - self.performer_start_rotation = VectorIntConfig( + self.performer_start_rotation = VectorFloatConfig( data.x or 0, data.y, data.z or 0 @@ -1255,7 +1264,7 @@ def set_wall_right_material(self, data: Any) -> None: def get_wall_material_data(self) -> Dict[str, MaterialTuple]: if self.side_wall_opposite_colors: material_choice = choose_material_tuple_from_material( - materials.OPPOSITE_MATERIALS + materials.WALL_OPPOSITE_MATERIALS ) data = { 'back': material_choice, @@ -1307,7 +1316,7 @@ def get_num_delayed_actions(self) -> int: (1 if self._delayed_position_label else 0) + (1 if self._delayed_rotation_label else 0)) - def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def run_delayed_actions(self, scene: Scene) -> Scene: if self._delayed_goal: self._attempt_goal(scene) if self._delayed_position_label: @@ -1321,7 +1330,7 @@ def get_delayed_action_error_strings(self): if self._delayed_goal and self._delayed_goal_reason: reasons.append(str(self._delayed_goal_reason)) if self._delayed_position_label and self._delayed_position_reason: - reasons.append(str(self._delayed_rotation_reason)) + reasons.append(str(self._delayed_position_reason)) if self._delayed_rotation_label and self._delayed_rotation_reason: reasons.append(str(self._delayed_rotation_reason)) return reasons @@ -1368,12 +1377,12 @@ def run_actions_at_end_of_scene_generation(self, scene: Scene) -> Scene: f'{"left" if use_left_group else "right"} side.' ) # Set the goal in the scene. - scene.goal['category'] = tags.SCENE.MULTI_RETRIEVAL - scene.goal['description'] = GoalServices.make_goal_description( - scene.goal['category'], + scene.goal.category = tags.SCENE.MULTI_RETRIEVAL + scene.goal.description = GoalServices.make_goal_description( + scene.goal.category, targets ) - scene.goal['metadata'] = { + scene.goal.metadata = { 'targets': [{'id': target['id']} for target in targets] } return scene diff --git a/ideal_learning_env/goal_services.py b/ideal_learning_env/goal_services.py index 5f77522..6eb27ab 100644 --- a/ideal_learning_env/goal_services.py +++ b/ideal_learning_env/goal_services.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import Any, Dict, List, Tuple, Union -from generator import ObjectBounds, Scene, tags +from generator import ObjectBounds, Scene, SceneObject, tags from .choosers import choose_random from .defs import ILEConfigurationException, find_bounds, return_list @@ -117,7 +117,7 @@ def _generate_goal_data( scene: Scene, goal_template: GoalConfig, bounds_list: List[ObjectBounds] - ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + ) -> Tuple[Dict[str, Any], List[SceneObject]]: if not goal_template: return None, [] @@ -189,7 +189,7 @@ def attempt_to_add_goal( scene: Scene, goal_template: GoalConfig, bounds_list: List[ObjectBounds] = None - ) -> List[Dict[str, Any]]: + ) -> List[SceneObject]: """Attempt to add the given goal to the scene. The goal_template must NOT have randomness resolved (a.k.a. be reconciled) before calling this function. Returns the list of target objects.""" @@ -199,11 +199,11 @@ def attempt_to_add_goal( goal_data, target_list = GoalServices._generate_goal_data( scene, goal_template, bounds_list) if goal_data: - scene.goal['category'] = goal_data['category'] - scene.goal['description'] = goal_data['description'] - scene.goal['metadata'] = goal_data['metadata'] + scene.goal.category = goal_data['category'] + scene.goal.description = goal_data['description'] + scene.goal.metadata = goal_data['metadata'] logger.trace( - f'Setting {scene.goal["category"]} goal with ' + f'Setting {scene.goal.category} goal with ' f'{len(target_list)} target(s): {scene.goal}' ) return target_list @@ -211,7 +211,7 @@ def attempt_to_add_goal( @staticmethod def make_goal_description( goal_category: str, - target_list: List[Dict[str, Any]] + target_list: List[SceneObject] ) -> str: """Return the goal description for the given goal category and target list.""" diff --git a/ideal_learning_env/interactable_object_service.py b/ideal_learning_env/interactable_object_service.py index 08f9be6..de65228 100644 --- a/ideal_learning_env/interactable_object_service.py +++ b/ideal_learning_env/interactable_object_service.py @@ -1,63 +1,275 @@ import copy import logging +import math +import random from dataclasses import dataclass from typing import Any, Dict, List, Union -from machine_common_sense.config_manager import PerformerStart, Vector3d +from machine_common_sense.config_manager import ( + PerformerStart, + Vector2dInt, + Vector3d +) from generator import ( ObjectBounds, ObjectDefinition, + SceneObject, base_objects, geometry, instances, - specific_objects + specific_objects, + tools ) +from generator.base_objects import LARGE_BLOCK_TOOLS_TO_DIMENSIONS from generator.materials import MaterialTuple from generator.mechanisms import CYLINDRICAL_SHAPES, create_placer from generator.scene import Scene -from ideal_learning_env.defs import ( + +from .choosers import ( + choose_material_tuple_from_material, + choose_position, + choose_rotation, + choose_scale, + choose_shape_material +) +from .defs import ( ILEConfigurationException, ILEDelayException, ILEException, ILESharedConfiguration, + RandomizableBool, RandomizableString, - find_bounds + find_bounds, + return_list ) -from ideal_learning_env.feature_creation_service import ( +from .feature_creation_service import ( BaseFeatureConfig, BaseObjectCreationService, FeatureCreationService, FeatureTypes, log_feature_template, position_relative_to, - save_to_object_repository + save_to_object_repository, + validate_floor_position ) -from ideal_learning_env.object_services import ( +from .numerics import ( + MinMaxFloat, + RandomizableFloat, + RandomizableInt, + RandomizableVectorFloat3d, + RandomizableVectorFloat3dOrFloat, + VectorFloatConfig +) +from .object_services import ( InstanceDefinitionLocationTuple, KeywordLocation, KeywordLocationConfig, ObjectRepository, RelativePositionConfig, - add_random_placement_tag + add_random_placement_tag, + calculate_rotated_position ) -from .choosers import ( - choose_material_tuple_from_material, - choose_position, - choose_rotation, - choose_scale, - choose_shape_material -) -from .numerics import ( - MinMaxFloat, - MinMaxInt, - RandomizableVectorFloat3dOrFloat, - VectorFloatConfig, - VectorIntConfig +logger = logging.getLogger(__name__) + + +@dataclass +class ToolConfig(BaseFeatureConfig): + """ + Defines details of a tool object. + + - `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of + MinMaxInt dicts): Number of tools to generate with these parameters + - `align_distance` (float, or [MinMaxFloat](#MinMaxFloat) dict, or list of + floats and/or MinMaxFloat dicts): The distance separating the edges of two + objects. Only used if `align_with` is also set. Default: between `1` and + `2` + - `align_with` (string, or list of strings): The label of an object (that + already exists in your configuration) with which this tool should be + aligned (this tool will be positioned behind the object, so you can push + this tool forward into the object). Overrides any configured `position` + and `rotation_y`. Use `align_distance` to set the distance separating the + edges of the two objects. Default: Use `position` + - `labels` (string, or list of strings): A label or labels to be assigned + to this object. Always automatically assigned "tools" + - `length` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list + of MinMaxInt dicts): The length of the tool. Tools only have specific + sizes and the values much match exactly. Valid lengths are integers + 4 to 9. If shape is set, this value is ignored. Default: random + - `position` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of + VectorFloatConfig dicts): The tool's position in the scene + - `rotation_y` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): The tool's rotation in the scene + - `shape` (string, or list of strings): The shape (object type) of this + object in each scene. For a list, a new shape will be randomly chosen for + each scene. Must be a valid [tool shape](#Lists). If set, ignores `length`, + `width`, and `tool_type`. Default: Use `tool_type` + - `tool_type` (str, or list of strs): The type of tool to generate, either + `rectangular`, `hooked`, or `small`. Default: `rectangular` or `hooked` + - `width` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, + or list of MinMaxFloat dicts): The width of the tool. Tools only have + specific sizes and the values much match exactly. Valid widths are + 0.5, 0.75, 1.0. If shape is set, this value is ignored. Default: random + - `material` (string, or list of strings): The material (color/texture) or + material category to use on this tool in each scene. For lists, a new + material will be randomly chosen for each scene. + Default: TOOL_MATERIALS + - `surrounded_by_lava` (bool, or list of bools): Whether or not this + tool will be surrounded by lava. If True, width of lava will be 1. + Default: False + """ + position: RandomizableVectorFloat3d = None + rotation_y: RandomizableFloat = None + shape: RandomizableString = None + length: RandomizableInt = None + width: RandomizableFloat = None + align_with: RandomizableString = None + align_distance: RandomizableFloat = None + tool_type: RandomizableString = None + material: RandomizableString = None + surrounded_by_lava: RandomizableBool = None + + +DEFAULT_TEMPLATE_TOOL = ToolConfig( + num=0, + position=VectorFloatConfig(None, 0, None), + rotation_y=MinMaxFloat(0, 359), + shape=None, + align_with=None, + align_distance=MinMaxFloat(1, 2), + tool_type=[tools.TOOL_TYPES.RECT, tools.TOOL_TYPES.HOOKED], + material="TOOL_MATERIALS", + surrounded_by_lava=False ) -logger = logging.getLogger(__name__) + +class ToolCreationService(BaseObjectCreationService): + + def __init__(self): + self._default_template = DEFAULT_TEMPLATE_TOOL + self._type = FeatureTypes.TOOLS + + def create_feature_from_specific_values( + self, scene: Scene, reconciled: ToolConfig, + source_template: ToolConfig): + """Creates a tool from the given template with + specific values.""" + args = { + 'object_type': reconciled.shape, + 'position_x': reconciled.position.x, + 'position_z': reconciled.position.z, + 'rotation_y': reconciled.rotation_y, + 'material_tuple': + choose_material_tuple_from_material(reconciled.material) + } + logger.trace(f'Creating tool:\nINPUT = {args}') + obj = tools.create_tool(**args) + labels = ( + source_template.labels + if source_template and source_template.labels + else [] + ) + labels = labels if isinstance(labels, list) else [labels] + obj['debug']['labels'] = [self._get_type().lower()] + labels + + if (reconciled.surrounded_by_lava is True): + create_surrounding_lava_positions(scene, obj) + + return obj + + def _handle_dependent_defaults( + self, scene: Scene, reconciled: ToolConfig, + source_template: ToolConfig + ) -> ToolConfig: + room_dim = scene.room_dimensions + def_dim = geometry.DEFAULT_ROOM_DIMENSIONS + room_width = room_dim.x or def_dim['x'] + room_length = room_dim.z or def_dim['z'] + surrounded_by_lava = ( + random.choice(source_template.surrounded_by_lava) if isinstance( + source_template.surrounded_by_lava, list) else + source_template.surrounded_by_lava) + + reconciled.surrounded_by_lava = True if surrounded_by_lava else False + + if not source_template.shape and ( + reconciled.width or reconciled.length or reconciled.tool_type + ): + reconciled.shape = tools.get_tool_shape( + reconciled.length, + reconciled.tool_type, + reconciled.width + ) + if not reconciled.shape: + tool_type = reconciled.tool_type + raise ILEException( + f'Unable to find a valid ' + f'{(tool_type + " tool") if tool_type else "tool"} with ' + f'dimensions width={reconciled.width or "Any"} and ' + f'length={reconciled.length or "Any"}' + ) + _, length = LARGE_BLOCK_TOOLS_TO_DIMENSIONS[reconciled.shape] + if source_template.align_with: + align_with = return_list(source_template.align_with).copy() + random.shuffle(align_with) + idl = None + object_repo = ObjectRepository.get_instance() + for label in align_with: + idl = object_repo.get_one_from_labeled_objects(label) + if idl: + break + if not idl: + raise ILEDelayException( + f'Cannot find object with one of the following labels for ' + f'tool to align_with: {source_template.align_with}' + ) + # By default, position this tool to the left of (-X) the other + # object (wheeled toys like cars with a Y rotation of 0 face toward + # the right), unless the other object is also a tool, in which case + # position this tool "behind" (-Z) the other object. + rotation = 90 + idl.instance['shows'][0]['rotation']['y'] - ( + 90 if idl.instance['type'].startswith('tool_') else + idl.instance['debug'].get('originalRotation', {'y': 0})['y'] + ) + offset_x = 0 + if idl.instance['type'].startswith('tool_hooked_'): + # Hooked tools are always three times wider than rect tools. + offset_x = idl.instance['debug']['dimensions']['x'] / 3.0 + if idl.instance['type'].startswith('tool_isosceles_'): + # Isosceles tools are always as wide as they are long. + width = tools.get_tool_width_from_type(idl.instance['type']) + modified = (idl.instance['debug']['dimensions']['x'] - width) + offset_x = modified / 2.0 + x, z = geometry.calculate_aligned_position( + idl.instance['shows'][0]['position']['x'], + idl.instance['shows'][0]['position']['z'], + rotation, + idl.instance['debug']['dimensions']['z'], + length, + reconciled.align_distance, + offset_x=offset_x + ) + reconciled.position.x = x + reconciled.position.z = z + reconciled.rotation_y = rotation + reconciled.position.x = ( + MinMaxFloat(-room_width / 2.0, room_width / 2.0).convert_value() + if reconciled.position.x is None else reconciled.position.x + ) + reconciled.position.z = ( + MinMaxFloat(-room_length / 2.0, room_length / 2.0).convert_value() + if reconciled.position.z is None else reconciled.position.z + ) + + return reconciled + + def _on_valid_instances( + self, scene: Scene, reconciled_template: ToolConfig, + new_obj: dict, key: str = 'objects'): + super()._on_valid_instances(scene, reconciled_template, + new_obj, key) + finalize_surrounding_lava(scene, reconciled_template, new_obj[0]) @dataclass @@ -111,8 +323,8 @@ class InteractableObjectConfig(BaseFeatureConfig): `position`. If configuring this as a list, then all listed options will be applied to each scene in the listed order, with later options overriding earlier options if necessary. Default: not used - - `rotation` ([VectorIntConfig](#VectorIntConfig) dict, or list of - VectorIntConfig dicts): The rotation of this object in each scene. For a + - `rotation` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of + VectorFloatConfig dicts): The rotation of this object in each scene. For a list, a new rotation will be randomly chosen for each scene. Default: random - `scale` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, @@ -150,6 +362,12 @@ class InteractableObjectConfig(BaseFeatureConfig): - `shape` (string, or list of strings): The shape (object type) of this object in each scene. For a list, a new shape will be randomly chosen for each scene. Default: random + - `surrounded_by_lava` (bool, or list of bools): Whether or not this + object will be surrounded by lava. Default: False + - `surrounding_lava_size`: (int, or list of ints): The width of the + surrounding lava. Only used if `surrounded_by_lava` is set to True. In + that case, default is 1, otherwise the value will be None. Note that + this value will need to fit within room bounds, otherwise may error. - `rotate_cylinders` (bool): Whether or not to rotate cylindrical shapes along their x axis so that they are placed on their round sides (needed for collision scenes). This would only apply to these shapes: 'cylinder', @@ -206,16 +424,15 @@ class InteractableObjectConfig(BaseFeatureConfig): """ dimensions: RandomizableVectorFloat3dOrFloat = None - material: Union[str, List[str]] = None - scale: Union[float, MinMaxFloat, VectorFloatConfig, - List[Union[float, MinMaxFloat, VectorFloatConfig]]] = None - shape: Union[str, List[str]] = None - position: Union[VectorFloatConfig, List[VectorFloatConfig]] = None - rotation: Union[VectorIntConfig, List[VectorIntConfig]] = None + material: RandomizableString = None + scale: RandomizableVectorFloat3dOrFloat = None + shape: RandomizableString = None + position: RandomizableVectorFloat3d = None + rotation: RandomizableVectorFloat3d = None keyword_location: Union[KeywordLocationConfig, List[KeywordLocationConfig]] = None - locked: Union[bool, List[bool]] = False - separate_lid: Union[int, List[int], MinMaxInt, List[MinMaxInt]] = None + locked: RandomizableBool = False + separate_lid: RandomizableInt = None identical_to: str = None identical_except_color: str = None position_relative: Union[ @@ -224,19 +441,26 @@ class InteractableObjectConfig(BaseFeatureConfig): ] = None rotate_cylinders: bool = False not_material: str = None - num_targets_minus: Union[ - int, - MinMaxInt, - List[Union[int, MinMaxInt]] - ] = None + num_targets_minus: RandomizableInt = None separate_lid_after: RandomizableString = None + surrounded_by_lava: RandomizableBool = False + surrounding_lava_size: RandomizableInt = None + + +@dataclass +class SurroundingLavaConfig(BaseFeatureConfig): + # Used internally for the surrounding lava case as a workaround + # for circular dependency issue when trying to import FloorAreaConfig + position_x: RandomizableInt = None + position_z: RandomizableInt = None DEFAULT_TEMPLATE_INTERACTABLE = InteractableObjectConfig( num=1, material=None, scale=1, shape=None, position=None, rotation=None, keyword_location=None, locked=False, separate_lid=None, labels=None, identical_to=None, identical_except_color=None, not_material=None, - separate_lid_after=None) + separate_lid_after=None, surrounded_by_lava=False, + surrounding_lava_size=None) class InteractableObjectCreationService(BaseObjectCreationService): @@ -309,6 +533,18 @@ def _handle_dependent_defaults( if position_z is not None: reconciled.position.z = position_z + surrounded_by_lava = ( + random.choice(source_template.surrounded_by_lava) if isinstance( + source_template.surrounded_by_lava, list) else + source_template.surrounded_by_lava) + + if (surrounded_by_lava is True): + reconciled.surrounded_by_lava = True + lava_size = (random.choice(source_template.surrounding_lava_size) + if isinstance(source_template.surrounding_lava_size, + list) else + source_template.surrounding_lava_size) + reconciled.surrounding_lava_size = lava_size if isinstance(lava_size, int) else 1 # noqa: E501 # Save ALL the labels. reconciled.labels = source_template.labels or [] @@ -331,6 +567,11 @@ def create_feature_from_specific_values( if idl: self.idl = idl add_random_placement_tag(idl.instance, source_template) + if (reconciled.surrounded_by_lava is True): + create_surrounding_lava_positions( + scene, + idl.instance, + reconciled.surrounding_lava_size) return idl.instance except ILEException as e: # If location can't be found, try again and therefore log @@ -360,6 +601,10 @@ def create_feature_from_specific_values( obj, defn, location) add_random_placement_tag( self.idl.instance, source_template) + if (reconciled.surrounded_by_lava is True): + create_surrounding_lava_positions( + scene, self.idl.instance, + reconciled.surrounding_lava_size) return self.idl.instance else: msg = (f"Failed to create instance. template=" @@ -377,7 +622,7 @@ def create_feature_from_specific_values( def _on_valid_instances( self, scene: Scene, reconciled_template: InteractableObjectConfig, - new_obj: dict, key: str = 'objects'): + new_obj: SceneObject, key: str = 'objects'): if new_obj is not None: for obj in new_obj: if (obj['type'] in specific_objects.get_lockable_shapes()): @@ -388,6 +633,7 @@ def _on_valid_instances( reconciled_template.labels ) scene.objects.append(obj) + finalize_surrounding_lava(scene, reconciled_template, obj) log_feature_template( self._get_type().lower().replace('_', ' '), @@ -401,12 +647,22 @@ def _on_valid_instances( def add_separate_lid( scene: Scene, lid_step_begin: int, - container_obj: Dict[str, Any] + container_obj: SceneObject ) -> None: container_material = container_obj['materials'][0] container_position = container_obj['shows'][0]['position'] container_rotation = container_obj['shows'][0]['rotation'] container_scale = container_obj['shows'][0]['scale'] + + # If container is positioned on top of a turntable, then adjust its + # position if needed based on the turntable's rotation. + adjusted_position = calculate_rotated_position( + scene, + lid_step_begin, + container_obj + ) + container_position = adjusted_position or container_position + lid_template = InteractableObjectConfig( position=Vector3d( x=container_position['x'], @@ -439,8 +695,8 @@ def add_separate_lid( @staticmethod def create_placer_for_separate_lid( scene: Scene, - lid: Dict[str, Any], - container: Dict[str, Any], + lid: SceneObject, + container: SceneObject, step_begin: int ) -> None: scene.debug['containsSeparateLids'] = True @@ -483,10 +739,10 @@ def _attach_location( 'rotation': vars(template.rotation) } location['position']['y'] += defn.positionY - if(rotate_cylinders): + if (rotate_cylinders): location['rotation']['x'] = 90 else: - if(rotate_cylinders): + if (rotate_cylinders): defn.rotation.x = 90 return geometry.calc_obj_pos( @@ -548,7 +804,7 @@ def _create_definition(self, template) -> ObjectDefinition: # If the object definition is intentionally turned sideways by # default, switch the X and Z scales, since the definition's # dimensions have already been switched. - if defn.rotation.y == 90: + if round(defn.rotation.y, 2) == 90: temp = template.scale.x template.scale.x = template.scale.z template.scale.z = temp @@ -574,18 +830,18 @@ def _create_definition(self, template) -> ObjectDefinition: return defn -FeatureCreationService.register_creation_service( - FeatureTypes.INTERACTABLE, InteractableObjectCreationService) - - class TargetCreationService(InteractableObjectCreationService): def __init__(self): super().__init__() self._type = FeatureTypes.TARGET +FeatureCreationService.register_creation_service( + FeatureTypes.INTERACTABLE, InteractableObjectCreationService) FeatureCreationService.register_creation_service( FeatureTypes.TARGET, TargetCreationService) +FeatureCreationService.register_creation_service( + FeatureTypes.TOOLS, ToolCreationService) def create_user_configured_interactable_object( @@ -593,7 +849,7 @@ def create_user_configured_interactable_object( bounds: List[ObjectBounds], object_config: InteractableObjectConfig, is_target: bool = False -) -> Dict[str, Any]: +) -> SceneObject: """Create and return a user-configured interactable object, that may have a config option like "identical_to" or "identical_except_color" which must be applied appropriately. The given object_config should NOT be reconciled @@ -656,3 +912,165 @@ def create_user_configured_interactable_object( raise ILEException('Random object accidentally matches in color') return obj + + +def create_surrounding_lava_positions(scene, instance, lava_size=1): + x_island_dim, z_island_dim = get_lava_island_dimensions(instance) + + current_obj_pos = instance["shows"][0]["position"] + + # shift object to the center of lava island, round to nearest + # 0.5 for even numbered x/z dimension, nearest whole number + # otherwise + x_loc_new = round( + current_obj_pos["x"] * 2.0) / 2.0 if ( + x_island_dim % + 2 == 0) else round( + current_obj_pos["x"]) + z_loc_new = round( + current_obj_pos["z"] * 2.0) / 2.0 if ( + z_island_dim % + 2 == 0) else round( + current_obj_pos["z"]) + + new_location = { + "position": {"x": x_loc_new, + "y": current_obj_pos["y"], + "z": z_loc_new}, + "rotation": instance["shows"][0]["rotation"] + } + + geometry.move_to_location(instance, new_location) + + # figure out min/max coords for lava and island + min_island_x = round(x_loc_new - ((x_island_dim - 1) / 2)) + max_island_x = round(x_loc_new + ((x_island_dim - 1) / 2)) + min_island_z = round(z_loc_new - ((z_island_dim - 1) / 2)) + max_island_z = round(z_loc_new + ((z_island_dim - 1) / 2)) + + if (min_island_z > max_island_z): + min_island_z = round(z_loc_new + ((z_island_dim - 1) / 2)) + max_island_z = round(z_loc_new - ((z_island_dim - 1) / 2)) + + if (min_island_x > max_island_x): + min_island_x = round(x_loc_new + ((x_island_dim - 1) / 2)) + max_island_x = round(x_loc_new - ((x_island_dim - 1) / 2)) + + min_lava_z = min_island_z - lava_size + max_lava_z = max_island_z + lava_size + min_lava_x = min_island_x - lava_size + max_lava_x = max_island_x + lava_size + + if (min_lava_z > max_lava_z): + min_lava_z = max_island_z + lava_size + max_lava_z = min_island_z - lava_size + + if (min_lava_x > max_lava_x): + min_lava_x = max_island_x + lava_size + max_lava_x = min_island_x - lava_size + + # create lava + x_range = range(min_lava_x, max_lava_x + 1) + z_range = range(min_lava_z, max_lava_z + 1) + lava_list = [] + + # Use copies of the scene and bounds for validation + # We're not actually putting the lava in the scene + # until we determine that the object in the middle + # is valid and can be placed in the scene + scene_copy = copy.deepcopy(scene) + bounds_copy = find_bounds(scene).copy() + object_to_add = instance['shows'][0]['boundingBox'] + bounds_copy.append(object_to_add) + + # Make sure lava island isn't where performer is placed + if ((scene.performer_start.position.x <= max_island_x and + scene.performer_start.position.x >= min_island_x) and + (scene.performer_start.position.z <= max_island_z and + scene.performer_start.position.z >= min_island_z)): + logger.debug( + f'Cannot place surrounding lava around performer start.\n' + f'performer start: {scene.performer_start}\n' + f'island x min/max: {min_island_x} to {max_island_x}\n' + f'island z min/max: {min_island_z} to {max_island_z}\n' + ) + raise ILEException( + 'Cannot place surrounding lava around performer start.' + ) + + for x_value in x_range: + for z_value in z_range: + if ((x_value <= max_island_x and + x_value >= min_island_x) and + (z_value <= max_island_z and + z_value >= min_island_z)): + continue + + lava_2d = Vector2dInt(x=x_value, z=z_value) + + valid, bb = validate_floor_position( + scene_copy, lava_2d, + 'lava', True, bounds_copy) + + if (valid): + bounds_copy.append(bb) + scene_copy.lava.append(lava_2d) + lava_list.append({"x": x_value, "z": z_value}) + else: + lava_list.clear() + logger.debug( + f'Error placing surrounding lava around object.\n' + f'object position: ' + f'{instance["shows"][0]["position"]}\n' + f'lava: X = {x_value}, Z = {z_value}\n' + f'room dimensions: {scene_copy.room_dimensions}\n' + f'performer start: {scene_copy.performer_start.position}\n' + ) + raise ILEException( + 'Error placing surrounding lava around object.' + ) + + # Add lava positions to the debug information for this object + # The lava will be added to the scene once this object passes + # validation and is added to the scene + instance['debug']['surroundingLava'] = lava_list + + +def get_lava_island_dimensions(instance): + obj_box_xz = instance['shows'][0]['boundingBox'].box_xz + obj_x_min = obj_box_xz[0].x + obj_x_max = obj_box_xz[0].x + obj_z_min = obj_box_xz[0].z + obj_z_max = obj_box_xz[0].z + + for vector in obj_box_xz: + if obj_x_min > vector.x: + obj_x_min = vector.x + if obj_z_min > vector.z: + obj_z_min = vector.z + + if obj_x_max < vector.x: + obj_x_max = vector.x + if obj_z_max < vector.z: + obj_z_max = vector.z + + x_island_dim = math.ceil(abs(obj_x_max - obj_x_min)) + z_island_dim = math.ceil(abs(obj_z_max - obj_z_min)) + return x_island_dim, z_island_dim + + +def finalize_surrounding_lava(scene, reconciled_template, obj): + if (reconciled_template.surrounded_by_lava is True and + 'surroundingLava' in obj['debug']): + for lava in obj['debug']['surroundingLava']: + lava = SurroundingLavaConfig( + num=1, + position_x=lava['x'], + position_z=lava['z'] + ) + FeatureCreationService.create_feature( + scene, FeatureTypes.LAVA, lava, + find_bounds(scene) + ) + + del obj['debug']['surroundingLava'] diff --git a/ideal_learning_env/interactable_objects_component.py b/ideal_learning_env/interactable_objects_component.py index 59a7e6c..8c4c313 100644 --- a/ideal_learning_env/interactable_objects_component.py +++ b/ideal_learning_env/interactable_objects_component.py @@ -7,11 +7,13 @@ from machine_common_sense.config_manager import Vector3d from generator import ( + ALL_LARGE_BLOCK_TOOLS, MAX_TRIES, DefinitionDataset, ImmutableObjectDefinition, ObjectBounds, ObjectDefinition, + SceneObject, base_objects, containers, definitions, @@ -22,12 +24,6 @@ ) from generator.containers import shift_lid_positions_based_on_movement from generator.scene import Scene -from ideal_learning_env.defs import ILEDelayException -from ideal_learning_env.feature_creation_service import ( - BaseFeatureConfig, - FeatureCreationService, - FeatureTypes -) from .choosers import ( choose_counts, @@ -38,16 +34,29 @@ from .components import ILEComponent from .decorators import ile_config_setter from .defs import ( + ILEDelayException, ILEException, ILESharedConfiguration, + RandomizableString, find_bounds, return_list ) +from .feature_creation_service import ( + BaseFeatureConfig, + FeatureCreationService, + FeatureTypes +) from .interactable_object_service import ( InteractableObjectConfig, + ToolConfig, create_user_configured_interactable_object ) -from .numerics import MinMaxInt, VectorFloatConfig, VectorIntConfig +from .numerics import ( + MinMaxInt, + RandomizableVectorFloat3d, + VectorFloatConfig, + VectorIntConfig +) from .object_services import ( InstanceDefinitionLocationTuple, KeywordLocation, @@ -110,8 +119,53 @@ class SpecificInteractableObjectsComponent(ILEComponent): max: 1 ``` """ + + tools: Union[ToolConfig, List[ToolConfig]] = 0 + """ + ([ToolConfig](#ToolConfig), or list of [ToolConfig](#ToolConfig) dict) -- + Groups of large block tool configurations and how many should be generated + from the given options. + Default: 0 + + + Simple Example: + ``` + tools: + - num: 0 + ``` + + Advanced Example: + ``` + tools: + - num: + min: 1 + max: 3 + - num: 1 + shape: tool_rect_1_00_x_9_00 + position: + x: [1,2] + y: 0 + z: + min: -3 + max: 3 + - num: [1, 3] + shape: + - tool_rect_0_50_x_4_00 + - tool_rect_0_75_x_4_00 + - tool_rect_1_00_x_4_00 + position: + x: [4, 5] + y: 0 + z: + min: -5 + max: -4 + + ``` + """ + _delayed_templates = [] _delayed_separate_lids = [] + _delayed_tools = [] @ile_config_setter(validator=ValidateNumber( props=['num'], min_value=0, null_ok=True)) @@ -128,6 +182,13 @@ class SpecificInteractableObjectsComponent(ILEComponent): def set_specific_interactable_objects(self, data: Any) -> None: self.specific_interactable_objects = data + @ile_config_setter(validator=ValidateNumber( + props=['num'], min_value=0)) + @ile_config_setter(validator=ValidateOptions( + props=['shape'], options=ALL_LARGE_BLOCK_TOOLS)) + def set_tools(self, data: Any) -> None: + self.tools = data + # Override def update_ile_scene(self, scene: Scene) -> Scene: logger.info('Configuring specific interactable objects...') @@ -135,9 +196,13 @@ def update_ile_scene(self, scene: Scene) -> Scene: bounds = find_bounds(scene) self._delayed_templates = [] self._delayed_separate_lids = [] + self._delayed_tools = [] templates = return_list(self.specific_interactable_objects) for template, num in choose_counts(templates): self._add_objects_from_template(scene, bounds, template, num) + tool_templates = return_list(self.tools) + for tool_template, num in choose_counts(tool_templates): + self._add_tools_from_template(scene, bounds, tool_template, num) return scene def run_delayed_actions(self, scene: Scene) -> Scene: @@ -147,6 +212,8 @@ def run_delayed_actions(self, scene: Scene) -> Scene: self._delayed_templates = [] separate_lids = self._delayed_separate_lids self._delayed_separate_lids = [] + tool_templates = self._delayed_tools + self._delayed_tools = [] # Try running the delayed actions again. for _, template, num in templates: self._add_objects_from_template(scene, bounds, template, num) @@ -157,18 +224,27 @@ def run_delayed_actions(self, scene: Scene) -> Scene: separate_lid, separate_lid_after ) + for _, tool_template, num in tool_templates: + self._add_tools_from_template(scene, bounds, tool_template, num) return scene def get_num_delayed_actions(self) -> bool: - return len(self._delayed_templates) + len(self._delayed_separate_lids) + return ( + len(self._delayed_templates) + + len(self._delayed_separate_lids) + + len(self._delayed_tools) + ) def get_delayed_action_error_strings(self) -> List[str]: - delayed = self._delayed_templates + self._delayed_separate_lids + delayed = ( + self._delayed_templates + self._delayed_separate_lids + + self._delayed_tools + ) # The first element in each tuple should be the exception. return [str(data[0]) for data in delayed] def run_actions_at_end_of_scene_generation( - self, scene: Dict[str, Any]) -> Dict[str, Any]: + self, scene: Scene) -> Scene: if scene.debug.get('containsSeparateLids'): scene.objects = shift_lid_positions_based_on_movement( scene.objects) @@ -228,7 +304,7 @@ def _add_objects_from_template( num = max((len(targets) - num_targets_minus), 0) except ILEDelayException as e: self._delayed_templates.append( - (template, num_targets_minus, e) + (e, template, num_targets_minus) ) logger.trace( @@ -241,7 +317,7 @@ def _add_objects_from_template( def _add_separate_lid( self, scene: Scene, - instance: Dict[str, Any], + instance: SceneObject, separate_lid: int, separate_lid_after: List[str] ) -> None: @@ -258,6 +334,24 @@ def _add_separate_lid( ) service.add_separate_lid(scene, separate_lid, instance) + def _add_tools_from_template( + self, + scene: Scene, + bounds: List[ObjectBounds], + tool_template: ToolConfig, + num: int + ) -> None: + for _ in range(num): + try: + FeatureCreationService.create_feature( + scene, + FeatureTypes.TOOLS, + tool_template, + bounds + ) + except ILEDelayException as e: + self._delayed_tools.append((e, tool_template, 1)) + class RandomInteractableObjectsComponent(ILEComponent): """Adds random objects to an ILE scene. Users can specify an exact number @@ -384,15 +478,15 @@ class KeywordObjectsConfig(BaseFeatureConfig): `position`. If configuring this as a list, then all listed options will be applied to each scene in the listed order, with later options overriding earlier options if necessary. Default: not used - - `rotation` ([VectorIntConfig](#VectorIntConfig) dict, or list of - VectorIntConfig dicts): The rotation of these objects in each scene. If + - `rotation` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of + VectorFloatConfig dicts): The rotation of these objects in each scene. If given a list, a rotation will be randomly chosen for each object and each scene. Is overridden by the `keyword_location`. Default: random """ - keyword: Union[str, List[str]] = None + keyword: RandomizableString = None keyword_location: KeywordLocationConfig = None - position: Union[VectorFloatConfig, List[VectorFloatConfig]] = None - rotation: Union[VectorIntConfig, List[VectorIntConfig]] = None + position: RandomizableVectorFloat3d = None + rotation: RandomizableVectorFloat3d = None class RandomKeywordObjectsComponent(ILEComponent): @@ -604,7 +698,7 @@ def _add_asymmetric_container( bounds: List[ObjectBounds], index: int, template: KeywordObjectsConfig - ) -> Dict[str, Any]: + ) -> SceneObject: return self._add_special_container( scene, bounds, @@ -621,7 +715,7 @@ def _add_bin_container( bounds: List[ObjectBounds], index: int, template: KeywordObjectsConfig - ) -> Dict[str, Any]: + ) -> SceneObject: return self._add_special_container( scene, bounds, @@ -770,7 +864,7 @@ def _add_open_topped_container( bounds: List[ObjectBounds], index: int, template: KeywordObjectsConfig - ) -> Dict[str, Any]: + ) -> SceneObject: return self._add_special_container( scene, bounds, @@ -790,7 +884,7 @@ def _add_special_container( keyword: str, label: str, dataset: DefinitionDataset - ) -> Dict[str, Any]: + ) -> SceneObject: def _choose_definition_callback() -> ObjectDefinition: return dataset.choose_random_definition() @@ -804,7 +898,7 @@ def _add_symmetric_container( bounds: List[ObjectBounds], index: int, template: KeywordObjectsConfig - ) -> Dict[str, Any]: + ) -> SceneObject: return self._add_special_container( scene, bounds, @@ -829,10 +923,10 @@ def _generate_instance_with_valid_location( bounds: List[ObjectBounds], choose_definition_callback: Callable[[], ObjectDefinition], index: int, - labels: Union[str, List[str]], + labels: RandomizableString, template: KeywordObjectsConfig, choose_rotation_callback: Callable[[], float] = None - ) -> Dict[str, Any]: + ) -> SceneObject: object_repo = ObjectRepository.get_instance() shared_config = ILESharedConfiguration.get_instance() diff --git a/ideal_learning_env/mock_component.py b/ideal_learning_env/mock_component.py index aa94eca..181af68 100644 --- a/ideal_learning_env/mock_component.py +++ b/ideal_learning_env/mock_component.py @@ -2,6 +2,8 @@ from dataclasses import dataclass from typing import Any, Dict, List, Union +from generator import Scene + from .components import ILEComponent from .decorators import ile_config_setter from .numerics import VectorFloatConfig @@ -125,7 +127,7 @@ def __get_scene_data(self, data: Any): return data # Override - def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def update_ile_scene(self, scene: Scene) -> Scene: for prop, attr in vars(self).items(): if attr is not None and not prop.startswith('_'): scene.debug[prop] = self.__get_scene_data(attr) diff --git a/ideal_learning_env/numerics.py b/ideal_learning_env/numerics.py index e53bd9d..0c5e690 100644 --- a/ideal_learning_env/numerics.py +++ b/ideal_learning_env/numerics.py @@ -164,6 +164,7 @@ def retrieve_all_vectors( ) +RandomizableVectorInt3d = Union[VectorIntConfig, List[VectorIntConfig]] RandomizableVectorFloat3d = Union[VectorFloatConfig, List[VectorFloatConfig]] RandomizableInt = Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] RandomizableFloat = Union[float, MinMaxFloat, List[Union[float, MinMaxFloat]]] diff --git a/ideal_learning_env/object_services.py b/ideal_learning_env/object_services.py index fa74a0d..6d1de20 100644 --- a/ideal_learning_env/object_services.py +++ b/ideal_learning_env/object_services.py @@ -8,31 +8,41 @@ Dict, List, NamedTuple, + Optional, Tuple, TypeVar, Union ) from machine_common_sense.config_manager import PerformerStart, Vector3d +from shapely import affinity from generator import ( ObjectBounds, ObjectDefinition, + Scene, + SceneException, + SceneObject, base_objects, containers, geometry, instances ) -from ideal_learning_env.choosers import choose_random -from ideal_learning_env.defs import ( + +from .choosers import choose_random +from .defs import ( ILEConfigurationException, ILEDelayException, ILEException, + RandomizableBool, RandomizableString, return_list ) - -from .numerics import MinMaxFloat, VectorFloatConfig, VectorIntConfig +from .numerics import ( + RandomizableFloat, + RandomizableVectorFloat3d, + VectorFloatConfig +) logger = logging.getLogger(__name__) @@ -49,6 +59,12 @@ class KeywordLocationConfig(): object must be referenced by the 'relative_object_label' field, and the distance away from the relative object must be set by the `adjacent_distance` field. + - `adjacent_corner` - The object will be placed near the corner + referenced by the 'relative_object_label' field. The corner labels + are 'front_left' (-X, +Z), 'front_right' (+X, +Z), + 'back_left' (-X, -Z), and 'back_right' (+X, -Z). The + distance away from the corner will be determined by the + `adjacent_distance` field. - `adjacent_performer` - The object will be placed next to the performer. The object can be placed in 'front', 'back', left, or 'right' of the performer using the 'direction'. The object @@ -117,9 +133,13 @@ class KeywordLocationConfig(): provided, a wall will be chosen at random. - `adjacent_distance` (VectorFloatConfig, or list of VectorFloatConfigs): The X/Z distance in global coordinates between this object's position and - the relative object's position. Only usable with the `adjacent` `keyword`. - By default, this object will be positioned 0.1 away from the relative - object in a random, valid direction. + the relative object's (or corner's) position. Only usable with the + `adjacent` or `adjacent_corner` keyword. By default, this object will be + positioned 0.1 away from the relative object (or corner) in a random, + valid direction. Note that if using this for a corner with the + `surrounded_by_lava` property, you'll need to use the same dimensions for + x/z and that they're divisible by 0.5 to make sure they line up correctly + with the lava island. - `container_label` (string, or list of strings): The label of a container object that already exists in your configuration. Only required by some keyword locations. @@ -135,25 +155,23 @@ class KeywordLocationConfig(): relative object has a rectangular boundary. """ - keyword: Union[str, List[str]] = None - container_label: Union[str, List[str]] = None - relative_object_label: Union[str, List[str]] = None - distance: Union[str, List[str]] = None - direction: Union[str, List[str]] = None - position_relative_to_start: Union[VectorFloatConfig, - List[VectorFloatConfig]] = None - adjacent_distance: Union[VectorFloatConfig, List[VectorFloatConfig]] = None - rotation: Union[VectorIntConfig, List[VectorIntConfig]] = None + keyword: RandomizableString = None + container_label: RandomizableString = None + relative_object_label: RandomizableString = None + distance: RandomizableString = None + direction: RandomizableString = None + position_relative_to_start: RandomizableVectorFloat3d = None + adjacent_distance: RandomizableVectorFloat3d = None + rotation: RandomizableVectorFloat3d = None class InstanceDefinitionLocationTuple(NamedTuple): """Object that is stored in the object repository. """ - # TODO MCS-697 turn into class - instance: dict + instance: SceneObject definition: ObjectDefinition # TODO MCS-698 turn into class - location: dict + location: Dict[str, Any] class ObjectRepository(): @@ -191,7 +209,7 @@ def has_label(self, label: str): def add_to_labeled_objects( self, obj_defn_loc_tuple: InstanceDefinitionLocationTuple, - labels: Union[str, List[str]]): + labels: RandomizableString): """Adds an object instance, definition, and location to a single or multiple labels. """ @@ -255,6 +273,7 @@ class KeywordLocation(): OPPOSITE_X = "opposite_x" OPPOSITE_Z = "opposite_z" ALONG_WALL = "along_wall" + ADJACENT_TO_CORNER = "adjacent_corner" ADJACENT_TO_PERFORMER_FRONT = "front" ADJACENT_TO_PERFORMER_BACK = "back" @@ -340,7 +359,7 @@ def associate_with_agent(keyword_location, instance): def move_to_keyword_location( reconciled: KeywordLocationConfig, source: KeywordLocationConfig, - instance: Dict[str, Any], + instance: SceneObject, performer_start: PerformerStart, bounds: List[ObjectBounds], room_dimensions: Vector3d, @@ -406,6 +425,39 @@ def move_to_keyword_location( return KeywordLocation._move_instance_or_raise_error( instance, location, keyword) + if keyword == KeywordLocation.ADJACENT_TO_CORNER: + # Choose a random corner to use if one isn't specified + corners = obj_tag or [ + geometry.FRONT_LEFT_CORNER, + geometry.FRONT_RIGHT_CORNER, + geometry.BACK_LEFT_CORNER, + geometry.BACK_RIGHT_CORNER] + corners = corners if isinstance(corners, list) else [corners] + random.shuffle(corners) + + # Use all of the configured distances (if any) from the source + # template, rather than the single randomly chosen distance that + # will be in the reconciled template. + distances = return_list(source.adjacent_distance) + if not distances: + # By default, position the object 0.1 away from the relative + # corner in the x or z direction. + distances = [ + Vector3d(x=0.1, y=0, z=0.1) + ] + # Loop over each configured (or default) distance in a random order + # and use the first valid adjacent location that's found. + random.shuffle(distances) + for distance in distances: + for corner in corners: + location = geometry.get_location_adjacent_to_corner( + performer_start, instance, room_dimensions, + distance_from_corner=distance, corner_label=corner) + if location: + break + return KeywordLocation._move_instance_or_raise_error( + instance, location, keyword) + if keyword != KeywordLocation.IN_CONTAINER: if not obj_tag or not obj_repo.has_label(obj_tag): raise ILEDelayException( @@ -492,7 +544,7 @@ def move_to_keyword_location( relative_instance['debug']['positionY'] ) # Mirror the object's rotation. - mirrored_rotation = (-location['rotation']['y']) % 360 + mirrored_rotation = round(-location['rotation']['y'], 2) % 360 location['rotation']['y'] = ( mirrored_rotation + # Add the configured rotation, if any. @@ -545,14 +597,18 @@ def move_to_keyword_location( 'relative_label' ) relative_instance['debug'][DEBUG_FINAL_POSITION_KEY] = True - except Exception: + if relative_instance['type'] == 'rotating_cog': + relative_id = relative_instance['id'] + instance['debug']['isRotatedBy'] = relative_id + except Exception as e: attempts += 1 if attempts == len(all_idls): # If the object is too big to be on top of all # relative objects, retry generating the scene raise ILEException( 'Object on top too big for object underneath.' - 'Retrying...') + 'Retrying...' + ) from e # If the object is too big to be on top of the relative # object, retry with another realtive object in the scene continue @@ -588,8 +644,8 @@ def move_to_keyword_location( # in the future. positioned_by = con_inst['debug'].get('positionedBy', None) - if(positioned_by == 'mechanism' and - con_inst.get('moves') is not None): + if (positioned_by == 'mechanism' and + con_inst.get('moves') is not None): ctr_moves = con_inst.get('moves') moves = instance.get('moves', []) for ctr_move in ctr_moves: @@ -597,7 +653,7 @@ def move_to_keyword_location( instance['moves'] = moves instance['kinematic'] = con_inst['kinematic'] - if(con_inst.get('togglePhysics') is not None): + if (con_inst.get('togglePhysics') is not None): ctr_tog_list = con_inst.get('togglePhysics') tog_list = instance.get('togglePhysics', []) for ctr_tog in ctr_tog_list: @@ -656,7 +712,7 @@ def move_to_keyword_location( @staticmethod def _move_instance_or_raise_error( - instance: Dict[str, Any], + instance: SceneObject, location: Dict[str, Any], keyword: str ) -> Dict[str, Any]: @@ -716,7 +772,7 @@ def reconcile_template(default_template: T, source_template: T) -> T: return obj_values -def add_random_placement_tag(objs: Union[list, dict], +def add_random_placement_tag(objs: Union[SceneObject, List[SceneObject]], template) -> None: random = True if hasattr(template, 'position'): @@ -768,16 +824,12 @@ class RelativePositionConfig(): position and the relative object's position. Useful for positioning objects behind occluders (especially in the passive physics tasks). """ - add_x: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None - add_z: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None - label: Union[str, List[str]] = None - use_x: Union[bool, List[bool]] = None - use_z: Union[bool, List[bool]] = None - view_angle_x: Union[bool, List[bool]] = None + add_x: RandomizableFloat = None + add_z: RandomizableFloat = None + label: RandomizableString = None + use_x: RandomizableBool = None + use_z: RandomizableBool = None + view_angle_x: RandomizableBool = None def get_step_after_movement(labels: RandomizableString) -> int: @@ -827,3 +879,75 @@ def get_step_after_movement_or_start(labels: RandomizableString) -> int: if step > last_step: last_step = step return (last_step + 1) if earliest_step == 1 else 1 + + +def calculate_rotated_position( + scene: Scene, + lid_step_begin: int, + container: SceneObject +) -> Optional[Dict[str, float]]: + """Calculate and return the position for the given object at the given + step, assuming that the object is on top of a rotating turntable.""" + + # Assumes the container was positioned using the "on_center" or + # "on_top" keyword location, which would set isRotatedBy + # (see KeywordLocation.move_to_keyword_location) + if not container['debug'].get('isRotatedBy'): + return None + + turntable = scene.get_object_by_id(container['debug']['isRotatedBy']) + if not turntable.get('rotates') or not turntable['rotates'][0]: + return None + + # Estimate the number of steps it will take for the lid placer to move. + container_position_y = container['shows'][0]['position']['y'] + container_dimensions_y = container['debug']['dimensions']['y'] + container_standing_y = container['debug']['positionY'] + container_top = ( + container_position_y + container_dimensions_y - + container_standing_y + ) + placer_steps = (scene.room_dimensions.y - container_top) / 0.25 + + # Placing the lid during rotation is currently unsupported. + rotate = turntable['rotates'][0] + if ( + lid_step_begin > 0 and + rotate['stepBegin'] <= (lid_step_begin + placer_steps) and + rotate['stepEnd'] >= (lid_step_begin + placer_steps) + ): + raise SceneException( + f'Cannot place separate lid at step {lid_step_begin} ' + f'and estimated placer movement steps {placer_steps} ' + f'because container {container["id"]} is on top ' + f'of turntable {turntable["id"]} which is rotating ' + f'between steps {rotate["stepBegin"]} and ' + f'{rotate["stepEnd"]}' + ) + + # If the lid will be placed after the turntable completely finishes + # rotating, then calculate the new position for the container. + turntable_position = turntable['shows'][0]['position'] + turntable_x = turntable_position['x'] + turntable_z = turntable_position['z'] + if ( + lid_step_begin > 0 and + (lid_step_begin + placer_steps) > rotate['stepEnd'] + ): + rotate_steps = rotate['stepEnd'] - rotate['stepBegin'] + 1 + rotate_degrees = rotate['vector']['y'] * rotate_steps + rotated_polygon = affinity.rotate( + container['shows'][0]['boundingBox'].polygon_xz, + -rotate_degrees, + origin=(turntable_x, turntable_z) + ) + rotated_point = rotated_polygon.centroid.coords[0] + container_position = { + 'x': rotated_point[0], + 'y': container_position_y, + 'z': rotated_point[1] + } + return container_position + + # Otherwise, no new position is needed. + return None diff --git a/ideal_learning_env/shortcut_component.py b/ideal_learning_env/shortcut_component.py index 4ea7810..b72a413 100644 --- a/ideal_learning_env/shortcut_component.py +++ b/ideal_learning_env/shortcut_component.py @@ -6,12 +6,13 @@ from enum import Enum from typing import Any, Dict, List, Union -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d from generator import ( MAX_TRIES, ObjectBounds, Scene, + SceneObject, geometry, instances, materials, @@ -21,60 +22,73 @@ LARGE_BLOCK_TOOLS_TO_DIMENSIONS, create_soccer_ball ) +from generator.imitation import ( + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE, + ImitationKidnapOptions, + ImitationTriggerOrder, + add_imitation_task +) +from generator.lava import LavaIslandSizes from generator.mechanisms import create_placer from generator.structures import ( BASE_DOOR_HEIGHT, BASE_DOOR_WIDTH, + create_guide_rails_around +) +from generator.tools import ( + HOOKED_TOOL_BUFFER, INACCESSIBLE_TOOL_BLOCKING_WALL_MINIMUM_SEPARATION, + MAX_TOOL_LENGTH, + MIN_LAVA_ISLAND_LONG_ROOM_DIMENSION_LENGTH, + MIN_LAVA_ISLAND_SHORT_ROOM_DIMENSION_LENGTH, + MIN_TOOL_CHOICE_X_DIMENSION, + TOOL_TYPES, create_broken_tool, - create_guide_rails_around, - create_inaccessible_tool + create_inaccessible_tool, + get_tool_shape ) -from ideal_learning_env.action_service import ActionService, TeleportConfig +from ideal_learning_env.action_service import ActionService from ideal_learning_env.actions_component import StepBeginEnd from ideal_learning_env.agent_service import ( AgentActionConfig, AgentConfig, AgentMovementConfig ) -from ideal_learning_env.goal_services import GoalConfig, GoalServices -from ideal_learning_env.interactable_object_service import ( + +from .components import ILEComponent +from .decorators import ile_config_setter +from .defs import ( + ILEDelayException, + ILEException, + RandomizableBool, + RandomizableString, + choose_random, + find_bounds, + return_list +) +from .goal_services import GoalConfig, GoalServices +from .interactable_object_service import ( InteractableObjectConfig, KeywordLocationConfig, + ToolConfig, create_user_configured_interactable_object ) -from ideal_learning_env.numerics import ( +from .numerics import ( MinMaxFloat, - MinMaxInt, RandomizableFloat, RandomizableInt, RandomizableVectorFloat3d, VectorFloatConfig, VectorIntConfig ) -from ideal_learning_env.object_services import ( +from .object_services import ( InstanceDefinitionLocationTuple, KeywordLocation, ObjectRepository, RelativePositionConfig ) -from ideal_learning_env.validators import ( - ValidateNumber, - ValidateOptions, - ValidateOr -) - -from .components import ILEComponent -from .decorators import ile_config_setter -from .defs import ( - ILEDelayException, - ILEException, - RandomizableBool, - RandomizableString, - choose_random, - find_bounds, - return_list -) from .structural_object_service import ( DOOR_MATERIAL_RESTRICTIONS, FeatureCreationService, @@ -83,9 +97,9 @@ PartitionFloorConfig, StructuralDoorConfig, StructuralPlacerConfig, - StructuralPlatformConfig, - ToolConfig + StructuralPlatformConfig ) +from .validators import ValidateNumber, ValidateOptions, ValidateOr logger = logging.getLogger(__name__) @@ -106,26 +120,11 @@ MIN_LAVA_WIDTH = 2 MAX_LAVA_WIDTH = 6 -MIN_LAVA_ISLAND_LONG_ROOM_DIMENSION_LENGTH = 13 -MIN_LAVA_ISLAND_SHORT_ROOM_DIMENSION_LENGTH = 7 - # Min lava with island width should be 5 MAX_LAVA_WITH_ISLAND_WIDTH = 9 MIN_LAVA_WITH_ISLAND_WIDTH_HOOKED_TOOL = 3 MAX_LAVA_WITH_ISLAND_WIDTH_HOOKED_TOOL = 7 -HOOKED_TOOL_BUFFER = 2 -MAX_TOOL_LENGTH = 9 - -TOOL_RECTANGULAR = 'rectangular' -TOOL_HOOKED = 'hooked' -TOOL_SMALL = 'small' -TOOL_BROKEN = 'broken' -TOOL_INACCESSIBLE = 'inaccessible' - -TOOL_LENGTH_TOO_SHORT = 1 -MIN_TOOL_CHOICE_X_DIMENSION = 20 - IMITATION_TASK_CONTAINER_SCALE = 1 IMITATION_TASK_TARGET_SEPARATION = 0.6 IMITATION_AGENT_START_X = 0.3 @@ -192,7 +191,8 @@ class LavaTargetToolConfig(): the lava or the island. If the `tool_type` is inaccessible the performer will randomly start on the side of the room where the target is where they cannot access the tool. Default: False - - `tool_rotation` (int, or list of ints, or [MinMaxInt](#MinMaxInt): + - `tool_rotation` (float, or list of floats, or + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Angle that tool should be rotated out of alignment with target. This option cannot be used with `guide_rails`. Default: 0 - `distance_between_performer_and_tool` (float, or list of floats, @@ -200,13 +200,13 @@ class LavaTargetToolConfig(): tool at start. The performer will be at random point around a rectangular perimeter surrounding the tool. This option cannot be used with `random_performer_position`. Default: None - - `tool_offset_backward_from_lava` ( - [RandomizableFloat](#RandomizableFloat)): The vertical offset of tool - either away from the lava pool. Must be greater than or equal to 0 + - `tool_offset_backward_from_lava` (float, or [MinMaxFloat](#MinMaxFloat) + dict, or list of floats and/or MinMaxFloat dicts): The vertical offset of + tool either away from the lava pool. Must be greater than or equal to 0 Default: 0 - - `tool_horizontal_offset` ([RandomizableFloat](#RandomizableFloat)): - The horizontal offset of tool either - left or right from being aligned with the target. If `tool_type` is + - `tool_horizontal_offset` (float, or [MinMaxFloat](#MinMaxFloat) dict, or + list of floats and/or MinMaxFloat dicts): The horizontal offset of tool + either left or right from being aligned with the target. If `tool_type` is inaccessible this has alternate behavior. See `inaccessible_tool_blocking_wall_horizontal_offset` for description. Default: 0 @@ -242,39 +242,27 @@ class LavaTargetToolConfig(): Default: `rectangular` """ - island_size: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - front_lava_width: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - rear_lava_width: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - left_lava_width: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - right_lava_width: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - guide_rails: Union[bool, List[bool]] = False - tool_rotation: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = 0 - random_performer_position: Union[bool, List[bool]] = False - random_target_position: Union[bool, List[bool]] = False - distance_between_performer_and_tool: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None + island_size: RandomizableInt = None + front_lava_width: RandomizableInt = None + rear_lava_width: RandomizableInt = None + left_lava_width: RandomizableInt = None + right_lava_width: RandomizableInt = None + guide_rails: RandomizableBool = False + tool_rotation: RandomizableFloat = 0 + random_performer_position: RandomizableBool = False + random_target_position: RandomizableBool = False + distance_between_performer_and_tool: RandomizableFloat = None tool_offset_backward_from_lava: RandomizableFloat = 0 tool_horizontal_offset: RandomizableFloat = 0 inaccessible_tool_blocking_wall_horizontal_offset: RandomizableFloat = None - tool_type: RandomizableString = TOOL_RECTANGULAR - - -@dataclass -class LavaIslandSizes(): - # internal class for storing lava widths and island sizes - island_size: int = 0 - front: int = 0 - rear: int = 0 - left: int = 0 - right: int = 0 + tool_type: RandomizableString = TOOL_TYPES.RECT @dataclass class InaccessibleToolProperties(): # internal class for storing inaccessible tool properties - tool: Dict[str, Any] = None - wall: Dict[str, Any] = None + tool: SceneObject = None + wall: SceneObject = None short_direction: str = None blocking_wall_pos_cutoff: float = None room_wall_pos_cutoff: float = None @@ -325,20 +313,16 @@ class TripleDoorConfig(): - `wall_material` (string, or list of strings): The material or material type for the wall. """ - start_drop_step: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = None - add_lips: Union[bool, List[bool]] = True - add_freeze: Union[bool, List[bool]] = True - restrict_open_doors: Union[bool, List[bool]] = True - door_material: Union[str, List[str]] = None - wall_material: Union[str, List[str]] = None - add_extension: Union[bool, List[bool]] = False - extension_length: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None - extension_position: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None - bigger_far_end: Union[bool, List[bool]] = False + start_drop_step: RandomizableInt = None + add_lips: RandomizableBool = True + add_freeze: RandomizableBool = True + restrict_open_doors: RandomizableBool = True + door_material: RandomizableString = None + wall_material: RandomizableString = None + add_extension: RandomizableBool = False + extension_length: RandomizableFloat = None + extension_position: RandomizableFloat = None + bigger_far_end: RandomizableBool = False @dataclass @@ -362,23 +346,28 @@ class DoubleDoorConfig(): - `door_material` (string, or list of strings): The material or material type for the doors. - `occluder_wall_position_z` (float, or list of floats, or - [MinMaxFloat](#MinMaxFloat) dict: Where the occluder wall will cross the - z-axis in the room. `performer_distance_from_occluder` will override this + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): + Where the occluder wall will cross the + z-axis in the room. `occluder_distance_from_performer` will override this value. Default: 0 (middle of the room) - `occluder_distance_from_performer` (float, or list of floats, or - [MinMaxFloat](#MinMaxFloat) dict: If there is a platform the, the performer + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): + If there is a platform the, the performer will start on top of the platform and the occluder wall will be placed this distance (`occluder_distance_from_performer`) from the performer. Must be greater than 1 or null Default: 6.5 - `platform_height` (float, or list of floats, or - [MinMaxFloat](#MinMaxFloat) dict: The height (y scale) of the platform + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): + The height (y scale) of the platform Default: 1.5 - `platform_length` (float, or list of floats, or - [MinMaxFloat](#MinMaxFloat) dict: The lenth (z scale) of the platform + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): + The lenth (z scale) of the platform Default: 1 - `platform_width` (float, or list of floats, or - [MinMaxFloat](#MinMaxFloat) dict: The width (z scale) of the platform + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): + The width (z scale) of the platform Default: 1 - `start_drop_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): Step number to start dropping the bisecting @@ -419,7 +408,7 @@ class AgentTargetConfig(): agent: Union[AgentConfig, List[AgentConfig]] = None agent_position: RandomizableVectorFloat3d = None movement_bounds: List[RandomizableVectorFloat3d] = None - movement: Union[bool, List[bool]] = None + movement: RandomizableBool = None @dataclass @@ -431,8 +420,11 @@ class BisectingPlatformConfig(): to choose a side to drop off of the platform, but this can be disabled. - `has_blocking_wall` (bool): Enables the blocking wall so that the performer has to stop and choose a side of the room. Default: True - - `has_long_blocking_wall` (bool): Enables the long blocking wall used in - Spatial Reorientation tasks. Overrides `has_blocking_wall`. Default: False + - `has_double_blocking_wall` (bool): Enables the double blocking wall used + in Spatial Reorientation tasks. Overrides both `has_blocking_wall` and + `has_long_blocking_wall`. Default: False + - `has_long_blocking_wall` (bool): Enables the extra-long blocking wall. + Overrides `has_blocking_wall`. Default: False - `is_short` (bool): Makes the platform short (a Y scale of 0.5 rather than 1). Default: False - `is_thin` (bool): Makes the platform thin (an X scale of 0.5 rather @@ -441,8 +433,12 @@ class BisectingPlatformConfig(): dict, or list of StructuralPlatformConfig dicts): Configurations to generate other platforms that may intersect with the bisecting platform. Default: No other platforms + - `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): The starting Z position for the + performer agent. Default: 0.5 away from the back wall """ has_blocking_wall: bool = True + has_double_blocking_wall: bool = False has_long_blocking_wall: bool = False is_short: bool = False is_thin: bool = False @@ -450,25 +446,7 @@ class BisectingPlatformConfig(): StructuralPlatformConfig, List[StructuralPlatformConfig] ] = None - - -class ImitationTriggerOrder(str, Enum): - LEFT = "left" - MIDDLE = "middle" - RIGHT = "right" - LEFT_MIDDLE = "left_middle" - LEFT_RIGHT = "left_right" - MIDDLE_LEFT = "middle_left" - MIDDLE_RIGHT = "middle_right" - RIGHT_MIDDLE = "right_middle" - RIGHT_LEFT = "right_left" - - -class ImitationKidnapOptions(str, Enum): - AGENT_ONLY = "agent_only" - CONTAINERS = "containers" - CONTAINERS_ROTATE = "containers_rotate" - PERFORMER = "performer" + position_z: RandomizableFloat = None @dataclass @@ -490,21 +468,36 @@ class ImitationTaskConfig(): to and the performer is kidnapped. Options are: 1) agent_only: The imitation agent teleports away from the containers but in view. Nothing else is teleported. - 2) containers: The containers are teleported but still in view. The - containers are still aligned with their start rotation. The imitation agent - is teleported away from the containers but in view. - 3) containers_rotate: The containers are teleported but still in view. The - containers are rotated 90 degrees to be perpendicular to how they started. + 2) containers: The containers are teleported anr rotated but still in view. The imitation agent is teleported away from the containers but in view. 4) performer: The performer is teleported to a random part of the room but looks at the center of the room where the containers still are. The imitation agent is teleported away from the containers but in view. - + Default: random + - `relative_container_rotation`: (int, or list of int): Dictates + what degree of rotation change the containers will rotate relative to + their clockwise starting rotation. For example, if the they are facing + 270 (starting on the right side) and container_rotation of 45 will make + the final rotation 315. Examples of what rotations containers cannot + rotate: + 1) 360 and 180 for both left and right because the containers will + face identical or opposite from their start. + 2) 90 on right side, 270 on left which will make the containers face + directly away from the performer. + Direction containers can rotate for right side: [45, 135, 225, 270, 315] + Direction containers can rotate for left side: [45, 90, 135, 225, 315] + Default: random + - `global_container_rotation`: (int, or list of int): Dictates + what degree of rotation change the containers will rotate on based on + the absolute global rotation. Options are: [45, 135, 180, 225, 315]. + Will override `relative_container_rotation` Default: random """ - trigger_order: Union[str, List[str]] = None - containers_on_right_side: Union[bool, List[bool]] = None - kidnap_option: Union[str, List[str]] = None + trigger_order: RandomizableString = None + containers_on_right_side: RandomizableBool = None + kidnap_option: RandomizableString = None + relative_container_rotation: RandomizableInt = None + global_container_rotation: RandomizableInt = None @dataclass @@ -565,8 +558,8 @@ class TurntablesAgentNonAgentConfig(): """ agent_label: str non_agent_label: str - turntable_labels: Union[str, List[str]] - direction_labels: Union[str, List[str]] + turntable_labels: RandomizableString + direction_labels: RandomizableString class ShortcutComponent(ILEComponent): @@ -960,8 +953,7 @@ def get_shortcut_triple_door_choice( ) )) @ile_config_setter(validator=ValidateNumber( - props=['occluder_distance_from_performer'], min_value=1, - null_ok=True)) + props=['occluder_distance_from_performer'], min_value=1, null_ok=True)) @ile_config_setter(validator=ValidateNumber( props=['platform_height'], min_value=1)) @ile_config_setter(validator=ValidateNumber( @@ -969,8 +961,7 @@ def get_shortcut_triple_door_choice( @ile_config_setter(validator=ValidateNumber( props=['platform_width'], min_value=1)) @ile_config_setter(validator=ValidateNumber( - props=['start_drop_step'], min_value=0, - null_ok=True)) + props=['start_drop_step'], min_value=0, null_ok=True)) @ile_config_setter(validator=ValidateOptions( props=['wall_material'], options=(materials.ALL_UNRESTRICTED_MATERIAL_LISTS_AND_STRINGS) @@ -1063,11 +1054,10 @@ def set_turntables_with_agent_and_non_agent(self, data: Any) -> None: ImitationTriggerOrder.RIGHT_LEFT) )) @ile_config_setter(validator=ValidateOptions( - props=['kidnap_options'], + props=['kidnap_option'], options=( ImitationKidnapOptions.AGENT_ONLY, ImitationKidnapOptions.CONTAINERS, - ImitationKidnapOptions.CONTAINERS_ROTATE, ImitationKidnapOptions.PERFORMER) )) def set_shortcut_imitation_task(self, data: Any) -> None: @@ -1123,15 +1113,15 @@ def get_shortcut_seeing_leads_to_knowing( return config # Override - def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def update_ile_scene(self, scene: Scene) -> Scene: logger.info('Configuring shortcut options for the scene...') room_dim = scene.room_dimensions scene = self._add_bisecting_platform(scene, room_dim) scene = self._add_triple_door_shortcut(scene, room_dim) scene = self._add_double_door_shortcut(scene, room_dim) - scene = self._delay_performer_placement(scene, room_dim) scene = self._add_lava_shortcut(scene, room_dim) + scene = self._delay_performer_placement(scene, room_dim) scene = self._add_tool_lava_goal(scene, room_dim) scene = self._add_agent_holds_target(scene) scene = self._add_imitation_task(scene) @@ -1157,13 +1147,15 @@ def _add_bisecting_platform(self, scene: Scene, room_dim: Vector3d): room_dim, blocking_wall=config.has_blocking_wall, platform_height=(0.5 if config.is_short else 1), + double_blocking_wall=config.has_double_blocking_wall, long_blocking_wall=config.has_long_blocking_wall, is_thin=config.is_thin, other_platforms=( config.other_platforms if isinstance(config.other_platforms, list) else [config.other_platforms] - ) if config.other_platforms else [] + ) if config.other_platforms else [], + position_z=choose_random(config.position_z, float) ) return scene @@ -1174,9 +1166,11 @@ def _do_add_bisecting_platform( room_dim: Vector3d, blocking_wall: bool = False, platform_height: float = 1, + double_blocking_wall: bool = False, long_blocking_wall: bool = False, is_thin: bool = False, - other_platforms: List[StructuralPlatformConfig] = None + other_platforms: List[StructuralPlatformConfig] = None, + position_z: float = None ) -> None: # Second platform is the wall to prevent performer from moving too @@ -1185,18 +1179,23 @@ def _do_add_bisecting_platform( # platform. bounds = find_bounds(scene) - performer_z = -room_dim.z / 2.0 + half_room_z = room_dim.z / 2.0 + performer_z = -half_room_z + 0.5 + if position_z is not None and -half_room_z < position_z < half_room_z: + performer_z = position_z scale_x = 0.5 if is_thin else 1 platform_config = StructuralPlatformConfig( num=1, position=VectorFloatConfig(0, 0, 0), rotation_y=0, scale=VectorFloatConfig(scale_x, platform_height, room_dim.z)) # Create the blocking wall. - position_z = 0 if long_blocking_wall else (performer_z + 1.5) + wall_position_z = 0 if long_blocking_wall else (performer_z + 1) + if double_blocking_wall: + wall_position_z = -half_room_z + 3 blocking_wall_config = StructuralPlatformConfig( num=1, material=materials.BLACK.material, - position=VectorFloatConfig(0, 0, position_z), + position=VectorFloatConfig(0, 0, wall_position_z), rotation_y=0, scale=VectorFloatConfig( scale_x - 0.01, @@ -1206,7 +1205,7 @@ def _do_add_bisecting_platform( ) scene.set_performer_start_position( - x=0, y=platform_config.scale.y, z=(performer_z) + 0.5) + x=0, y=platform_config.scale.y, z=performer_z) # Start looking down if the room is short. rotation_x = (10 if scene.room_dimensions.z < 10 else 0) scene.set_performer_start_rotation(rotation_x, 0) @@ -1220,13 +1219,23 @@ def _do_add_bisecting_platform( bounds.copy() )[0] - if blocking_wall or long_blocking_wall: - FeatureCreationService.create_feature( + if blocking_wall or long_blocking_wall or double_blocking_wall: + wall = FeatureCreationService.create_feature( scene, FeatureTypes.PLATFORMS, blocking_wall_config, bounds.copy() - ) + )[0] + wall['id'] = wall['id'].replace('platform', 'blocking_wall') + if double_blocking_wall: + blocking_wall_config.position.z = -blocking_wall_config.position.z + wall = FeatureCreationService.create_feature( + scene, + FeatureTypes.PLATFORMS, + blocking_wall_config, + bounds.copy() + )[0] + wall['id'] = wall['id'].replace('platform', 'blocking_wall') for other_config in (other_platforms or []): if not other_config.material: @@ -1257,7 +1266,7 @@ def _add_double_door_shortcut(self, scene, room_dim): def _add_platform_extension( self, scene: Scene, - platform: Dict[str, Any], + platform: SceneObject, extension_length: float, extension_position: float, bounds: List[ObjectBounds] @@ -1411,7 +1420,7 @@ def _do_add_triple_door_shortcut( self._add_door_drops(scene, config.start_drop_step, add_y, door_index) if config.add_freeze: - goal = scene.goal or {} + goal = scene.goal or Goal() step_end = (int)(config.start_drop_step + add_y * 4) freezes = [StepBeginEnd(1, step_end)] ActionService.add_freezes(goal, freezes) @@ -1523,7 +1532,7 @@ def _do_add_double_door_shortcut( self._add_door_drops(scene, config.start_drop_step, add_y, door_index) if config.add_freeze: - goal = scene.goal or {} + goal = scene.goal or Goal() step_end = (int)(config.start_drop_step + add_y * 4) freezes = [StepBeginEnd(1, step_end)] ActionService.add_freezes(goal, freezes) @@ -1673,8 +1682,9 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): num_prev_lava = len(scene.lava) if config.guide_rails and ( - config.tool_rotation or config.tool_type == TOOL_INACCESSIBLE or - config.tool_type == TOOL_BROKEN): + config.tool_rotation or + config.tool_type == TOOL_TYPES.INACCESSIBLE or + config.tool_type == TOOL_TYPES.BROKEN): raise ILEException( "Unable to use 'guide_rails' and 'tool_rotation' or " "tool_types: 'inaccessible' or 'broken' from " @@ -1686,7 +1696,7 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): "and random_performer_position" ) if (config.distance_between_performer_and_tool is not None and - config.tool_type == TOOL_INACCESSIBLE): + config.tool_type == TOOL_TYPES.INACCESSIBLE): raise ILEException( "Cannot have distance_between_performer_and_tool " "or random_performer_position with an " @@ -1746,10 +1756,10 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): sizes.front + sizes.island_size) - if config.tool_type == TOOL_HOOKED: + if config.tool_type == TOOL_TYPES.HOOKED: tool_length = random.randint( tool_length + HOOKED_TOOL_BUFFER, MAX_TOOL_LENGTH) - if config.tool_type == TOOL_SMALL: + if config.tool_type == TOOL_TYPES.SMALL: tool_length = 1 # if size 13, edge is whole tiles at 6, buffer should be 6, @@ -1757,11 +1767,12 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): # buffer here not used for hooked tools far_island_buffer = ( - 1 if config.tool_type != TOOL_HOOKED else 0) + 1 if config.tool_type != TOOL_TYPES.HOOKED else 0) # additional buffer of lava needed for hooked tool scenes # with even sized long dimension rear_lava_buffer = ( - 1 if config.tool_type == TOOL_HOOKED and long_length % + 1 if config.tool_type == + TOOL_TYPES.HOOKED and long_length % 2 == 0 else 0) long_buffer_coord = math.floor(long_length / 2.0 - 0.5) @@ -1816,7 +1827,7 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): short_right_island_coord) # Add block tool - tool_shape = self._get_tool_shape( + tool_shape = get_tool_shape( tool_length, config.tool_type) tool = self._add_tool( scene, @@ -1854,7 +1865,7 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): target = scene.objects[-1] if config.random_performer_position: - if config.tool_type == TOOL_INACCESSIBLE: + if config.tool_type == TOOL_TYPES.INACCESSIBLE: self._place_performer_in_inaccessible_zone( scene, short_key, long_key, long_near_island_coord, @@ -1867,14 +1878,14 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): long_key, short_key, long_near_island_coord, long_far_island_coord, sizes) elif config.distance_between_performer_and_tool is not None: - if config.tool_type == TOOL_BROKEN: + if config.tool_type == TOOL_TYPES.BROKEN: broken_tool = random.choice(tool) (x, z) = geometry.get_position_distance_away_from_obj( scene.room_dimensions, broken_tool, config.distance_between_performer_and_tool, bounds + [island_bounds]) scene.set_performer_start_position(x=x, y=None, z=z) - elif config.tool_type == TOOL_HOOKED: + elif config.tool_type == TOOL_TYPES.HOOKED: (x, z) = geometry.get_position_distance_away_from_hooked_tool( # noqa scene.room_dimensions, tool, config.distance_between_performer_and_tool, @@ -1897,7 +1908,7 @@ def _add_tool_lava_goal(self, scene: Scene, room_dim: Vector3d): scene.objects = scene.objects[:num_prev_objs] scene.lava = scene.lava[:num_prev_lava] if self.remove_target_on_error: - meta = (scene.goal or {}).get('metadata', {}) + meta = (scene.goal or Goal()).metadata or {} if 'target' in meta: meta.pop('target') config = self.get_shortcut_lava_target_tool() @@ -1969,7 +1980,7 @@ def _add_lava_tool_choice_goal(self, scene: Scene, room_dim: Vector3d): True, "front_lava_width", "rear_lava_width", - TOOL_RECTANGULAR) + TOOL_TYPES.RECT) (_, left, right) = self._get_island_and_lava_size_by_dimension( short_length, island_size, @@ -1978,7 +1989,7 @@ def _add_lava_tool_choice_goal(self, scene: Scene, room_dim: Vector3d): False, "left_lava_width", "right_lava_width", - TOOL_RECTANGULAR) + TOOL_TYPES.RECT) sizes = LavaIslandSizes(island_size, front, rear, left, right) @@ -2064,7 +2075,7 @@ def _add_lava_tool_choice_goal(self, scene: Scene, room_dim: Vector3d): ) # Add forced rotation at the beginning of tool choice scenes - scene.goal['action_list'] = ([['RotateRight']] * 36) + scene.goal.action_list = ([['RotateRight']] * 36) return scene except Exception as e: @@ -2078,7 +2089,7 @@ def _add_lava_tool_choice_goal(self, scene: Scene, room_dim: Vector3d): scene.objects = scene.objects[:num_prev_objs] scene.lava = scene.lava[:num_prev_lava] if self.remove_target_on_error: - meta = (scene.goal or {}).get('metadata', {}) + meta = (scene.goal or Goal()).metadata or {} if 'target' in meta: meta.pop('target') config = self.get_shortcut_tool_choice() @@ -2098,7 +2109,7 @@ def create_lava_on_one_side(self, scene, bounds, valid_tool=True): # Find or create the target and position it on the island - if(valid_tool): + if (valid_tool): self._add_target_to_lava_island( scene, long_key, @@ -2151,11 +2162,11 @@ def create_lava_on_one_side(self, scene, bounds, short_right_island_coord) # Add block tool - if(valid_tool): - tool_shape = self._get_tool_shape(tool_length, TOOL_RECTANGULAR) + if (valid_tool): + tool_shape = get_tool_shape(tool_length, TOOL_TYPES.RECT) else: # if improbable_option set to no_tool, nothing left to do, return - if(config.improbable_option == ImprobableToolOption.NO_TOOL): + if (config.improbable_option == ImprobableToolOption.NO_TOOL): return tool_shape = self._get_too_small_tool() @@ -2171,7 +2182,7 @@ def create_lava_on_one_side(self, scene, bounds, sizes.front, tool_length, long_near_island_coord, - TOOL_RECTANGULAR, + TOOL_TYPES.RECT, tool_shape, False) @@ -2207,7 +2218,7 @@ def _add_guide_rails(self, scene, long_key, rot = tool['shows'][0]['rotation']['y'] end_guide_rail = copy.deepcopy(target['shows'][0]['position']) center = VectorIntConfig(y=tool_pos['y']) - if TOOL_HOOKED in tool['type']: + if TOOL_TYPES.HOOKED in tool['type']: max_long_dim = (scene.room_dimensions.z if short_key == 'x' else scene.room_dimensions.x) / 2.0 end_guide_rail[short_key] = tool_pos[short_key] @@ -2240,7 +2251,7 @@ def _get_guide_rail_material_tuple( (scene.floor_material or ""), (scene.wall_material or "")] invalid_mats = set(invalid_mats) - room_mats = scene.room_materials or {} + room_mats = vars(scene.room_materials) if scene.room_materials else {} for mat in room_mats.values(): invalid_mats.add(mat) valid = False @@ -2387,7 +2398,7 @@ def _add_tool(self, scene, bounds, island_size, long_key, short_key, front_lava_width - tool_length / 2.0 - 0.5) setattr(tool_pos, short_key, short_coord) - if tool_type == TOOL_INACCESSIBLE: + if tool_type == TOOL_TYPES.INACCESSIBLE: if not blocking_wall_horizontal_offset: # If no value is setup use the island size to minimum range # as a default offset @@ -2397,7 +2408,7 @@ def _add_tool(self, scene, bounds, island_size, long_key, short_key, random.choice([negative, positive]) tool_horizontal_offset = abs(tool_horizontal_offset) * \ (-1 if blocking_wall_horizontal_offset < 0 else 1) - material = random.choice(materials._CUSTOM_WOOD_MATERIALS) + material = random.choice(materials.CUSTOM_WOOD_MATERIALS) (tool, wall, width_direction, blocking_wall_pos_cutoff, room_wall_pos_cutoff) = create_inaccessible_tool( tool_type=tool_shape, @@ -2453,7 +2464,7 @@ def _add_tool(self, scene, bounds, island_size, long_key, short_key, # Direction left is negative when long direction is on z axis # But positive when long direction is x axis and tool is facing right direction_left = 1 if long_key == 'z' else -1 - if tool_type == TOOL_BROKEN: + if tool_type == TOOL_TYPES.BROKEN: max_tool_pos = ( long_near_island_coord - front_lava_width - 1 - tool_offset_backward_from_lava) @@ -2493,7 +2504,7 @@ def _add_tool(self, scene, bounds, island_size, long_key, short_key, tool['debug']['length'] = 1 return tools - if(tool_type == TOOL_HOOKED): + if (tool_type == TOOL_TYPES.HOOKED): bounds_to_check = [] tool_width = LARGE_BLOCK_TOOLS_TO_DIMENSIONS[tool_shape][0] tool_buffer = 1.0 - (tool_width / 3.0) @@ -2521,7 +2532,7 @@ def _add_tool(self, scene, bounds, island_size, long_key, short_key, # helps with distance_between_performer_and_tool calculations for # hooked bounding box shapes tool = scene.objects[-1] - if tool_type == TOOL_HOOKED: + if tool_type == TOOL_TYPES.HOOKED: tool['debug']['tool_thickness'] = tool_width / 3.0 tool['debug']['length'] = tool_length else: @@ -2578,7 +2589,7 @@ def _get_island_and_lava_size_by_dimension( min_lava_width = ( MIN_LAVA_WIDTH_HOOKED_TOOL - if tool_type == TOOL_HOOKED else MIN_LAVA_WIDTH) + if tool_type == TOOL_TYPES.HOOKED else MIN_LAVA_WIDTH) buffer = 1.5 if long_side else \ (3 if dimension_length % 2 == 0 else 1.5) total_max_width_in_dimension = math.floor( @@ -2587,7 +2598,7 @@ def _get_island_and_lava_size_by_dimension( MAX_LAVA_WITH_ISLAND_WIDTH, total_max_width_in_dimension) - if long_side and tool_type == TOOL_HOOKED: + if long_side and tool_type == TOOL_TYPES.HOOKED: if (not side_one and not side_two): total_max_width_in_dimension = ( MIN_LAVA_WITH_ISLAND_WIDTH_HOOKED_TOOL if ( @@ -2606,14 +2617,15 @@ def _get_island_and_lava_size_by_dimension( f"Island size ({island_size}) is larger than " f"max island size ({max_island_size})") else: - island_size = (1 if tool_type == TOOL_HOOKED else random.randint( - MIN_LAVA_ISLAND_SIZE, max_island_size)) + island_size = (1 if tool_type == TOOL_TYPES.HOOKED + else random.randint( + MIN_LAVA_ISLAND_SIZE, max_island_size)) total_accumulated_size += island_size # for the left and right lava sides in hooked tool scenes, # make sure lava extends to the wall - if(not long_side and tool_type == TOOL_HOOKED): + if (not long_side and tool_type == TOOL_TYPES.HOOKED): side_size = math.ceil((dimension_length - island_size) / 2.0) return island_size, side_size, side_size # Side 1 @@ -2621,7 +2633,7 @@ def _get_island_and_lava_size_by_dimension( max_side_one_width = ( total_max_width_in_dimension - total_accumulated_size - side_two) - if(tool_type == TOOL_HOOKED and (side_two or side_one)): + if (tool_type == TOOL_TYPES.HOOKED and (side_two or side_one)): max_side_one_width = min( max_side_one_width, MAX_LAVA_WIDTH_HOOKED_TOOL) @@ -2647,7 +2659,7 @@ def _get_island_and_lava_size_by_dimension( max_side_two_width = total_max_width_in_dimension - \ total_accumulated_size - if(tool_type == TOOL_HOOKED and (side_two or side_one)): + if (tool_type == TOOL_TYPES.HOOKED and (side_two or side_one)): max_side_two_width = min( max_side_two_width, MAX_LAVA_WIDTH_HOOKED_TOOL) @@ -2715,34 +2727,12 @@ def _add_lava_around_island( # sourcery recommends the 'from e' raise e from e - def _get_tool_shape(self, tool_length, tool_type): - tools = [] - tool_type = \ - TOOL_RECTANGULAR if tool_type == TOOL_INACCESSIBLE else tool_type - if tool_type == TOOL_SMALL or tool_type == TOOL_BROKEN: - tools = [ - tool - for tool, (_, length) in - LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items() - if "rect" in tool and length == 1 - ] - return random.choice(tools) - tools = [ - tool - for tool, (_, length) in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items() - if (length == tool_length and - ((TOOL_HOOKED in tool and tool_type == TOOL_HOOKED) or - ("rect" in tool and length != 1 and - tool_type == TOOL_RECTANGULAR))) - ] - return random.choice(tools) - # Using only tools with a length of 1 for now def _get_too_small_tool(self): tools = [ tool for tool, (_, length) in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items() - if tool.startswith('tool_rect') and length == TOOL_LENGTH_TOO_SHORT + if tool.startswith('tool_rect') and length == 1 ] return random.choice(tools) @@ -2773,7 +2763,7 @@ def _add_agent_holds_target(self, scene: Scene): return scene - def _get_or_add_soccer_ball_target(self, scene: Scene) -> Dict[str, Any]: + def _get_or_add_soccer_ball_target(self, scene: Scene) -> SceneObject: """Returns the existing target, or creates and returns a new soccer ball target with scale 1/1/1.""" targets = scene.get_targets() @@ -2846,7 +2836,8 @@ def _update_turntables_with_agent_and_non_agent( for turntable in all_turntables: # If the agent is above this turntable, then it should NOT rotate. if geometry.is_above(agent, turntable): - del turntable['rotates'] + if 'rotates' in turntable: + del turntable['rotates'] continue # If the non-agent is above this turntable, then it should rotate. if geometry.is_above(non_agent, turntable): @@ -2865,7 +2856,9 @@ def _update_turntables_with_agent_and_non_agent( 'mirroredRotation', non_agent['shows'][0]['rotation']['y'] ) - rotation_amount = (ending_rotation - starting_rotation) % 360 + rotation_amount = ( + round(ending_rotation - starting_rotation, 2) % 360 + ) # Rotate clockwise by default. clockwise = True if rotation_amount > 180: @@ -2893,523 +2886,6 @@ def _update_turntables_with_agent_and_non_agent( self._delayed_turntables_with_agent_and_non_agent_reason = None return scene - def _setup_containers_for_imitation_task( - self, scene: Scene, - config: ImitationTaskConfig) -> List: - containers = [] - left_container_start_pos_z = \ - 1 if config.containers_on_right_side else -1 - separation_between_containers = \ - -1 if config.containers_on_right_side else 1 - # Positive to negative z axis, positive is left, negative is right - container_range = range(left_container_start_pos_z, - -left_container_start_pos_z * 2, - separation_between_containers) - container_colors_used = [] - material_choices = materials._CUSTOM_WOOD_MATERIALS - for container_index in container_range: - pos_z = container_index - rotation_y = -90 if config.containers_on_right_side else 90 - for _ in range(MAX_TRIES): - try_new_color = False - material = random.choice(material_choices) - colors = material[1] - for color in colors: - if color in container_colors_used: - try_new_color = True - break - if try_new_color: - continue - for color in colors: - container_colors_used.append(color) - break - container_template = InteractableObjectConfig( - position=Vector3d( - x=0.9 if config.containers_on_right_side else -0.9, - y=0, - z=pos_z), - rotation=Vector3d(y=rotation_y), - scale=Vector3d( - x=IMITATION_TASK_CONTAINER_SCALE, - y=IMITATION_TASK_CONTAINER_SCALE, - z=IMITATION_TASK_CONTAINER_SCALE), - shape='chest_1', - num=1, - material=material[0] - ) - FeatureCreationService.create_feature( - scene, FeatureTypes.INTERACTABLE, - container_template, find_bounds(scene)) - container_index = scene.objects[-1] - containers.append(container_index) - return containers, separation_between_containers - - def _setup_target_and_placer_imitation_task(self, scene: Scene, - config: ImitationTaskConfig, - containers): - self.remove_target_on_error = True - goal_template = GoalConfig( - category=tags.SCENE.IMITATION, - target=InteractableObjectConfig( - position=Vector3d(x=0, y=0, z=0), - shape='soccer_ball', - scale=1)) - GoalServices.attempt_to_add_goal(scene, goal_template) - - target = scene.objects[-1] - target['shows'][0]['position']['y'] = \ - scene.room_dimensions.y + target['debug']['dimensions']['y'] * 2 - placer = create_placer( - target['shows'][0]['position'], target['debug']['dimensions'], - target['debug']['positionY'], 0, 0, scene.room_dimensions.y - ) - placer['triggeredBy'] = True - scene.objects.append(placer) - - target['triggeredBy'] = True - target['kinematic'] = True - target['moves'] = [placer['moves'][0]] - target['togglePhysics'] = [ - {'stepBegin': placer['changeMaterials'][0]['stepBegin']}] - target['shows'][0]['position']['x'] = \ - containers[0]['shows'][0]['position']['x'] - placer['shows'][0]['position']['x'] = \ - target['shows'][0]['position']['x'] - - # position in front of the left containers if containers on left - # position in front of the right containers if containers on right - target_separation = IMITATION_TASK_TARGET_SEPARATION - container_to_put_in_front_of_index = \ - -1 if config.containers_on_right_side else 0 - target['shows'][0]['position']['z'] = ( - containers[container_to_put_in_front_of_index - ]['shows'][0]['position']['z'] - target_separation) - placer['shows'][0]['position']['z'] = \ - target['shows'][0]['position']['z'] - return target, placer - - def _setup_trigger_order_for_imitation_task(self, scene: Scene, - config: ImitationTaskConfig, - containers): - trigger_order_ids = [] - solo_options = ['left', 'middle', 'right'] - left_options = ['left_middle', 'left_right'] - middle_options = ['middle_left', 'middle_right'] - right_options = ['right_middle', 'right_left'] - containers_to_open_indexes = [] - if config.trigger_order in solo_options: - container_index = solo_options.index(config.trigger_order) - trigger_order_ids.append(containers[container_index]['id']) - containers_to_open_indexes.append(container_index) - elif config.trigger_order in left_options: - trigger_order_ids.append(containers[0]['id']) - container_index = left_options.index(config.trigger_order) - trigger_order_ids.append( - containers[1 if container_index == 0 else 2]['id']) - containers_to_open_indexes.append(0) - containers_to_open_indexes.append(1 if container_index == 0 else 2) - elif config.trigger_order in middle_options: - trigger_order_ids.append(containers[1]['id']) - container_index = middle_options.index(config.trigger_order) - trigger_order_ids.append( - containers[0 if container_index == 0 else 2]['id']) - containers_to_open_indexes.append(1) - containers_to_open_indexes.append(0 if container_index == 0 else 2) - elif config.trigger_order in right_options: - trigger_order_ids.append(containers[2]['id']) - container_index = right_options.index(config.trigger_order) - trigger_order_ids.append( - containers[1 if container_index == 0 else 0]['id']) - containers_to_open_indexes.append(2) - containers_to_open_indexes.append(1 if container_index == 0 else 0) - return trigger_order_ids, containers_to_open_indexes - - def _setup_agent_for_imitation_task( - self, scene: Scene, config: ImitationTaskConfig, containers, - containers_to_open_indexes): - """ - Agent Setup - 1. Enter in front of starting chest - 2. Walk to chest - 3. Open (if only open one chest then end here and face performer) - 4. Walk to other chest - 5. Rotate to face chest - 6. Open the chest - 7. Face performer - """ - step_begin_open_first_chest = 18 - step_end_open_first_chest = 28 - open_animation = "TPE_jump" - turn_left_animation = "TPM_turnL45" - turn_right_animation = "TPM_turnR45" - walk_animation = "TPM_walk" - - movement_points = [] - number_of_containers = 0 - start_turning_step = None - rotates = None - for container_index in containers_to_open_indexes: - movement_points.append( - Vector3d( - x=(IMITATION_AGENT_END_X if config.containers_on_right_side - else -IMITATION_AGENT_END_X), - y=0, - z=containers[container_index]['shows'][0]['position']['z'] - ) - ) - number_of_containers += 1 - if number_of_containers > 1: - """ - Example of chest on the right: - Rotate left because the agent walks toward the performer. - Start - | c opened - | c - Agent > c open this - - Performer - """ - containers_on_right_side_agent_moving_toward_performer = ( - config.containers_on_right_side and - container_index > containers_to_open_indexes[0]) - containers_on_left_side_agent_moving_away_from_performer = ( - not config.containers_on_right_side and - container_index > containers_to_open_indexes[0]) - # negative is left turn, positive is right turn - direction = ( - -1 if - containers_on_right_side_agent_moving_toward_performer or - containers_on_left_side_agent_moving_away_from_performer - else 1) - is_adjacent_container = ( - container_index == containers_to_open_indexes[0] + 1 or - container_index == containers_to_open_indexes[0] - 1) - # for some reason the left side needs one extra step - extra_step = ( - 1 if - containers_on_left_side_agent_moving_away_from_performer - else 0) - # With an origin point of the start container z position, - # 57 and 82 are the number of steps required to reach the - # adjacent or far container z position and be centered in - # front of it - start_turning_step = 57 + extra_step if \ - is_adjacent_container else 82 + extra_step - rotation_per_step = 9 * direction - rotates = { - "stepBegin": start_turning_step, - "stepEnd": start_turning_step + 10, - "vector": { - "x": 0, - "y": rotation_per_step, - "z": 0 - } - } - - # End position facing the performer - end_point_z = movement_points[-1].z - 0.15 - movement_points.append( - Vector3d( - x=(IMITATION_AGENT_END_X if config.containers_on_right_side - else -IMITATION_AGENT_END_X), - y=0, - z=end_point_z)) - movement = AgentMovementConfig( - animation=walk_animation, - step_begin=1, - points=movement_points, - repeat=False - ) - - # Animations - actions = [] - # The steps that each container is opened - open_steps = [] - first_open = AgentActionConfig( - step_begin=step_begin_open_first_chest, - step_end=step_end_open_first_chest, - is_loop_animation=False, - id=open_animation - ) - actions.append(first_open) - open_steps.append(step_begin_open_first_chest) - - # Check if we are opening more than one chest - # A turning animation is required to face the second chest - if start_turning_step is not None: - turn = AgentActionConfig( - step_begin=start_turning_step, - step_end=start_turning_step + 10, - is_loop_animation=False, - id=(turn_left_animation if rotates['vector']['y'] < 1 - else turn_right_animation) - ) - second_open = AgentActionConfig( - step_begin=start_turning_step + 10, - step_end=start_turning_step + 20, - is_loop_animation=False, - id=open_animation - ) - actions.append(turn) - actions.append(second_open) - open_steps.append(start_turning_step + 10) - - # Config the agent in front of the first chest to open - start_position = Vector3d( - x=(-IMITATION_AGENT_START_X if - config.containers_on_right_side else - IMITATION_AGENT_START_X), y=0, - z=(containers[containers_to_open_indexes[0]] - ['shows'][0]['position']['z'])) - rotation_y = 90 if config.containers_on_right_side else -90 - agentConfig = AgentConfig( - position=start_position, - rotation_y=rotation_y, - actions=actions, - movement=movement - ) - agent = FeatureCreationService.create_feature( - scene, FeatureTypes.AGENT, agentConfig, [])[0] - if rotates: - agent['rotates'] = [rotates] - - # Open containers with animation timing - i = 0 - for container_index in containers_to_open_indexes: - containers[container_index]['openClose'] = [{ - 'step': open_steps[i] + 4, - 'open': True - }] - i += 1 - - return agent, open_steps - - def _change_container_and_agent_positions_during_kidnap( - self, scene: Scene, config: ImitationTaskConfig, containers, - separation_between_containers, kidnap_step, target, - placer, agent): - # Need to slightly shift depending on the start side since - # the performer is offset to stay in the performers view - buffer_for_all_containers_to_fit = 2 - buffer_for_agent_to_stand_behind = 1 - rotate_90 = (random.choice([True, False]) if - config.kidnap_option - is None else config.kidnap_option == - ImitationKidnapOptions.CONTAINERS_ROTATE) - if not rotate_90: - start_x = round(random.uniform(-2.5, 2.5), 2) - if not config.containers_on_right_side: - start_z = round(random.uniform( - 0, - scene.room_dimensions.z / 2 - - buffer_for_all_containers_to_fit - - buffer_for_agent_to_stand_behind), 2) - else: - start_z = round(random.uniform( - 2, - scene.room_dimensions.z / 2 - - buffer_for_agent_to_stand_behind), 2) - else: - start_x = round(random.uniform(-2.5, 0.5), 2) - start_z = round(random.uniform( - 0, - scene.room_dimensions.z / - 2 - - buffer_for_agent_to_stand_behind), 2) - for container in containers: - container['shows'].append({ - 'stepBegin': kidnap_step, - 'position': { - 'x': start_x, - 'y': 0, - 'z': start_z - }, - 'rotation': { - 'y': ( - 180 if rotate_90 - else container['shows'][0]['rotation']['y']) - } - }) - if not rotate_90: - start_z += separation_between_containers - else: - start_x += abs(separation_between_containers) - - end_container = containers[ - -1 if config.containers_on_right_side else 0] - - # target and placer need to shift too - target_separation = IMITATION_TASK_TARGET_SEPARATION - if rotate_90: - target['shows'][1]['position']['x'] = ( - end_container['shows'][1]['position']['x'] + - (target_separation * - (1 if config.containers_on_right_side else -1))) - target['shows'][1]['position']['z'] = \ - end_container['shows'][1]['position']['z'] - else: - target['shows'][1]['position']['x'] = \ - end_container['shows'][1]['position']['x'] - target['shows'][1]['position']['z'] = \ - end_container['shows'][1]['position']['z'] - target_separation - placer['shows'][1]['position']['x'] = \ - target['shows'][1]['position']['x'] - placer['shows'][1]['position']['z'] = \ - target['shows'][1]['position']['z'] - - # Place the agent behind - end_container_pos = \ - containers[0 if config.containers_on_right_side - else -1]['shows'][1]['position']['z'] - separation = 1 - agent_z = end_container_pos + separation - agent_x = random.choice( - [containers[0]['shows'][1]['position']['x'] - separation, - containers[-1]['shows'][1]['position']['x'] + separation]) - agent['shows'].append({ - 'stepBegin': kidnap_step, - 'position': { - 'x': agent_x, - 'y': 0, - 'z': agent_z - }, - 'rotation': { - 'y': 180 - } - }) - - teleport_pos_x = scene.performer_start.position.x - teleport_pos_z = scene.performer_start.position.z - teleport_rot_y = scene.performer_start.rotation.y - ActionService.add_teleports( - scene.goal, [ - TeleportConfig( - step=kidnap_step, position_x=teleport_pos_x, - position_z=teleport_pos_z, rotation_y=teleport_rot_y)], - False) - - def _teleport_performer_for_imitation_task( - self, scene: Scene, agent, - kidnap_step): - # pick an x and z not in the center x of the room - # so the teleport is substantial - shift = 2.5 - x1 = round(random.uniform( - -scene.room_dimensions.x / 2 + geometry.PERFORMER_WIDTH, - -shift), 2) - x2 = round(random.uniform( - shift, scene.room_dimensions.x / 2 - geometry.PERFORMER_WIDTH), - 2) - teleport_pos_x = random.choice([x1, x2]) - z1 = round(random.uniform( - -scene.room_dimensions.z / 2 + geometry.PERFORMER_WIDTH, - -shift), 2) - z2 = round(random.uniform( - shift, scene.room_dimensions.z / 2 - geometry.PERFORMER_WIDTH), - 2) - teleport_pos_z = random.choice([z1, z2]) - ActionService.add_teleports( - scene.goal, [ - TeleportConfig( - step=kidnap_step, position_x=teleport_pos_x, - position_z=teleport_pos_z, look_at_center=True)], - False) - end_habituation_string = scene.goal['action_list'][-1][0] - teleport_rot_y_index = end_habituation_string.rfind('=') - teleport_rot_y = int( - end_habituation_string[teleport_rot_y_index + 1:]) - - agent_z = random.choice([-2, 2]) - agent['shows'].append({ - 'stepBegin': kidnap_step, - # Put the agent still close the containers - 'position': { - 'x': random.uniform(-2, 2), - 'y': 0, - 'z': agent_z - }, - 'rotation': { - 'y': 180 if agent_z > 0 else 0 - } - }) - scene.debug['endHabituationStep'] = kidnap_step - scene.debug['endHabituationTeleportPositionX'] = teleport_pos_x - scene.debug['endHabituationTeleportPositionZ'] = teleport_pos_z - scene.debug['endHabituationTeleportRotationY'] = teleport_rot_y - - def _kidnap_performer_for_imitation_task( - self, scene: Scene, config: ImitationTaskConfig, target, placer, - agent, last_open_step, containers, containers_to_open_indexes, - separation_between_containers): - kidnap_step = last_open_step + placer['moves'][-1]['stepEnd'] + 10 - ActionService.add_freezes(scene.goal, [StepBeginEnd(1, kidnap_step)]) - - placer_first_position = placer['shows'][0]['position'] - placer['shows'].append({ - 'stepBegin': kidnap_step, - 'position': { - 'x': placer_first_position['x'], - 'y': placer_first_position['y'], - 'z': placer_first_position['z'] - } - }) - target_first_position = target['shows'][0]['position'] - target['shows'].append({ - 'stepBegin': kidnap_step, - 'position': { - 'x': target_first_position['x'], - 'y': target_first_position['y'], - 'z': target_first_position['z'] - } - }) - - # Close containers - for container_index in containers_to_open_indexes: - containers[container_index]['openClose'].append({ - 'step': kidnap_step, - 'open': False - }) - - scene.debug['endHabituationStep'] = kidnap_step - scene.debug['endHabituationTeleportPositionX'] = \ - scene.performer_start.position.x - scene.debug['endHabituationTeleportPositionZ'] = \ - scene.performer_start.position.z - scene.debug['endHabituationTeleportRotationY'] = \ - scene.performer_start.rotation.y - # If we do NOT need to teleport anything, teleport the agent only - if config.kidnap_option == ImitationKidnapOptions.AGENT_ONLY: - agent['shows'].append({ - 'stepBegin': kidnap_step, - 'position': { - 'x': random.uniform(-2, 2), - 'y': 0, - 'z': 2 - }, - 'rotation': { - 'y': 180 - } - }) - ActionService.add_teleports( - scene.goal, [ - TeleportConfig( - step=kidnap_step, - position_x=scene.performer_start.position.x, - position_z=scene.performer_start.position.z, - rotation_y=scene.performer_start.rotation.y)], - False) - # If we need to teleport the containers - elif (config.kidnap_option == ImitationKidnapOptions.CONTAINERS or - config.kidnap_option == ImitationKidnapOptions.CONTAINERS_ROTATE - ): - self._change_container_and_agent_positions_during_kidnap( - scene, config, containers, separation_between_containers, - kidnap_step, target, placer, agent) - # If we need to teleport the performer - else: - self._teleport_performer_for_imitation_task( - scene, agent, kidnap_step) - def _add_seeing_leads_to_knowing(self, scene: Scene): config = self.get_shortcut_seeing_leads_to_knowing() if not config: @@ -3451,7 +2927,7 @@ def _add_seeing_leads_to_knowing(self, scene: Scene): return scene def _create_bins_placers_seeing_leads_to_knowing( - self, scene: Scene, target: Dict[str, Any]): + self, scene: Scene, target: SceneObject): # bin/bucket setup bin_shape_choices = ['cup_2_static', 'cup_3_static', 'cup_6_static'] bin_shape = random.choice(bin_shape_choices) @@ -3495,7 +2971,7 @@ def _create_bins_placers_seeing_leads_to_knowing( # if target should be in this bucket, move target to # corresponding placer - if(bin_pos == target_bucket): + if (bin_pos == target_bucket): relative_bin = RelativePositionConfig( label=bin_pos['label'] ) @@ -3529,7 +3005,7 @@ def _create_bins_placers_seeing_leads_to_knowing( deactivation_step=PLACER_DEACTIVATION_STEP, end_height=end_placer_height, max_height=scene.room_dimensions.y, - last_step=scene.goal['last_step'] + last_step=scene.goal.last_step ) scene.objects.append(non_target_placer) @@ -3555,24 +3031,24 @@ def _create_target_goal_seeing_leads_to_knowing(self, scene: Scene): scene.objects.append(target_inst) total_steps = 105 - scene.goal = { - 'metadata': { + scene.goal = Goal( + metadata={ 'target': { 'id': target_inst['id'] } }, - 'category': 'passive', - 'answer': { + category='passive', + answer={ 'choice': 'plausible' }, - 'last_step': total_steps, - 'action_list': [['Pass']] * total_steps, - 'description': '' - } + last_step=total_steps, + action_list=([['Pass']] * total_steps), + description='' + ) def _agent_setup_seeing_leads_to_knowing( self, config: SeeingLeadsToKnowingConfig, - scene: Scene, target: Dict[str, Any]): + scene: Scene, target: SceneObject): """ Agent Setup 1. Enter from left or right @@ -3584,7 +3060,7 @@ def _agent_setup_seeing_leads_to_knowing( agent_x_start_left = -2 agent_x_start_right = 2 - if(config.target_behind_agent): + if (config.target_behind_agent): agent_x_start = ( agent_x_start_left if target['shows'][0]['position']['x'] == BIN_LEFT_X_POS @@ -3625,7 +3101,7 @@ def _agent_setup_seeing_leads_to_knowing( is_target_behind_agent = (config.target_behind_agent or ((agent_x_start * target['shows'][0]['position']['x']) > 0)) # noqa - if(is_target_behind_agent): + if (is_target_behind_agent): movement_points.append( VectorFloatConfig( x=target['shows'][0]['position']['x'], @@ -3670,45 +3146,58 @@ def _add_imitation_task(self, scene: Scene): if config.containers_on_right_side is None: config.containers_on_right_side = choose_random([True, False]) if config.kidnap_option is None: - config.kidnap_options = choose_random( + config.kidnap_option = choose_random( [option.value for option in ImitationKidnapOptions]) - scene.performer_start.position.x = 0 - scene.performer_start.position.z = -3.75 - scene.performer_start.rotation.y = 0 - # Make a rectangular room - base_dimension = random.randint(8, 10) - rectangle_dimension = base_dimension * 2 - rectangle_direction = random.randint(0, 1) - scene.room_dimensions.x = \ - base_dimension if rectangle_direction == 0 else rectangle_dimension - scene.room_dimensions.y = 2 - scene.room_dimensions.z = \ - rectangle_dimension if rectangle_direction == 0 else base_dimension - - containers, separation_between_containers = \ - self._setup_containers_for_imitation_task(scene, config) - - target, placer = self._setup_target_and_placer_imitation_task( - scene, config, containers) - - trigger_order_ids, containers_to_open_indexes = \ - self._setup_trigger_order_for_imitation_task( - scene, config, containers) - - scene.goal['triggeredByTargetSequence'] = trigger_order_ids - - agent, open_steps = self._setup_agent_for_imitation_task( - scene, config, containers, containers_to_open_indexes) - - # Now Kidnap the performer!!! ヽ(°o°)ノ - self._kidnap_performer_for_imitation_task( - scene, config, target, placer, agent, open_steps[-1], containers, - containers_to_open_indexes, separation_between_containers) + rotation_to_use = None + relative_rotation = False + if config.global_container_rotation is not None or \ + config.relative_container_rotation is None: + config.global_container_rotation = \ + choose_random( + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL if + config.global_container_rotation is None else + config.global_container_rotation) + rotation_to_use = config.global_container_rotation + relative_rotation = False + if config.global_container_rotation not in \ + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL: + raise ILEException( + "Imitation Task Container Global Rotation " + f"({config.global_container_rotation}) must " + "be equal to a vale in " + f"{IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL} " + ) + else: + config.relative_container_rotation = choose_random( + config.relative_container_rotation) + rotation_to_use = config.relative_container_rotation + relative_rotation = True + if rotation_to_use not in \ + (IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE if + config.containers_on_right_side else + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE): + raise ILEException( + "Imitation Task Container Relative Rotation " + f"({config.relative_container_rotation}) must " + "be a value allowed on their container start side. " + "RIGHT SIDE: " + f"{IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE}" + ", LEFT SIDE: " + f"{IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE}." + ) - # raise the ceiling so the target and placer are visible - scene.room_dimensions.y = 3 - return scene + goal_template = GoalConfig( + category=tags.SCENE.IMITATION, + target=InteractableObjectConfig( + position=Vector3d(x=0, y=0, z=4), + shape='soccer_ball', + scale=1)) + GoalServices.attempt_to_add_goal(scene, goal_template) + return add_imitation_task( + scene, config.trigger_order, config.containers_on_right_side, + config.kidnap_option, containers_teleport_rotation=rotation_to_use, + relative_rotation=relative_rotation) def get_num_delayed_actions(self) -> int: count = 0 @@ -3716,7 +3205,7 @@ def get_num_delayed_actions(self) -> int: count += 1 if self._delayed_turntables_with_agent_and_non_agent else 0 return count - def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def run_delayed_actions(self, scene: Scene) -> Scene: if self._delayed_perf_pos: try: self._position_performer_on_platform(scene) diff --git a/ideal_learning_env/structural_object_service.py b/ideal_learning_env/structural_object_service.py index 2e8cc2c..85d0550 100644 --- a/ideal_learning_env/structural_object_service.py +++ b/ideal_learning_env/structural_object_service.py @@ -7,16 +7,20 @@ from dataclasses import dataclass from enum import Enum from itertools import combinations -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import shapely -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import ( + FloorTexturesConfig, + Vector2dInt, + Vector3d +) from generator import ( - ALL_LARGE_BLOCK_TOOLS, MAX_TRIES, MaterialTuple, ObjectBounds, + SceneObject, geometry, gravity_support_objects, instances, @@ -27,7 +31,6 @@ specific_objects, structures ) -from generator.base_objects import LARGE_BLOCK_TOOLS_TO_DIMENSIONS from generator.intuitive_physics_util import ( COLLISION_SPEEDS, MAX_TARGET_Z, @@ -46,26 +49,6 @@ from generator.movements import BASE_MOVE_LIST, TOSS_MOVE_LIST from generator.occluders import occluder_gap_positioning from generator.scene import PartitionFloor, Scene -from ideal_learning_env.global_settings_component import ( - ROOM_MIN_XZ, - ROOM_MIN_Y -) -from ideal_learning_env.interactable_object_service import ( - InteractableObjectConfig, - InteractableObjectCreationService -) -from ideal_learning_env.object_services import ( - DEBUG_FINAL_POSITION_KEY, - InstanceDefinitionLocationTuple, - KeywordLocation, - KeywordLocationConfig, - ObjectDefinition, - ObjectRepository, - RelativePositionConfig, - add_random_placement_tag, - get_step_after_movement, - get_step_after_movement_or_start -) from .choosers import ( SOCCER_BALL_SCALE_MAX, @@ -94,7 +77,13 @@ FeatureTypes, log_feature_template, position_relative_to, - validate_all_locations_and_update_bounds + validate_all_locations_and_update_bounds, + validate_floor_position +) +from .global_settings_component import ROOM_MIN_XZ, ROOM_MIN_Y +from .interactable_object_service import ( + InteractableObjectConfig, + InteractableObjectCreationService ) from .numerics import ( MinMaxFloat, @@ -102,10 +91,24 @@ RandomizableFloat, RandomizableInt, RandomizableVectorFloat3d, + RandomizableVectorFloat3dOrFloat, VectorFloatConfig, VectorIntConfig, retrieve_all_vectors ) +from .object_services import ( + DEBUG_FINAL_POSITION_KEY, + InstanceDefinitionLocationTuple, + KeywordLocation, + KeywordLocationConfig, + ObjectDefinition, + ObjectRepository, + RelativePositionConfig, + add_random_placement_tag, + calculate_rotated_position, + get_step_after_movement, + get_step_after_movement_or_start +) logger = logging.getLogger(__name__) @@ -170,11 +173,10 @@ BOTTOM_PLATFORM_SCALE_BUFFER_MIN = geometry.PERFORMER_WIDTH BOTTOM_PLATFORM_SCALE_BUFFER_MAX = 5 -# used to determine where ramps can fit next to platforms. When on the floor, -# we don't have a good way to determine this (particularly when rotated) so we -# use an arbitrarily large number and let the bounds checking determine if -# locations are valid later. -DEFAULT_AVAIABLE_LENGTHS = (10, 10, 10, 10) +# Use an arbitrarily large number and let the bounds checking determine if +# locations are valid later. We can't determine exactly how much space to use +# when the floor isn't rotated the same. +DEFAULT_AVAILABLE_LENGTHS = (10, 10, 10, 10) RAMP_ROTATIONS = (90, 180, -90, 0) WALL_SIDES = ['left', 'right', 'front', 'back', 'back_left_corner', @@ -230,7 +232,7 @@ def _check_for_collisions( def _retrieve_object_height_at_step( scene: Scene, - instance: Dict[str, Any], + instance: SceneObject, step: int ) -> float: """Returns the height of the given object at the given step, including the @@ -658,27 +660,48 @@ def create_feature_from_specific_values( """Creates a dropper from the given template with specific values.""" room_dim = scene.room_dimensions - self.target, self.target_exists = _get_projectile_idl( - reconciled, - scene, - self.bounds or [], - DROPPER_SHAPES_TO_SCALES - ) - projectile_dimensions = vars(self.target.definition.dimensions) - args = { - 'position_x': reconciled.position_x, - 'position_z': reconciled.position_z, - 'room_dimensions_y': room_dim.y, - 'object_dimensions': projectile_dimensions, - 'last_step': scene.goal.get('last_step'), - 'dropping_step': reconciled.drop_step, - 'is_round': ('ball' in self.target.definition.shape) - } + + args = {} + if (reconciled.no_projectile): + self.target = None + self.target_exists = False + dummy_dimensions = Vector3d(x=random.uniform(0.5, 1.0), + y=random.uniform(0.5, 1.0), + z=random.uniform(0.5, 1.0) + ) + projectile_dimensions = vars(dummy_dimensions) + args = { + 'position_x': reconciled.position_x, + 'position_z': reconciled.position_z, + 'room_dimensions_y': room_dim.y, + 'object_dimensions': projectile_dimensions, + 'last_step': scene.goal.last_step, + 'dropping_step': reconciled.drop_step, + } + else: + self.target, self.target_exists = _get_projectile_idl( + reconciled, + scene, + self.bounds or [], + DROPPER_SHAPES_TO_SCALES + ) + projectile_dimensions = vars(self.target.definition.dimensions) + args = { + 'position_x': reconciled.position_x, + 'position_z': reconciled.position_z, + 'room_dimensions_y': room_dim.y, + 'object_dimensions': projectile_dimensions, + 'last_step': scene.goal.last_step, + 'dropping_step': reconciled.drop_step, + 'is_round': ('ball' in self.target.definition.shape) + } + logger.trace(f'Creating dropper:\nINPUT = {args}') new_obj = [mechanisms.create_dropping_device(**args)] self.dropper = new_obj[0] - if not self.target_exists: - new_obj.append(self.target.instance) + if not reconciled.no_projectile: + if not self.target_exists: + new_obj.append(self.target.instance) # In passive physics scenes, change the dropper's position to just # outside the camera's viewport (instead of attached to the ceiling). @@ -721,6 +744,10 @@ def _handle_dependent_defaults( # Save the projectile labels from the source template. self._target_labels = source_template.projectile_labels + if isinstance(reconciled.projectile_dimensions, (int, float)): + val = reconciled.projectile_dimensions + reconciled.projectile_dimensions = Vector3d(x=val, y=val, z=val) + return reconciled def _on_valid_instances(self, scene, reconciled_template, new_obj): @@ -728,34 +755,45 @@ def _on_valid_instances(self, scene, reconciled_template, new_obj): self._do_post_add(scene, reconciled_template) def _do_post_add(self, scene, reconciled): - target = self.target + target = None + args = {} + if self.target is None: + args = { + 'dropping_device': self.dropper, + 'dropping_step': reconciled.drop_step + } + else: + target = self.target + args = { + 'instance': target.instance, + 'dropping_device': self.dropper, + 'dropping_step': reconciled.drop_step + } - args = { - 'instance': target.instance, - 'dropping_device': self.dropper, - 'dropping_step': reconciled.drop_step - } logger.trace(f'Positioning dropper object:\nINPUT = {args}') - mechanisms.drop_object(**args) - target.instance['debug']['positionedBy'] = 'mechanism' - target.instance['debug'][DEBUG_FINAL_POSITION_KEY] = True + if target is not None: + mechanisms.drop_object(**args) + target.instance['debug']['positionedBy'] = 'mechanism' + target.instance['debug'][DEBUG_FINAL_POSITION_KEY] = True # Override other properties for a passive physics scene (if needed). - if scene.intuitive_physics: + if scene.intuitive_physics and target is not None: # Only show the target on the step that it's dropped. target.instance['shows'][0]['stepBegin'] = reconciled.drop_step # Don't show the dropper at all. self.dropper['shows'][0]['stepBegin'] = -1 - if not self.target_exists: + if not self.target_exists and target is not None: log_feature_template('dropper object', 'id', target.instance['id']) else: - for i in range(len(scene.objects)): - if scene.objects[i]['id'] == target.instance['id']: - scene.objects[i] = target.instance + if target is not None: + for i in range(len(scene.objects)): + if scene.objects[i]['id'] == target.instance['id']: + scene.objects[i] = target.instance object_repo = ObjectRepository.get_instance() - object_repo.add_to_labeled_objects(target, self._target_labels) + if target is not None: + object_repo.add_to_labeled_objects(target, self._target_labels) class StructuralThrowerCreationService( @@ -783,19 +821,33 @@ def create_feature_from_specific_values( ) scene_copy = copy.deepcopy(scene) scene_copy.room_dimensions = room_dimensions_extended - self.target, self.target_exists = _get_projectile_idl( - reconciled, - scene_copy, - self.bounds or [], - THROWER_SHAPES_TO_SCALES - ) + projectile_dimensions = None + + if reconciled.no_projectile: + self.target = None + self.target_exists = False + dummy_dimensions = Vector3d(x=random.uniform(0.5, 1.0), + y=random.uniform(0.5, 1.0), + z=random.uniform(0.5, 1.0) + ) + projectile_dimensions = vars(dummy_dimensions) + + else: + self.target, self.target_exists = _get_projectile_idl( + reconciled, + scene_copy, + self.bounds or [], + THROWER_SHAPES_TO_SCALES + ) + projectile_dimensions = vars(self.target.definition.dimensions) + wall_rot = { WallSide.LEFT.value: 0, WallSide.RIGHT: 180, WallSide.FRONT: 90, WallSide.BACK: 270 } - projectile_dimensions = vars(self.target.definition.dimensions) + max_scale = max(projectile_dimensions['x'], projectile_dimensions['z']) # If passive_physics_setup is set, then override all default and @@ -933,22 +985,38 @@ def create_feature_from_specific_values( f'{self._path_relative_object["id"]}' ) - args = { - 'position_x': pos_x, - 'position_y': reconciled.height, - 'position_z': pos_z, - 'rotation_y': rotation_y, - 'rotation_z': reconciled.rotation_z, - 'object_dimensions': projectile_dimensions, - 'object_rotation_y': self.target.definition.rotation.y, - 'last_step': scene.goal.get('last_step'), - 'throwing_step': reconciled.throw_step, - 'is_round': ('ball' in self.target.definition.shape) - } + args = {} + + if (reconciled.no_projectile): + args = { + 'position_x': pos_x, + 'position_y': reconciled.height, + 'position_z': pos_z, + 'rotation_y': rotation_y, + 'rotation_z': reconciled.rotation_z, + 'object_dimensions': projectile_dimensions, + 'last_step': scene.goal.last_step, + 'throwing_step': reconciled.throw_step, + } + + else: + args = { + 'position_x': pos_x, + 'position_y': reconciled.height, + 'position_z': pos_z, + 'rotation_y': rotation_y, + 'rotation_z': reconciled.rotation_z, + 'object_dimensions': projectile_dimensions, + 'object_rotation_y': self.target.definition.rotation.y, + 'last_step': scene.goal.last_step, + 'throwing_step': reconciled.throw_step, + 'is_round': ('ball' in self.target.definition.shape) + } + logger.trace(f'Creating thrower:\nINPUT = {args}') new_obj = [mechanisms.create_throwing_device(**args)] self.thrower = new_obj[0] - if not self.target_exists: + if not self.target_exists and not reconciled.no_projectile: new_obj.append(self.target.instance) add_random_placement_tag(new_obj, source_template) @@ -1013,6 +1081,10 @@ def _handle_dependent_defaults( # Save the projectile labels from the source template. self._target_labels = source_template.projectile_labels + if isinstance(reconciled.projectile_dimensions, (int, float)): + val = reconciled.projectile_dimensions + reconciled.projectile_dimensions = Vector3d(x=val, y=val, z=val) + return reconciled def is_valid(self, scene, new_obj, bounds, try_num, retries): @@ -1257,67 +1329,75 @@ def __use_throw_force_config(self, scene, reconciled) -> float: def _do_post_add(self, scene, reconciled): target = self.target - if reconciled.stop_position: - try: - force_x, force_y = self.__use_stop_position_config( - scene, - reconciled - ) - except Exception as exception: - # If something goes wrong, ensure the corresponding objects - # do not remain in the scene (they will be remade). - scene.objects = [ - instance for instance in scene.objects - if instance['id'] not in - [target.instance['id'], self.thrower['id']] - ] - raise exception - else: - force_x, force_y = self.__use_throw_force_config(scene, reconciled) + if (target is not None): + if reconciled.stop_position: + try: + force_x, force_y = self.__use_stop_position_config( + scene, + reconciled + ) + except Exception as exception: + # If something goes wrong, ensure the corresponding objects + # do not remain in the scene (they will be remade). + scene.objects = [ + instance for instance in scene.objects + if instance['id'] not in + [target.instance['id'], self.thrower['id']] + ] + raise exception from exception + else: + force_x, force_y = self.__use_throw_force_config( + scene, reconciled) - # Override other properties for a passive physics scene (if needed). - if scene.intuitive_physics: - # Only show the target on the step that it's thrown. - target.instance['shows'][0]['stepBegin'] = reconciled.throw_step - # Don't show the thrower at all. - self.thrower['shows'][0]['stepBegin'] = -1 + # Override other properties for passive physics + # scene (if needed). + if scene.intuitive_physics: + # Only show the target on the step that it's thrown. + target.instance['shows'][0]['stepBegin'] = \ + reconciled.throw_step + # Don't show the thrower at all. + self.thrower['shows'][0]['stepBegin'] = -1 - # Make sure we multiply the force by the target's mass! - force_x *= target.definition.mass - force_y *= target.definition.mass + # Make sure we multiply the force by the target's mass! + force_x *= target.definition.mass + force_y *= target.definition.mass - # Update the target to be thrown. - args = { - 'instance': target.instance, - 'throwing_device': self.thrower, - 'throwing_force': force_x, - 'throwing_step': reconciled.throw_step, - 'impulse': reconciled.impulse - } - logger.trace(f'Positioning thrower object:\nINPUT = {args}') - target.instance['debug']['positionedBy'] = 'mechanism' - target.instance['debug'][DEBUG_FINAL_POSITION_KEY] = True - mechanisms.throw_object(**args) - - # Update the thrown object's Y force if needed. - target.instance['forces'][0]['vector']['y'] = force_y - - # If the thrown object should start on the ground, make sure it isn't - # centered in the thrower instead. - if reconciled.height == 0: - starting_y = target.definition.positionY - target.instance['shows'][0]['position']['y'] = starting_y - - # Make sure the target exists in the scene. - if not self.target_exists: - log_feature_template('thrower object', 'id', target.instance['id']) - else: - for i in range(len(scene.objects)): - if scene.objects[i]['id'] == target.instance['id']: - scene.objects[i] = target.instance + # Update the target to be thrown. + args = { + 'instance': target.instance, + 'throwing_device': self.thrower, + 'throwing_force': force_x, + 'throwing_step': reconciled.throw_step, + 'impulse': reconciled.impulse + } + logger.trace(f'Positioning thrower object:\nINPUT = {args}') + target.instance['debug']['positionedBy'] = 'mechanism' + target.instance['debug'][DEBUG_FINAL_POSITION_KEY] = True + mechanisms.throw_object(**args) + + # Update the thrown object's Y force if needed. + target.instance['forces'][0]['vector']['y'] = force_y + + # If the thrown object should start on the ground, make sure it + # isn't centered in the thrower instead. + if reconciled.height == 0: + starting_y = target.definition.positionY + target.instance['shows'][0]['position']['y'] = starting_y + + # Make sure the target exists in the scene. + if not self.target_exists: + log_feature_template( + 'thrower object', 'id', target.instance['id']) + else: + for i in range(len(scene.objects)): + if scene.objects[i]['id'] == target.instance['id']: + scene.objects[i] = target.instance - object_repo = ObjectRepository.get_instance() - object_repo.add_to_labeled_objects(target, self._target_labels) + object_repo = ObjectRepository.get_instance() + object_repo.add_to_labeled_objects(target, self._target_labels) + + else: + return class StructuralMovingOccluderCreationService( @@ -1335,7 +1415,7 @@ def create_feature_from_specific_values( """Creates a moving occluder from the given template with specific values.""" room_dim = scene.room_dimensions - last_step_arg = scene.goal.get('last_step') if ( + last_step_arg = scene.goal.last_step if ( not reconciled.repeat_movement and reconciled.move_up_before_last_step ) else None @@ -1477,7 +1557,7 @@ def create_feature_from_specific_values( source_template: FloorAreaConfig): """Creates lava from the given template with specific values.""" - return {'x': reconciled.position_x, 'z': reconciled.position_z} + return Vector2dInt(x=reconciled.position_x, z=reconciled.position_z) def _handle_dependent_defaults( self, scene: Scene, reconciled: FloorAreaConfig, source_template @@ -1513,7 +1593,7 @@ def create_feature_from_specific_values( source_template: FloorAreaConfig): """Creates a hole from the given template with specific values.""" - return {'x': reconciled.position_x, 'z': reconciled.position_z} + return Vector2dInt(x=reconciled.position_x, z=reconciled.position_z) def _handle_dependent_defaults( self, scene: Scene, reconciled: FloorAreaConfig, source_template @@ -1531,7 +1611,12 @@ def is_valid(self, scene: Scene, lava_pos: List, bounds, try_num, retries): try_num, retries, self._get_type()) - def _on_valid_instances(self, scene, reconciled_template, new_obj): + def _on_valid_instances( + self, + scene: Scene, + reconciled_template, + new_obj + ): scene.holes += new_obj log_feature_template( 'holes', 'holes', new_obj, [reconciled_template]) @@ -1576,13 +1661,13 @@ def is_valid(self, scene: Scene, objs: List, bounds, try_num, retries): # make sure there is no lava here lava = scene.lava or [] - pos = {'x': template.position_x, 'z': template.position_z} + pos = Vector2dInt(x=template.position_x, z=template.position_z) if pos in lava: return False for existing in scene.floor_textures: - for existing_pos in existing['positions']: - if (existing_pos['x'] == template.position_x and - existing_pos['z'] == template.position_z): + for existing_pos in existing.positions: + if (existing_pos.x == template.position_x and + existing_pos.z == template.position_z): return False return True @@ -1590,14 +1675,17 @@ def _on_valid_instances( self, scene: Scene, reconciled: FloorMaterialConfig, new_obj): mat = reconciled.material - pos = {'x': reconciled.position_x, 'z': reconciled.position_z} + pos = Vector2dInt(x=reconciled.position_x, z=reconciled.position_z) added = False for existing in scene.floor_textures: - if existing['material'] == mat: - existing['positions'].append(pos) + if existing.material == mat: + existing.positions.append(pos) added = True if not added: - scene.floor_textures.append({'material': mat, 'positions': [pos]}) + scene.floor_textures.append(FloorTexturesConfig( + material=mat, + positions=[pos] + )) log_feature_template( 'floor_materials', 'floor_materials', new_obj, [reconciled]) @@ -1741,7 +1829,7 @@ def create_feature_from_specific_values( ) except Exception as e: self._cleanup_on_failure() - raise e + raise e from e def reconcile( self, @@ -1752,7 +1840,7 @@ def reconcile( return super().reconcile(scene, source_template) except Exception as e: self._cleanup_on_failure() - raise e + raise e from e def _cleanup_on_failure(self) -> None: if self.object_idl: @@ -1791,7 +1879,7 @@ def _create_feature_from_specific_values_helper( start_height = room_dim.y max_height = room_dim.y - last_step = scene.goal.get("last_step") + last_step = scene.goal.last_step instance = idl.instance defn = idl.definition @@ -2099,14 +2187,28 @@ def _handle_dependent_defaults( ).instance above_position = above_object['shows'][0]['position'].copy() above_debug = above_object['debug'] - # If the object was moved by something like a placer before this - # placer activates, use the object's moved position. + + # If the object was moved by something like another placer before + # this placer activates, then use the object's moved position. if ( above_debug.get('moveToPosition') and above_debug['moveToPositionBy'] <= reconciled.activation_step ): above_position['x'] = above_debug['moveToPosition']['x'] above_position['z'] = above_debug['moveToPosition']['z'] + + # If the object was rotated by a turntable before this placer + # activates, then use the object's rotated position. + if above_debug.get('isRotatedBy'): + adjusted_position = calculate_rotated_position( + scene, + reconciled.activation_step, + above_object + ) + if adjusted_position: + above_position['x'] = adjusted_position['x'] + above_position['z'] = adjusted_position['z'] + reconciled.placed_object_position = Vector3d( x=above_position['x'], y=above_position['y'], @@ -2268,7 +2370,7 @@ def _handle_dependent_defaults( def _on_valid_instances( self, scene: Scene, reconciled_template: StructuralPlacerConfig, - new_obj: dict, key: str = 'objects'): + new_obj: SceneObject, key: str = 'objects'): self.object_idl.instance['debug']['positionedBy'] = 'mechanism' self.object_idl.instance['debug'][DEBUG_FINAL_POSITION_KEY] = True @@ -2355,19 +2457,36 @@ def _handle_dependent_defaults( def_dim = geometry.DEFAULT_ROOM_DIMENSIONS room_width = room_dim.x or def_dim['x'] room_length = room_dim.z or def_dim['z'] + + # Wall and Door materials should not be the same unless + # both are configured to be the same + door_materials = materials.METAL_MATERIALS + \ + materials.PLASTIC_MATERIALS + materials.WOOD_MATERIALS + prohibited_wall_material = None + # If wall_material is provided in the source template + if source_template.wall_material is not None: + # Reconcile the wall material + self._wall_material_tuple = _reconcile_material( + source_template.wall_material, materials.ROOM_WALL_MATERIALS) + reconciled.wall_material = self._wall_material_tuple.material + # Prohibit using the same material for the door + prohibited_wall_material = reconciled.wall_material + + # Reconcile the door material while avoiding the + # prohibited_wall_material which may be an str or None self._door_material_tuple = _reconcile_material( - source_template.material, - materials.METAL_MATERIALS + materials.PLASTIC_MATERIALS + - materials.WOOD_MATERIALS, - ) + source_template.material, door_materials, + prohibited_material=prohibited_wall_material) reconciled.material = self._door_material_tuple.material - self._wall_material_tuple = _reconcile_material( - source_template.wall_material, - materials.ROOM_WALL_MATERIALS, - # Do not use exactly the same material as the door, if possible. - prohibited_material=reconciled.material - ) - reconciled.wall_material = self._wall_material_tuple.material + + # If wall_material is not provided in the source template + if source_template.wall_material is None: + # Reconcile the wall material while avoiding the door material + self._wall_material_tuple = _reconcile_material( + source_template.wall_material, materials.ROOM_WALL_MATERIALS, + prohibited_material=reconciled.material) + reconciled.wall_material = self._wall_material_tuple.material + reconciled.position.x = choose_random( MinMaxFloat(-room_width / 2.0, room_width / 2.0) if reconciled.position.x is None else reconciled.position.x @@ -2428,7 +2547,6 @@ def create_feature_from_specific_values( logger.trace(f'Creating turntable:\nINPUT = {args}') turntable = structures.create_turntable(**args) - turntable = turntable[0] _post_instance( scene, turntable, @@ -2447,9 +2565,11 @@ def _handle_dependent_defaults( room_width = room_dim.x or def_dim['x'] room_length = room_dim.z or def_dim['z'] reconciled = _handle_position_defaults(scene, reconciled) + # By default, always use the same material for each turntable, but + # let users configure other materials if desired. self._material_tuple = _reconcile_material( source_template.material, - materials.ROOM_WALL_MATERIALS + [DEFAULT_TURNTABLE_MATERIAL] ) reconciled.material = self._material_tuple.material @@ -2483,79 +2603,84 @@ def _handle_dependent_defaults( return reconciled -class StructuralToolsCreationService( - BaseObjectCreationService): +class StructuralTubeOccluderCreationService(BaseObjectCreationService): def __init__(self): - self._default_template = DEFAULT_TEMPLATE_TOOL - self._type = FeatureTypes.TOOLS + self._default_template = DEFAULT_TEMPLATE_TUBE_OCCLUDER + self._type = FeatureTypes.TUBE_OCCLUDERS + self._material_tuple = None def create_feature_from_specific_values( - self, scene: Scene, reconciled: ToolConfig, - source_template: ToolConfig): - """Creates a tool from the given template with - specific values.""" + self, + scene: Scene, + reconciled: StructuralTubeOccluderConfig, + source_template: StructuralTubeOccluderConfig + ): + if not self._material_tuple: + self._material_tuple = choose_material_tuple_from_material( + reconciled.material + ) args = { - 'object_type': reconciled.shape, - 'position_x': reconciled.position.x, - 'position_z': reconciled.position.z, - 'rotation_y': reconciled.rotation_y + 'down_step': reconciled.down_step, + 'material_tuple': self._material_tuple, + 'radius': reconciled.radius, + 'room_height': scene.room_dimensions.y, + 'position_x': reconciled.position_x, + 'position_z': reconciled.position_z, + 'up_step': reconciled.up_step } - logger.trace(f'Creating tool:\nINPUT = {args}') - obj = structures.create_tool(**args) - _post_instance( + logger.trace(f'Creating tube occluder:\nINPUT = {args}') + tube_occluder = structures.create_tube_occluder(**args) + tube_occluder = _post_instance( scene, - obj, + tube_occluder, reconciled, source_template, - self._get_type()) - return obj + self._get_type() + ) + return tube_occluder def _handle_dependent_defaults( - self, scene: Scene, reconciled: ToolConfig, - source_template: ToolConfig - ) -> ToolConfig: - room_dim = scene.room_dimensions - def_dim = geometry.DEFAULT_ROOM_DIMENSIONS - room_width = room_dim.x or def_dim['x'] - room_length = room_dim.z or def_dim['y'] - reconciled.position.x = ( - MinMaxFloat(-room_width / 2.0, room_width / 2.0).convert_value() - if reconciled.position.x is None else reconciled.position.x + self, + scene: Scene, + reconciled: StructuralTubeOccluderConfig, + source_template: StructuralTubeOccluderConfig + ) -> StructuralTubeOccluderConfig: + if reconciled.up_after: + step = get_step_after_movement([ + label for label in return_list(source_template.up_after) + if label + ]) + if step >= 1: + reconciled.up_step = step + if reconciled.down_after: + step = get_step_after_movement([ + label for label in return_list(source_template.down_after) + if label + ]) + if step >= 1: + reconciled.down_step = step + if not (source_template.up_step or source_template.up_after): + reconciled.up_step = 0 + + limit_x = (scene.room_dimensions.x - reconciled.radius) / 2.0 + limit_z = (scene.room_dimensions.z - reconciled.radius) / 2.0 + reconciled.position_x = ( + reconciled.position_x if reconciled.position_x is not None + else random.uniform(-limit_x, limit_x) ) - reconciled.position.z = ( - MinMaxFloat(-room_length / 2.0, room_length / 2.0).convert_value() - if reconciled.position.z is None else reconciled.position.z + reconciled.position_z = ( + reconciled.position_z if reconciled.position_z is not None + else random.uniform(-limit_z, limit_z) ) - if not source_template.shape and ( - reconciled.width or reconciled.length): - reconciled.shape = self.get_tool_from_dimensions( - reconciled.width, reconciled.length) - return reconciled + self._material_tuple = _reconcile_material( + source_template.material, + materials.ROOM_WALL_MATERIALS + ) + reconciled.material = self._material_tuple.material - def get_tool_from_dimensions(self, orig_width, orig_length): - width = orig_width - length = orig_length - valid_widths = set() - valid_lengths = set() - for dim in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.values(): - valid_widths.add(dim[0]) - valid_lengths.add(dim[1]) - if not width: - width = choose_random(list(valid_widths)) - if not length: - length = choose_random(list(valid_lengths)) - for shape, dim in LARGE_BLOCK_TOOLS_TO_DIMENSIONS.items(): - if dim == (width, length): - return shape - # For exception message, if no width or length specified, - # just apply the word 'any' - width = width or "Any" - length = length or "Any" - raise ILEException( - f"Unable to find valid tool with dimensions width={width} " - f"length={length}") + return reconciled @dataclass @@ -2671,8 +2796,7 @@ class StructuralPlatformConfig(PositionableStructuralObjectsConfig): """ lips: Union[StructuralPlatformLipsConfig, List[StructuralPlatformLipsConfig]] = None - scale: Union[float, MinMaxFloat, List[Union[float, MinMaxFloat]], - VectorFloatConfig, List[VectorFloatConfig]] = None + scale: RandomizableVectorFloat3dOrFloat = None attached_ramps: RandomizableInt = None platform_underneath: RandomizableBool = None platform_underneath_attached_ramps: RandomizableInt = None # noqa @@ -2784,6 +2908,10 @@ class StructuralDropperConfig(BaseFeatureConfig): - `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Position in the z direction of the of the ceiling where the dropper should be placed. + - `projectile_dimensions` (float, or list of floats, or + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Dimensions + of the projectile. Overrides the `projectile_scale` if configured. + Default: use `projectile_scale` - `projectile_labels` (string, or list of strings): A label for an existing object in your ILE configuration that will be used as this device's projectile, or new label(s) to associate with a new projectile object. @@ -2799,16 +2927,19 @@ class StructuralDropperConfig(BaseFeatureConfig): the projectile. Default is based on the shape. - `projectile_shape` (string, or list of strings): The shape or type of the projectile. + - `no_projectile` (bool, or list of bools): If `true`, this device will + not be holding a projectile; other "projectile" options will be ignored. + Default: `false` """ position_x: RandomizableFloat = None position_z: RandomizableFloat = None drop_step: RandomizableInt = None - projectile_shape: RandomizableString = None - projectile_material: RandomizableString = None - projectile_scale: Union[float, MinMaxFloat, - List[Union[float, MinMaxFloat]], - VectorFloatConfig, List[VectorFloatConfig]] = None + no_projectile: RandomizableBool = False + projectile_dimensions: RandomizableVectorFloat3dOrFloat = None projectile_labels: RandomizableString = None + projectile_material: RandomizableString = None + projectile_scale: RandomizableVectorFloat3dOrFloat = None + projectile_shape: RandomizableString = None position_relative: Union[ RelativePositionConfig, List[RelativePositionConfig] @@ -2907,6 +3038,10 @@ class StructuralThrowerConfig(BaseFeatureConfig): - `position_wall` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): The position along the wall that the thrower will be placed. + - `projectile_dimensions` (float, or list of floats, or + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Dimensions + of the projectile. Overrides the `projectile_scale` if configured. + Default: use `projectile_scale` - `projectile_labels` (string, or list of strings): A label for an existing object in your ILE configuration that will be used as this device's projectile, or new label(s) to associate with a new projectile object. @@ -2952,6 +3087,9 @@ class StructuralThrowerConfig(BaseFeatureConfig): your custom config files. - `wall` (string, or list of strings): Which wall the thrower should be placed on. Options are: left, right, front, back. + - `no_projectile` (bool, or list of bools): If `true`, this device will + not be holding a projectile; other "projectile" options will be ignored. + Default: `false` """ wall: RandomizableString = None position_wall: RandomizableFloat = None @@ -2960,20 +3098,18 @@ class StructuralThrowerConfig(BaseFeatureConfig): rotation_z: RandomizableFloat = None throw_step: RandomizableInt = None throw_force: RandomizableFloat = None - projectile_shape: RandomizableString = None - projectile_material: RandomizableString = None - projectile_scale: Union[float, MinMaxFloat, - List[Union[float, MinMaxFloat]], - VectorFloatConfig, List[VectorFloatConfig]] = None + no_projectile: RandomizableBool = False + projectile_dimensions: RandomizableVectorFloat3dOrFloat = None projectile_labels: RandomizableString = None + projectile_material: RandomizableString = None + projectile_scale: RandomizableVectorFloat3dOrFloat = None + projectile_shape: RandomizableString = None position_relative: Union[ RelativePositionConfig, List[RelativePositionConfig] ] = None impulse: RandomizableBool = True - throw_force_multiplier: Union[ - float, MinMaxFloat, List[Union[float, MinMaxFloat]] - ] = None + throw_force_multiplier: RandomizableFloat = None passive_physics_collision_force: RandomizableBool = False passive_physics_setup: RandomizableString = None passive_physics_throw_force: RandomizableBool = False @@ -3034,9 +3170,9 @@ class StructuralMovingOccluderConfig(BaseFeatureConfig): - `reverse_direction` (bool, or list of bools): Reverse the rotation direction of a sideways wall by rotating the wall 180 degrees. Only used if `origin` is set to a wall and not `top`. Default: [true, false] - - `rotation_y` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, - or list of MinMaxInt dicts): Y rotation of a non-sideways occluder wall; - only used if any `origin` is set to `top`. Default is 0 to 359. + - `rotation_y` (float, or list of floats, or [MinMaxInt](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): Y rotation of a non-sideways occluder + wall; only used if any `origin` is set to `top`. Default is 0 to 359. - `wall_material` (string, or list of strings): Material of the occluder wall (cube) """ @@ -3052,7 +3188,7 @@ class StructuralMovingOccluderConfig(BaseFeatureConfig): repeat_movement: RandomizableBool = None repeat_interval: RandomizableInt = None reverse_direction: RandomizableBool = None - rotation_y: RandomizableInt = None + rotation_y: RandomizableFloat = None move_up_before_last_step: RandomizableBool = None move_down_only: RandomizableBool = None @@ -3130,8 +3266,7 @@ class StructuralOccludingWallConfig(PositionableStructuralObjectsConfig): Default: ['occludes', 'occludes', 'occludes', 'short', 'thin', 'hole'] """ type: RandomizableString = None - scale: Union[float, MinMaxFloat, List[Union[float, MinMaxFloat]], - VectorFloatConfig, List[VectorFloatConfig]] = None + scale: RandomizableVectorFloat3dOrFloat = None keyword_location: Union[KeywordLocationConfig, List[KeywordLocationConfig]] = None @@ -3149,21 +3284,22 @@ class StructuralPlacerConfig(BaseFeatureConfig): - `activate_after`: (str, or list of strs): Overrides the `activation_step` (overriding the manual config) based on the movement of other object(s) in the scene. Should be set to one or more labels for mechanical objects that - may move or rotate, like placers or turntables. The `activation_step` of - this object will be set to the step immediately after ALL of the objects - finish moving and rotating. If multiple labels are configured, all labels - will be used. Default: Use `activation_step` + may move or rotate, like placers or turntables, as well as agents. The + `activation_step` of this object will be set to the step immediately after + ALL of the objects and agents finish moving and rotating. If multiple + labels are configured, all labels will be used. Default: Use + `activation_step` - `activate_on_start_or_after`: (str, or list of strs, or bool): Overrides the `activation_step` (overriding the manual config) based on the movement of other object(s) in the scene. Should be set to one or more labels for - mechanical objects that can move or rotate, like placers or turntables. If - ANY of the objects begin moving or rotating immediately at the start of the - scene (step 1), then the `activation_step` of this object will be set to - the step immediately after ALL of the objects finish moving and rotating; - otherwise, if ALL of the objects begin moving and rotating after step 1, - then the `activation_step` of this object will be set to 1. If multiple - labels are configured, all labels will be used. Default: Use - `activation_step` + mechanical objects that can move or rotate, like placers or turntables, as + well as agents. If ANY of the objects or agents begin moving or rotating + immediately at the start of the scene (step 1), then the `activation_step` + of this object will be set to the step immediately after ALL of the objects + and agents finish moving and rotating; otherwise, if ALL of the objects and + agents begin moving and rotating after step 1, then the `activation_step` + of this object will be set to 1. If multiple labels are configured, all + labels will be used. Default: Use `activation_step` - `activation_step`: (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): Step on which the placer should begin its downward movement. Default: between 0 and 10 @@ -3222,8 +3358,8 @@ class StructuralPlacerConfig(BaseFeatureConfig): - `placed_object_position`: ([VectorFloatConfig](#VectorFloatConfig) dict, or list of VectorFloatConfig dicts): The placed object's position in the scene - - `placed_object_rotation`: (int, or list of ints, or - [MinMaxInt](#MinMaxInt) dict, or list of MinMaxInt dicts): The placed + - `placed_object_rotation`: (float, or list of floats, or + [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): The placed object's rotation on the y axis. - `placed_object_scale`: (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, or list of MinMaxFloat dicts): Placed @@ -3252,11 +3388,8 @@ class StructuralPlacerConfig(BaseFeatureConfig): end_height: RandomizableFloat = None end_height_relative_object_label: str = None placed_object_position: RandomizableVectorFloat3d = None - placed_object_scale: Union[float, MinMaxFloat, - VectorFloatConfig, - List[Union[float, MinMaxFloat, - VectorFloatConfig]]] = None - placed_object_rotation: RandomizableInt = None + placed_object_scale: RandomizableVectorFloat3dOrFloat = None + placed_object_rotation: RandomizableFloat = None placed_object_shape: RandomizableString = None placed_object_material: RandomizableString = None placed_object_labels: RandomizableString = None @@ -3377,42 +3510,57 @@ class StructuralTurntableConfig(PositionableStructuralObjectsConfig): List[StructuralObjectMovementConfig]] = None -# TODO MCS-1206 Move into the interactable object component @dataclass -class ToolConfig(BaseFeatureConfig): +class StructuralTubeOccluderConfig(BaseFeatureConfig): """ - Defines details of a tool object. + Defines details of a structural tube occluder (or "tube-cluder"). - `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list of - MinMaxInt dicts): Number of structures to be created with these parameters - - `guide_rails` (bool, or list of bools): If True, guide rails will be - generated to guide the tool in the direction it is oriented. If a target - exists, the guide rails will extend to the target. Default: random - - `labels` (string, or list of strings): A label or labels to be assigned - to this object. Always automatically assigned "platforms" - - `position` ([VectorFloatConfig](#VectorFloatConfig) dict, or list of - VectorFloatConfig dicts): The structure's position in the scene - - `rotation_y` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) - dict, or list of MinMaxFloat dicts): The structure's rotation in the scene - - `shape` (string, or list of strings): The shape (object type) of this - object in each scene. For a list, a new shape will be randomly chosen for - each scene. Must be a valid [tool shape](#Lists). If set, `length` and - `width` are ignored. Default: random - - `length` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or list - of MinMaxInt dicts): The length of the tool. Tools only have specific - sizes and the values much match exactly. Valid lengths are integers - 4 to 9. If shape is set, this value is ignored. Default: Use shape - - `width` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) dict, - or list of MinMaxFloat dicts): The width of the tool. Tools only have - specific sizes and the values much match exactly. Valid widths are - 0.5, 0.75, 1.0. If shape is set, this value is ignored. Default: Use shape + MinMaxInt dicts): Number of structures to generate with these options. + - `labels` (string, or list of strings): A label or labels to assign to + these object(s). Always automatically assigned "tube_occluders" + - `down_after`: (string, or list of strings): Overrides the `down_step` + (overriding the manual config) based on the movement of other object(s) in + the scene. Should be set to one or more labels for mechanical objects that + may move or rotate, like placers or turntables, as well as agents. The + `down_step` of this object will be set to the step immediately after ALL of + the objects and agents finish moving and rotating. If multiple labels are + configured, all labels will be used. Default: Use `down_step` + - `down_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or + list of MinMaxInt dicts): The step on which the tube occluder should begin + to move down from the ceiling to the floor. Note that this should happen + before `up_step`. Default: between 1 and 10 + - `material` (string, or list of strings): The structure's material or + material type. Default: random + - `position_x` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): The structure's X position. + Default: random + - `position_z` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): The structure's Z position. + Default: random + - `radius` (float, or list of floats, or [MinMaxFloat](#MinMaxFloat) + dict, or list of MinMaxFloat dicts): The structure's radius. Default: + between 0.5 and 5 + - `up_after`: (string, or list of strings): Overrides the `up_step` + (overriding the manual config) based on the movement of other object(s) in + the scene. Should be set to one or more labels for mechanical objects that + may move or rotate, like placers or turntables, as well as agents. The + `up_step` of this object will be set to the step immediately after ALL of + the objects and agents finish moving and rotating. If multiple labels are + configured, all labels will be used. Default: Use `up_step` + - `up_step` (int, or list of ints, or [MinMaxInt](#MinMaxInt) dict, or + list of MinMaxInt dicts): The step on which the tube occluder should begin + to move up from the floor to the ceiling. Note that this should happen + after `down_step`. Default: between 51 and 60 """ - position: RandomizableVectorFloat3d = None - rotation_y: RandomizableFloat = None - shape: RandomizableString = None - length: RandomizableInt = None - width: RandomizableFloat = None - guide_rails: RandomizableBool = False + down_after: RandomizableString = None + down_step: RandomizableInt = None + material: RandomizableString = None + position_x: RandomizableFloat = None + position_z: RandomizableFloat = None + radius: RandomizableFloat = None + up_after: RandomizableString = None + up_step: RandomizableInt = None DEFAULT_TEMPLATE_DROPPER = StructuralDropperConfig( @@ -3522,7 +3670,7 @@ class ToolConfig(BaseFeatureConfig): 0, placed_object_above=None, placed_object_position=None, - placed_object_rotation=MinMaxInt(0, 359), + placed_object_rotation=MinMaxFloat(0, 359), placed_object_scale=None, placed_object_shape=PLACER_SHAPES, activation_step=MinMaxInt(0, 10), @@ -3547,14 +3695,30 @@ class ToolConfig(BaseFeatureConfig): wall_material=materials.ROOM_WALL_MATERIALS, wall_scale_x=None, wall_scale_y=None) +DEFAULT_TEMPLATE_TUBE_OCCLUDER = StructuralTubeOccluderConfig( + num=0, + down_after=None, + down_step=MinMaxInt(1, 10), + material=None, + position_x=None, + position_z=None, + radius=MinMaxFloat(0.5, 5), + up_after=None, + up_step=MinMaxInt(51, 60) +) + DEFAULT_TEMPLATE_STRUCT_OBJ_MOVEMENT = StructuralObjectMovementConfig( step_begin=MinMaxInt(0, 10), step_end=None, rotation_y=[-5, 5], end_after_rotation=[90, 180, 270, 360] ) +DEFAULT_TURNTABLE_MATERIAL = MaterialTuple( + 'Custom/Materials/GreyWoodMCS', + ['grey'] +) DEFAULT_TEMPLATE_TURNTABLE = StructuralTurntableConfig( num=0, position=VectorFloatConfig(x=None, y=0, z=None), rotation_y=0, - material="Custom/Materials/GreyWoodMCS", + material=DEFAULT_TURNTABLE_MATERIAL.material, turntable_height=DEFAULT_TURNTABLE_HEIGHT, turntable_radius=MinMaxFloat( DEFAULT_TURNTABLE_MIN_RADIUS, @@ -3562,11 +3726,6 @@ class ToolConfig(BaseFeatureConfig): ), turntable_movement=DEFAULT_TEMPLATE_STRUCT_OBJ_MOVEMENT ) -DEFAULT_TEMPLATE_TOOL = ToolConfig( - num=0, position=VectorFloatConfig(None, 0, None), - rotation_y=MinMaxInt(0, 359), - shape=ALL_LARGE_BLOCK_TOOLS.copy(), guide_rails=False -) # This is structural only @@ -3591,7 +3750,20 @@ def _post_instance(scene, new_obj, template, source_template, type): else: new_obj["debug"]["labels"] = [LABEL_CONNECTED_TO_RAMP] # if we ever try to attach to l_occluders, this won't work - new_objs = _add_platform_attached_objects(scene, template, new_obj) + start_scene = copy.deepcopy(scene) + last_exception = None + for _ in range(MAX_TRIES): + try: + new_objs = _add_platform_attached_objects( + scene, template, new_obj) + return new_objs + except ILEException as e: + scene = copy.deepcopy(start_scene) + last_exception = e + continue + raise ILEException( + f'Failed adding object attached to platform={new_obj}' + ) from last_exception return new_objs @@ -3639,34 +3811,10 @@ def _handle_position_defaults( def _is_valid_floor( - scene: Scene, floor_pos, key, restrict_under_user, + scene: Scene, floor_pos: Vector2dInt, key, restrict_under_user, bounds, try_num, retries, type_str): - room_dim = scene.room_dimensions - x = floor_pos['x'] - z = floor_pos['z'] - perf_x = round(scene.performer_start.position.x) - perf_z = round(scene.performer_start.position.z) - xmax = math.floor(room_dim.x / 2) - zmax = math.floor(room_dim.z / 2) - valid = not (x < -xmax or x > xmax or z < -zmax or z > zmax) - bb = geometry.generate_floor_area_bounds( - floor_pos['x'], - floor_pos['z'] - ) - # It is expected that some holes/lava will extend beyond the walls, so we - # extend the room bounds. - room_dim_extended = Vector3d( - x=scene.room_dimensions.x + 1, - y=scene.room_dimensions.y, - z=scene.room_dimensions.z + 1) - valid = valid and geometry.validate_location_rect( - bb, - vars(scene.performer_start.position), - bounds, - vars(room_dim_extended)) - valid = valid and floor_pos not in getattr(scene, key, '') - restricted = restrict_under_user and x == perf_x and z == perf_z - valid = valid and not restricted + valid, bb = validate_floor_position( + scene, floor_pos, key, restrict_under_user, bounds) if valid: bounds.append(bb) return valid @@ -3734,17 +3882,20 @@ def _add_platform_attached_objects( new_platform['debug']['adjacent_to_wall'] = \ orig_template.adjacent_to_wall if attached_ramps or long_with_two_ramps: - # These values are just large to tell the system they have - # essentially unlimited space for ramps when we don't know. - # We can't determine exactly how much space when the base flooring - # isn't rotated the same (I.E. the floor) - available_lengths = DEFAULT_AVAIABLE_LENGTHS if below_pre_rot_pos: available_lengths = _get_space_around_platform( top_pos=top_pos, top_scale=top_scale, bottom_pos=below_pre_rot_pos, - bottom_scale=below_scale) + bottom_scale=below_scale + ) + else: + available_lengths = _get_space_around_platform( + top_pos=top_pos, + top_scale=top_scale, + bottom_pos={'x': 0, 'y': 0, 'z': 0}, + bottom_scale=scene.room_dimensions.dict() + ) if int(rotation_y) % 90 == 0 else DEFAULT_AVAILABLE_LENGTHS # Attach ramps gaps = [] @@ -3762,7 +3913,7 @@ def _add_platform_attached_objects( logger.trace( f"Attempting to attach ramp {i}/" f"{orig_template.attached_ramps} to platform.") - gap = _add_valid_ramp_with_retries( + gap = _add_valid_ramp( scene, objs, bounds=local_bounds, pre_rot_pos=top_pos, scale=top_scale, @@ -3788,13 +3939,19 @@ def _add_platform_attached_objects( scale = obj['shows'][0]['scale'] short_scales = \ ['x-', 'x+'] if scale['x'] < scale['z'] else ['z-', 'z+'] + available_lengths = _get_space_around_platform( + top_pos=below_pre_rot_pos, + top_scale=below_scale, + bottom_pos={'x': 0, 'y': 0, 'z': 0}, + bottom_scale=scene.room_dimensions.dict() + ) if int(rotation_y) % 90 == 0 else DEFAULT_AVAILABLE_LENGTHS for i in range(ramps_to_add): logger.trace( f"Attempting to attach ramp {i}/" f"{orig_template.platform_underneath_attached_ramps} to " f"underneath platform.") new_mat = new_platform['materials'][0] - gap = _add_valid_ramp_with_retries( + gap = _add_valid_ramp( scene, objs, bounds=local_bounds, pre_rot_pos=below_pre_rot_pos, scale=below_scale, @@ -3802,7 +3959,7 @@ def _add_platform_attached_objects( rotation_point=rotation_point, material=new_mat, max_angle=45, - available_lengths=DEFAULT_AVAIABLE_LENGTHS, + available_lengths=available_lengths, short_scale_long_with_two_ramps=(None if not short_scales else short_scales[i]) # noqa ) gaps.append(gap) @@ -3897,6 +4054,11 @@ def _add_platform_below(scene, obj, rotation_point, top_template): new_template = StructuralPlatformConfig( num=1, + material=[ + material_tuple.material + for material_tuple in materials.ROOM_WALL_MATERIALS + if material_tuple.material != obj['materials'][0] + ], position=VectorFloatConfig(x, 0, z), rotation_y=show['rotation']['y'], scale=VectorFloatConfig( @@ -3973,28 +4135,25 @@ def _get_pre_rotate_under_position( return random.uniform(pos_min, pos_max) -def _add_valid_ramp_with_retries( +def _add_valid_ramp( scene, objs, bounds, pre_rot_pos, scale, rotation_y, rotation_point, material, max_angle, available_lengths, short_scale_long_with_two_ramps=None): """ Returns gap location""" - for i in range(MAX_TRIES): - logger.trace(f"attempting to find ramp, try #{i}") - ramp, gap = _get_attached_ramp( - scene, - pre_rot_pos=pre_rot_pos, scale=scale, - rotation_y=rotation_y, - rotation_point=rotation_point, - material=material, - available_lengths=available_lengths, - max_angle=max_angle, - short_scale_long_with_two_ramps=short_scale_long_with_two_ramps) - if validate_all_locations_and_update_bounds( - [ramp], scene, bounds): - objs.append(ramp) - return gap + ramp, gap = _get_attached_ramp( + scene, + pre_rot_pos=pre_rot_pos, scale=scale, + rotation_y=rotation_y, + rotation_point=rotation_point, + material=material, + available_lengths=available_lengths, + max_angle=max_angle, + short_scale_long_with_two_ramps=short_scale_long_with_two_ramps) + if validate_all_locations_and_update_bounds([ramp], scene, bounds): + objs.append(ramp) + return gap raise ILEException("Unable to find valid location to attach ramp to " "platform. This is usually due too many ramps for the" "amount of space.") @@ -4038,8 +4197,11 @@ def _get_attached_ramp(scene: Scene, pre_rot_pos: dict, scale: dict, available_lengths[edge] - performer_buffer, ATTACHED_RAMP_MAX_LENGTH) - min_ramp_length = psy / math.tan(math.radians(max_angle)) + if max_ramp_length <= 0: + continue + min_ramp_length = round(psy / math.tan(math.radians(max_angle)), 4) min_ramp_length = max(min_ramp_length, ATTACHED_RAMP_MIN_LENGTH) + # what angle do we need to get to the necessary height given the max # length. angle_needed = math.degrees(math.atan(psy / (max_ramp_length))) @@ -4267,14 +4429,19 @@ def _reconcile_material( default_materials: List[MaterialTuple], prohibited_material: str = None ) -> MaterialTuple: + # If the material_choice is a MaterialTuple, return it. if isinstance(material_choice, MaterialTuple): return material_choice output = None + # If the material_choice is a string, convert it to a MaterialTuple. + # If a string list, choose a random string, and then convert it. + # Ensure it is not the prohibited_material. if material_choice: output = choose_material_tuple_from_material( material_choice, prohibited_material ) + # Otherwise randomly choose from the other given materials. if not output: output = choose_material_tuple_from_material( [item.material for item in default_materials], @@ -4302,7 +4469,7 @@ def _modify_for_hole(type, base, target_dimensions): def _convert_base_occluding_wall_to_holed_wall( - base: dict, target_dim: Vector3d): + base: SceneObject, target_dim: Vector3d): l_col = copy.deepcopy(base) r_col = copy.deepcopy(base) top = copy.deepcopy(base) @@ -4415,8 +4582,9 @@ def _get_projectile_idl( return idl, True use_random = template is None or (template.projectile_shape is None and + template.projectile_scale is None and template.projectile_material is None and - template.projectile_scale is None) + template.projectile_dimensions is None) if use_random: proj = InteractableObjectConfig(labels=labels) else: @@ -4429,7 +4597,9 @@ def _get_projectile_idl( # If the shape isn't in shapes_to_scales, then something is wrong, # so throw an error. scale = template.projectile_scale or shapes_to_scales[shape] + dimensions = template.projectile_dimensions or None proj = InteractableObjectConfig( + dimensions=dimensions, labels=( [label for label in labels if label != TARGET_LABEL] if isinstance(labels, list) else labels @@ -4449,16 +4619,16 @@ def _get_projectile_idl( return idl, False -def is_wall_too_close(new_wall: Dict[str, Any]) -> bool: +def is_wall_too_close(new_wall: SceneObject) -> bool: """Return if the given wall object is too close to any existing parallel walls in the object repository.""" - new_wall_rotation = new_wall['shows'][0]['rotation'] + new_wall_rotation = round(new_wall['shows'][0]['rotation']['y'], 2) # Only run this check if the wall is perfectly horizontal or vertical. # TODO Should we check all existing walls that are parallel to this wall, # regardless of starting rotation? We'd need to update the math. - if new_wall_rotation['y'] % 90 != 0: + if new_wall_rotation % 90 != 0: return False - new_wall_is_horizontal = (new_wall_rotation['y'] % 180 == 0) + new_wall_is_horizontal = (new_wall_rotation % 180 == 0) new_wall_position = new_wall['shows'][0]['position'] new_wall_scale = new_wall['shows'][0]['scale'] new_wall_thickness_halved = (new_wall_scale['z'] / 2.0) @@ -4466,16 +4636,17 @@ def is_wall_too_close(new_wall: Dict[str, Any]) -> bool: object_repository = ObjectRepository.get_instance() walls = object_repository.get_all_from_labeled_objects('walls') or [] for old_wall in walls: - old_wall_position = old_wall.instance['shows'][0]['position'] - old_wall_rotation = old_wall.instance['shows'][0]['rotation'] + old_wall_show = old_wall.instance['shows'][0] + old_wall_position = old_wall_show['position'] + old_wall_rotation = round(old_wall_show['rotation']['y'], 2) # Only check this wall if it's perfectly horizontal or vertical. - if old_wall_rotation['y'] % 90 != 0: + if old_wall_rotation % 90 != 0: continue - old_wall_is_horizontal = (old_wall_rotation['y'] % 180 == 0) + old_wall_is_horizontal = (old_wall_rotation % 180 == 0) if old_wall_is_horizontal == new_wall_is_horizontal: major_axis = 'z' if old_wall_is_horizontal else 'x' minor_axis = 'x' if old_wall_is_horizontal else 'z' - old_wall_scale = old_wall.instance['shows'][0]['scale'] + old_wall_scale = old_wall_show['scale'] old_wall_thickness_halved = (old_wall_scale['z'] / 2.0) old_wall_width_halved = (old_wall_scale['x'] / 2.0) distance_adjacent = (abs( @@ -4506,7 +4677,7 @@ def is_wall_too_close(new_wall: Dict[str, Any]) -> bool: (FeatureTypes.RAMPS, StructuralRampCreationService), (FeatureTypes.THROWERS, StructuralThrowerCreationService), (FeatureTypes.WALLS, StructuralWallCreationService), - (FeatureTypes.TOOLS, StructuralToolsCreationService), + (FeatureTypes.TUBE_OCCLUDERS, StructuralTubeOccluderCreationService), (FeatureTypes.TURNTABLES, StructuralTurntableCreationService) ]: FeatureCreationService.register_creation_service( diff --git a/ideal_learning_env/structural_objects_component.py b/ideal_learning_env/structural_objects_component.py index b96c038..9110f2a 100644 --- a/ideal_learning_env/structural_objects_component.py +++ b/ideal_learning_env/structural_objects_component.py @@ -2,9 +2,22 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional, Union -from generator import ALL_LARGE_BLOCK_TOOLS, materials +from generator import materials from generator.scene import Scene -from ideal_learning_env.structural_object_service import ( + +from .choosers import choose_counts, choose_random +from .components import ILEComponent +from .decorators import ile_config_setter +from .defs import ( + TARGET_LABEL, + ILEDelayException, + RandomizableString, + find_bounds, + return_list +) +from .numerics import MinMaxInt, RandomizableInt +from .object_services import KeywordLocationConfig, ObjectRepository +from .structural_object_service import ( DOOR_MATERIAL_RESTRICTIONS, DROPPER_SHAPES, PLACER_SHAPES, @@ -27,18 +40,11 @@ StructuralPlatformConfig, StructuralRampConfig, StructuralThrowerConfig, + StructuralTubeOccluderConfig, StructuralTurntableConfig, StructuralWallConfig, - ToolConfig, WallSide ) - -from .choosers import choose_counts, choose_random -from .components import ILEComponent -from .decorators import ile_config_setter -from .defs import TARGET_LABEL, ILEDelayException, find_bounds, return_list -from .numerics import MinMaxInt -from .object_services import KeywordLocationConfig, ObjectRepository from .validators import ValidateNumber, ValidateOptions logger = logging.getLogger(__name__) @@ -73,6 +79,8 @@ class RandomStructuralObjectConfig(): - `throwers`: A random thrower that throws a random object between step 0 and step 10. Throw force is 500 to 1000 times the mass of the thrown object. + - `tube_occluders`: A random tube occluder. + - `turntables`: A random turntable. - `walls`: A random interior room wall. Default: All types - `num` (int, or list of ints, or [MinMaxInt](#MinMaxInt)): The number of @@ -90,12 +98,12 @@ class RandomStructuralObjectConfig(): structural object types. """ - type: Union[str, List[str]] = None - num: Union[int, MinMaxInt, List[Union[int, MinMaxInt]]] = 0 + type: RandomizableString = None + num: RandomizableInt = 0 keyword_location: Union[KeywordLocationConfig, List[KeywordLocationConfig]] = None - labels: Union[str, List[str]] = None - relative_object_label: Union[str, List[str]] = None + labels: RandomizableString = None + relative_object_label: RandomizableString = None class SpecificStructuralObjectsComponent(ILEComponent): @@ -591,6 +599,43 @@ class SpecificStructuralObjectsComponent(ILEComponent): ``` """ + structural_tube_occluders: Union[ + StructuralTubeOccluderConfig, + List[StructuralTubeOccluderConfig] + ] = None + """ + ([StructuralTubeOccluderConfig](#StructuralTubeOccluderConfig), or list + of [StructuralTubeOccluderConfig](#StructuralTubeOccluderConfig) dict) -- + One or more templates containing properties needed to generate tube + occluders. Default: None + + + Simple Example: + ``` + structural_tube_occluders: + - num: 0 + ``` + + Advanced Example: + ``` + structural_tube_occluders: + - num: + min: 1 + max: 3 + - num: 1 + position: + x: [1, 2] + y: 0 + z: + min: -3 + max: 3 + radius: 4 + material: WOOD_MATERIALS + down_step: 21 + up_step: 81 + ``` + """ + structural_turntables: Union[StructuralTurntableConfig, List[StructuralTurntableConfig]] = 0 """ @@ -645,50 +690,6 @@ class SpecificStructuralObjectsComponent(ILEComponent): material: AI2-THOR/Materials/Metals/BrushedAluminum_Blue - ``` - """ - - tools: Union[ToolConfig, List[ToolConfig]] = 0 - """ - ([ToolConfig](#ToolConfig), or list of [ToolConfig](#ToolConfig) dict) -- - Groups of large block tool configurations and how many should be generated - from the given options. - Default: 0 - - - Simple Example: - ``` - tools: - - num: 0 - ``` - - Advanced Example: - ``` - tools: - - num: - min: 1 - max: 3 - - num: 1 - shape: tool_rect_1_00_x_9_00 - position: - x: [1,2] - y: 0 - z: - min: -3 - max: 3 - - num: [1, 3] - shape: - - tool_rect_0_50_x_4_00 - - tool_rect_0_75_x_4_00 - - tool_rect_1_00_x_4_00 - position: - x: [4, 5] - y: 0 - z: - min: -5 - max: -4 - - ``` """ @@ -843,6 +844,22 @@ def set_lava(self, data: Any) -> None: def set_structural_occluding_walls(self, data: Any) -> None: self.structural_occluding_walls = data + @ile_config_setter(validator=ValidateNumber( + props=['down_step'], min_value=1, null_ok=True + )) + @ile_config_setter(validator=ValidateOptions( + props=['material'], + options=(materials.ALL_UNRESTRICTED_MATERIAL_LISTS_AND_STRINGS) + )) + @ile_config_setter(validator=ValidateNumber( + props=['radius'], min_value=0.5, null_ok=True + )) + @ile_config_setter(validator=ValidateNumber( + props=['up_step'], min_value=0, null_ok=True + )) + def set_structural_tube_occluders(self, data: Any) -> None: + self.structural_tube_occluders = data + # cogs have the same material restrictions as doors @ile_config_setter(validator=ValidateOptions( props=['material'], @@ -887,14 +904,8 @@ def set_placers(self, data: Any) -> None: def set_doors(self, data: Any) -> None: self.doors = data - @ile_config_setter(validator=ValidateNumber( - props=['num'], min_value=0)) - @ile_config_setter(validator=ValidateOptions( - props=['shape'], options=ALL_LARGE_BLOCK_TOOLS)) - def set_tools(self, data: Any) -> None: - self.tools = data - # Override + def update_ile_scene(self, scene: Scene) -> Scene: logger.info('Configuring specific structural objects...') self._delayed_templates = [] @@ -916,8 +927,8 @@ def update_ile_scene(self, scene: Scene) -> Scene: (FeatureTypes.OCCLUDING_WALLS, self.structural_occluding_walls), (FeatureTypes.PLACERS, self.placers), (FeatureTypes.DOORS, self.doors), + (FeatureTypes.TUBE_OCCLUDERS, self.structural_tube_occluders), (FeatureTypes.TURNTABLES, self.structural_turntables), - (FeatureTypes.TOOLS, self.tools) ] for s_type, templates in structural_type_templates: @@ -941,7 +952,7 @@ def update_ile_scene(self, scene: Scene) -> Scene: def get_num_delayed_actions(self) -> int: return len(self._delayed_templates) - def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def run_delayed_actions(self, scene: Scene) -> Scene: delayed = self._delayed_templates self._delayed_templates = [] if delayed: @@ -962,7 +973,8 @@ def get_delayed_action_error_strings(self) -> List[str]: FeatureTypes.AGENT, FeatureTypes.INTERACTABLE, FeatureTypes.PARTITION_FLOOR, - FeatureTypes.TARGET + FeatureTypes.TARGET, + FeatureTypes.TOOLS ] ALL_STRUCTURAL_TYPES = [ item.name.lower() for item in FeatureTypes @@ -973,8 +985,7 @@ def get_delayed_action_error_strings(self) -> List[str]: item.name.lower() for item in FeatureTypes if item not in (STRUCTURAL_TYPES_NEEDING_TARGET + NON_STRUCTURAL_TYPES) ] -# TODO MCS-1206 Include tools here for now, but move them to the default -# keyword_objects setting in the future since they're not structures. + DEFAULT_VALUE = [RandomStructuralObjectConfig( ALL_STRUCTURAL_TYPES_NO_TARGET, MinMaxInt(2, 4) @@ -1041,7 +1052,6 @@ class RandomStructuralObjectsComponent(ILEComponent): - platforms - ramps - throwers - - tools - turntables - walls num: @@ -1058,7 +1068,7 @@ def __init__(self, data: Dict[str, Any]): super().__init__(data) # Override - def update_ile_scene(self, scene: Scene) -> Dict[str, Any]: + def update_ile_scene(self, scene: Scene) -> Scene: logger.info('Configuring random structural objects...') bounds = find_bounds(scene) @@ -1138,10 +1148,10 @@ def _create_random_template( labels=template.labels, projectile_labels=relative_object_label ) + if structural_type == FeatureTypes.TUBE_OCCLUDERS: + return StructuralTubeOccluderConfig(labels=template.labels) if structural_type == FeatureTypes.TURNTABLES: return StructuralTurntableConfig(labels=template.labels) - if structural_type == FeatureTypes.TOOLS: - return ToolConfig(labels=template.labels) if structural_type == FeatureTypes.WALLS: return StructuralWallConfig(labels=template.labels) # Otherwise return None; defaults will be used automatically. diff --git a/ideal_learning_env/validation_component.py b/ideal_learning_env/validation_component.py index f524aaa..c038d47 100644 --- a/ideal_learning_env/validation_component.py +++ b/ideal_learning_env/validation_component.py @@ -3,12 +3,13 @@ from extremitypathfinder import PolygonEnvironment from extremitypathfinder.plotting import PlottingEnvironment +from machine_common_sense.config_manager import Vector2dInt from shapely.geometry import JOIN_STYLE, mapping -from generator import ObjectBounds, geometry -from ideal_learning_env.decorators import ile_config_setter +from generator import ObjectBounds, Scene, geometry from .components import ILEComponent +from .decorators import ile_config_setter from .defs import ILEException from .structural_object_service import ( LABEL_BIDIRECTIONAL_RAMP, @@ -60,7 +61,7 @@ def __init__(self, data: Dict[str, Any]): super().__init__(data) # Override - def update_ile_scene(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def update_ile_scene(self, scene: Scene) -> Scene: # Might be useful later but also helps with testing self.last_path = None self.last_distance = None @@ -110,7 +111,7 @@ def _performer_not_within_room(self, scene) -> bool: "z": scene.room_dimensions.z }) - def _find_valid_path(self, scene): + def _find_valid_path(self, scene: Scene): if self.check_valid_path: logger.info('Running path validation check...') @@ -187,18 +188,18 @@ def _compute_boundary(self, scene): return [(-x, -y), (x, -y), (x, y), (-x, y)] def _is_ramp_with_path(self, obj): - if(obj and obj["debug"] and "labels" in obj["debug"]): + if (obj and obj["debug"] and "labels" in obj["debug"]): labels = obj["debug"]["labels"] - if(LABEL_RAMP in labels and + if (LABEL_RAMP in labels and LABEL_BIDIRECTIONAL_RAMP in labels): return True return False def _is_platform_with_path(self, obj): - if(obj and obj["debug"] and "labels" in obj["debug"]): + if (obj and obj["debug"] and "labels" in obj["debug"]): labels = obj["debug"]["labels"] - if(LABEL_PLATFORM in labels and + if (LABEL_PLATFORM in labels and LABEL_CONNECTED_TO_RAMP in labels): return True return False @@ -239,7 +240,7 @@ def _get_platform_side(self, platform, bounds_list, side, gaps = platform["debug"]["gaps"] isFrontBack = side in ["front", "back"] bb_width = 0.1 - if(gaps.get(side) is not None): + if (gaps.get(side) is not None): count = 0 max = len(gaps[side]) @@ -379,16 +380,16 @@ def _add_polygon_to_blocked(self, bounding_box, buffer, blocked_area): def _add_blocked_areas( self, - areas: list, + areas: List[Vector2dInt], blocked_area: list, area_buffer: float = 0.5 + geometry.PERFORMER_HALF_WIDTH ) -> None: for area in areas: blocked_area.append([ - (area["x"] - area_buffer, area["z"] - area_buffer), - (area["x"] - area_buffer, area["z"] + area_buffer), - (area["x"] + area_buffer, area["z"] + area_buffer), - (area["x"] + area_buffer, area["z"] - area_buffer) + (area.x - area_buffer, area.z - area_buffer), + (area.x - area_buffer, area.z + area_buffer), + (area.x + area_buffer, area.z + area_buffer), + (area.x + area_buffer, area.z - area_buffer) ]) def is_object_path_blocking(self, obj, tgt): @@ -416,7 +417,7 @@ def get_num_delayed_actions(self) -> int: return 1 if (delay or no_valid_path) else 0 - def run_delayed_actions(self, scene: Dict[str, Any]) -> Dict[str, Any]: + def run_delayed_actions(self, scene: Scene) -> Scene: delay = self._delayed_target or self._delayed_performer_start no_valid_path = (self.last_path == [] and self.last_distance is None) diff --git a/ile.py b/ile.py index 2d9a1ee..a4874d6 100755 --- a/ile.py +++ b/ile.py @@ -96,7 +96,23 @@ def _handle_delayed_actions(component_list, scene): f"Failed to execute any delayed actions. This can occur when" f" a required label doesn't exist, a label is mispelled, or " f"there is a circular dependency. Please verify all spellings" - f".{nl}Reasons:{nl} {f'{nl} '.join(reasons)}") + f".{nl}Reasons:{nl} {f'{nl} '.join(reasons)}" + ) + + # Swap the GlobalSettingsComponent to be after ShortcutComponent so + # performer_look_at happens after any position change from + # shortcut_component + global_settings_component_index = component_list.index([ + c for c in component_list if + isinstance(c, GlobalSettingsComponent)][0]) + shortcut_component_index = component_list.index([ + c for c in component_list if + isinstance(c, ShortcutComponent)][0]) + temp = component_list[global_settings_component_index] + component_list[global_settings_component_index] = component_list[ + shortcut_component_index] + component_list[shortcut_component_index] = temp + # Loop through components and run delayed actions if the components # have any. for component in component_list: @@ -129,6 +145,7 @@ def main(args): component_class(config_data) for component_class in ILE_COMPONENTS ] + max_tries = 1 if args.throw_error else MAX_TRIES suffix = ".json" for index in list(range(args.number)): scene = None @@ -148,13 +165,13 @@ def main(args): # Try creating the scene multiple times, in case a randomized setup # doesn't work the first time. tries = 0 - while tries < MAX_TRIES: + while tries < max_tries: tries += 1 try: if (tries > 1): logger.info( f'Retrying generaton of scene {index + 1} of ' - f'{args.number} (try {tries} / {MAX_TRIES}), ' + f'{args.number} (try {tries} / {max_tries}), ' f'filename: {scene_filename}{suffix}' ) scene = generate_ile_scene(component_list, scene_index) @@ -169,14 +186,14 @@ def main(args): ): error_message = ( f'Failed to generate scene {index + 1} of ' - f'{args.number} (try {tries} / {MAX_TRIES}), ' + f'{args.number} (try {tries} / {max_tries}), ' f'filename: {scene_filename}{suffix}' ) - if logger.isEnabledFor(logging.DEBUG) or (tries >= MAX_TRIES): + if logger.isEnabledFor(logging.DEBUG) or (tries >= max_tries): logging.exception(error_message) else: logger.info(error_message) - if tries >= MAX_TRIES: + if tries >= max_tries: sys.exit(1) # If successful, save the normal and debug JSON scene files. diff --git a/ile_configs/auto_generated_null_template.yaml b/ile_configs/auto_generated_null_template.yaml index 64246dd..82f1f7b 100644 --- a/ile_configs/auto_generated_null_template.yaml +++ b/ile_configs/auto_generated_null_template.yaml @@ -62,6 +62,8 @@ structural_ramps: num: 0 structural_throwers: num: 0 +structural_tube_occluders: + - num: 0 structural_turntables: - num: 0 structural_walls: diff --git a/ile_configs/interactive_collision.yaml b/ile_configs/interactive_collision.yaml index fb61da2..75d58c4 100644 --- a/ile_configs/interactive_collision.yaml +++ b/ile_configs/interactive_collision.yaml @@ -22,11 +22,13 @@ goal: rotation: - y: 0 -# Use a specific room size for this task. +# Use a room with a specific width and a pseudo-variable depth. room_dimensions: x: 12 y: 4 - z: 24 + z: + min: 18 + max: 30 # Generate an occluding wall containing two doors (called a "door occluder"), # a line of lava bisecting the room, and a platform at one side of the room, @@ -39,7 +41,7 @@ shortcut_double_door_choice: start_drop_step: min: 1 max: 10 - occluder_wall_position_z: -5 + occluder_distance_from_performer: 6.5 # Allow only one door to be opened in each scene (forced choice). restrict_open_doors: true @@ -70,8 +72,8 @@ structural_throwers: # Roll the green ball immediately as the scene begins with strong force. throw_step: 1 throw_force: - min: 12.5 - max: 15.0 + min: 8.0 + max: 12.0 # Only the thrower's green ball can be green (not the walls or the floor). excluded_colors: green diff --git a/ile_configs/interactive_hidden_set_rotation.yaml b/ile_configs/interactive_hidden_set_rotation.yaml new file mode 100644 index 0000000..943b992 --- /dev/null +++ b/ile_configs/interactive_hidden_set_rotation.yaml @@ -0,0 +1,125 @@ +# Use a specific room size and starting position/rotation for this task. +room_dimensions: + x: 12 + y: 4 + z: 12 +performer_start_position: + x: 0 + y: 0 + z: -3.5 +performer_start_rotation: + x: 0 + y: 0 + +# The performer agent is frozen until the turntable is finished rotating and placers are done moving. +freeze_while_moving: [placers, turntables] + +# If desired, force the performer agent to circumnavigate the turntable. +# 1. Uncomment the "sidesteps" and "freezes" config options below. +# 2. Remove the "freeze_while_moving" config option above. +# 3. Change the "up_step" in "structural_tube_occluders" from 170 to 405, +# so the tube occluder does not go up until the performer agent has +# finished circumnavigating the turntable up to 360 degrees. +# 4. Change the "turntable_movement.rotation_y" in "structural_turntables" +# from [5, -5] to 0, so the turntable does not rotate as well. +# sidesteps: +# - begin: 96 +# object_label: turntables +# degrees: [90, 180, 270, 360, -90, -180, -270, -360] +# freezes: +# - begin: 1 +# end: 95 + +# Add a Retrieval goal with a soccer ball as the target object. +goal: + category: retrieval + target: + shape: soccer_ball + scale: 1.0 + +# Only the target can be a soccer ball. +excluded_shapes: soccer_ball + +# Generate a large turntable (cog) with a specific color, height, position, +# radius, and starting rotation (facing). +structural_turntables: + - num: 1 + position: + x: 0 + y: 0 + z: 1 + rotation_y: 0 + turntable_radius: 2 + # Start rotating the turntable after the lids have been placed on their + # containers and the tube occluder has gone down to hide it. It will + # rotate by either 5 or -5 degrees on each step, for a total rotation of + # either 90, 180, 270, or 360 degrees. + turntable_movement: + step_begin: 96 + rotation_y: [5, -5] + end_after_rotation: [90, 180, 270, 360] + +# Generate a tube occluder large enough to surround the turntable. +structural_tube_occluders: + - num: 1 + position_x: 0 + position_z: 1 + radius: 5.2 + # The tube occluder goes down to hide the turntable after the lids have + # been placed on their containers. + down_step: 81 + # The tube occluder goes back up to reveal the turntable after enough + # time for the turntable to rotate up to 360 degrees (though it may have + # actually rotated less than 360 degrees). + up_step: 170 + +specific_interactable_objects: + # Generate one, two, or three identical containers using a specific scale + # and shape that are each positioned on top of the turntable (cog). + - num: [1, 2, 3] + labels: containers + scale: 0.4 + shape: separate_container + keyword_location: + keyword: on_center + relative_object_label: turntables + # Each container will either be on the left edge, the right edge, or + # the middle of the turntable (cog). + position_relative_to_start: + - x: -0.9 + z: 0 + - x: 0 + z: 0 + - x: 0.9 + z: 0 + # The containers should all have the same starting rotation (facing). + rotation: + x: 0 + y: 0 + z: 0 + # The containers' lids descend after the target_placer finishes moving. + separate_lid_after: target_placer + +placers: + # Generate a placer that starts holding the target object, descends from + # the ceiling above one of the containers, releases the target object into + # the chosen container, and then ascends back into the ceiling. + - num: 1 + # Always start moving immediately after the scene begins (step 1). + activation_step: 1 + placed_object_labels: target + placed_object_above: containers + placed_object_rotation: 0 + end_height: 0.35 + labels: target_placer + +# Can only open one container in each scene. +restrict_open_objects: true + +# Override the defaults to avoid randomly generating any other objects besides +# the turntable, containers, lids, placers, and target. Remove these lines if +# you want additional randomly generated objects. +random_structural_objects: + - num: 0 +keyword_objects: + - num: 0 diff --git a/ile_configs/interactive_imitation.yaml b/ile_configs/interactive_imitation.yaml index 6a3f45d..b7ca996 100644 --- a/ile_configs/interactive_imitation.yaml +++ b/ile_configs/interactive_imitation.yaml @@ -34,12 +34,18 @@ shortcut_imitation_task: # the other agent is moved out of the way. kidnap_option: - agent_only - # The containers are repositioned, but still in the same order. + # The containers are repositioned and rotated - containers - # The containers are repositioned AND rotated. - - containers_rotate # The performer agent is repositioned, but still faces the containers. - performer + # Relative clockwise rotation containers will rotate if the kidnap option + # is containers. + # Options for RIGHT SIDE: [45, 135, 225, 270, 315] + # Options for LEFT SIDE: [45, 90, 135, 225, 315] + relative_container_rotation: [45, 90, 135, 225, 270, 315] + # Global absolute rotation containers will rotate if the kidnap option + # is containers. Will override `relative_container_rotation`. + global_container_rotation: [45, 135, 180, 225, 315] # Override the defaults to avoid randomly generating any other objects besides # the ones required for the Imitation task. Remove these lines if you want diff --git a/ile_configs/interactive_moving_target_prediction.yaml b/ile_configs/interactive_moving_target_prediction.yaml index c19a72d..e39f8b0 100644 --- a/ile_configs/interactive_moving_target_prediction.yaml +++ b/ile_configs/interactive_moving_target_prediction.yaml @@ -22,11 +22,10 @@ room_dimensions: z: 12 # Start on top of a platform, looking directly at the target object. -performer_start_position: - x: 0 - y: 1.762 - z: -5.5 +shortcut_start_on_platform: true + performer_look_at: target + structural_platforms: num: 1 position: @@ -38,6 +37,7 @@ structural_platforms: x: 1 y: 1 z: 1 + labels: start_structure # Must rotate in a complete circle at the start of each scene. circles: [1] diff --git a/ile_configs/interactive_multi_tool_use.yaml b/ile_configs/interactive_multi_tool_use.yaml new file mode 100644 index 0000000..1de8cf3 --- /dev/null +++ b/ile_configs/interactive_multi_tool_use.yaml @@ -0,0 +1,77 @@ +# Add a Retrieval goal with a soccer ball as the target object. +goal: + category: retrieval + target: + shape: soccer_ball + scale: + min: 1.0 + max: 3.0 + # The soccer ball is always positioned in a corner of the room. + keyword_location: + keyword: adjacent_corner + adjacent_distance: + x: 3 + y: 0 + z: 3 + # The soccer ball is always surrounded by lava of size 3 to ensure the + # rectangular tool alone cannot be used to retrieve it. + surrounded_by_lava: true + surrounding_lava_size: 3 + +# 1. Generate a hooked tool in the middle of the room and surrounded by lava. +# 2. Generate a rectangular tool of length 3 aligned with the hooked tool. +tools: + - num: 1 + # Adjust the tool_type, position, and/or rotation as desired. + tool_type: [hooked, isosceles] + position: + x: + min: -3.0 + max: 3.0 + y: 0 + z: + min: -3.0 + max: 3.0 + rotation_y: [0, 45, 90, 135, 180, 225, 270, 315] + surrounded_by_lava: true + labels: hooked_tool + - num: 1 + shape: + - tool_rect_0_50_x_3_00 + - tool_rect_0_75_x_3_00 + - tool_rect_1_00_x_3_00 + align_with: hooked_tool + # Starting distance between the rectangular tool and the hooked tool. + align_distance: + min: 1.0 + max: 2.0 + labels: rect_tool + +# The performer agent always starts near and looking at the rectangular tool. +# Remove these two options if you want to be randomly positioned in the room. +performer_look_at: rect_tool +performer_starts_near: + label: rect_tool + distance: + min: 0.5 + max: 1.0 + +# Only generate medium or large rooms. +room_dimensions: + x: + min: 15 + max: 30 + y: 3 + z: + min: 15 + max: 30 +room_shape: square + +# Only the target can be a soccer ball. +excluded_shapes: soccer_ball + +# Override the defaults to avoid randomly generating any other objects. +keyword_objects: + - num: 0 +random_structural_objects: + - num: 0 diff --git a/ile_configs/interactive_number_comparison.yaml b/ile_configs/interactive_number_comparison.yaml index fddba05..050cda8 100644 --- a/ile_configs/interactive_number_comparison.yaml +++ b/ile_configs/interactive_number_comparison.yaml @@ -64,6 +64,39 @@ room_dimensions: # Generate a platform bisecting the room. shortcut_bisecting_platform: is_short: true + is_thin: true + +# The performer agent is frozen until all the objects placers and moving_occluders are finished moving. +freeze_while_moving: [left_occluder, right_occluder] + +# Generate two occluding walls that descend at the beginning of the scene to +# completely occlude the objects positioned on each side of the room. +# The performer agent must remember how many objects are on each side. +# If you do not want occluders in your scenes, remove this entire config +# option. +structural_moving_occluders: + - num: 1 + occluder_height: 1.5 + occluder_thickness: 0.1 + occluder_width: 2.5 + origin: 'left' + position_x: -1.55 + position_z: 0 + reverse_direction: false + rotation_y: 0 + labels: left_occluder + move_down_only: true + - num: 1 + occluder_height: 1.5 + occluder_thickness: 0.1 + occluder_width: 2.5 + origin: 'right' + position_x: 1.55 + position_z: 0 + reverse_direction: false + rotation_y: 0 + labels: right_occluder + move_down_only: true # Generate one to four objects identical to the target objects but on the # opposite side of the room. There will always be fewer of these objects than diff --git a/ile_configs/interactive_spatial_reference.yaml b/ile_configs/interactive_spatial_reference.yaml index d44b14c..ab49af1 100644 --- a/ile_configs/interactive_spatial_reference.yaml +++ b/ile_configs/interactive_spatial_reference.yaml @@ -4,6 +4,9 @@ room_dimensions: y: 4 z: 6 +# The performer agent is frozen until all the agents and turntables are finished moving. +freeze_while_moving: [agent, turntables] + # Generate a short, thin platform bisecting the room, and another platform on # the far wall of the room. shortcut_bisecting_platform: diff --git a/ile_configs/interactive_spatial_reorientation.yaml b/ile_configs/interactive_spatial_reorientation.yaml index 109b7c8..03dc0d8 100644 --- a/ile_configs/interactive_spatial_reorientation.yaml +++ b/ile_configs/interactive_spatial_reorientation.yaml @@ -16,7 +16,7 @@ room_dimensions: # Choose the wall material from a color palette with bright, distinct colors. # All four exterior room walls will be the same color. -wall_material: OPPOSITE_MATERIALS +wall_material: WALL_OPPOSITE_MATERIALS # Sometimes one of the exterior room walls will be the opposite color instead. side_wall_opposite_colors: [true, false] @@ -26,7 +26,8 @@ trapezoidal_room: [true, false] # Generate a platform bisecting the room. shortcut_bisecting_platform: - has_long_blocking_wall: true + has_double_blocking_wall: true + position_z: -5.5 # Generate a random bin on each side of the room. keyword_objects: diff --git a/ile_configs/interactive_support_relations.yaml b/ile_configs/interactive_support_relations.yaml index 0792173..3853790 100644 --- a/ile_configs/interactive_support_relations.yaml +++ b/ile_configs/interactive_support_relations.yaml @@ -45,6 +45,7 @@ placers: y: 7.305 z: 0.75 placed_object_rotation: [0, 180] + placed_object_scale: 0.75 placed_object_shape: - container_symmetric_01 - container_symmetric_02 @@ -67,7 +68,6 @@ placers: - container_asymmetric_07 - container_asymmetric_08 - container_asymmetric_09 - - container_asymmetric_10 - container_asymmetric_11 - container_asymmetric_12 # The placer activates on the first action/step, and deactivates diff --git a/ile_configs/interactive_trajectory.yaml b/ile_configs/interactive_trajectory.yaml index 9e2f991..74dfc50 100644 --- a/ile_configs/interactive_trajectory.yaml +++ b/ile_configs/interactive_trajectory.yaml @@ -6,11 +6,13 @@ goal: shape: soccer_ball scale: 2.0 -# Use a specific room size for this task. +# Use a room with a specific width and a pseudo-variable depth. room_dimensions: x: 12 y: 4 - z: 24 + z: + min: 18 + max: 30 # Generate an occluding wall containing two doors (called a "door occluder"), # a line of lava bisecting the room, and a platform at one side of the room, @@ -23,7 +25,7 @@ shortcut_double_door_choice: start_drop_step: min: 1 max: 10 - occluder_wall_position_z: -5 + occluder_distance_from_performer: 6.5 # Allow only one door to be opened in each scene (forced choice). restrict_open_doors: true @@ -74,8 +76,8 @@ structural_throwers: # Roll the soccer ball immediately as the scene begins with strong force. throw_step: 1 throw_force: - min: 12.5 - max: 15.0 + min: 8.0 + max: 12.0 # Only the thrower's green ball can be green (not the walls or the floor). excluded_colors: green diff --git a/ile_configs/tools.yaml b/ile_configs/tools.yaml index 74cbda4..eb6e4a2 100644 --- a/ile_configs/tools.yaml +++ b/ile_configs/tools.yaml @@ -10,6 +10,8 @@ shortcut_lava_target_tool: tool_rotation: [0, 15, 30, 45, 60, 75, 90] # Generate either a rectangular or a hooked (L-shaped) tool. tool_type: [rectangular, hooked] + # Whether to generate guide rails for the tool. + guide_rails: False # Sometimes (25%) make an extra tool that is "too small" to use effectively. # Remove this whole block if you do not wish to generate this extra tool. diff --git a/requirements.txt b/requirements.txt index 39e4fff..b227a45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,15 @@ -i https://pypi.org/simple -autopep8==1.5.4 +autopep8==1.7.1; python_version<"3.8" +autopep8==2.0.2; python_version>="3.8" extremitypathfinder==2.1.0 -flake8==3.8.3 -isort==5.8.0 -machine_common_sense==0.6.4 -numpy==1.18.5 -pdoc3==0.9.2 -pre-commit==2.16.0 -pytest==6.0.1 -pyyaml==5.3.1 -shapely==1.7.0 +flake8==5.0.4; python_version<"3.8" +flake8==6.0.0; python_version>="3.8" +isort==5.11.5; python_version<"3.8" +isort==5.12.0; python_version>="3.8" +machine_common_sense==0.7.0 +numpy==1.23.5 +pdoc3==0.10.0 +pre-commit==2.21.0; python_version<"3.8" +pre-commit==3.2.2; python_version>="3.8" +pytest==7.3.0 +pyyaml==6.0 diff --git a/setup.py b/setup.py index 149e540..6457e50 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ import setuptools -from machine_common_sense import _version as version with open('README.md', 'r') as readme_file: long_description = readme_file.read() @@ -7,7 +6,7 @@ setuptools.setup( name='mcs_scene_generator', - version=version.__version__, + version=1.9, maintainer='Next Century, a wholly owned subsidiary of CACI', maintainer_email='mcs-ta2@machinecommonsense.com', url='https://github.com/NextCenturyCorporation/mcs-scene-generator/', @@ -24,7 +23,7 @@ python_requires=">=3.8", packages=setuptools.find_packages(), install_requires=[ - 'machine-common-sense>=0.6.4', + 'machine-common-sense>=0.7.0', 'pyyaml>=5.3.1', 'shapely>=1.7.0' ], diff --git a/tests/agent_scene_pair_json_converter_test.py b/tests/agent_scene_pair_json_converter_test.py index 51b4602..9285809 100644 --- a/tests/agent_scene_pair_json_converter_test.py +++ b/tests/agent_scene_pair_json_converter_test.py @@ -1,5 +1,7 @@ +import copy + import pytest -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d from generator import ( MaterialTuple, @@ -18,6 +20,7 @@ AgentConfig, ObjectConfig, ObjectConfigWithMaterial, + OccluderMode, TrueObjectBounds, _append_each_show_to_object, _choose_config_list, @@ -29,6 +32,8 @@ _create_key_object, _create_lock_wall_object_list, _create_object, + _create_occluder_object, + _create_paddle_object, _create_scene, _create_show, _create_static_wall_object_list, @@ -37,9 +42,11 @@ _fix_key_location, _identify_trial_index_starting_step, _make_true_bounds, + _move_agent_adjacent_to_goal, _move_agent_past_lock_location, _remove_extraneous_object_show, _remove_intersecting_agent_steps, + _reposition_agents_away_from_paddle, _retrieve_unit_size ) @@ -58,16 +65,301 @@ def create_ellipsoidal_bounds(center_x, center_z, radius_x, radius_z): ) -def create_simple_bounds(x1, x2, z1, z2): - rect = [(x1, z1), (x1, z2), (x2, z2), (x2, z1)] +def create_simple_bounds(x1, x2, z1, z2, y=0): + rect = [(x2, z2), (x2, z1), (x1, z1), (x1, z2)] bounds = ObjectBounds( box_xz=[Vector3d(x=x, y=0, z=z) for x, z in rect], - max_y=0, + max_y=y, min_y=0 ) return TrueObjectBounds(bounds=bounds, true_poly=bounds.polygon_xz) +def create_test_agent_moving_diagonally(trial_2=False): + bounds_a = create_simple_bounds(-2.5, -2.25, -2.5, -2.25, y=0.25) + bounds_b = create_simple_bounds(-2.25, -2.0, -2.25, -2.0, y=0.25) + bounds_c = create_simple_bounds(-2.0, -1.75, -2.0, -1.75, y=0.25) + bounds_d = create_simple_bounds(-1.75, -1.5, -1.75, -1.5, y=0.25) + bounds_e = create_simple_bounds(-1.5, -1.25, -1.5, -1.25, y=0.25) + bounds_f = create_simple_bounds(-1.25, -1.0, -1.25, -1.0, y=0.25) + bounds_g = create_simple_bounds(-1.0, -0.75, -1.0, -0.75, y=0.25) + bounds_h = create_simple_bounds(-0.75, -0.5, -0.75, -0.5, y=0.25) + bounds_i = create_simple_bounds(-0.5, -0.25, -0.5, -0.25, y=0.25) + bounds_j = create_simple_bounds(-0.25, 0, -0.25, 0, y=0.25) + bounds_k = create_simple_bounds(0, 0.25, 0, 0.25, y=0.25) + bounds_l = create_simple_bounds(0.25, 0.5, 0.25, 0.5, y=0.25) + bounds_m = create_simple_bounds(0.5, 0.75, 0.5, 0.75, y=0.25) + bounds_n = create_simple_bounds(0.75, 1.0, 0.75, 1.0, y=0.25) + bounds_o = create_simple_bounds(1.0, 1.25, 1.0, 1.25, y=0.25) + bounds_p = create_simple_bounds(1.25, 1.5, 1.25, 1.5, y=0.25) + bounds_q = create_simple_bounds(1.5, 1.75, 1.5, 1.75, y=0.25) + bounds_r = create_simple_bounds(1.75, 2.0, 1.75, 2.0, y=0.25) + position_a = {'x': -2.375, 'y': 0.125, 'z': -2.375} + position_b = {'x': -2.125, 'y': 0.125, 'z': -2.125} + position_c = {'x': -1.875, 'y': 0.125, 'z': -1.875} + position_d = {'x': -1.625, 'y': 0.125, 'z': -1.625} + position_e = {'x': -1.375, 'y': 0.125, 'z': -1.375} + position_f = {'x': -1.125, 'y': 0.125, 'z': -1.125} + position_g = {'x': -0.875, 'y': 0.125, 'z': -0.875} + position_h = {'x': -0.625, 'y': 0.125, 'z': -0.625} + position_i = {'x': -0.375, 'y': 0.125, 'z': -0.375} + position_j = {'x': -0.125, 'y': 0.125, 'z': -0.125} + position_k = {'x': 0.125, 'y': 0.125, 'z': 0.125} + position_l = {'x': 0.375, 'y': 0.125, 'z': 0.375} + position_m = {'x': 0.625, 'y': 0.125, 'z': 0.625} + position_n = {'x': 0.875, 'y': 0.125, 'z': 0.875} + position_o = {'x': 1.125, 'y': 0.125, 'z': 1.125} + position_p = {'x': 1.375, 'y': 0.125, 'z': 1.375} + position_q = {'x': 1.625, 'y': 0.125, 'z': 1.625} + position_r = {'x': 1.875, 'y': 0.125, 'z': 1.875} + agent = { + 'id': 'agent_1', + 'type': 'sphere', + 'debug': { + 'boundsAtStep': [ + bounds_a, bounds_a, bounds_a, bounds_a, bounds_a, + bounds_b, bounds_c, bounds_d, bounds_e, bounds_f, + bounds_g, bounds_h, bounds_i, bounds_j, bounds_k, + bounds_l, bounds_m, bounds_n, bounds_o, bounds_p, + bounds_q, bounds_r, bounds_r, bounds_r, bounds_r + ], + 'dimensions': {'x': 0.25, 'y': 0.25, 'z': 0.25}, + 'positionY': 0.125, + 'trialToSteps': { + 0: (0, 24) + } + }, + 'shows': [{ + 'stepBegin': 0, + 'position': position_a, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_a + }, { + 'stepBegin': 5, + 'position': position_b, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_b + }, { + 'stepBegin': 6, + 'position': position_c, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_c + }, { + 'stepBegin': 7, + 'position': position_d, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_d + }, { + 'stepBegin': 8, + 'position': position_e, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_e + }, { + 'stepBegin': 9, + 'position': position_f, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_f + }, { + 'stepBegin': 10, + 'position': position_g, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_g + }, { + 'stepBegin': 11, + 'position': position_h, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_h + }, { + 'stepBegin': 12, + 'position': position_i, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_i + }, { + 'stepBegin': 13, + 'position': position_j, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_j + }, { + 'stepBegin': 14, + 'position': position_k, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_k + }, { + 'stepBegin': 15, + 'position': position_l, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_l + }, { + 'stepBegin': 16, + 'position': position_m, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_m + }, { + 'stepBegin': 17, + 'position': position_n, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_n + }, { + 'stepBegin': 18, + 'position': position_o, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_o + }, { + 'stepBegin': 19, + 'position': position_p, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_p + }, { + 'stepBegin': 20, + 'position': position_q, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_q + }, { + 'stepBegin': 21, + 'position': position_r, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_r + }] + } + if trial_2: + bounds_2a = create_simple_bounds(2.5, 2.25, 2.5, 2.25, y=0.25) + bounds_2b = create_simple_bounds(2.25, 2.0, 2.25, 2.0, y=0.25) + bounds_2c = create_simple_bounds(2.0, 1.75, 2.0, 1.75, y=0.25) + bounds_2d = create_simple_bounds(1.75, 1.5, 1.75, 1.5, y=0.25) + bounds_2e = create_simple_bounds(1.5, 1.25, 1.5, 1.25, y=0.25) + bounds_2f = create_simple_bounds(1.25, 1.0, 1.25, 1.0, y=0.25) + bounds_2g = create_simple_bounds(1.0, 0.75, 1.0, 0.75, y=0.25) + bounds_2h = create_simple_bounds(0.75, 0.5, 0.75, 0.5, y=0.25) + bounds_2i = create_simple_bounds(0.5, 0.25, 0.5, 0.25, y=0.25) + bounds_2j = create_simple_bounds(0.25, 0, 0.25, 0, y=0.25) + bounds_2k = create_simple_bounds(0, -0.25, 0, -0.25, y=0.25) + bounds_2l = create_simple_bounds(-0.25, -0.5, -0.25, -0.5, y=0.25) + bounds_2m = create_simple_bounds(-0.5, -0.75, -0.5, -0.75, y=0.25) + bounds_2n = create_simple_bounds(-0.75, -1.0, -0.75, -1.0, y=0.25) + bounds_2o = create_simple_bounds(-1.0, -1.25, -1.0, -1.25, y=0.25) + bounds_2p = create_simple_bounds(-1.25, -1.5, -1.25, -1.5, y=0.25) + bounds_2q = create_simple_bounds(-1.5, -1.75, -1.5, -1.75, y=0.25) + bounds_2r = create_simple_bounds(-1.75, -2.0, -1.75, -2.0, y=0.25) + position_2a = {'x': 2.375, 'y': 0.125, 'z': 2.375} + position_2b = {'x': 2.125, 'y': 0.125, 'z': 2.125} + position_2c = {'x': 1.875, 'y': 0.125, 'z': 1.875} + position_2d = {'x': 1.625, 'y': 0.125, 'z': 1.625} + position_2e = {'x': 1.375, 'y': 0.125, 'z': 1.375} + position_2f = {'x': 1.125, 'y': 0.125, 'z': 1.125} + position_2g = {'x': 0.875, 'y': 0.125, 'z': 0.875} + position_2h = {'x': 0.625, 'y': 0.125, 'z': 0.625} + position_2i = {'x': 0.375, 'y': 0.125, 'z': 0.375} + position_2j = {'x': 0.125, 'y': 0.125, 'z': 0.125} + position_2k = {'x': -0.125, 'y': 0.125, 'z': -0.125} + position_2l = {'x': -0.375, 'y': 0.125, 'z': -0.375} + position_2m = {'x': -0.625, 'y': 0.125, 'z': -0.625} + position_2n = {'x': -0.875, 'y': 0.125, 'z': -0.875} + position_2o = {'x': -1.125, 'y': 0.125, 'z': -1.125} + position_2p = {'x': -1.375, 'y': 0.125, 'z': -1.375} + position_2q = {'x': -1.625, 'y': 0.125, 'z': -1.625} + position_2r = {'x': -1.875, 'y': 0.125, 'z': -1.875} + agent['debug']['boundsAtStep'].extend([ + bounds_2a, bounds_2a, bounds_2a, bounds_2a, bounds_2a, + bounds_2b, bounds_2c, bounds_2d, bounds_2e, bounds_2f, + bounds_2g, bounds_2h, bounds_2i, bounds_2j, bounds_2k, + bounds_2l, bounds_2m, bounds_2n, bounds_2o, bounds_2p, + bounds_2q, bounds_2r, bounds_2r, bounds_2r, bounds_2r + ]) + agent['debug']['trialToSteps'][1] = (25, 49) + agent['shows'].extend([{ + 'stepBegin': 25, + 'position': position_2a, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2a + }, { + 'stepBegin': 30, + 'position': position_2b, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2b + }, { + 'stepBegin': 31, + 'position': position_2c, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2c + }, { + 'stepBegin': 32, + 'position': position_2d, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2d + }, { + 'stepBegin': 33, + 'position': position_2e, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2e + }, { + 'stepBegin': 34, + 'position': position_2f, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2f + }, { + 'stepBegin': 35, + 'position': position_2g, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2g + }, { + 'stepBegin': 36, + 'position': position_2h, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2h + }, { + 'stepBegin': 37, + 'position': position_2i, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2i + }, { + 'stepBegin': 38, + 'position': position_2j, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2j + }, { + 'stepBegin': 39, + 'position': position_2k, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2k + }, { + 'stepBegin': 40, + 'position': position_2l, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2l + }, { + 'stepBegin': 41, + 'position': position_2m, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2m + }, { + 'stepBegin': 42, + 'position': position_2n, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2n + }, { + 'stepBegin': 43, + 'position': position_2o, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2o + }, { + 'stepBegin': 44, + 'position': position_2p, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2p + }, { + 'stepBegin': 45, + 'position': position_2q, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2q + }, { + 'stepBegin': 46, + 'position': position_2r, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': bounds_2r + }]) + return agent + + def verify_bounds(mcs_object, step, x1, x2, z1, z2): bounds_at_step = mcs_object['debug']['boundsAtStep'][step].box_xz assert bounds_at_step[0].x == pytest.approx(x1) @@ -88,7 +380,7 @@ def verify_key_properties(key_object): assert key_object['physics'] assert not key_object.get('structure') assert key_object['debug']['info'] == [ - 'maroon', 'triangle', 'maroon triangle' + 'maroon', 'red', 'triangle', 'maroon red triangle' ] assert key_object['debug']['configHeight'] == [0.05, 0.35] assert key_object['debug']['configSize'] == [0.1, 0.35] @@ -115,7 +407,7 @@ def verify_lock_properties(lock_object): assert lock_object['kinematic'] assert lock_object['structure'] assert lock_object['debug']['info'] == [ - 'lime', 'lock_wall', 'lime lock_wall' + 'lime', 'green', 'lock_wall', 'lime green lock_wall' ] assert lock_object['debug']['configHeight'] == [0.05, 0.1] assert lock_object['debug']['configSize'] == [0.495, 0.495] @@ -182,7 +474,9 @@ def verify_fuse_wall_list_trial_1(wall_object_list): assert wall_object['materials'] == ['Custom/Materials/Lime'] assert wall_object['kinematic'] assert wall_object['structure'] - assert wall_object['debug']['info'] == ['lime', 'cube', 'lime cube'] + assert wall_object['debug']['info'] == [ + 'lime', 'green', 'cube', 'lime green cube' + ] assert wall_object['debug']['configHeight'] == [0.05, 0.1] assert wall_object['debug']['configSize'] == [0.495, 0.495] @@ -292,43 +586,46 @@ def test_blobs(): def test_materials(): - agent_materials = [material[0] for material in AGENT_OBJECT_MATERIAL_LIST] - goal_materials = [material[0] for material in GOAL_OBJECT_MATERIAL_LIST] - for material in (agent_materials + goal_materials): - assert material in materials.ADJACENT_SETS + # Multi-goal scenes must have one or more goal object colors for any + # possible combination of agent colors. + # Assume such scenes will only ever have one agent. + for agent_material in AGENT_OBJECT_MATERIAL_LIST: + goal_1_materials = materials.filter_used_colors( + GOAL_OBJECT_MATERIAL_LIST, + [agent_material] + ) + for goal_1_material in goal_1_materials: + goal_2_materials = materials.filter_used_colors( + GOAL_OBJECT_MATERIAL_LIST, + [agent_material, goal_1_material] + ) + assert len(goal_2_materials) > 1 + for goal_2_material in goal_2_materials: + goal_3_materials = materials.filter_used_colors( + GOAL_OBJECT_MATERIAL_LIST, + [agent_material, goal_1_material, goal_2_material] + ) + assert len(goal_3_materials) >= 1 # Multi-agent scenes must have one or more goal object colors for any # possible combination of agent colors. # Assume such scenes will only ever have one or two goal objects. - for agent_material_1 in agent_materials: - for agent_material_2 in agent_materials: - if agent_material_1 == agent_material_2: - continue - used_materials = ( - [agent_material_1] + - materials.ADJACENT_SETS[agent_material_1] + - [agent_material_2] + - materials.ADJACENT_SETS[agent_material_2] + for agent_1_material in AGENT_OBJECT_MATERIAL_LIST: + agent_2_materials = materials.filter_used_colors( + AGENT_OBJECT_MATERIAL_LIST, + [agent_1_material] + ) + for agent_2_material in agent_2_materials: + goal_1_materials = materials.filter_used_colors( + GOAL_OBJECT_MATERIAL_LIST, + [agent_1_material, agent_2_material] ) - filtered_goal_materials = [ - material for material in goal_materials - if material not in used_materials - ] - assert len(filtered_goal_materials) > 1 - - # Multiple object scenes must have one or more agent colors for any - # possible combination of goal object colors. - # Assume such scenes will only ever have one agent. - for goal_material_1 in goal_materials: - for goal_material_2 in goal_materials: - if goal_material_1 == goal_material_2: - continue - used_materials = [goal_material_1, goal_material_2] - filtered_agent_materials = [ - material for material in agent_materials - if material not in used_materials - ] - assert len(filtered_agent_materials) + for goal_1_material in goal_1_materials: + goal_2_materials = materials.filter_used_colors( + GOAL_OBJECT_MATERIAL_LIST, + [agent_1_material, agent_2_material, goal_1_material] + ) + assert len(goal_2_materials) >= 1 def test_append_each_show_to_object(): @@ -447,13 +744,137 @@ def test_append_each_show_to_object_agent(): def test_append_each_show_to_object_imitated_agent(): - # TODO - pass + object_1 = { + 'type': 'cube', + 'debug': { + 'boundsAtStep': [], + 'configHeight': [0.25, 0.5], + 'configSize': [0.4, 0.4] + }, + 'shows': [] + } + object_2 = { + 'type': 'sphere', + 'debug': { + 'boundsAtStep': [], + 'configHeight': [0.25, 0.5], + 'configSize': [0.6, 0.6] + }, + 'shows': [] + } + + trial = [{ + 'other_agents': [[[5, 5], 5], [[95, 95], 5]] + }, { + 'other_agents': [[[95, 95], 5], [[185, 185], 5]] + }] + + result_1 = _append_each_show_to_object( + object_1, + trial, + 1, + 'other_agents', + UNIT_SIZE, + json_index=0 + ) + assert result_1 == object_1 + assert len(result_1['debug']['boundsAtStep']) == 3 + verify_bounds(result_1, 0, -2.05, -2.45, -2.05, -2.45) + verify_bounds(result_1, 1, 0.2, -0.2, 0.2, -0.2) + verify_bounds(result_1, 2, 0.2, -0.2, 0.2, -0.2) + assert len(result_1['shows']) == 2 + assert result_1['shows'][0]['stepBegin'] == 1 + assert result_1['shows'][0]['position'] == { + 'x': -2.25, 'y': 0.25, 'z': -2.25 + } + assert result_1['shows'][0]['scale'] == {'x': 0.4, 'y': 0.5, 'z': 0.4} + assert result_1['shows'][0]['boundingBox'] + assert result_1['shows'][1]['stepBegin'] == 2 + assert result_1['shows'][1]['position'] == {'x': 0, 'y': 0.25, 'z': 0} + assert 'scale' not in result_1['shows'][1] + assert result_1['shows'][1]['boundingBox'] + + result_2 = _append_each_show_to_object( + object_2, + trial, + 1, + 'other_agents', + UNIT_SIZE, + json_index=1 + ) + assert result_2 == object_2 + assert len(result_2['debug']['boundsAtStep']) == 3 + verify_bounds(result_2, 0, 0.3, -0.3, 0.3, -0.3) + verify_bounds(result_2, 1, 2.55, 1.95, 2.55, 1.95) + assert len(result_2['shows']) == 2 + assert result_2['shows'][0]['stepBegin'] == 1 + assert result_2['shows'][0]['position'] == {'x': 0, 'y': 0.25, 'z': 0} + assert result_2['shows'][0]['scale'] == {'x': 0.6, 'y': 0.5, 'z': 0.6} + assert result_2['shows'][0]['boundingBox'] + assert result_2['shows'][1]['stepBegin'] == 2 + assert result_2['shows'][1]['position'] == { + 'x': 2.25, 'y': 0.25, 'z': 2.25 + } + assert 'scale' not in result_2['shows'][1] + assert result_2['shows'][1]['boundingBox'] def test_append_each_show_to_object_paddle(): - # TODO - pass + mcs_object = { + 'type': 'cube', + 'debug': { + 'boundsAtStep': [], + 'configHeight': [0.5, 1.0], + 'configSize': [0.25, 1.0] + }, + 'shows': [] + } + + # The 'agent' property does not have nested lists. + trial = [{ + 'paddle': [[100, 100], [10, 40], 15] + }, { + 'paddle': [[100, 100], [10, 40], 20] + }, { + 'paddle': [[100, 100], [10, 40], 25] + }, { + 'paddle': [[100, 100], [10, 40], 30] + }] + result = _append_each_show_to_object( + mcs_object, + trial, + 1, + 'paddle', + UNIT_SIZE + ) + assert result == mcs_object + bounds_at_step = result['debug']['boundsAtStep'] + assert len(bounds_at_step) == 5 + for index, bounds in enumerate(bounds_at_step): + assert bounds + if index < (len(bounds_at_step) - 2): + assert bounds != bounds_at_step[index + 1] + assert len(result['shows']) == 4 + assert result['shows'][0]['stepBegin'] == 1 + assert result['shows'][0]['position'] == {'x': 0, 'y': 0.5, 'z': 0} + assert result['shows'][0]['rotation'] == {'x': 0, 'y': 345, 'z': 0} + assert result['shows'][0]['scale'] == {'x': 0.25, 'y': 1.0, 'z': 1.0} + assert result['shows'][0]['boundingBox'] == bounds_at_step[0] + assert result['shows'][1]['stepBegin'] == 2 + assert result['shows'][1]['position'] == {'x': 0, 'y': 0.5, 'z': 0} + assert result['shows'][1]['rotation'] == {'x': 0, 'y': 340, 'z': 0} + assert 'scale' not in result['shows'][1] + assert result['shows'][1]['boundingBox'] == bounds_at_step[1] + assert result['shows'][2]['stepBegin'] == 3 + assert result['shows'][2]['position'] == {'x': 0, 'y': 0.5, 'z': 0} + assert result['shows'][2]['rotation'] == {'x': 0, 'y': 335, 'z': 0} + assert 'scale' not in result['shows'][2] + assert result['shows'][2]['boundingBox'] == bounds_at_step[2] + assert result['shows'][3]['stepBegin'] == 4 + assert result['shows'][3]['position'] == {'x': 0, 'y': 0.5, 'z': 0} + assert result['shows'][3]['rotation'] == {'x': 0, 'y': 330, 'z': 0} + assert 'scale' not in result['shows'][3] + assert result['shows'][3]['boundingBox'] == bounds_at_step[3] def test_choose_config_list_with_agent(): @@ -463,10 +884,10 @@ def test_choose_config_list_with_agent(): for index, object_type in enumerate(config_object_type_list) ] material_list = [ - ('Custom/Materials/Cyan', ['cyan']), - ('Custom/Materials/Grey', ['grey']), - ('Custom/Materials/Magenta', ['magenta']), - ('Custom/Materials/Yellow', ['yellow']) + MaterialTuple('Custom/Materials/Cyan', ['cyan']), + MaterialTuple('Custom/Materials/Grey', ['grey']), + MaterialTuple('Custom/Materials/Magenta', ['magenta']), + MaterialTuple('Custom/Materials/Yellow', ['yellow']) ] trial_list = [[{ @@ -481,7 +902,7 @@ def test_choose_config_list_with_agent(): [], [] ) - assert len(chosen_config_list) == 3 + assert len(chosen_config_list) == 2 assert chosen_config_list[0].object_type in config_object_type_list assert chosen_config_list[0].material in material_list @@ -493,10 +914,10 @@ def test_choose_config_list_with_agent_in_instrumental_action_task(): for index, object_type in enumerate(config_object_type_list) ] material_list = [ - ('Custom/Materials/Cyan', ['cyan']), - ('Custom/Materials/Grey', ['grey']), - ('Custom/Materials/Magenta', ['magenta']), - ('Custom/Materials/Yellow', ['yellow']) + MaterialTuple('Custom/Materials/Cyan', ['cyan']), + MaterialTuple('Custom/Materials/Grey', ['grey']), + MaterialTuple('Custom/Materials/Magenta', ['magenta']), + MaterialTuple('Custom/Materials/Yellow', ['yellow']) ] trial_list = [[{ @@ -512,7 +933,7 @@ def test_choose_config_list_with_agent_in_instrumental_action_task(): [], [] ) - assert len(chosen_config_list) == 3 + assert len(chosen_config_list) == 2 assert chosen_config_list[0].object_type != 'cone' assert chosen_config_list[0].object_type != 'pyramid' assert chosen_config_list[0].material in material_list @@ -525,10 +946,10 @@ def test_choose_config_list_with_objects(): for index, object_type in enumerate(config_object_type_list) ] material_list = [ - ('Custom/Materials/Cyan', ['cyan']), - ('Custom/Materials/Grey', ['grey']), - ('Custom/Materials/Magenta', ['magenta']), - ('Custom/Materials/Yellow', ['yellow']) + MaterialTuple('Custom/Materials/Cyan', ['cyan']), + MaterialTuple('Custom/Materials/Grey', ['grey']), + MaterialTuple('Custom/Materials/Magenta', ['magenta']), + MaterialTuple('Custom/Materials/Yellow', ['yellow']) ] trial_list_a = [[{ @@ -580,10 +1001,10 @@ def test_choose_config_list_no_types_same_as_used_types(): for index, object_type in enumerate(mock_config_object_type_list) ] material_list = [ - ('Custom/Materials/Cyan', ['cyan']), - ('Custom/Materials/Grey', ['grey']), - ('Custom/Materials/Magenta', ['magenta']), - ('Custom/Materials/Yellow', ['yellow']) + MaterialTuple('Custom/Materials/Cyan', ['cyan']), + MaterialTuple('Custom/Materials/Grey', ['grey']), + MaterialTuple('Custom/Materials/Magenta', ['magenta']), + MaterialTuple('Custom/Materials/Yellow', ['yellow']) ] trial_list = [[{ @@ -609,10 +1030,10 @@ def test_choose_config_list_no_types_same_as_used_types_multiple(): for index, object_type in enumerate(mock_config_object_type_list) ] material_list = [ - ('Custom/Materials/Cyan', ['cyan']), - ('Custom/Materials/Grey', ['grey']), - ('Custom/Materials/Magenta', ['magenta']), - ('Custom/Materials/Yellow', ['yellow']) + MaterialTuple('Custom/Materials/Cyan', ['cyan']), + MaterialTuple('Custom/Materials/Grey', ['grey']), + MaterialTuple('Custom/Materials/Magenta', ['magenta']), + MaterialTuple('Custom/Materials/Yellow', ['yellow']) ] trial_list = [[{ @@ -700,6 +1121,7 @@ def test_create_agent_object_list(): assert agent_object['debug']['configHeight'] == [0.125, 0.25] assert agent_object['debug']['configSize'] == [0.25, 0.25] + assert len(agent_object.get('hides', [])) == 0 assert len(agent_object['shows']) == 10 verify_show(agent_object, 0, 0, -1.75, 0.125, -1.75) assert agent_object['shows'][0]['rotation']['y'] == 0 @@ -761,13 +1183,202 @@ def test_create_agent_object_list_rotation_y(): def test_create_agent_object_list_hide_in_some_trials(): - # TODO - pass + trial_list = [[{ + 'agent': [[25, 25], 5, 'agent_1'] + }], [{ + 'agent': [[95, 95], 5, 'agent_1'] + }], [{ + 'agent': [[95, 95], 5, 'agent_2'] + }]] + + object_config_with_material_list = [ + ObjectConfigWithMaterial( + AgentConfig('cube', 1), + MaterialTuple('test_material_1', ['test_color_a']) + ), + ObjectConfigWithMaterial( + AgentConfig('sphere', 1), + MaterialTuple('test_material_2', ['test_color_b']) + ) + ] + + agent_object_list = _create_agent_object_list( + trial_list, + object_config_with_material_list, + UNIT_SIZE + ) + + assert len(agent_object_list) == 2 + agent_object_1 = agent_object_list[0] + agent_object_2 = agent_object_list[1] + + assert agent_object_1['id'].startswith('agent_') + assert agent_object_1['type'] == 'cube' + assert agent_object_1['materials'] == ['test_material_1'] + assert agent_object_1['kinematic'] + assert agent_object_1['physics'] + assert not agent_object_1.get('structure') + assert agent_object_1['debug']['configHeight'] == [0.125, 0.25] + assert agent_object_1['debug']['configSize'] == [0.25, 0.25] + + assert len(agent_object_1['hides']) == 1 + assert agent_object_1['hides'][0]['stepBegin'] == 4 + assert len(agent_object_1['shows']) == 2 + verify_show(agent_object_1, 0, 0, -1.75, 0.125, -1.75) + assert agent_object_1['shows'][0]['rotation']['y'] == 0 + assert agent_object_1['shows'][0]['scale']['x'] == 0.25 + assert agent_object_1['shows'][0]['scale']['y'] == 0.25 + assert agent_object_1['shows'][0]['scale']['z'] == 0.25 + verify_show(agent_object_1, 1, 2, 0, 0.125, 0) + + assert len(agent_object_1['debug']['boundsAtStep']) == 6 + verify_bounds(agent_object_1, 0, -1.625, -1.875, -1.625, -1.875) + verify_bounds(agent_object_1, 1, -1.625, -1.875, -1.625, -1.875) + verify_bounds(agent_object_1, 2, 0.125, -0.125, 0.125, -0.125) + verify_bounds(agent_object_1, 3, 0.125, -0.125, 0.125, -0.125) + assert agent_object_1['debug']['boundsAtStep'][4] is None + assert agent_object_1['debug']['boundsAtStep'][5] is None + + assert agent_object_2['id'].startswith('agent_') + assert agent_object_2['type'] == 'sphere' + assert agent_object_2['materials'] == ['test_material_2'] + assert agent_object_2['kinematic'] + assert agent_object_2['physics'] + assert not agent_object_2.get('structure') + assert agent_object_2['debug']['configHeight'] == [0.125, 0.25] + assert agent_object_2['debug']['configSize'] == [0.25, 0.25] + + assert len(agent_object_2['hides']) == 2 + assert agent_object_2['hides'][0]['stepBegin'] == 0 + assert agent_object_2['hides'][1]['stepBegin'] == 2 + assert len(agent_object_2['shows']) == 1 + verify_show(agent_object_2, 0, 4, 0, 0.125, 0) + assert agent_object_2['shows'][0]['rotation']['y'] == 0 + assert agent_object_2['shows'][0]['scale']['x'] == 0.25 + assert agent_object_2['shows'][0]['scale']['y'] == 0.25 + assert agent_object_2['shows'][0]['scale']['z'] == 0.25 + + assert len(agent_object_2['debug']['boundsAtStep']) == 6 + assert agent_object_2['debug']['boundsAtStep'][0] is None + assert agent_object_2['debug']['boundsAtStep'][1] is None + assert agent_object_2['debug']['boundsAtStep'][2] is None + assert agent_object_2['debug']['boundsAtStep'][3] is None + verify_bounds(agent_object_2, 4, 0.125, -0.125, 0.125, -0.125) + verify_bounds(agent_object_2, 5, 0.125, -0.125, 0.125, -0.125) + + +def test_create_agent_object_list_imitated_agents(): + trial_list = [[{ + 'agent': [[25, 25], 5, 'agent_1'], + 'other_agents': [ + [[30, 30], 5, 'agent_2'], + [[35, 35], 5, 'agent_3'] + ] + }], [{ + 'agent': [[95, 95], 5, 'agent_1'], + 'other_agents': [ + [[95, 105], 5, 'agent_2'], + [[165, 165], 5, 'agent_3'] + ] + }]] + object_config_with_material_list = [ + ObjectConfigWithMaterial( + AgentConfig('cube', 1), + MaterialTuple('test_material_1', ['test_color_a']) + ), + ObjectConfigWithMaterial( + AgentConfig('sphere', 1), + MaterialTuple('test_material_2', ['test_color_b']) + ), + ObjectConfigWithMaterial( + AgentConfig('tube_wide', 1), + MaterialTuple('test_material_3', ['test_color_c']) + ) + ] + + agent_object_list = _create_agent_object_list( + trial_list, + object_config_with_material_list, + UNIT_SIZE + ) -def test_create_agent_object_list_imitated_agent(): - # TODO - pass + assert len(agent_object_list) == 3 + agent_object_1 = agent_object_list[0] + agent_object_2 = agent_object_list[1] + agent_object_3 = agent_object_list[2] + + assert agent_object_1['id'].startswith('agent_') + assert agent_object_1['type'] == 'cube' + assert agent_object_1['materials'] == ['test_material_1'] + assert agent_object_1['kinematic'] + assert agent_object_1['physics'] + assert not agent_object_1.get('structure') + assert agent_object_1['debug']['configHeight'] == [0.125, 0.25] + assert agent_object_1['debug']['configSize'] == [0.25, 0.25] + + assert len(agent_object_1.get('hides', [])) == 0 + assert len(agent_object_1['shows']) == 2 + verify_show(agent_object_1, 0, 0, -1.75, 0.125, -1.75) + assert agent_object_1['shows'][0]['rotation']['y'] == 0 + assert agent_object_1['shows'][0]['scale']['x'] == 0.25 + assert agent_object_1['shows'][0]['scale']['y'] == 0.25 + assert agent_object_1['shows'][0]['scale']['z'] == 0.25 + verify_show(agent_object_1, 1, 2, 0, 0.125, 0) + + assert len(agent_object_1['debug']['boundsAtStep']) == 4 + verify_bounds(agent_object_1, 0, -1.625, -1.875, -1.625, -1.875) + verify_bounds(agent_object_1, 1, -1.625, -1.875, -1.625, -1.875) + verify_bounds(agent_object_1, 2, 0.125, -0.125, 0.125, -0.125) + verify_bounds(agent_object_1, 3, 0.125, -0.125, 0.125, -0.125) + + assert agent_object_2['id'].startswith('other_agents_') + assert agent_object_2['type'] == 'sphere' + assert agent_object_2['materials'] == ['test_material_2'] + assert agent_object_2['kinematic'] + assert agent_object_2['physics'] + assert not agent_object_2.get('structure') + assert agent_object_2['debug']['configHeight'] == [0.125, 0.25] + assert agent_object_2['debug']['configSize'] == [0.25, 0.25] + + assert len(agent_object_2.get('hides', [])) == 0 + assert len(agent_object_2['shows']) == 2 + verify_show(agent_object_2, 0, 0, -1.625, 0.125, -1.625) + assert agent_object_2['shows'][0]['rotation']['y'] == 0 + assert agent_object_2['shows'][0]['scale']['x'] == 0.25 + assert agent_object_2['shows'][0]['scale']['y'] == 0.25 + assert agent_object_2['shows'][0]['scale']['z'] == 0.25 + verify_show(agent_object_2, 1, 2, 0, 0.125, 0.25) + + assert len(agent_object_2['debug']['boundsAtStep']) == 4 + verify_bounds(agent_object_2, 0, -1.5, -1.75, -1.5, -1.75) + verify_bounds(agent_object_2, 1, -1.5, -1.75, -1.5, -1.75) + verify_bounds(agent_object_2, 2, 0.125, -0.125, 0.375, 0.125) + verify_bounds(agent_object_2, 3, 0.125, -0.125, 0.375, 0.125) + + assert agent_object_3['id'].startswith('other_agents_') + assert agent_object_3['type'] == 'tube_wide' + assert agent_object_3['materials'] == ['test_material_3'] + assert agent_object_3['kinematic'] + assert agent_object_3['physics'] + assert not agent_object_3.get('structure') + assert agent_object_3['debug']['configHeight'] == [0.125, 0.25] + assert agent_object_3['debug']['configSize'] == [0.25, 0.25] + + assert len(agent_object_3.get('hides', [])) == 0 + assert len(agent_object_3['shows']) == 2 + verify_show(agent_object_3, 0, 0, -1.5, 0.125, -1.5) + assert agent_object_3['shows'][0]['rotation']['y'] == 0 + assert agent_object_3['shows'][0]['scale']['x'] == 0.25 + assert agent_object_3['shows'][0]['scale']['y'] == 0.25 + assert agent_object_3['shows'][0]['scale']['z'] == 0.25 + verify_show(agent_object_3, 1, 2, 1.75, 0.125, 1.75) + + assert len(agent_object_3['debug']['boundsAtStep']) == 4 + verify_bounds(agent_object_3, 0, -1.375, -1.625, -1.375, -1.625) + verify_bounds(agent_object_3, 1, -1.375, -1.625, -1.375, -1.625) + verify_bounds(agent_object_3, 2, 1.875, 1.625, 1.875, 1.625) + verify_bounds(agent_object_3, 3, 1.875, 1.625, 1.875, 1.625) def test_create_fuse_wall_object_list(): @@ -812,15 +1423,15 @@ def test_create_fuse_wall_object_list_multiple_trials(): def test_create_goal_object_list_single_object(): trial_list = [[{ 'objects': [ - [[25, 25], 5, 'obj_1'] + [[25, 25], 5, 'obj_1', 'purple'] ] }], [{ 'objects': [ - [[95, 95], 5, 'obj_1'] + [[95, 95], 5, 'obj_1', 'purple'] ] }], [{ 'objects': [ - [[165, 165], 5, 'obj_1'] + [[165, 165], 5, 'obj_1', 'purple'] ] }]] @@ -886,13 +1497,13 @@ def test_create_goal_object_list_single_object(): def test_create_goal_object_list_multiple_object(): trial_list = [[{ 'objects': [ - [[25, 165], 5, 'obj_1'], - [[160, 20], 10, 'obj_2'] + [[25, 165], 5, 'obj_1', 'yellow'], + [[160, 20], 10, 'obj_2', 'purple'] ] }], [{ 'objects': [ - [[90, 100], 5, 'obj_1'], - [[95, 85], 10, 'obj_2'] + [[90, 100], 5, 'obj_1', 'yellow'], + [[95, 85], 10, 'obj_2', 'purple'] ] }]] @@ -984,13 +1595,13 @@ def test_create_goal_object_list_multiple_object(): def test_create_goal_object_list_multiple_object_swap_icon(): trial_list = [[{ 'objects': [ - [[25, 165], 5, 'obj_1'], - [[160, 20], 10, 'obj_2'] + [[25, 165], 5, 'obj_1', 'yellow'], + [[160, 20], 10, 'obj_2', 'purple'] ] }], [{ 'objects': [ - [[90, 100], 5, 'obj_2'], - [[95, 85], 10, 'obj_1'] + [[90, 100], 5, 'obj_2', 'purple'], + [[95, 85], 10, 'obj_1', 'yellow'] ] }]] @@ -1082,15 +1693,15 @@ def test_create_goal_object_list_multiple_object_swap_icon(): def test_create_goal_object_list_single_object_on_home(): trial_list = [[{ 'objects': [ - [[20, 20], 5, 'obj_1'] + [[20, 20], 5, 'obj_1', 'purple'] ] }], [{ 'objects': [ - [[90, 90], 5, 'obj_1'] + [[90, 90], 5, 'obj_1', 'purple'] ] }], [{ 'objects': [ - [[160, 160], 5, 'obj_1'] + [[160, 160], 5, 'obj_1', 'purple'] ] }]] @@ -1123,8 +1734,8 @@ def test_create_goal_object_list_single_object_on_home(): def test_create_goal_object_list_multiple_object_on_home(): trial_list = [[{ 'objects': [ - [[20, 20], 5, 'obj_1'], - [[100, 100], 5, 'obj_2'] + [[20, 20], 5, 'obj_1', 'yellow'], + [[100, 100], 5, 'obj_2', 'purple'] ] }]] @@ -1158,60 +1769,284 @@ def test_create_goal_object_list_multiple_object_on_home(): ) -def test_create_home_object(): +def test_create_goal_object_list_object_hide_in_final_trial(): trial_list = [[{ - 'home': [[95, 95], 5] + 'objects': [ + [[25, 25], 5, 'obj_1', 'purple'] + ] + }], [{ + 'objects': [ + [[95, 95], 5, 'obj_1', 'purple'] + ] + }], [{ + 'objects': [] }]] - home_object = _create_home_object(trial_list, UNIT_SIZE) + object_config_with_material_list = [ + ObjectConfigWithMaterial( + ObjectConfig('cube', 1, 2), + MaterialTuple('test_material', ['test_color_a', 'test_color_b']) + ) + ] - assert home_object['id'].startswith('home_') - assert home_object['type'] == 'cube' - assert home_object['materials'] == ['Custom/Materials/Magenta'] - assert home_object['kinematic'] - assert home_object['structure'] - assert home_object['debug']['info'] == ['magenta', 'cube', 'magenta cube'] - assert home_object['debug']['configHeight'] == [0.01, 0.02] - assert home_object['debug']['configSize'] == [0.5, 0.5] + # We're not testing this right now, so just use a silly value. + mock_agent_start_bounds = ObjectBounds(box_xz=[ + Vector3d(x=10, y=0, z=10), Vector3d(x=10, y=0, z=12), + Vector3d(x=12, y=0, z=12), Vector3d(x=12, y=0, z=10) + ], max_y=0, min_y=0) + mock_agent_start_bounds = TrueObjectBounds( + bounds=mock_agent_start_bounds, + true_poly=mock_agent_start_bounds.polygon_xz + ) - assert len(home_object['shows']) == 1 - verify_show(home_object, 0, 0, 0, 0.01, 0) - assert home_object['shows'][0]['scale']['x'] == 0.5 - assert home_object['shows'][0]['scale']['y'] == 0.02 - assert home_object['shows'][0]['scale']['z'] == 0.5 + goal_object_list = _create_goal_object_list( + trial_list, + object_config_with_material_list, + mock_agent_start_bounds, + 'filename', + UNIT_SIZE + ) + assert len(goal_object_list) == 1 + goal_object_1 = goal_object_list[0] -def test_create_home_object_uses_first_frame_of_first_trial(): - trial_list = [[{ - 'home': [[20, 160], 10] - }, { - 'home': [[40, 140], 10] - }], [{ - 'home': [[60, 120], 10] - }]] + assert goal_object_1['id'].startswith('object_') + assert goal_object_1['type'] == 'cube' + assert goal_object_1['materials'] == ['test_material'] + assert goal_object_1['kinematic'] + assert goal_object_1['physics'] + assert not goal_object_1.get('structure') + assert goal_object_1['debug']['info'] == [ + 'test_color_a', 'test_color_b', 'cube', + 'test_color_a test_color_b cube' + ] + assert goal_object_1['debug']['configHeight'] == [0.25, 0.5] + assert goal_object_1['debug']['configSize'] == [0.25, 0.25] - home_object = _create_home_object(trial_list, UNIT_SIZE) + assert len(goal_object_1['shows']) == 2 + verify_show(goal_object_1, 0, 0, -1.75, 0.25, -1.75) + assert goal_object_1['shows'][0]['scale']['x'] == 0.25 + assert goal_object_1['shows'][0]['scale']['y'] == 0.5 + assert goal_object_1['shows'][0]['scale']['z'] == 0.25 - assert home_object['id'].startswith('home_') - assert home_object['type'] == 'cube' - assert home_object['materials'] == ['Custom/Materials/Magenta'] - assert home_object['kinematic'] - assert home_object['structure'] - assert home_object['debug']['info'] == ['magenta', 'cube', 'magenta cube'] - assert home_object['debug']['configHeight'] == [0.01, 0.02] - assert home_object['debug']['configSize'] == [0.5, 0.5] + verify_show(goal_object_1, 1, 2, 0, 0.25, 0) - assert len(home_object['shows']) == 1 - verify_show(home_object, 0, 0, -1.75, 0.01, 1.75) - assert home_object['shows'][0]['scale']['x'] == 0.5 - assert home_object['shows'][0]['scale']['y'] == 0.02 - assert home_object['shows'][0]['scale']['z'] == 0.5 + assert len(goal_object_1['debug']['boundsAtStep']) == 6 + verify_bounds(goal_object_1, 0, -1.625, -1.875, -1.625, -1.875) + verify_bounds(goal_object_1, 1, -1.625, -1.875, -1.625, -1.875) + verify_bounds(goal_object_1, 2, 0.125, -0.125, 0.125, -0.125) + verify_bounds(goal_object_1, 3, 0.125, -0.125, 0.125, -0.125) + assert goal_object_1['debug']['boundsAtStep'][4] is None + assert goal_object_1['debug']['boundsAtStep'][5] is None + assert len(goal_object_1['hides']) == 1 + assert goal_object_1['hides'][0]['stepBegin'] == 4 -def test_create_key_object(): - # negative_x + +def test_create_goal_object_list_object_show_in_final_trial(): trial_list = [[{ - 'key': [[[90, 90], 10, 'triangle0.png']] + 'objects': [] + }], [{ + 'objects': [] + }], [{ + 'objects': [ + [[165, 165], 5, 'obj_1', 'purple'] + ] + }]] + + object_config_with_material_list = [ + ObjectConfigWithMaterial( + ObjectConfig('cube', 1, 2), + MaterialTuple('test_material', ['test_color_a', 'test_color_b']) + ) + ] + + # We're not testing this right now, so just use a silly value. + mock_agent_start_bounds = ObjectBounds(box_xz=[ + Vector3d(x=10, y=0, z=10), Vector3d(x=10, y=0, z=12), + Vector3d(x=12, y=0, z=12), Vector3d(x=12, y=0, z=10) + ], max_y=0, min_y=0) + mock_agent_start_bounds = TrueObjectBounds( + bounds=mock_agent_start_bounds, + true_poly=mock_agent_start_bounds.polygon_xz + ) + + goal_object_list = _create_goal_object_list( + trial_list, + object_config_with_material_list, + mock_agent_start_bounds, + 'filename', + UNIT_SIZE + ) + + assert len(goal_object_list) == 1 + goal_object_1 = goal_object_list[0] + + assert goal_object_1['id'].startswith('object_') + assert goal_object_1['type'] == 'cube' + assert goal_object_1['materials'] == ['test_material'] + assert goal_object_1['kinematic'] + assert goal_object_1['physics'] + assert not goal_object_1.get('structure') + assert goal_object_1['debug']['info'] == [ + 'test_color_a', 'test_color_b', 'cube', + 'test_color_a test_color_b cube' + ] + assert goal_object_1['debug']['configHeight'] == [0.25, 0.5] + assert goal_object_1['debug']['configSize'] == [0.25, 0.25] + + assert len(goal_object_1['shows']) == 1 + verify_show(goal_object_1, 0, 4, 1.75, 0.25, 1.75) + assert goal_object_1['shows'][0]['scale']['x'] == 0.25 + assert goal_object_1['shows'][0]['scale']['y'] == 0.5 + assert goal_object_1['shows'][0]['scale']['z'] == 0.25 + + assert len(goal_object_1['debug']['boundsAtStep']) == 6 + assert goal_object_1['debug']['boundsAtStep'][0] is None + assert goal_object_1['debug']['boundsAtStep'][1] is None + assert goal_object_1['debug']['boundsAtStep'][2] is None + assert goal_object_1['debug']['boundsAtStep'][3] is None + verify_bounds(goal_object_1, 4, 1.875, 1.625, 1.875, 1.625) + verify_bounds(goal_object_1, 5, 1.875, 1.625, 1.875, 1.625) + + +def test_create_goal_object_list_agent_touches(): + trial_list = [[{ + 'objects': [ + [[25, 165], 5, 'obj_1', 'yellow'], + [[160, 20], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[25, 165], 5, 'obj_1', 'yellow'], + [[160, 20], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[90, 100], 5, 'obj_1', 'red'], + [[95, 85], 10, 'obj_2', 'red'] + ] + }], [{ + 'objects': [ + [[90, 100], 5, 'obj_1', 'yellow'], + [[95, 85], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[90, 100], 5, 'obj_1', 'yellow'], + [[95, 85], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[90, 100], 5, 'obj_1', 'yellow'], + [[95, 85], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[90, 100], 5, 'obj_1', 'yellow'], + [[95, 85], 10, 'obj_2', 'purple'] + ] + }, { + 'objects': [ + [[90, 100], 5, 'obj_1', 'red'], + [[95, 85], 10, 'obj_2', 'red'] + ] + }]] + + object_config_with_material_list = [ + ObjectConfigWithMaterial( + ObjectConfig('cube', 1, 2), + MaterialTuple('test_material_1', ['test_color_a', 'test_color_b']) + ), + ObjectConfigWithMaterial( + ObjectConfig('sphere', 5, 6), + MaterialTuple('test_material_2', ['test_color_c']) + ) + ] + + # We're not testing this right now, so just use a silly value. + mock_agent_start_bounds = ObjectBounds(box_xz=[ + Vector3d(x=10, y=0, z=10), Vector3d(x=10, y=0, z=12), + Vector3d(x=12, y=0, z=12), Vector3d(x=12, y=0, z=10) + ], max_y=0, min_y=0) + mock_agent_start_bounds = TrueObjectBounds( + bounds=mock_agent_start_bounds, + true_poly=mock_agent_start_bounds.polygon_xz + ) + + goal_object_list = _create_goal_object_list( + trial_list, + object_config_with_material_list, + mock_agent_start_bounds, + 'filename', + UNIT_SIZE + ) + + assert len(goal_object_list) == 2 + goal_object_1 = goal_object_list[0] + goal_object_2 = goal_object_list[1] + assert goal_object_1['debug']['agentTouches'] == {0: [2], 1: [8]} + assert goal_object_2['debug']['agentTouches'] == {0: [2], 1: [8]} + + +def test_create_home_object(): + trial_list = [[{ + 'home': [[95, 95], 5] + }]] + + home_object = _create_home_object(trial_list, UNIT_SIZE) + + assert home_object['id'].startswith('home_') + assert home_object['type'] == 'cube' + assert home_object['materials'] == ['Custom/Materials/Magenta'] + assert home_object['kinematic'] + assert home_object['structure'] + assert home_object['debug']['info'] == [ + 'magenta', 'purple', 'red', 'cube', 'magenta purple red cube' + ] + assert home_object['debug']['configHeight'] == [0.01, 0.02] + assert home_object['debug']['configSize'] == [0.5, 0.5] + + assert len(home_object['shows']) == 1 + verify_show(home_object, 0, 0, 0, 0.01, 0) + assert home_object['shows'][0]['scale']['x'] == 0.5 + assert home_object['shows'][0]['scale']['y'] == 0.02 + assert home_object['shows'][0]['scale']['z'] == 0.5 + + +def test_create_home_object_uses_first_frame_of_first_trial(): + trial_list = [[{ + 'home': [[20, 160], 10] + }, { + 'home': [[40, 140], 10] + }], [{ + 'home': [[60, 120], 10] + }]] + + home_object = _create_home_object(trial_list, UNIT_SIZE) + + assert home_object['id'].startswith('home_') + assert home_object['type'] == 'cube' + assert home_object['materials'] == ['Custom/Materials/Magenta'] + assert home_object['kinematic'] + assert home_object['structure'] + assert home_object['debug']['info'] == [ + 'magenta', 'purple', 'red', 'cube', 'magenta purple red cube' + ] + assert home_object['debug']['configHeight'] == [0.01, 0.02] + assert home_object['debug']['configSize'] == [0.5, 0.5] + + assert len(home_object['shows']) == 1 + verify_show(home_object, 0, 0, -1.75, 0.01, 1.75) + assert home_object['shows'][0]['scale']['x'] == 0.5 + assert home_object['shows'][0]['scale']['y'] == 0.02 + assert home_object['shows'][0]['scale']['z'] == 0.5 + + +def test_create_key_object(): + # negative_x + trial_list = [[{ + 'key': [[[90, 90], 10, 'triangle0.png']] }]] key_object = _create_key_object(trial_list, UNIT_SIZE, 0.5) @@ -1636,15 +2471,303 @@ def test_create_object(): verify_bounds(mcs_object, 0, -0.25, -3.25, 1, -3) +def test_create_occluder_evaluation(): + trial_list = [[{}], [{}]] + agent_object = { + 'debug': { + 'boundsAtStep': ( + [create_simple_bounds(-0.6, -0.4, -0.6, -0.4, y=0)] * 40 + + [create_simple_bounds(-0.5, -0.3, -0.5, -0.3, y=0)] + ), + 'dimensions': {'x': 0.3, 'y': 0.5, 'z': 0.3} + }, + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 2, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 41, + 'position': {'x': -0.4, 'y': 0, 'z': -0.4}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.4, 'y': 0, 'z': -0.4}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }] + } + paddle_object = { + 'shows': [{ + 'stepBegin': index, + 'position': {'x': -1.5, 'y': 0, 'z': -1}, + 'rotation': {'x': 0, 'y': rotation, 'z': 0}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.25, 'y': 0.5, 'z': 1}, + offset=None, + position={'x': -1.5, 'y': 0, 'z': -1}, + rotation={'x': 0, 'y': rotation, 'z': 0}, + standing_y=0 + ) + } for index, rotation in enumerate(range(360, 5, -5))] + } + + occluder_object = _create_occluder_object( + trial_list, + agent_object, + paddle_object, + [], + OccluderMode.EVAL, + UNIT_SIZE + ) + + assert occluder_object['id'].startswith('occluder_') + assert occluder_object['type'] == 'cube' + assert occluder_object['materials'] == ['Custom/Materials/White'] + assert occluder_object['kinematic'] + assert occluder_object['structure'] + + assert len(occluder_object['shows']) == 2 + verify_show(occluder_object, 0, 0, -3.25, 0.75, -1.45) + assert occluder_object['shows'][0]['rotation']['y'] == 0 + assert occluder_object['shows'][0]['scale']['x'] == 0.25 + assert occluder_object['shows'][0]['scale']['y'] == 1.5 + assert occluder_object['shows'][0]['scale']['z'] == 1.05 + verify_show(occluder_object, 1, 2, 0.225, 0.75, -1.225) + assert occluder_object['shows'][1]['rotation']['y'] == 45 + + +def test_create_occluder_training(): + trial_list = [[{}], [{}]] + agent_object = { + 'debug': { + 'boundsAtStep': ( + [create_simple_bounds(-0.6, -0.4, -0.6, -0.4, y=0)] * 40 + + [create_simple_bounds(-0.5, -0.3, -0.5, -0.3, y=0)] + ), + 'dimensions': {'x': 0.3, 'y': 0.5, 'z': 0.3} + }, + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 2, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 41, + 'position': {'x': -0.4, 'y': 0, 'z': -0.4}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.4, 'y': 0, 'z': -0.4}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }] + } + paddle_object = { + 'debug': {}, + 'shows': [{ + 'stepBegin': index, + 'position': {'x': -1.5, 'y': 0, 'z': -1}, + 'rotation': {'x': 0, 'y': rotation, 'z': 0}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.25, 'y': 0.5, 'z': 1}, + offset=None, + position={'x': -1.5, 'y': 0, 'z': -1}, + rotation={'x': 0, 'y': rotation, 'z': 0}, + standing_y=0 + ) + } for index, rotation in enumerate(range(360, 5, -5))] + } + paddle_object['debug']['boundsAtStep'] = [ + show['boundingBox'] for show in paddle_object['shows'] + ] + + positions = [] + for _ in range(100): + occluder_object = _create_occluder_object( + trial_list, + agent_object, + paddle_object, + [], + OccluderMode.TRAINING, + UNIT_SIZE + ) + + assert occluder_object['id'].startswith('occluder_') + assert occluder_object['type'] == 'cube' + assert occluder_object['materials'] == ['Custom/Materials/White'] + assert occluder_object['kinematic'] + assert occluder_object['structure'] + + assert len(occluder_object['shows']) == 2 + verify_show(occluder_object, 0, 0, -3.25, 0.75, -1.45) + assert occluder_object['shows'][0]['rotation']['y'] == 0 + assert occluder_object['shows'][0]['scale']['x'] == 0.25 + assert occluder_object['shows'][0]['scale']['y'] == 1.5 + assert occluder_object['shows'][0]['scale']['z'] == 1.05 + + assert occluder_object['shows'][1]['stepBegin'] == 2 + occluder_position = occluder_object['shows'][1]['position'] + if occluder_position not in positions: + positions.append(occluder_position) + assert -2 < occluder_position['x'] < 2 + assert occluder_position['y'] == 0.75 + assert -2 < occluder_position['z'] < 2 + assert occluder_object['shows'][1]['rotation']['y'] == 45 + assert 'scale' not in occluder_object['shows'][1] + + # Verify there are a lot of random positions. + assert len(positions) > 1 + + +def test_create_occluder_none(): + trial_list = [[{}], [{}]] + agent_object = { + 'debug': { + 'boundsAtStep': ( + [create_simple_bounds(-0.6, -0.4, -0.6, -0.4, y=0)] * 40 + + [create_simple_bounds(-0.5, -0.3, -0.5, -0.3, y=0)] + ), + 'dimensions': {'x': 0.3, 'y': 0.5, 'z': 0.3} + }, + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 2, + 'position': {'x': -0.5, 'y': 0, 'z': -0.5}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.5, 'y': 0, 'z': -0.5}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }, { + 'stepBegin': 41, + 'position': {'x': -0.4, 'y': 0, 'z': -0.4}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.3, 'y': 0.5, 'z': 0.3}, + offset=None, + position={'x': -0.4, 'y': 0, 'z': -0.4}, + rotation={'x': 0, 'y': 0, 'z': 0}, + standing_y=0 + ) + }] + } + paddle_object = { + 'shows': [{ + 'stepBegin': index, + 'position': {'x': -1.5, 'y': 0, 'z': -1}, + 'rotation': {'x': 0, 'y': rotation, 'z': 0}, + 'boundingBox': wrap_create_bounds( + dimensions={'x': 0.25, 'y': 0.5, 'z': 1}, + offset=None, + position={'x': -1.5, 'y': 0, 'z': -1}, + rotation={'x': 0, 'y': rotation, 'z': 0}, + standing_y=0 + ) + } for index, rotation in enumerate(range(360, 5, -5))] + } + + occluder_object = _create_occluder_object( + trial_list, + agent_object, + paddle_object, + [], + OccluderMode.NONE, + UNIT_SIZE + ) + assert occluder_object is None + + def test_create_paddle(): - # TODO - pass + trial_list = [[ + {'paddle': [[20, 20], [10, 40], 30]}, + {'paddle': [[20, 20], [10, 40], 35]}, + {'paddle': [[20, 20], [10, 40], 40]}, + {'paddle': [[20, 20], [10, 40], 45]}, + ], [ + {'paddle': [[100, 100], [10, 40], 300]}, + {'paddle': [[100, 100], [10, 40], 305]}, + {'paddle': [[100, 100], [10, 40], 310]}, + {'paddle': [[100, 100], [10, 40], 315]} + ]] + + paddle_object = _create_paddle_object(trial_list, UNIT_SIZE) + + assert paddle_object['id'].startswith('paddle_') + assert paddle_object['type'] == 'cube' + assert paddle_object['materials'] == ['Custom/Materials/Black'] + assert paddle_object['kinematic'] + assert paddle_object['debug']['configHeight'] == [0.25, 0.5] + assert paddle_object['debug']['configSize'] == [0.25, 1.0] + + assert len(paddle_object['shows']) == 8 + assert paddle_object['shows'][0]['scale']['x'] == 0.25 + assert paddle_object['shows'][0]['scale']['y'] == 0.5 + assert paddle_object['shows'][0]['scale']['z'] == 1.0 + verify_show(paddle_object, 0, 0, -2.0, 0.25, -2.0) + verify_show(paddle_object, 1, 1, -2.0, 0.25, -2.0) + verify_show(paddle_object, 2, 2, -2.0, 0.25, -2.0) + verify_show(paddle_object, 3, 3, -2.0, 0.25, -2.0) + assert paddle_object['shows'][0]['rotation']['y'] == 330 + assert paddle_object['shows'][1]['rotation']['y'] == 325 + assert paddle_object['shows'][2]['rotation']['y'] == 320 + assert paddle_object['shows'][3]['rotation']['y'] == 315 + verify_show(paddle_object, 4, 5, 0, 0.25, 0) + verify_show(paddle_object, 5, 6, 0, 0.25, 0) + verify_show(paddle_object, 6, 7, 0, 0.25, 0) + verify_show(paddle_object, 7, 8, 0, 0.25, 0) + assert paddle_object['shows'][4]['rotation']['y'] == 60 + assert paddle_object['shows'][5]['rotation']['y'] == 55 + assert paddle_object['shows'][6]['rotation']['y'] == 50 + assert paddle_object['shows'][7]['rotation']['y'] == 45 def test_create_scene(): starter_scene = Scene() goal_template = hypercubes.initialize_goal( - {'category': 'mock', 'domainsInfo': {}, 'sceneInfo': {}} + Goal(category='mock', domains_info={}, scene_info={}) ) agent_object_config_list = [ @@ -1659,7 +2782,132 @@ def test_create_scene(): MaterialTuple('test_material', ['test_color']) ), ObjectConfigWithMaterial( - ObjectConfig('cube', 0.1, 0.2), + ObjectConfig('cube', 0.1, 0.2), + MaterialTuple('test_material', ['test_color']) + ) + ] + + trial_list = [[{ + 'agent': [[25, 25], 5, 'agent_1'], + 'home': [[25, 25], 5], + 'objects': [ + [[25, 45], 5, 'obj_1', 'yellow'], + [[95, 95], 5, 'obj_2', 'purple'] + ], + 'size': [200, 200], + 'walls': [ + [[60, 60], [20, 20]], + [[60, 120], [20, 20]], + [[120, 60], [20, 20]], + [[120, 120], [20, 20]] + ] + }, { + 'agent': [[25, 25], 5, 'agent_1'] + }, { + 'agent': [[25, 30], 5, 'agent_1'] + }, { + 'agent': [[25, 35], 5, 'agent_1'] + }, { + 'agent': [[25, 35], 5, 'agent_1'] + }], [{ + 'agent': [[25, 25], 5, 'agent_1'], + 'objects': [ + [[45, 25], 5, 'obj_1', 'yellow'], + [[165, 165], 5, 'obj_2', 'purple'] + ], + 'walls': [ + [[60, 60], [20, 20]], + [[60, 120], [20, 20]], + [[120, 60], [20, 20]], + [[120, 120], [20, 20]] + ] + }, { + 'agent': [[25, 25], 5, 'agent_1'] + }, { + 'agent': [[30, 25], 5, 'agent_1'] + }, { + 'agent': [[35, 25], 5, 'agent_1'] + }, { + 'agent': [[35, 25], 5, 'agent_1'] + }]] + + for is_expected in [True, False]: + print(f'Testing is_expected={is_expected}') + scene = _create_scene( + starter_scene, + goal_template, + agent_object_config_list, + goal_object_config_list, + trial_list, + 'filename', + MaterialTuple('test_material', ['test_color']), + is_expected + ) + + assert scene.isometric + assert scene.goal.answer['choice'] == ( + 'expected' if is_expected else 'unexpected' + ) + assert scene.goal.action_list == [ + ['Pass'], ['Pass'], ['Pass'], ['Pass'], ['Pass'], + ['EndHabituation'], + ['Pass'], ['Pass'], ['Pass'], ['Pass'], ['Pass'] + ] + assert scene.goal.habituation_total == 1 + assert scene.goal.last_step == 11 + + assert len(scene.objects) == 13 + agent_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'agent' + ] + assert len(agent_object_list) == 1 + home_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'home' + ] + assert len(home_object_list) == 1 + non_target_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'non target' + ] + assert len(non_target_object_list) == 1 + target_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'target' + ] + assert len(target_object_list) == 1 + platform_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'structural' + ] + assert len(platform_object_list) == 1 + wall_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'wall' + ] + assert len(wall_object_list) == 8 + + +def test_create_scene_with_paddle(): + starter_scene = Scene() + goal_template = hypercubes.initialize_goal( + Goal(category='mock', domains_info={}, scene_info={}) + ) + + agent_object_config_list = [ + ObjectConfigWithMaterial( + AgentConfig('cube', 1, rotation_y=0), + MaterialTuple('test_material', ['test_color']) + ) + ] + goal_object_config_list = [ + ObjectConfigWithMaterial( + ObjectConfig('cube', 0.707, 1, rotation_y=45), + MaterialTuple('test_material', ['test_color']) + ), + ObjectConfigWithMaterial( + ObjectConfig('sphere', 1, 1), MaterialTuple('test_material', ['test_color']) ) ] @@ -1668,9 +2916,10 @@ def test_create_scene(): 'agent': [[25, 25], 5, 'agent_1'], 'home': [[25, 25], 5], 'objects': [ - [[25, 45], 5, 'obj_1'], - [[95, 95], 5, 'obj_2'] + [[25, 45], 5, 'obj_1', 'yellow'], + [[95, 95], 5, 'obj_2', 'purple'] ], + 'paddle': [[160, 160], [10, 40], 120], 'size': [200, 200], 'walls': [ [[60, 60], [20, 20]], @@ -1679,19 +2928,24 @@ def test_create_scene(): [[120, 120], [20, 20]] ] }, { - 'agent': [[25, 25], 5, 'agent_1'] + 'agent': [[25, 25], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 125] }, { - 'agent': [[25, 30], 5, 'agent_1'] + 'agent': [[25, 30], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 130] }, { - 'agent': [[25, 35], 5, 'agent_1'] + 'agent': [[25, 35], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 135] }, { - 'agent': [[25, 35], 5, 'agent_1'] + 'agent': [[25, 35], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 140] }], [{ 'agent': [[25, 25], 5, 'agent_1'], 'objects': [ - [[45, 25], 5, 'obj_1'], - [[165, 165], 5, 'obj_2'] + [[45, 25], 5, 'obj_1', 'yellow'], + [[165, 165], 5, 'obj_2', 'purple'] ], + 'paddle': [[160, 160], [10, 40], 120], 'walls': [ [[60, 60], [20, 20]], [[60, 120], [20, 20]], @@ -1699,18 +2953,59 @@ def test_create_scene(): [[120, 120], [20, 20]] ] }, { - 'agent': [[25, 25], 5, 'agent_1'] + 'agent': [[25, 25], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 125] }, { - 'agent': [[30, 25], 5, 'agent_1'] + 'agent': [[30, 25], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 130] }, { - 'agent': [[35, 25], 5, 'agent_1'] + 'agent': [[35, 25], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 135] }, { - 'agent': [[35, 25], 5, 'agent_1'] + 'agent': [[35, 25], 5, 'agent_1'], + 'paddle': [[160, 160], [10, 40], 140] }]] - for is_expected in [True, False]: - print(f'Testing is_expected={is_expected}') - scene = _create_scene( + # Valid scene. + scene = _create_scene( + starter_scene, + goal_template, + agent_object_config_list, + goal_object_config_list, + trial_list, + 'filename', + MaterialTuple('test_material', ['test_color']), + True + ) + assert scene + paddle_object_list = [ + mcs_object for mcs_object in scene.objects + if mcs_object['debug']['role'] == 'paddle' + ] + assert len(paddle_object_list) == 1 + + # Still a valid scene. + for frame in trial_list[-1]: + # Move the paddle to be near the agent, but not overlap it. + frame['paddle'][0] = [18, 18] + scene = _create_scene( + starter_scene, + goal_template, + agent_object_config_list, + goal_object_config_list, + trial_list, + 'filename', + MaterialTuple('test_material', ['test_color']), + True + ) + assert scene + + # Invalid scene. + for frame in trial_list[-1]: + # Move the paddle to overlap with the agent. + frame['paddle'][0] = [19, 19] + with pytest.raises(exceptions.SceneException): + _create_scene( starter_scene, goal_template, agent_object_config_list, @@ -1718,53 +3013,9 @@ def test_create_scene(): trial_list, 'filename', MaterialTuple('test_material', ['test_color']), - is_expected + True ) - assert scene.isometric - assert scene.goal['answer']['choice'] == ( - 'expected' if is_expected else 'unexpected' - ) - assert scene.goal['action_list'] == [ - ['Pass'], ['Pass'], ['Pass'], ['Pass'], ['Pass'], - ['EndHabituation'], - ['Pass'], ['Pass'], ['Pass'], ['Pass'], ['Pass'] - ] - assert scene.goal['habituation_total'] == 1 - assert scene.goal['last_step'] == 11 - - assert len(scene.objects) == 13 - agent_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'agent' - ] - assert len(agent_object_list) == 1 - home_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'home' - ] - assert len(home_object_list) == 1 - non_target_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'non target' - ] - assert len(non_target_object_list) == 1 - target_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'target' - ] - assert len(target_object_list) == 1 - platform_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'structural' - ] - assert len(platform_object_list) == 1 - wall_object_list = [ - mcs_object for mcs_object in scene.objects - if mcs_object['debug']['role'] == 'wall' - ] - assert len(wall_object_list) == 8 - def test_create_show(): show = _create_show( @@ -1785,10 +3036,10 @@ def test_create_show(): assert show['scale']['y'] == 2 assert show['scale']['z'] == 4 assert show['boundingBox'].box_xz == [ - Vector3d(x=-1.36, y=0, z=-0.28), - Vector3d(x=-1.36, y=0, z=-1.72), - Vector3d(x=-2.14, y=0, z=-1.72), - Vector3d(x=-2.14, y=0, z=-0.28) + Vector3d(x=-1.36, y=0, z=-0.4), + Vector3d(x=-1.36, y=0, z=-1.6), + Vector3d(x=-2.14, y=0, z=-1.6), + Vector3d(x=-2.14, y=0, z=-0.4) ] assert show['boundingBox'].max_y == pytest.approx(1.8) assert show['boundingBox'].min_y == pytest.approx(0.2) @@ -1864,11 +3115,6 @@ def test_create_static_wall_object_list_change_in_some_trials(): verify_static_wall_list_properties(wall_object_list[11:]) -def test_create_static_wall_object_list_agent_non_agent_tasks(): - # TODO - pass - - def test_create_trial_frame_list(): trial = [ {'agent': [[20, 20]]}, @@ -1876,6 +3122,7 @@ def test_create_trial_frame_list(): {'agent': [[20, 20]]}, {'agent': [[20, 20]]}, {'agent': [[20, 20]]}, + {'agent': [[20, 20]]}, {'agent': [[21, 20]]}, {'agent': [[21, 21]]}, {'agent': [[22, 21]]}, @@ -1892,7 +3139,7 @@ def test_create_trial_frame_list(): {'agent': [[25, 25]]}, {'agent': [[25, 25]]} ] - converted_trial = _create_trial_frame_list(trial) + converted_trial = _create_trial_frame_list(trial, 0) assert converted_trial == [ {'agent': [[20, 20]]}, {'agent': [[20, 20]]}, @@ -1903,7 +3150,6 @@ def test_create_trial_frame_list(): {'agent': [[24, 23]]}, {'agent': [[25, 24]]}, {'agent': [[25, 25]]}, - {'agent': [[25, 25]]}, {'agent': [[25, 25]]} ] @@ -2127,7 +3373,7 @@ def test_create_trial_frame_list_instrumental_action(): } ] - converted_trial = _create_trial_frame_list(trial) + converted_trial = _create_trial_frame_list(trial, 0) assert converted_trial == [ { 'agent': [[20, 20]], @@ -2264,6 +3510,107 @@ def test_create_trial_frame_list_instrumental_action(): ] +def test_create_trial_frame_list_imitated_agents(): + trial = [ + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 13]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 14]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 15]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 16]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 15]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 14]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 13]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[31, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[41, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[51, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[61, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[51, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[41, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[31, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 3]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 4]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 5]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 6]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 5]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 4]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 3]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]} + ] + converted_trial = _create_trial_frame_list(trial, 0) + assert converted_trial == [ + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 13]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 15]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 15]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 13]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[41, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[61, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[41, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 3]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 5]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 5]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 3]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]}, + {'agent': [[1, 2]], 'other_agents': [[[11, 12]], [[21, 22]]]} + ] + + +def test_create_trial_frame_list_paddle(): + trial = [ + {'agent': [[10, 10]], 'paddle': [[150, 150], i]} for i in range(180) + ] + for i in range(0, 60): + trial[i + 60]['agent'][0][0] += i + 1 + trial[i + 120]['agent'][0][0] += 60 + + converted_trial = _create_trial_frame_list(trial, 0) + + assert converted_trial == [ + {'agent': [[10, 10]], 'paddle': [[150, 150], 0]}, + {'agent': [[10, 10]], 'paddle': [[150, 150], 1]}, + ] + [ + {'agent': [[10, 10]], 'paddle': [[150, 150], i]} + for i in range(3, 60, 2) + ] + [ + {'agent': [[10 + i + 1, 10]], 'paddle': [[150, 150], 60 + i]} + for i in range(1, 60, 2) + ] + [ + {'agent': [[70, 10]], 'paddle': [[150, 150], 120 + i]} + for i in range(1, 10, 2) + ] + + def test_create_wall_object_list(): trial_list = [[{ 'walls': create_wall_json_list() @@ -2389,11 +3736,6 @@ def test_create_wall_object_list_with_fuse_multiple_trials(): verify_fuse_wall_list_trial_2(wall_object_list[9:]) -def test_create_wall_object_list_agent_non_agent_tasks(): - # TODO - pass - - def test_fix_key_location(): # negative_x json_key = [[90, 90], 10, 'triangle0.png'] @@ -2625,8 +3967,580 @@ def test_identify_trial_index_starting_step(): def test_move_agent_adjacent_to_goal(): - # TODO - pass + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 18 + for i in range(0, 17): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }] + + +def test_move_agent_adjacent_to_goal_too_far_away(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.25, 2.25, 3.25, 2.25) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.75, 'y': 0, 'z': 2.75}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 18 + for i in range(0, 18): + assert agent['shows'][i] == original[i] + for show in agent['shows']: + step = show['stepBegin'] + assert agent['debug']['boundsAtStep'][step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [] + + +def test_move_agent_adjacent_to_goal_multiple_trials(): + agent = create_test_agent_moving_diagonally(trial_2=True) + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(2.5, 1.5, 3.1, 2.1) + goal_bounds_2 = create_simple_bounds(-2.1, -3.1, -1.5, -2.5) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 25 + [goal_bounds_2] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }, { + 'stepBegin': 25, + 'position': {'x': -2.55, 'y': 0, 'z': -2}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_2 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24, [{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 36 + for i in list(range(0, 17)) + list(range(18, 35)): + assert agent['shows'][i] == original[i] + bounds_at_step = agent['debug']['boundsAtStep'] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }, { + 'stepBegin': 46, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 50, + 'materials': ['Custom/Materials/Orange'] + }] + # Trial 1 + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.875, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + # Trial 2 + assert agent['shows'][35]['stepBegin'] == 46 + expected_position = {'x': -1.975, 'y': 0.125, 'z': -1.875} + assert agent['shows'][35]['position'] == expected_position + assert agent['shows'][35]['boundingBox'] != original[35]['boundingBox'] + assert bounds_at_step[46] != original[35]['boundingBox'] + assert bounds_at_step[47] == bounds_at_step[46] + assert bounds_at_step[48] == bounds_at_step[46] + + +def test_move_agent_adjacent_to_goal_multiple_objects(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_bounds_2 = create_simple_bounds(3.1, 2.1, 0.5, -0.5) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + goal_object_2 = { + 'id': 'goal_2', + 'type': 'cube_hollow_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_2] * 25 + }, + 'materials': ['Custom/Materials/Grey'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 0}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_2 + }] + } + goal_object_list = [goal_object_1, goal_object_2] + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, goal_object_list, trial_list) + + assert len(agent['shows']) == 18 + for i in range(0, 17): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }] + assert goal_object_2['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Grey'] + }] + + +def test_move_agent_adjacent_to_goal_small_overlap(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(2.9, 1.9, 2.9, 1.9) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.45, 'y': 0, 'z': 2.45}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + # Minor overlap is OK; if not, other parts of the code will likely raise + # an exception. + assert len(agent['shows']) == 18 + for i in range(0, 18): + assert agent['shows'][i] == original[i] + for show in agent['shows']: + step = show['stepBegin'] + assert agent['debug']['boundsAtStep'][step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }] + + +def test_move_agent_adjacent_to_goal_toward_then_away(): + agent = create_test_agent_moving_diagonally() + agent['debug']['boundsAtStep'] = agent['debug']['boundsAtStep'][:-3] + extras = list(reversed(copy.deepcopy(agent['shows'][:-1]))) + for i in range(0, 17): + extras[i]['stepBegin'] = 22 + i + agent['debug']['boundsAtStep'].append(extras[i]['boundingBox']) + agent['shows'].extend(extras) + agent['debug']['boundsAtStep'].extend([extras[-1]['boundingBox']] * 3) + agent['debug']['trialToSteps'][0] = (0, 41) + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_1] * 42 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 41] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 35 + for i in list(range(0, 17)) + list(range(18, 35)): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 42, + 'materials': ['Custom/Materials/Orange'] + }] + + +def test_move_agent_adjacent_to_goal_agent_touches(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'agentTouches': { + 0: [21] + }, + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 18 + for i in range(0, 17): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }] + + +def test_move_agent_adjacent_to_goal_agent_touches_multiple_touches(): + agent = create_test_agent_moving_diagonally() + agent['debug']['boundsAtStep'] = agent['debug']['boundsAtStep'][:-3] + extras = ( + list(reversed(copy.deepcopy(agent['shows'][:-1]))) + + list(copy.deepcopy(agent['shows'][1:])) + ) + for i in range(0, 34): + extras[i]['stepBegin'] = 22 + i + agent['debug']['boundsAtStep'].append(extras[i]['boundingBox']) + agent['shows'].extend(extras) + agent['debug']['boundsAtStep'].extend([extras[-1]['boundingBox']] * 3) + agent['debug']['trialToSteps'][0] = (0, 58) + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'agentTouches': { + 0: [21, 55] + }, + 'boundsAtStep': [goal_bounds_1] * 59 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 58] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 52 + for i in list(range(0, 17)) + list(range(18, 51)): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert agent['shows'][51]['stepBegin'] == 55 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][51]['position'] == expected_position + assert agent['shows'][51]['boundingBox'] != original[47]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[55] != original[51]['boundingBox'] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 55, + 'materials': ['Custom/Materials/Orange'] + }, { + 'stepBegin': 59, + 'materials': ['Custom/Materials/Orange'] + }] + + +def test_move_agent_adjacent_to_goal_agent_touches_multiple_trials(): + agent = create_test_agent_moving_diagonally(trial_2=True) + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(2.5, 1.5, 3.1, 2.1) + goal_bounds_2 = create_simple_bounds(-2.1, -3.1, -1.5, -2.5) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'agentTouches': { + 0: [21], + 1: [46] + }, + 'boundsAtStep': [goal_bounds_1] * 25 + [goal_bounds_2] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }, { + 'stepBegin': 25, + 'position': {'x': -2.55, 'y': 0, 'z': -2}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_2 + }] + } + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24, [{}] * 24] + + _move_agent_adjacent_to_goal(agent, [goal_object_1], trial_list) + + assert len(agent['shows']) == 36 + for i in list(range(0, 17)) + list(range(18, 35)): + assert agent['shows'][i] == original[i] + bounds_at_step = agent['debug']['boundsAtStep'] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }, { + 'stepBegin': 46, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 50, + 'materials': ['Custom/Materials/Orange'] + }] + # Trial 1 + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.875, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + # Trial 2 + assert agent['shows'][35]['stepBegin'] == 46 + expected_position = {'x': -1.975, 'y': 0.125, 'z': -1.875} + assert agent['shows'][35]['position'] == expected_position + assert agent['shows'][35]['boundingBox'] != original[35]['boundingBox'] + assert bounds_at_step[46] != original[35]['boundingBox'] + assert bounds_at_step[47] == bounds_at_step[46] + assert bounds_at_step[48] == bounds_at_step[46] + + +def test_move_agent_adjacent_to_goal_agent_touches_multiple_objects(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + goal_bounds_1 = create_simple_bounds(3.1, 2.1, 3.1, 2.1) + goal_bounds_2 = create_simple_bounds(3.1, 2.1, 0.5, -0.5) + goal_object_1 = { + 'id': 'goal_1', + 'type': 'tube_wide', + 'changeMaterials': [], + 'debug': { + 'agentTouches': { + 0: [21] + }, + 'boundsAtStep': [goal_bounds_1] * 25 + }, + 'materials': ['Custom/Materials/Orange'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 2.55}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_1 + }] + } + goal_object_2 = { + 'id': 'goal_2', + 'type': 'cube_hollow_wide', + 'changeMaterials': [], + 'debug': { + 'boundsAtStep': [goal_bounds_2] * 25 + }, + 'materials': ['Custom/Materials/Grey'], + 'shows': [{ + 'stepBegin': 0, + 'position': {'x': 2.55, 'y': 0, 'z': 0}, + 'rotation': {'x': 0, 'y': 0, 'z': 0}, + 'boundingBox': goal_bounds_2 + }] + } + goal_object_list = [goal_object_1, goal_object_2] + # Number of frames in each trial is always one less than number of bounds. + trial_list = [[{}] * 24] + + _move_agent_adjacent_to_goal(agent, goal_object_list, trial_list) + + assert len(agent['shows']) == 18 + for i in range(0, 17): + assert agent['shows'][i] == original[i] + assert agent['shows'][17]['stepBegin'] == 21 + expected_position = {'x': 1.975, 'y': 0.125, 'z': 1.975} + assert agent['shows'][17]['position'] == expected_position + assert agent['shows'][17]['boundingBox'] != original[17]['boundingBox'] + bounds_at_step = agent['debug']['boundsAtStep'] + assert bounds_at_step[21] != original[17]['boundingBox'] + assert bounds_at_step[22] == bounds_at_step[21] + assert bounds_at_step[23] == bounds_at_step[21] + for show in agent['shows']: + step = show['stepBegin'] + assert bounds_at_step[step] == show['boundingBox'] + assert goal_object_1['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Orange'] + }] + assert goal_object_2['changeMaterials'] == [{ + 'stepBegin': 21, + 'materials': ['Custom/Materials/Red'] + }, { + 'stepBegin': 25, + 'materials': ['Custom/Materials/Grey'] + }] def test_move_agent_past_lock_location(): @@ -3290,28 +5204,31 @@ def test_remove_extraneous_object_show_multiple_agents_across_trials(): def test_remove_intersecting_agent_steps_with_no_intersection(): + bounds_a = create_simple_bounds(1, -1, 1, -1) + bounds_b = create_simple_bounds(2, 0, 2, 0) + bounds_c = create_simple_bounds(3, 1, 3, 1) + bounds_d = create_simple_bounds(4, 2, 4, 2) agent_object_list = [{ + 'id': 'agent_1', 'debug': { 'boundsAtStep': [ - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(2, 0, 2, 0), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(4, 2, 4, 2), - create_simple_bounds(4, 2, 4, 2), - create_simple_bounds(4, 2, 4, 2) - ] + bounds_a, bounds_a, bounds_a, bounds_b, bounds_c, + bounds_d, bounds_d, bounds_d + ], + 'trialToSteps': { + 0: (0, 7) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 3}, - {'stepBegin': 4}, - {'stepBegin': 5} + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 3, 'boundingBox': bounds_b}, + {'stepBegin': 4, 'boundingBox': bounds_c}, + {'stepBegin': 5, 'boundingBox': bounds_d} ] }] goal_object_list = [{ + 'id': 'goal_1', 'debug': { 'boundsAtStep': [create_simple_bounds(6.1, 4.1, 6.1, 4.1)] * 8 } @@ -3323,36 +5240,36 @@ def test_remove_intersecting_agent_steps_with_no_intersection(): def test_remove_intersecting_agent_steps_with_intersection(): + bounds_a = create_simple_bounds(1, -1, 1, -1) + bounds_b = create_simple_bounds(2, 0, 2, 0) + bounds_c = create_simple_bounds(3, 1, 3, 1) + bounds_d = create_simple_bounds(1, -1, 1, -1) + bounds_e = create_simple_bounds(0, -2, 0, -2) + bounds_f = create_simple_bounds(-1, -3, -1, -3) agent_object_list = [{ + 'id': 'agent_1', 'debug': { 'boundsAtStep': [ - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(2, 0, 2, 0), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(0, -2, 0, -2), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3) - ] + bounds_a, bounds_a, bounds_a, bounds_b, bounds_c, + bounds_c, bounds_c, bounds_d, bounds_d, bounds_d, + bounds_e, bounds_f, bounds_f, bounds_f + ], + 'trialToSteps': { + 0: (0, 13) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 3}, - {'stepBegin': 4}, - {'stepBegin': 7}, - {'stepBegin': 10}, - {'stepBegin': 11} + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 3, 'boundingBox': bounds_b}, + {'stepBegin': 4, 'boundingBox': bounds_c}, + {'stepBegin': 7, 'boundingBox': bounds_d}, + {'stepBegin': 10, 'boundingBox': bounds_e}, + {'stepBegin': 11, 'boundingBox': bounds_f} ] }] goal_object_list = [{ + 'id': 'goal_1', 'debug': { 'boundsAtStep': [create_simple_bounds(3.9, 2.9, 3.9, 2.9)] * 14 } @@ -3369,63 +5286,58 @@ def test_remove_intersecting_agent_steps_with_intersection(): def test_remove_intersecting_agent_steps_with_multiple_agents(): + bounds_a = create_simple_bounds(1, -1, 1, -1) + bounds_b = create_simple_bounds(2, 0, 2, 0) + bounds_c = create_simple_bounds(3, 1, 3, 1) + bounds_d = create_simple_bounds(1, -1, 1, -1) + bounds_e = create_simple_bounds(0, -2, 0, -2) + bounds_f = create_simple_bounds(-1, -3, -1, -3) agent_object_list = [{ + 'id': 'agent_1', 'debug': { 'boundsAtStep': [ - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(2, 0, 2, 0), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(0, -2, 0, -2), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3) - ] + bounds_a, bounds_a, bounds_a, bounds_b, bounds_c, + bounds_c, bounds_c, bounds_d, bounds_d, bounds_d, + bounds_e, bounds_f, bounds_f, bounds_f + ], + 'trialToSteps': { + 0: (0, 13) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 3}, - {'stepBegin': 4}, - {'stepBegin': 7}, - {'stepBegin': 10}, - {'stepBegin': 11} + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 3, 'boundingBox': bounds_b}, + {'stepBegin': 4, 'boundingBox': bounds_c}, + {'stepBegin': 7, 'boundingBox': bounds_d}, + {'stepBegin': 10, 'boundingBox': bounds_e}, + {'stepBegin': 11, 'boundingBox': bounds_f} ] }, { + 'id': 'agent_2', 'debug': { 'boundsAtStep': [ - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(0, -2, 0, -2), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(2, 0, 2, 0), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1) - ] + # Intentionally out-of-order + bounds_d, bounds_d, bounds_d, bounds_e, bounds_f, + bounds_f, bounds_f, bounds_a, bounds_a, bounds_a, + bounds_b, bounds_c, bounds_c, bounds_c + ], + 'trialToSteps': { + 0: (0, 13) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 3}, - {'stepBegin': 4}, - {'stepBegin': 7}, - {'stepBegin': 10}, - {'stepBegin': 11} + # Intentionally out-of-order + {'stepBegin': 0, 'boundingBox': bounds_d}, + {'stepBegin': 3, 'boundingBox': bounds_e}, + {'stepBegin': 4, 'boundingBox': bounds_f}, + {'stepBegin': 7, 'boundingBox': bounds_a}, + {'stepBegin': 10, 'boundingBox': bounds_b}, + {'stepBegin': 11, 'boundingBox': bounds_c} ] }] goal_object_list = [{ + 'id': 'goal_1', 'debug': { 'boundsAtStep': [create_simple_bounds(3.9, 2.9, 3.9, 2.9)] * 14 } @@ -3449,40 +5361,41 @@ def test_remove_intersecting_agent_steps_with_multiple_agents(): def test_remove_intersecting_agent_steps_with_multiple_objects(): + bounds_a = create_simple_bounds(1, -1, 1, -1) + bounds_b = create_simple_bounds(2, 0, 2, 0) + bounds_c = create_simple_bounds(3, 1, 3, 1) + bounds_d = create_simple_bounds(1, -1, 1, -1) + bounds_e = create_simple_bounds(0, -2, 0, -2) + bounds_f = create_simple_bounds(-1, -3, -1, -3) agent_object_list = [{ + 'id': 'agent_1', 'debug': { 'boundsAtStep': [ - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(2, 0, 2, 0), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(3, 1, 3, 1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(1, -1, 1, -1), - create_simple_bounds(0, -2, 0, -2), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3), - create_simple_bounds(-1, -3, -1, -3) - ] + bounds_a, bounds_a, bounds_a, bounds_b, bounds_c, + bounds_c, bounds_c, bounds_d, bounds_d, bounds_d, + bounds_e, bounds_f, bounds_f, bounds_f + ], + 'trialToSteps': { + 0: (0, 13) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 3}, - {'stepBegin': 4}, - {'stepBegin': 7}, - {'stepBegin': 10}, - {'stepBegin': 11} + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 3, 'boundingBox': bounds_b}, + {'stepBegin': 4, 'boundingBox': bounds_c}, + {'stepBegin': 7, 'boundingBox': bounds_d}, + {'stepBegin': 10, 'boundingBox': bounds_e}, + {'stepBegin': 11, 'boundingBox': bounds_f} ] }] goal_object_list = [{ + 'id': 'goal_1', 'debug': { 'boundsAtStep': [create_simple_bounds(2.9, 1.9, 3.9, 2.9)] * 14 } }, { + 'id': 'goal_2', 'debug': { 'boundsAtStep': [create_simple_bounds(-3.9, -2.9, -2.9, -1.9)] * 14 } @@ -3498,28 +5411,35 @@ def test_remove_intersecting_agent_steps_with_multiple_objects(): def test_remove_intersecting_agent_steps_with_ellipses(): + bounds_1 = create_ellipsoidal_bounds(0, 0, 0.5, 1) + bounds_2 = create_ellipsoidal_bounds(0.5, 0.5, 0.5, 1) + bounds_3 = create_ellipsoidal_bounds(1, 1, 0.5, 1) + bounds_4 = create_ellipsoidal_bounds(1.5, 1.5, 0.5, 1) + bounds_5 = create_ellipsoidal_bounds(2, 2, 0.5, 1) + bounds_6 = bounds_5 agent_object_list = [{ + 'id': 'agent_1', 'debug': { 'boundsAtStep': [ - create_ellipsoidal_bounds(0, 0, 0.5, 1), - create_ellipsoidal_bounds(0.5, 0.5, 0.5, 1), - create_ellipsoidal_bounds(1, 1, 0.5, 1), - create_ellipsoidal_bounds(1.5, 1.5, 0.5, 1), - create_ellipsoidal_bounds(2, 2, 0.5, 1) - ] + bounds_1, bounds_2, bounds_3, bounds_4, bounds_5, bounds_6 + ], + 'trialToSteps': { + 0: (0, 5) + } }, 'shows': [ - {'stepBegin': 0}, - {'stepBegin': 1}, - {'stepBegin': 2}, - {'stepBegin': 3}, - {'stepBegin': 4} + {'stepBegin': 0, 'boundingBox': bounds_1}, + {'stepBegin': 1, 'boundingBox': bounds_2}, + {'stepBegin': 2, 'boundingBox': bounds_3}, + {'stepBegin': 3, 'boundingBox': bounds_4}, + {'stepBegin': 4, 'boundingBox': bounds_5} ] }] goal_object_list = [{ + 'id': 'goal_1', 'debug': { - 'boundsAtStep': [create_ellipsoidal_bounds(2, 2, 0.5, 1)] * 5 + 'boundsAtStep': [create_ellipsoidal_bounds(2, 2, 0.5, 1)] * 6 } }] @@ -3531,6 +5451,246 @@ def test_remove_intersecting_agent_steps_with_ellipses(): assert agent_object_list[0]['shows'][2]['stepBegin'] == 2 +def test_remove_intersecting_agent_steps_with_object_in_middle_of_path(): + bounds_a = create_simple_bounds(-2.5, -2.25, -2.5, -2.25) + bounds_b = create_simple_bounds(-2.25, -2.0, -2.25, -2.0) + bounds_c = create_simple_bounds(-2.0, -1.75, -2.0, -1.75) + bounds_d = create_simple_bounds(-1.75, -1.5, -1.75, -1.5) + bounds_e = create_simple_bounds(-1.5, -1.25, -1.5, -1.25) + bounds_f = create_simple_bounds(-1.25, -1.0, -1.25, -1.0) + bounds_g = create_simple_bounds(-1.0, -0.75, -1.0, -0.75) + bounds_h = create_simple_bounds(-0.75, -0.5, -0.75, -0.5) + bounds_i = create_simple_bounds(-0.5, -0.25, -0.5, -0.25) + bounds_j = create_simple_bounds(-0.25, 0, -0.25, 0) + bounds_k = create_simple_bounds(0, 0.25, 0, 0.25) + bounds_l = create_simple_bounds(0.25, 0.5, 0.25, 0.5) + bounds_m = create_simple_bounds(0.5, 0.75, 0.5, 0.75) + bounds_n = create_simple_bounds(0.75, 1.0, 0.75, 1.0) + bounds_o = create_simple_bounds(1.0, 1.25, 1.0, 1.25) + bounds_p = create_simple_bounds(1.25, 1.5, 1.25, 1.5) + bounds_q = create_simple_bounds(1.5, 1.75, 1.5, 1.75) + bounds_r = create_simple_bounds(1.75, 2.0, 1.75, 2.0) + agent_object_list = [{ + 'id': 'agent_1', + 'debug': { + 'boundsAtStep': [ + bounds_a, bounds_a, bounds_a, bounds_a, bounds_a, + bounds_b, bounds_c, bounds_d, bounds_e, bounds_f, + bounds_g, bounds_h, bounds_i, bounds_j, bounds_k, + bounds_l, bounds_m, bounds_n, bounds_o, bounds_p, + bounds_q, bounds_r, bounds_r, bounds_r, bounds_r + ], + 'trialToSteps': { + 0: (0, 24) + } + }, + 'shows': [ + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 5, 'boundingBox': bounds_b}, + {'stepBegin': 6, 'boundingBox': bounds_c}, + {'stepBegin': 7, 'boundingBox': bounds_d}, + {'stepBegin': 8, 'boundingBox': bounds_e}, + {'stepBegin': 9, 'boundingBox': bounds_f}, + {'stepBegin': 10, 'boundingBox': bounds_g}, + {'stepBegin': 11, 'boundingBox': bounds_h}, + {'stepBegin': 12, 'boundingBox': bounds_i}, + {'stepBegin': 13, 'boundingBox': bounds_j}, + {'stepBegin': 14, 'boundingBox': bounds_k}, + {'stepBegin': 15, 'boundingBox': bounds_l}, + {'stepBegin': 16, 'boundingBox': bounds_m}, + {'stepBegin': 17, 'boundingBox': bounds_n}, + {'stepBegin': 18, 'boundingBox': bounds_o}, + {'stepBegin': 19, 'boundingBox': bounds_p}, + {'stepBegin': 20, 'boundingBox': bounds_q}, + {'stepBegin': 21, 'boundingBox': bounds_r} + ] + }] + + non_agent_list = [{ + 'id': 'goal_1', + 'debug': { + 'boundsAtStep': [create_simple_bounds(0.5, -0.5, 0.5, -0.5)] * 25 + } + }] + + _remove_intersecting_agent_steps(agent_object_list, non_agent_list) + + # Never show an agent intersecting another object by a significantly large + # amount. In a real case this egregious, other parts of the code will + # likely raise an exception before calling this function. + assert len(agent_object_list[0]['shows']) == 14 + assert agent_object_list[0]['shows'][0]['stepBegin'] == 0 + assert agent_object_list[0]['shows'][1]['stepBegin'] == 5 + assert agent_object_list[0]['shows'][2]['stepBegin'] == 6 + assert agent_object_list[0]['shows'][3]['stepBegin'] == 7 + assert agent_object_list[0]['shows'][4]['stepBegin'] == 8 + assert agent_object_list[0]['shows'][5]['stepBegin'] == 9 + assert agent_object_list[0]['shows'][6]['stepBegin'] == 10 + assert agent_object_list[0]['shows'][7]['stepBegin'] == 11 + assert agent_object_list[0]['shows'][8]['stepBegin'] == 16 + assert agent_object_list[0]['shows'][9]['stepBegin'] == 17 + assert agent_object_list[0]['shows'][10]['stepBegin'] == 18 + assert agent_object_list[0]['shows'][11]['stepBegin'] == 19 + assert agent_object_list[0]['shows'][12]['stepBegin'] == 20 + assert agent_object_list[0]['shows'][13]['stepBegin'] == 21 + + +def test_remove_intersecting_agent_steps_with_wall_in_middle_of_path(): + bounds_a = create_simple_bounds(-2.5, -2.25, -2.5, -2.25) + bounds_b = create_simple_bounds(-2.25, -2.0, -2.25, -2.0) + bounds_c = create_simple_bounds(-2.0, -1.75, -2.0, -1.75) + bounds_d = create_simple_bounds(-1.75, -1.5, -1.75, -1.5) + bounds_e = create_simple_bounds(-1.5, -1.25, -1.5, -1.25) + bounds_f = create_simple_bounds(-1.25, -1.0, -1.25, -1.0) + bounds_g = create_simple_bounds(-1.0, -0.75, -1.0, -0.75) + bounds_h = create_simple_bounds(-0.75, -0.5, -0.75, -0.5) + bounds_i = create_simple_bounds(-0.5, -0.25, -0.5, -0.25) + bounds_j = create_simple_bounds(-0.25, 0, -0.25, 0) + bounds_k = create_simple_bounds(0, 0.25, 0, 0.25) + bounds_l = create_simple_bounds(0.25, 0.5, 0.25, 0.5) + bounds_m = create_simple_bounds(0.5, 0.75, 0.5, 0.75) + bounds_n = create_simple_bounds(0.75, 1.0, 0.75, 1.0) + bounds_o = create_simple_bounds(1.0, 1.25, 1.0, 1.25) + bounds_p = create_simple_bounds(1.25, 1.5, 1.25, 1.5) + bounds_q = create_simple_bounds(1.5, 1.75, 1.5, 1.75) + bounds_r = create_simple_bounds(1.75, 2.0, 1.75, 2.0) + agent_object_list = [{ + 'id': 'agent_1', + 'debug': { + 'boundsAtStep': [ + bounds_a, bounds_a, bounds_a, bounds_a, bounds_a, + bounds_b, bounds_c, bounds_d, bounds_e, bounds_f, + bounds_g, bounds_h, bounds_i, bounds_j, bounds_k, + bounds_l, bounds_m, bounds_n, bounds_o, bounds_p, + bounds_q, bounds_r, bounds_r, bounds_r, bounds_r + ], + 'trialToSteps': { + 0: (0, 24) + } + }, + 'shows': [ + {'stepBegin': 0, 'boundingBox': bounds_a}, + {'stepBegin': 5, 'boundingBox': bounds_b}, + {'stepBegin': 6, 'boundingBox': bounds_c}, + {'stepBegin': 7, 'boundingBox': bounds_d}, + {'stepBegin': 8, 'boundingBox': bounds_e}, + {'stepBegin': 9, 'boundingBox': bounds_f}, + {'stepBegin': 10, 'boundingBox': bounds_g}, + {'stepBegin': 11, 'boundingBox': bounds_h}, + {'stepBegin': 12, 'boundingBox': bounds_i}, + {'stepBegin': 13, 'boundingBox': bounds_j}, + {'stepBegin': 14, 'boundingBox': bounds_k}, + {'stepBegin': 15, 'boundingBox': bounds_l}, + {'stepBegin': 16, 'boundingBox': bounds_m}, + {'stepBegin': 17, 'boundingBox': bounds_n}, + {'stepBegin': 18, 'boundingBox': bounds_o}, + {'stepBegin': 19, 'boundingBox': bounds_p}, + {'stepBegin': 20, 'boundingBox': bounds_q}, + {'stepBegin': 21, 'boundingBox': bounds_r} + ] + }] + original = copy.deepcopy(agent_object_list[0]['shows']) + + non_agent_list = [{ + 'id': 'wall_1', + 'debug': { + 'boundsAtStep': [create_simple_bounds(0.5, -0.5, 0.5, -0.5)] * 25 + } + }] + + _remove_intersecting_agent_steps(agent_object_list, non_agent_list) + + # It's OK to show an agent intersecting with a wall in the middle of its + # path. In a real case this egregious, other parts of the code will + # likely raise an exception before calling this function. Normally the + # agent may just clip the wall a little. + assert original == agent_object_list[0]['shows'] + + +def test_reposition_agents_away_from_paddle_unnecessary_no_intersection(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + + paddle_bounds_a = create_simple_bounds(-3.2, -2.7, 1.5, 3.5) + paddle_bounds_b = create_simple_bounds(-3.95, -1.95, 1.5, 3.5) + paddle = { + 'id': 'paddle', + 'debug': { + 'boundsAtStep': ([paddle_bounds_a] + ([paddle_bounds_b] * 9)) * 3 + }, + 'shows': [{'stepBegin': i} for i in range(0, 25)] + } + + _reposition_agents_away_from_paddle([agent], paddle) + + assert len(agent['shows']) == 18 + for i in range(0, 18): + assert agent['shows'][i] == original[i] + + +def test_reposition_agents_away_from_paddle_necessary(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + + paddle_bounds_a = create_simple_bounds(-3.2, -2.7, -3.5, -1.5) + paddle_bounds_b = create_simple_bounds(-3.95, -1.95, -3.5, -1.5) + paddle = { + 'id': 'paddle', + 'debug': { + 'boundsAtStep': ([paddle_bounds_a] + ([paddle_bounds_b] * 9)) * 3 + }, + 'shows': [{'stepBegin': i} for i in range(0, 25)] + } + + _reposition_agents_away_from_paddle([agent], paddle) + + assert len(agent['shows']) == 18 + assert agent['shows'][0]['stepBegin'] == 0 + expected_position = {'x': -1.875, 'y': 0.125, 'z': -1.875} + assert agent['shows'][0]['position'] == expected_position + assert agent['shows'][1]['stepBegin'] == 5 + assert agent['shows'][1]['position'] == expected_position + assert agent['shows'][2]['stepBegin'] == 6 + assert agent['shows'][2]['position'] == expected_position + for i in range(3, 18): + assert agent['shows'][i] == original[i] + + +def test_reposition_agents_away_from_paddle_unnecessary_intersection_midway(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + + paddle_bounds_a = create_simple_bounds(-0.25, 0.25, 1, 1) + paddle_bounds_b = create_simple_bounds(-1, 1, -1, 1) + paddle = { + 'id': 'paddle', + 'debug': { + 'boundsAtStep': ([paddle_bounds_a] + ([paddle_bounds_b] * 9)) * 3 + }, + 'shows': [{'stepBegin': i} for i in range(0, 25)] + } + + _reposition_agents_away_from_paddle([agent], paddle) + + # Never remove steps from an agent's path due to the paddle after the agent + # starts moving. In a real case this egregious, other parts of the code + # will likely raise an exception before calling this function. + assert len(agent['shows']) == 18 + for i in range(0, 18): + assert agent['shows'][i] == original[i] + + +def test_reposition_agents_away_from_paddle_no_paddle(): + agent = create_test_agent_moving_diagonally() + original = copy.deepcopy(agent['shows']) + + _reposition_agents_away_from_paddle([agent], None) + + assert len(agent['shows']) == 18 + for i in range(0, 18): + assert agent['shows'][i] == original[i] + + def test_retrieve_unit_size(): assert _retrieve_unit_size([[{'size': [200, 200]}]]) == UNIT_SIZE assert _retrieve_unit_size([[{'size': [100, 400]}]]) == [0.05, 0.0125] diff --git a/tests/agents_test.py b/tests/agents_test.py index f069fd6..836a7c7 100644 --- a/tests/agents_test.py +++ b/tests/agents_test.py @@ -2,16 +2,47 @@ from machine_common_sense.config_manager import Vector3d from generator.agents import ( + FAMILIAR_AGENTS_FEMALE, + FAMILIAR_AGENTS_MALE, + NOVEL_AGENTS_FEMALE, + NOVEL_AGENTS_MALE, + WALL_MATERIALS_DARK_SKIN, + WALL_MATERIALS_LIGHT_SKIN, + WALL_MATERIALS_NO_AGENTS, add_agent_action, add_agent_pointing, create_agent, - create_blob + create_blob, + get_random_agent_settings ) from generator.exceptions import SceneException -from generator.materials import MaterialTuple +from generator.materials import ROOM_WALL_MATERIALS, MaterialTuple from ideal_learning_env.agent_service import AgentSettings +def test_agent_types(): + assert len(FAMILIAR_AGENTS_FEMALE) + assert len(FAMILIAR_AGENTS_MALE) + assert len(NOVEL_AGENTS_FEMALE) + assert len(NOVEL_AGENTS_MALE) + for agent_type in FAMILIAR_AGENTS_FEMALE: + assert agent_type not in FAMILIAR_AGENTS_MALE + assert agent_type not in NOVEL_AGENTS_FEMALE + assert agent_type not in NOVEL_AGENTS_MALE + for agent_type in FAMILIAR_AGENTS_MALE: + assert agent_type not in FAMILIAR_AGENTS_FEMALE + assert agent_type not in NOVEL_AGENTS_FEMALE + assert agent_type not in NOVEL_AGENTS_MALE + for agent_type in NOVEL_AGENTS_FEMALE: + assert agent_type not in FAMILIAR_AGENTS_FEMALE + assert agent_type not in FAMILIAR_AGENTS_MALE + assert agent_type not in NOVEL_AGENTS_MALE + for agent_type in NOVEL_AGENTS_MALE: + assert agent_type not in FAMILIAR_AGENTS_FEMALE + assert agent_type not in FAMILIAR_AGENTS_MALE + assert agent_type not in NOVEL_AGENTS_FEMALE + + def test_create_agent(): settings = vars(AgentSettings(chest=2, chestMaterial=4)) agent = create_agent( @@ -153,7 +184,8 @@ def test_create_blob_simple(): position_x=1, position_z=2, rotation_y=0, - material_tuple=MaterialTuple('test_material', ['test_color']) + material_tuple=MaterialTuple('test_material', ['test_color']), + height=0.9 ) assert blob['id'].startswith('blob') @@ -221,7 +253,8 @@ def test_create_blob_type_with_standing_y(): position_x=1, position_z=2, rotation_y=0, - material_tuple=MaterialTuple('test_material', ['test_color']) + material_tuple=MaterialTuple('test_material', ['test_color']), + height=0.9 ) assert blob['id'].startswith('blob') @@ -246,3 +279,161 @@ def test_create_blob_type_with_standing_y(): ] assert blob['shows'][0]['boundingBox'].min_y == 0 assert blob['shows'][0]['boundingBox'].max_y == 0.9 + + +def test_create_blob_with_nose(): + blob = create_blob( + type='blob_02', + position_x=1, + position_z=2, + rotation_y=0, + material_tuple=MaterialTuple('test_material', ['test_color']), + height=0.9, + with_nose=True + ) + + assert blob['id'].startswith('blob') + assert blob['type'] == 'blob_02_nose' + assert blob['physics'] is True + assert blob['mass'] + assert blob['materials'] == ['test_material'] + assert blob['debug']['color'] == ['test_color'] + assert blob['shows'][0]['position'] == {'x': 1, 'y': 0.45, 'z': 2} + assert blob['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert blob['shows'][0]['scale'] == {'x': 1.1538, 'y': 1.1538, 'z': 1.1538} + assert blob['debug']['dimensions'] == { + 'x': 0.3808, + 'y': 0.9, + 'z': 0.3808 + } + assert blob['shows'][0]['boundingBox'].box_xz == [ + Vector3d(x=1.1904, y=0, z=2.1904), + Vector3d(x=1.1904, y=0, z=1.8096), + Vector3d(x=0.8096, y=0, z=1.8096), + Vector3d(x=0.8096, y=0, z=2.1904), + ] + assert blob['shows'][0]['boundingBox'].min_y == 0 + assert blob['shows'][0]['boundingBox'].max_y == 0.9 + + +def verify_agent_settings(settings): + assert settings['chest'] is not None + assert settings['chestMaterial'] is not None + assert settings['eyes'] is not None + assert settings['feet'] is not None + assert settings['feetMaterial'] is not None + assert settings['hair'] is not None + assert settings['hairMaterial'] is not None + assert settings['hatMaterial'] is not None + assert settings['legs'] is not None + assert settings['legsMaterial'] is not None + assert settings['skin'] is not None + assert settings['hideHair'] in [True, False] + assert settings['isElder'] in [True, False] + assert settings['glasses'] == 0 + assert settings['jacket'] == 0 + assert settings['jacketMaterial'] == 0 + assert settings['tie'] == 0 + assert settings['tieMaterial'] == 0 + assert settings['showBeard'] is False + assert settings['showGlasses'] is False + assert settings['showJacket'] is False + assert settings['showTie'] is False + + +def test_get_random_agent_settings_male(): + settings = get_random_agent_settings('agent_male_01') + verify_agent_settings(settings) + + +def test_get_random_agent_settings_female(): + settings = get_random_agent_settings('agent_female_01') + verify_agent_settings(settings) + + +def test_get_random_agent_settings_male_teen(): + settings = get_random_agent_settings('agent_male_08') + verify_agent_settings(settings) + assert settings['isElder'] is False + + +def test_get_random_agent_settings_female_teen(): + settings = get_random_agent_settings('agent_female_05') + verify_agent_settings(settings) + assert settings['isElder'] is False + + +def test_get_random_agent_settings_short_sleeves_male(): + settings = get_random_agent_settings( + 'agent_male_01', + short_sleeves_only=True + ) + assert settings['chest'] in [1, 3, 6] + verify_agent_settings(settings) + + +def test_get_random_agent_settings_short_sleeves_female(): + settings = get_random_agent_settings( + 'agent_female_01', + short_sleeves_only=True + ) + assert settings['chest'] in [0, 1, 3, 5, 8] + verify_agent_settings(settings) + + +def test_get_random_agent_settings_custom_settings(): + settings = get_random_agent_settings( + 'agent_male_01', + settings={'chest': 1, 'skin': 3} + ) + assert settings['chest'] == 1 + assert settings['skin'] == 3 + verify_agent_settings(settings) + + +def test_get_random_agent_settings_custom_settings_override(): + settings = get_random_agent_settings('agent_male_01', settings={ + 'glasses': 1, + 'jacket': 1, + 'jacketMaterial': 1, + 'showBeard': True, + 'showGlasses': True, + 'showJacket': True, + 'showTie': True, + 'tie': 1, + 'tieMaterial': 1 + }) + assert settings['chest'] is not None + assert settings['chestMaterial'] is not None + assert settings['eyes'] is not None + assert settings['feet'] is not None + assert settings['feetMaterial'] is not None + assert settings['hair'] is not None + assert settings['hairMaterial'] is not None + assert settings['hatMaterial'] is not None + assert settings['legs'] is not None + assert settings['legsMaterial'] is not None + assert settings['skin'] is not None + assert settings['hideHair'] in [True, False] + assert settings['isElder'] in [True, False] + assert settings['glasses'] == 1 + assert settings['jacket'] == 1 + assert settings['jacketMaterial'] == 1 + assert settings['tie'] == 1 + assert settings['tieMaterial'] == 1 + assert settings['showBeard'] is True + assert settings['showGlasses'] is True + assert settings['showJacket'] is True + assert settings['showTie'] is True + + +def test_wall_materials(): + assert len(ROOM_WALL_MATERIALS) + assert len(WALL_MATERIALS_DARK_SKIN) + assert len(WALL_MATERIALS_LIGHT_SKIN) + for wall_material in ROOM_WALL_MATERIALS: + assert ( + wall_material in WALL_MATERIALS_DARK_SKIN or + wall_material in WALL_MATERIALS_LIGHT_SKIN or + wall_material in WALL_MATERIALS_NO_AGENTS + ) diff --git a/tests/all_objects_test.py b/tests/all_objects_test.py index 94a24d5..bba01ac 100644 --- a/tests/all_objects_test.py +++ b/tests/all_objects_test.py @@ -66,7 +66,7 @@ def test_intuitive_physics_all_objects_diagonal_size(): unshuffled=True ) ]: - for definition in dataset.definitions(unshuffled=True): + for definition in dataset.definitions_unique_shape_scale(): print(f'{definition}\n========================================') # If diagonal size is too big, it will cause an occassional issue # with implausible event calculations in intuitive physics scenes. diff --git a/tests/containers_test.py b/tests/containers_test.py index da82d7e..2fa6711 100644 --- a/tests/containers_test.py +++ b/tests/containers_test.py @@ -42,23 +42,13 @@ CONTAINERS = specific_objects.get_container_openable_definition_dataset( unshuffled=True ) -CONTAINER_DEFINITIONS = [ - # Just use the first variation (color) of each object for faster testing. - definition_variations[0] - for definition_selections in CONTAINERS._definition_groups - for definition_variations in definition_selections -] +CONTAINER_DEFINITIONS = CONTAINERS.definitions_unique_shape_scale() PICKUPABLES = specific_objects.get_pickupable_definition_dataset( unshuffled=True ) -PICKUPABLE_DEFINITIONS = [ - # Just use the first variation (color) of each object for faster testing. - definition_variations[0] - for definition_selections in PICKUPABLES._definition_groups - for definition_variations in definition_selections -] +PICKUPABLE_DEFINITIONS = PICKUPABLES.definitions_unique_shape_scale() def get_valid_containments(object_a, object_b=None): @@ -76,7 +66,7 @@ def get_valid_containments(object_a, object_b=None): @pytest.mark.slow def test_put_object_in_container(): for obj_def in PICKUPABLE_DEFINITIONS: - print(f'\nOBJECT={obj_def}') + print(f'\nOBJECT={obj_def.type} {obj_def.scale}') obj_location = geometry.calc_obj_pos( {'x': 1, 'y': 0, 'z': 1}, [], obj_def) obj = instances.instantiate_object(obj_def, obj_location) @@ -94,7 +84,7 @@ def test_put_object_in_container(): assert False for container_def, containment in containments: - print(f'\nCONTAINER={container_def}') + print(f'CONTAINER={container_def.type} {container_def.scale}') area_index, rotations = containment container_location = geometry.calc_obj_pos( {'x': -1, 'y': 0, 'z': -1}, [], container_def) diff --git a/tests/definitions_test.py b/tests/definitions_test.py index 178f8b9..a34875b 100644 --- a/tests/definitions_test.py +++ b/tests/definitions_test.py @@ -114,7 +114,7 @@ def test_create_dataset(): assert definition.dimensions == Vector3d(x=3, y=3, z=3) assert definition.mass == 3 - assert len(dataset._definition_groups[1][1]) == 38 + assert len(dataset._definition_groups[1][1]) == 42 for definition in dataset._definition_groups[1][1]: assert definition.type == 'd' assert definition.materialCategory == ['wood'] @@ -130,7 +130,7 @@ def test_create_dataset(): assert definition.dimensions == Vector3d(x=4, y=4, z=4) assert definition.mass == 4 - assert len(dataset._definition_groups[1][3]) == 38 + assert len(dataset._definition_groups[1][3]) == 42 for definition in dataset._definition_groups[1][3]: assert definition.type == 'd' assert definition.materialCategory == ['wood'] @@ -154,7 +154,7 @@ def test_definition_dataset_choose_random_definition(): def test_definition_dataset_definitions(): dataset = create_interesting_dataset() definitions = dataset.definitions(unshuffled=True) - assert len(definitions) == 100 + assert len(definitions) == 108 for definition in definitions: assert isinstance(definition, ObjectDefinition) @@ -505,11 +505,11 @@ def test_retrieve_complete_definition_list_choose_material(): assert len(actual[0][0]) == 10 assert len(actual[0][1]) == 7 assert len(actual[0][2]) == 2 - assert len(actual[0][3]) == 38 + assert len(actual[0][3]) == 42 assert len(actual[0][4]) == 10 assert len(actual[0][5]) == 7 assert len(actual[0][6]) == 2 - assert len(actual[0][7]) == 38 + assert len(actual[0][7]) == 42 for definition in actual[0][0]: assert definition.type == 'metal_thing' diff --git a/tests/geometry_test.py b/tests/geometry_test.py index 115f25b..1c04db0 100644 --- a/tests/geometry_test.py +++ b/tests/geometry_test.py @@ -4,12 +4,7 @@ import shapely from machine_common_sense.config_manager import Vector3d -from generator import ( - DefinitionDataset, - ObjectBounds, - geometry, - specific_objects -) +from generator import ObjectBounds, geometry, specific_objects from generator.base_objects import create_soccer_ball from generator.geometry import calculate_rotations, rotate_point_around_origin from generator.instances import instantiate_object @@ -22,17 +17,8 @@ DATASET = specific_objects.get_interactable_definition_dataset(unshuffled=True) -ALL_DEFINITIONS = [ - # Just use the first variation (color) of each object for faster testing. - definition_variations[0] - for definition_selections in DATASET._definition_groups - for definition_variations in definition_selections -] -# Reassign the dataset to just have one variation (color) for faster testing. -DATASET = DefinitionDataset([ - [[variations[0]] for variations in selections] - for selections in DATASET._definition_groups -]) +DATASET = DATASET.dataset_unique_shape_scale() +ALL_DEFINITIONS = DATASET.definitions(unshuffled=True) # Use the sofa because it should obstruct any pickupable object. @@ -3306,6 +3292,40 @@ def test_get_along_wall_xz_fail(): geometry.get_along_wall_xz(wall_label, room_dimensions, dimensions) +def test_get_adjacent_to_corner_xz(): + room_dimensions = {'x': 10, 'y': 3, 'z': 30} + dimensions = {'x': 0.4, 'y': 1, 'z': 0.6} + corner_label = "front_left" + x, z = geometry.get_adjacent_to_corner_xz( + corner_label, room_dimensions, dimensions) + assert z == 14.7 + assert x == -4.8 + corner_label = "front_right" + x, z = geometry.get_adjacent_to_corner_xz( + corner_label, room_dimensions, dimensions) + assert z == 14.7 + assert x == 4.8 + corner_label = "back_left" + x, z = geometry.get_adjacent_to_corner_xz( + corner_label, room_dimensions, dimensions) + assert z == -14.7 + assert x == -4.8 + corner_label = "back_right" + x, z = geometry.get_adjacent_to_corner_xz( + corner_label, room_dimensions, dimensions) + assert z == -14.7 + assert x == 4.8 + + +def test_get_adjacent_to_corner_xz_fail(): + room_dimensions = {'x': 10, 'y': 3, 'z': 30} + dimensions = {'x': 0.4, 'y': 1, 'z': 0.6} + corner_label = "invalid" + with pytest.raises(Exception): + geometry.get_adjacent_to_corner_xz( + corner_label, room_dimensions, dimensions) + + def test_calculate_rotations_no_rounding(): v1 = Vector3d(x=0, y=0, z=0) result = calculate_rotations(v1, Vector3d(x=0, y=0, z=1), True) @@ -3882,3 +3902,119 @@ def test_rotate_point_around_origin(): assert x == x_points[index] assert z == z_points[index] index += 1 + + +def test_nearby_equidistant_locations(): + x, z = geometry._nearby_equidistant_locations_helper( + x_1=-1, + z_1=3, + start_x=-1, + start_z=1, + x_min=-5, + x_max=5, + z_min=1, + z_max=5 + ) + assert (x, z) == (0.3858, 2.426) or (x, z) == (-2.3858, 2.426) + + x, z = geometry._nearby_equidistant_locations_helper( + x_1=2, + z_1=1, + start_x=-1, + start_z=1, + x_min=-1, + x_max=5, + z_min=-5, + z_max=5 + ) + assert (x, z) == (1.5646, -0.4354) or (x, z) == (1.5646, 2.4354) + + x, z = geometry._nearby_equidistant_locations_helper( + x_1=-1, + z_1=-4, + start_x=-1, + start_z=1, + x_min=-5, + x_max=5, + z_min=-5, + z_max=1 + ) + assert (x, z) == (-2.4712, -3.7074) or (x, z) == (0.4712, -3.7074) + + x, z = geometry._nearby_equidistant_locations_helper( + x_1=-3, + z_1=1, + start_x=-1, + start_z=1, + x_min=-5, + x_max=-1, + z_min=-5, + z_max=5 + ) + assert (x, z) == (-2.426, -0.3858) or (x, z) == (-2.426, 2.3858) + + +def test_nearby_equidistant_locations_exception(): + with pytest.raises(Exception): + geometry.nearby_equidistant_locations( + start_x=-1, + start_z=1, + x_min=-1, + x_max=-1, + z_min=2, + z_max=2 + ) + + +def test_calculate_aligned_position(): + # Test rotations + x, z = geometry.calculate_aligned_position(0, 0, 0, 5, 3, 2) + assert (x, z) == (0, -6) + x, z = geometry.calculate_aligned_position(0, 0, 45, 5, 3, 2) + assert (x, z) == (-4.2426, -4.2426) + x, z = geometry.calculate_aligned_position(0, 0, 90, 5, 3, 2) + assert (x, z) == (-6, 0) + x, z = geometry.calculate_aligned_position(0, 0, 180, 5, 3, 2) + assert (x, z) == (0, 6) + x, z = geometry.calculate_aligned_position(0, 0, 270, 5, 3, 2) + assert (x, z) == (6, 0) + + # Test sizes + x, z = geometry.calculate_aligned_position(0, 0, 0, 1, 0.5, 2) + assert (x, z) == (0, -2.75) + x, z = geometry.calculate_aligned_position(0, 0, 45, 1, 0.5, 2) + assert (x, z) == (-1.9445, -1.9445) + + # Test positions + x, z = geometry.calculate_aligned_position(-1, -2, 0, 5, 3, 2) + assert (x, z) == (-1, -8) + x, z = geometry.calculate_aligned_position(-1, -2, 45, 5, 3, 2) + assert (x, z) == (-5.2426, -6.2426) + + # Test X axis + x, z = geometry.calculate_aligned_position(0, 0, 0, 5, 3, 2, 'x') + assert (x, z) == (-6, 0) + + # Test X offset + x, z = geometry.calculate_aligned_position(0, 0, 0, 5, 3, 2, offset_x=1) + assert (x, z) == (1, -6) + x, z = geometry.calculate_aligned_position(0, 0, 45, 5, 3, 2, offset_x=1) + assert (x, z) == (-3.5355, -4.9497) + x, z = geometry.calculate_aligned_position(0, 0, 90, 5, 3, 2, offset_x=1) + assert (x, z) == (-6, -1) + x, z = geometry.calculate_aligned_position(0, 0, 180, 5, 3, 2, offset_x=1) + assert (x, z) == (-1, 6) + x, z = geometry.calculate_aligned_position(0, 0, 270, 5, 3, 2, offset_x=1) + assert (x, z) == (6, 1) + + # Test Z offset + x, z = geometry.calculate_aligned_position(0, 0, 0, 5, 3, 2, offset_z=1) + assert (x, z) == (0, -5) + x, z = geometry.calculate_aligned_position(0, 0, 45, 5, 3, 2, offset_z=1) + assert (x, z) == (-3.5355, -3.5355) + x, z = geometry.calculate_aligned_position(0, 0, 90, 5, 3, 2, offset_z=1) + assert (x, z) == (-5, 0) + x, z = geometry.calculate_aligned_position(0, 0, 180, 5, 3, 2, offset_z=1) + assert (x, z) == (0, 5) + x, z = geometry.calculate_aligned_position(0, 0, 270, 5, 3, 2, offset_z=1) + assert (x, z) == (5, 0) diff --git a/tests/gravity_support_objects_test.py b/tests/gravity_support_objects_test.py index 5ceb18f..1160a42 100644 --- a/tests/gravity_support_objects_test.py +++ b/tests/gravity_support_objects_test.py @@ -24,7 +24,7 @@ def test_does_have_definitions(): dataset = gravity_support_objects.get_asymmetric_target_definition_dataset( unshuffled=True ) - definitions = dataset.definitions() + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) @@ -32,7 +32,7 @@ def test_does_have_definitions(): dataset = gravity_support_objects.get_symmetric_target_definition_dataset( unshuffled=True ) - definitions = dataset.definitions() + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) diff --git a/tests/hypercubes_test.py b/tests/hypercubes_test.py index 39360c9..8c47e19 100644 --- a/tests/hypercubes_test.py +++ b/tests/hypercubes_test.py @@ -91,162 +91,164 @@ def create_tags_test_object_2(): } -def test_Hypercube_create_scenes_on_init(): +def test_Hypercube_does_not_generate_scenes_on_init(): hypercube = MockHypercube() - assert len(hypercube._scenes) == 1 + assert len(hypercube._scenes) == 0 -def test_Hypercube_init_scenes(): +def test_Hypercube_generate_scenes(): hypercube = MockHypercube() - scene = hypercube.get_scenes()[0] - assert 'category' in scene.goal - assert 'domainsInfo' in scene.goal - assert 'objectsInfo' in scene.goal - assert 'sceneInfo' in scene.goal + scenes = hypercube.generate_scenes() + assert len(scenes) == 1 + scene = scenes[0] + assert scene.goal.category + assert scene.goal.domains_info + assert scene.goal.objects_info + assert scene.goal.scene_info def test_Hypercube_tags(): hypercube = MockHypercube() target = create_tags_test_object_1() - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_multiple_target(): hypercube = MockHypercube() target_1 = create_tags_test_object_1() target_2 = create_tags_test_object_2() - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target_1, target_2]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['target'] == 2 + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['target'] == 2 def test_Hypercube_tags_with_obstacle(): hypercube = MockHypercube() target = create_tags_test_object_1() obstacle = create_tags_test_object_2() - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_multiple_target_multiple_obstacle(): @@ -255,7 +257,7 @@ def test_Hypercube_tags_multiple_target_multiple_obstacle(): target_2 = create_tags_test_object_1() obstacle_1 = create_tags_test_object_2() obstacle_2 = create_tags_test_object_2() - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, @@ -265,55 +267,55 @@ def test_Hypercube_tags_multiple_target_multiple_obstacle(): } ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - - assert scene.goal['sceneInfo']['count']['all'] == 4 - assert scene.goal['sceneInfo']['count']['obstacle'] == 2 - assert scene.goal['sceneInfo']['count']['target'] == 2 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + + assert scene.goal.scene_info['count']['all'] == 4 + assert scene.goal.scene_info['count']['obstacle'] == 2 + assert scene.goal.scene_info['count']['target'] == 2 def test_Hypercube_tags_with_intuitive_physics_occluder(): @@ -322,7 +324,7 @@ def test_Hypercube_tags_with_intuitive_physics_occluder(): occluder_wall = {'debug': {'info': ['white']}} occluder_pole = {'debug': {'info': ['brown']}} occluder_tag = 'intuitive_physics_occluder' - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, @@ -332,280 +334,280 @@ def test_Hypercube_tags_with_intuitive_physics_occluder(): } ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'intuitive physics occluder', 'tiny', 'light', 'blue', 'plastic', 'ball', 'white', 'brown', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo'][occluder_tag]) == { + assert set(scene.goal.objects_info[occluder_tag]) == { 'white', 'brown' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained'][occluder_tag] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory'][occluder_tag] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor'][occluder_tag] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination'][occluder_tag] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape'][occluder_tag] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize'][occluder_tag] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count'][occluder_tag] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 - assert scene.goal['sceneInfo']['present'][occluder_tag] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] + assert not scene.goal.scene_info['contained'][occluder_tag] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory'][occluder_tag] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor'][occluder_tag] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination'][occluder_tag] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape'][occluder_tag] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize'][occluder_tag] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count'][occluder_tag] == 1 + assert scene.goal.scene_info['count']['target'] == 1 + assert scene.goal.scene_info['present'][occluder_tag] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['uncontained']['target'] def test_Hypercube_tags_target_enclosed(): hypercube = MockHypercube() target = create_tags_test_object_1() target['locationParent'] = 'parent' - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'contained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'contained' } - assert not scene.goal['sceneInfo']['uncontained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['uncontained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['contained']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] + assert scene.goal.scene_info['contained']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_untrained_category(): hypercube = MockHypercube() target = create_tags_test_object_1() target['debug']['untrainedCategory'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'untrained category', 'trained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'untrained category', 'trained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedCategory']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedCategory']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_untrained_color(): hypercube = MockHypercube() target = create_tags_test_object_1() target['debug']['untrainedColor'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'untrained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'untrained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedColor']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedColor']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedColor']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_untrained_combination(): hypercube = MockHypercube() target = create_tags_test_object_1() target['debug']['untrainedCombination'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'untrained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'untrained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['trainedCombination']['target'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['trainedCombination']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedCombination']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedCombination']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_untrained_shape(): hypercube = MockHypercube() target = create_tags_test_object_1() target['debug']['untrainedShape'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'untrained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'untrained shape', 'trained size', 'uncontained', 'trained combination' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedShape']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedShape']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedShape']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_untrained_size(): hypercube = MockHypercube() target = create_tags_test_object_1() target['debug']['untrainedSize'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'untrained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'untrained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedSize']['target'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedSize']['target'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedSize']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_enclosed_untrained_everything(): @@ -616,39 +618,39 @@ def test_Hypercube_tags_target_enclosed_untrained_everything(): target['debug']['untrainedColor'] = True target['debug']['untrainedShape'] = True target['debug']['untrainedSize'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects(scene, {'target': [target]}) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'tiny', 'light', 'blue', 'plastic', 'ball', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert not scene.goal['sceneInfo']['trainedCategory']['target'] - assert not scene.goal['sceneInfo']['trainedColor']['target'] - assert not scene.goal['sceneInfo']['trainedShape']['target'] - assert not scene.goal['sceneInfo']['trainedSize']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['uncontained']['target'] + assert not scene.goal.scene_info['trainedCategory']['target'] + assert not scene.goal.scene_info['trainedColor']['target'] + assert not scene.goal.scene_info['trainedShape']['target'] + assert not scene.goal.scene_info['trainedSize']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['uncontained']['target'] - assert scene.goal['sceneInfo']['contained']['target'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['untrainedCategory']['target'] - assert scene.goal['sceneInfo']['untrainedColor']['target'] - assert scene.goal['sceneInfo']['untrainedShape']['target'] - assert scene.goal['sceneInfo']['untrainedSize']['target'] + assert scene.goal.scene_info['contained']['target'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['untrainedCategory']['target'] + assert scene.goal.scene_info['untrainedColor']['target'] + assert scene.goal.scene_info['untrainedShape']['target'] + assert scene.goal.scene_info['untrainedSize']['target'] - assert scene.goal['sceneInfo']['count']['all'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert scene.goal.scene_info['count']['all'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_enclosed(): @@ -656,63 +658,63 @@ def test_Hypercube_tags_obstacle_enclosed(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['locationParent'] = 'parent' - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'contained', 'uncontained' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'contained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['uncontained']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['contained']['obstacle'] - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['uncontained']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['contained']['obstacle'] + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_untrained_category(): @@ -720,62 +722,62 @@ def test_Hypercube_tags_obstacle_untrained_category(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['debug']['untrainedCategory'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained', 'untrained category' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'untrained category', 'trained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedCategory']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_untrained_color(): @@ -783,62 +785,62 @@ def test_Hypercube_tags_obstacle_untrained_color(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['debug']['untrainedColor'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained', 'untrained color', } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'untrained color', 'trained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedColor']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedColor']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_untrained_combination(): @@ -846,62 +848,62 @@ def test_Hypercube_tags_obstacle_untrained_combination(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['debug']['untrainedCombination'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained', 'untrained combination' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'untrained combination', 'trained shape', 'trained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedCombination']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_untrained_shape(): @@ -909,14 +911,14 @@ def test_Hypercube_tags_obstacle_untrained_shape(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['debug']['untrainedShape'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', @@ -924,48 +926,48 @@ def test_Hypercube_tags_obstacle_untrained_shape(): 'trained shape', 'trained size', 'uncontained', 'untrained shape' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'untrained shape', 'trained size', 'uncontained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedShape']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['obstacle'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedShape']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_untrained_size(): @@ -973,62 +975,62 @@ def test_Hypercube_tags_obstacle_untrained_size(): target = create_tags_test_object_1() obstacle = create_tags_test_object_2() obstacle['debug']['untrainedSize'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained', 'untrained size' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'trained category', 'trained color', 'trained combination', 'trained shape', 'untrained size', 'uncontained' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['obstacle'] - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['obstacle'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedSize']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['obstacle'] + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedSize']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['obstacle'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['obstacle'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['obstacle'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['obstacle'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['obstacle'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['obstacle'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedSize']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_obstacle_enclosed_untrained_everything(): @@ -1040,14 +1042,14 @@ def test_Hypercube_tags_obstacle_enclosed_untrained_everything(): obstacle['debug']['untrainedColor'] = True obstacle['debug']['untrainedShape'] = True obstacle['debug']['untrainedSize'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', @@ -1056,48 +1058,48 @@ def test_Hypercube_tags_obstacle_enclosed_untrained_everything(): 'untrained category', 'untrained color', 'untrained shape', 'untrained size' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'trained category', 'trained color', 'trained combination', 'trained shape', 'trained size', 'uncontained' } - assert not scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['uncontained']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCategory']['target'] - assert not scene.goal['sceneInfo']['untrainedColor']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - assert not scene.goal['sceneInfo']['untrainedShape']['target'] - assert not scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['contained']['obstacle'] - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCategory']['target'] - assert scene.goal['sceneInfo']['trainedColor']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['trainedShape']['target'] - assert scene.goal['sceneInfo']['trainedSize']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] - assert scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert scene.goal['sceneInfo']['untrainedSize']['obstacle'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['trainedCategory']['obstacle'] + assert not scene.goal.scene_info['trainedColor']['obstacle'] + assert not scene.goal.scene_info['trainedShape']['obstacle'] + assert not scene.goal.scene_info['trainedSize']['obstacle'] + assert not scene.goal.scene_info['uncontained']['obstacle'] + assert not scene.goal.scene_info['untrainedCategory']['target'] + assert not scene.goal.scene_info['untrainedColor']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + assert not scene.goal.scene_info['untrainedShape']['target'] + assert not scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['contained']['obstacle'] + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCategory']['target'] + assert scene.goal.scene_info['trainedColor']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['trainedShape']['target'] + assert scene.goal.scene_info['trainedSize']['target'] + assert scene.goal.scene_info['uncontained']['target'] + assert scene.goal.scene_info['untrainedCategory']['obstacle'] + assert scene.goal.scene_info['untrainedColor']['obstacle'] + assert scene.goal.scene_info['untrainedShape']['obstacle'] + assert scene.goal.scene_info['untrainedSize']['obstacle'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def test_Hypercube_tags_target_obstacle_enclosed_untrained_everything(): @@ -1114,62 +1116,62 @@ def test_Hypercube_tags_target_obstacle_enclosed_untrained_everything(): obstacle['debug']['untrainedColor'] = True obstacle['debug']['untrainedShape'] = True obstacle['debug']['untrainedSize'] = True - scene = hypercube.get_scenes()[0] + scene = hypercube.generate_scenes()[0] print(f'{scene}') scene = update_scene_objects( scene, {'target': [target], 'obstacle': [obstacle]} ) - assert set(scene.goal['objectsInfo']['all']) == { + assert set(scene.goal.objects_info['all']) == { 'target', 'obstacle', 'tiny', 'light', 'blue', 'plastic', 'ball', 'medium', 'light', 'yellow', 'plastic', 'cube', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert set(scene.goal['objectsInfo']['obstacle']) == { + assert set(scene.goal.objects_info['obstacle']) == { 'medium', 'light', 'yellow', 'plastic', 'cube', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert set(scene.goal['objectsInfo']['target']) == { + assert set(scene.goal.objects_info['target']) == { 'tiny', 'light', 'blue', 'plastic', 'ball', 'untrained category', 'untrained color', 'untrained shape', 'untrained size', 'contained', 'trained combination' } - assert not scene.goal['sceneInfo']['trainedCategory']['obstacle'] - assert not scene.goal['sceneInfo']['trainedCategory']['target'] - assert not scene.goal['sceneInfo']['trainedColor']['obstacle'] - assert not scene.goal['sceneInfo']['trainedColor']['target'] - assert not scene.goal['sceneInfo']['trainedShape']['obstacle'] - assert not scene.goal['sceneInfo']['trainedShape']['target'] - assert not scene.goal['sceneInfo']['trainedSize']['obstacle'] - assert not scene.goal['sceneInfo']['trainedSize']['target'] - assert not scene.goal['sceneInfo']['uncontained']['obstacle'] - assert not scene.goal['sceneInfo']['uncontained']['target'] - assert not scene.goal['sceneInfo']['untrainedCombination']['obstacle'] - assert not scene.goal['sceneInfo']['untrainedCombination']['target'] - - assert scene.goal['sceneInfo']['contained']['obstacle'] - assert scene.goal['sceneInfo']['contained']['target'] - assert scene.goal['sceneInfo']['present']['obstacle'] - assert scene.goal['sceneInfo']['present']['target'] - assert scene.goal['sceneInfo']['trainedCombination']['obstacle'] - assert scene.goal['sceneInfo']['trainedCombination']['target'] - assert scene.goal['sceneInfo']['untrainedCategory']['obstacle'] - assert scene.goal['sceneInfo']['untrainedCategory']['target'] - assert scene.goal['sceneInfo']['untrainedColor']['obstacle'] - assert scene.goal['sceneInfo']['untrainedColor']['target'] - assert scene.goal['sceneInfo']['untrainedShape']['obstacle'] - assert scene.goal['sceneInfo']['untrainedShape']['target'] - assert scene.goal['sceneInfo']['untrainedSize']['obstacle'] - assert scene.goal['sceneInfo']['untrainedSize']['target'] - - assert scene.goal['sceneInfo']['count']['all'] == 2 - assert scene.goal['sceneInfo']['count']['obstacle'] == 1 - assert scene.goal['sceneInfo']['count']['target'] == 1 + assert not scene.goal.scene_info['trainedCategory']['obstacle'] + assert not scene.goal.scene_info['trainedCategory']['target'] + assert not scene.goal.scene_info['trainedColor']['obstacle'] + assert not scene.goal.scene_info['trainedColor']['target'] + assert not scene.goal.scene_info['trainedShape']['obstacle'] + assert not scene.goal.scene_info['trainedShape']['target'] + assert not scene.goal.scene_info['trainedSize']['obstacle'] + assert not scene.goal.scene_info['trainedSize']['target'] + assert not scene.goal.scene_info['uncontained']['obstacle'] + assert not scene.goal.scene_info['uncontained']['target'] + assert not scene.goal.scene_info['untrainedCombination']['obstacle'] + assert not scene.goal.scene_info['untrainedCombination']['target'] + + assert scene.goal.scene_info['contained']['obstacle'] + assert scene.goal.scene_info['contained']['target'] + assert scene.goal.scene_info['present']['obstacle'] + assert scene.goal.scene_info['present']['target'] + assert scene.goal.scene_info['trainedCombination']['obstacle'] + assert scene.goal.scene_info['trainedCombination']['target'] + assert scene.goal.scene_info['untrainedCategory']['obstacle'] + assert scene.goal.scene_info['untrainedCategory']['target'] + assert scene.goal.scene_info['untrainedColor']['obstacle'] + assert scene.goal.scene_info['untrainedColor']['target'] + assert scene.goal.scene_info['untrainedShape']['obstacle'] + assert scene.goal.scene_info['untrainedShape']['target'] + assert scene.goal.scene_info['untrainedSize']['obstacle'] + assert scene.goal.scene_info['untrainedSize']['target'] + + assert scene.goal.scene_info['count']['all'] == 2 + assert scene.goal.scene_info['count']['obstacle'] == 1 + assert scene.goal.scene_info['count']['target'] == 1 def retrieve_object_list_from_data(object_data): diff --git a/tests/ile_action_component_test.py b/tests/ile_action_component_test.py index 84d4373..df89c13 100644 --- a/tests/ile_action_component_test.py +++ b/tests/ile_action_component_test.py @@ -1,12 +1,12 @@ -import random - import pytest +from machine_common_sense.config_manager import Goal from ideal_learning_env.actions_component import ActionRestrictionsComponent from ideal_learning_env.defs import ILEConfigurationException, ILEException from .ile_helper import ( create_placers_turntables_scene, + create_random_agent_placer_turntable_scene, create_test_obj_scene, prior_scene ) @@ -23,8 +23,8 @@ def test_action_restrictions_defaults(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - assert not hasattr(goal, 'action_list') + assert isinstance(goal, Goal) + assert goal.action_list is None def test_action_restrictions_passive(): @@ -36,11 +36,11 @@ def test_action_restrictions_passive(): last_step = 100 scene = component.update_ile_scene(prior_scene(last_step)) goal = scene.goal - assert isinstance(goal, dict) - category = goal['category'] + assert isinstance(goal, Goal) + category = goal.category assert isinstance(category, str) assert category == 'intuitive physics' - al = goal['action_list'] + al = goal.action_list assert isinstance(al, list) assert len(al) == last_step for inner in al: @@ -64,8 +64,8 @@ def test_action_restrictions_freeze_start(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == 100 for idx, inner in enumerate(al): @@ -95,8 +95,8 @@ def test_action_restrictions_freeze_start_end(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -136,8 +136,8 @@ def test_action_restrictions_freeze_choice(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) choice = [1, 3, 5].index(len(al)) start = [1, 3, 5][choice] @@ -176,8 +176,8 @@ def test_action_restrictions_freeze_ok_overlap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -213,8 +213,8 @@ def test_action_restrictions_freeze_gap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -266,8 +266,8 @@ def test_action_restrictions_freeze_just_end(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -303,8 +303,8 @@ def test_action_restrictions_freeze_empty_list(): assert isinstance(fzs, list) scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - assert not hasattr(goal, 'action_list') + assert isinstance(goal, Goal) + assert goal.action_list is None def test_action_restrictions_circles(): @@ -316,8 +316,8 @@ def test_action_restrictions_circles(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - action_list = goal['action_list'] + assert isinstance(goal, Goal) + action_list = goal.action_list assert isinstance(action_list, list) assert len(action_list) == 36 for i, actions_per_step in enumerate(action_list): @@ -337,8 +337,8 @@ def test_action_restrictions_circles_late(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - action_list = goal['action_list'] + assert isinstance(goal, Goal) + action_list = goal.action_list assert isinstance(action_list, list) assert len(action_list) == 46 for i, actions_per_step in enumerate(action_list): @@ -358,8 +358,8 @@ def test_action_restrictions_circles_multiple(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - action_list = goal['action_list'] + assert isinstance(goal, Goal) + action_list = goal.action_list assert isinstance(action_list, list) assert len(action_list) == 136 for i, actions_per_step in enumerate(action_list): @@ -379,8 +379,8 @@ def test_action_restrictions_circles_option(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - action_list = goal['action_list'] + assert isinstance(goal, Goal) + action_list = goal.action_list assert isinstance(action_list, list) assert len(action_list) in [36, 136] choice_1 = (len(action_list) == 36) @@ -407,8 +407,8 @@ def test_action_restrictions_swivel_start(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == 100 for idx, inner in enumerate(al): @@ -441,8 +441,8 @@ def test_action_restrictions_swivel_start_end(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 @@ -486,8 +486,8 @@ def test_action_restrictions_swivel_choice(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) choice = [1, 3, 5].index(len(al)) start = [1, 3, 5][choice] @@ -529,8 +529,8 @@ def test_action_restrictions_swivel_ok_overlap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -569,8 +569,8 @@ def test_action_restrictions_swivel_gap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -625,8 +625,8 @@ def test_action_restrictions_swivel_just_end(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -665,8 +665,8 @@ def test_action_restrictions_swivel_empty_list(): assert isinstance(svls, list) scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - assert not hasattr(goal, 'action_list') + assert isinstance(goal, Goal) + assert goal.action_list is None def test_action_restrictions_swivel_then_freeze_ok(): @@ -702,8 +702,8 @@ def test_action_restrictions_swivel_then_freeze_ok(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == fend - 1 for idx, inner in enumerate(al): @@ -754,8 +754,8 @@ def test_action_restrictions_freeze_then_swivel_ok(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end - 1 for idx, inner in enumerate(al): @@ -805,8 +805,8 @@ def test_action_restrictions_freeze_then_swivel_ok_overlap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -857,8 +857,8 @@ def test_action_restrictions_swivel_then_freeze_ok_overlap(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end_step - 1 for idx, inner in enumerate(al): @@ -1017,8 +1017,8 @@ def test_action_restriction_teleport_missing_rot_y(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == step for idx, inner in enumerate(al): @@ -1052,8 +1052,8 @@ def test_action_restriction_teleport(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == step for idx, inner in enumerate(al): @@ -1076,7 +1076,7 @@ def test_action_restriction_teleport_look_at_center_facing_forward(): }] }) scene = component.update_ile_scene(prior_scene()) - assert scene.goal['action_list'][0] == [ + assert scene.goal.action_list[0] == [ 'EndHabituation,xPosition=0,zPosition=-4.5,yRotation=0' ] @@ -1091,7 +1091,7 @@ def test_action_restriction_teleport_look_at_center_facing_back(): }] }) scene = component.update_ile_scene(prior_scene()) - assert scene.goal['action_list'][0] == [ + assert scene.goal.action_list[0] == [ 'EndHabituation,xPosition=0,zPosition=4.5,yRotation=180' ] @@ -1120,8 +1120,8 @@ def test_action_restriction_teleport_choice(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) in [3, 6] num_end_hab = 0 @@ -1179,8 +1179,8 @@ def test_action_restriction_teleport_multi(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == step3 for idx, inner in enumerate(al): @@ -1264,8 +1264,8 @@ def test_action_restriction_freeze_teleport_combined(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == max(end - 1, tstep) for idx, inner in enumerate(al): @@ -1313,8 +1313,8 @@ def test_action_restriction_swivel_teleport_combined(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == max(end - 1, tstep) for idx, inner in enumerate(al): @@ -1377,8 +1377,8 @@ def test_action_restriction_freeze_swivel_teleport_combined(): scene = component.update_ile_scene(prior_scene()) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == end - 1 @@ -1491,8 +1491,8 @@ def test_action_restriction_passive_teleport(): scene = component.update_ile_scene(prior_scene(100)) goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == 100 for idx, inner in enumerate(al): @@ -1584,8 +1584,8 @@ def test_action_sidesteps(): assert component.get_num_delayed_actions() == 0 goal = scene.goal - assert isinstance(goal, dict) - al = goal['action_list'] + assert isinstance(goal, Goal) + al = goal.action_list assert isinstance(al, list) assert len(al) == 218 for action in al: @@ -1624,30 +1624,48 @@ def test_action_sidesteps_error(): def test_action_freeze_while_moving(): - labels = ['placers', 'turntables', ['placers', 'turntables']] + labels = ['placers', 'turntables', 'agent', + ['placers', 'turntables', 'agent']] for label in labels: component = ActionRestrictionsComponent({ 'freeze_while_moving': label }) - scene = component.update_ile_scene(create_placers_turntables_scene( - random.randint(1, 50), random.randint(1, 50))) + scene = component.update_ile_scene( + create_random_agent_placer_turntable_scene()) freeze_while_moving = component.freeze_while_moving assert (isinstance(freeze_while_moving, str) or isinstance(freeze_while_moving, list)) assert freeze_while_moving in labels component.run_actions_at_end_of_scene_generation(scene) - # middle objects are the multiple placers - moves = \ - max(scene.objects[1:-1], - key=lambda x: 0 if not x.get('moves') else - x['moves'][-1]['stepEnd'])['moves'][-1]['stepEnd'] - # last object is the one turntable - rotates = scene.objects[-1]['rotates'][-1]['stepEnd'] + + action_lists = [ + obj.get('actions') for obj in scene.objects if + obj.get('actions') is not None] + moves_lists = [ + obj.get('moves') for obj in scene.objects if + obj.get('moves') is not None] + rotates_lists = [ + obj.get('rotates') for obj in scene.objects if + obj.get('rotates') is not None] # noqa + + actions_max_step_end = max( + item['stepEnd'] for action_list in action_lists + for item in action_list if not item.get('isLoopAnimation', False)) + moves_max_step_end = max( + move['stepEnd'] for moves in moves_lists for move in moves) + rotates_max_step_end = max( + rotate['stepEnd'] for rotates in rotates_lists for + rotate in rotates) + passes = \ - [["Pass"]] * (moves if freeze_while_moving == 'placers' else - rotates if freeze_while_moving == 'turntables' else - max(moves, rotates)) - assert scene.goal['action_list'] == passes + [["Pass"]] * ( + actions_max_step_end if freeze_while_moving == 'agent' else + moves_max_step_end if freeze_while_moving == 'placers' else + rotates_max_step_end if freeze_while_moving == 'turntables' else # noqa + max(actions_max_step_end, + moves_max_step_end, + rotates_max_step_end)) + assert scene.goal.action_list == passes def test_action_freeze_while_moving_error(): diff --git a/tests/ile_action_service_test.py b/tests/ile_action_service_test.py index 39a3d99..0c9c0f0 100644 --- a/tests/ile_action_service_test.py +++ b/tests/ile_action_service_test.py @@ -1,4 +1,7 @@ +import random + import pytest +from machine_common_sense.config_manager import Goal from ideal_learning_env.action_service import ( ActionService, @@ -8,54 +11,60 @@ ) from ideal_learning_env.defs import ILEException -from .ile_helper import create_placers_turntables_scene, create_test_obj_scene +from .ile_helper import ( + create_agent_scene, + create_placers_turntables_scene, + create_random_agent_placer_turntable_scene, + create_specific_agent_placer_turntable_scene, + create_test_obj_scene +) def test_action_freezes_empty_array(): - goal = {} + goal = Goal() ActionService.add_freezes(goal, []) - assert goal['action_list'] == [] + assert goal.action_list == [] def test_action_freezes_single(): - goal = {} + goal = Goal() steps = [StepBeginEnd(1, 3)] ActionService.add_freezes(goal, steps) - assert goal['action_list'] == [['Pass'], ['Pass']] + assert goal.action_list == [['Pass'], ['Pass']] def test_action_freezes_multiple(): - goal = {} + goal = Goal() steps = [StepBeginEnd(2, 3), StepBeginEnd(5, 6)] ActionService.add_freezes(goal, steps) - assert goal['action_list'] == [[], ['Pass'], [], [], ['Pass']] + assert goal.action_list == [[], ['Pass'], [], [], ['Pass']] def test_action_freezes_bad_step(): - goal = {} + goal = Goal() steps = [StepBeginEnd(3, 1)] with pytest.raises(ILEException): ActionService.add_freezes(goal, steps) def test_action_freezes_overlap(): - goal = {} + goal = Goal() steps = [StepBeginEnd(1, 3), StepBeginEnd(2, 4)] with pytest.raises(ILEException): ActionService.add_freezes(goal, steps) def test_action_teleports_empty_array(): - goal = {} + goal = Goal() ActionService.add_teleports(goal, [], False) - assert goal['action_list'] == [] + assert goal.action_list == [] def test_action_teleports_single(): - goal = {} + goal = Goal() teleports = [TeleportConfig(2, 3, 4, 90)] ActionService.add_teleports(goal, teleports, False) - al = goal['action_list'] + al = goal.action_list assert al assert len(al) == 2 action = al[1] @@ -63,10 +72,10 @@ def test_action_teleports_single(): def test_action_teleports_multiple(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -2, 1, 180), TeleportConfig(3, 2, -1, 270)] ActionService.add_teleports(goal, teleports, False) - al = goal['action_list'] + al = goal.action_list assert al assert len(al) == 3 action = al[0] @@ -76,10 +85,10 @@ def test_action_teleports_multiple(): def test_action_teleports_multiple_out_of_order(): - goal = {} + goal = Goal() teleports = [TeleportConfig(3, -2, 1, 180), TeleportConfig(1, 2, -1, 270)] ActionService.add_teleports(goal, teleports, False) - al = goal['action_list'] + al = goal.action_list assert al assert len(al) == 3 action = al[0] @@ -89,181 +98,181 @@ def test_action_teleports_multiple_out_of_order(): def test_action_teleports_look_at_center_facing_front(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 0, -7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=0,zPosition=-7.5,yRotation=0'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 0.25, -7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=0.25,zPosition=-7.5,yRotation=0'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -0.25, -7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=-0.25,zPosition=-7.5,yRotation=0'] def test_action_teleports_look_at_center_facing_back(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 0, 7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=0,zPosition=7.5,yRotation=180'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 0.25, 7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=0.25,zPosition=7.5,yRotation=180'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -0.25, 7.5, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert ( cmd == ['EndHabituation,xPosition=-0.25,zPosition=7.5,yRotation=180'] ) def test_action_teleports_look_at_center_facing_left(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 7.5, 0, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=7.5,zPosition=0,yRotation=270'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 7.5, 0.25, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=7.5,zPosition=0.25,yRotation=270'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 7.5, -0.25, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert ( cmd == ['EndHabituation,xPosition=7.5,zPosition=-0.25,yRotation=270'] ) def test_action_teleports_look_at_center_facing_right(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -7.5, 0, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=-7.5,zPosition=0,yRotation=90'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -7.5, 0.25, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=-7.5,zPosition=0.25,yRotation=90'] - goal = {} + goal = Goal() teleports = [TeleportConfig(1, -7.5, -0.25, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert ( cmd == ['EndHabituation,xPosition=-7.5,zPosition=-0.25,yRotation=90'] ) def test_action_teleports_look_at_center_rounds_to_tens(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 2, 2, None, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=2,zPosition=2,yRotation=220'] def test_action_teleports_look_at_center_ignores_rotation_y(): - goal = {} + goal = Goal() teleports = [TeleportConfig(1, 0, 7.5, 90, look_at_center=True)] ActionService.add_teleports(goal, teleports, False) - assert len(goal['action_list']) == 1 - cmd = goal['action_list'][0] + assert len(goal.action_list) == 1 + cmd = goal.action_list[0] assert cmd == ['EndHabituation,xPosition=0,zPosition=7.5,yRotation=180'] def test_action_circles_empty_array(): - goal = {} + goal = Goal() ActionService.add_circles(goal, []) - assert goal['action_list'] == [] + assert goal.action_list == [] def test_action_circles_single(): - goal = {} + goal = Goal() ActionService.add_circles(goal, [1]) circle = ['RotateRight'] - assert goal['action_list'] == [circle] * 36 + assert goal.action_list == [circle] * 36 def test_action_circles_multiple(): - goal = {} + goal = Goal() ActionService.add_circles(goal, [1, 101]) circle = ['RotateRight'] - assert goal['action_list'] == [circle] * 36 + [[]] * 64 + [circle] * 36 + assert goal.action_list == [circle] * 36 + [[]] * 64 + [circle] * 36 def test_action_swivels_empty_array(): - goal = {} + goal = Goal() ActionService.add_swivels(goal, []) - assert goal['action_list'] == [] + assert goal.action_list == [] def test_action_swivels_single(): - goal = {} + goal = Goal() steps = [StepBeginEnd(3, 5)] ActionService.add_swivels(goal, steps) swivel = ['LookDown', 'LookUp', 'RotateLeft', 'RotateRight'] - assert goal['action_list'] == [[], [], swivel, swivel] + assert goal.action_list == [[], [], swivel, swivel] def test_action_swivels_multiple(): - goal = {} + goal = Goal() steps = [StepBeginEnd(1, 2), StepBeginEnd(4, 6)] ActionService.add_swivels(goal, steps) swivel = ['LookDown', 'LookUp', 'RotateLeft', 'RotateRight'] - assert goal['action_list'] == [swivel, [], [], swivel, swivel] + assert goal.action_list == [swivel, [], [], swivel, swivel] def test_action_swivels_bad_step(): - goal = {} + goal = Goal() steps = [StepBeginEnd(5, 2)] with pytest.raises(ILEException): ActionService.add_swivels(goal, steps) def test_action_swivels_overlap(): - goal = {} + goal = Goal() steps = [StepBeginEnd(3, 5), StepBeginEnd(4, 7)] with pytest.raises(ILEException): ActionService.add_swivels(goal, steps) def test_action_sidesteps_empty_array(): - goal = {} + goal = Goal() ActionService.add_sidesteps(goal, [], create_test_obj_scene(0, 3)) - assert goal['action_list'] == [] + assert goal.action_list == [] def test_action_sidesteps_single_minimum_distance(): @@ -666,65 +675,100 @@ def test_action_sidesteps_single_minimum_distance(): # Try +x, -x, +z, -z. They should all be the same. # Test all degree options [90, 180, 270, 360, -90, -180, -270, -360] - for i in range(4): - # Setup scene and object - x = minimum_distance if i == 0 else -minimum_distance if i == 1 else 0 - z = minimum_distance if i == 2 else -minimum_distance if i == 3 else 0 - scene = create_test_obj_scene(x, z) - - # +90 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=90)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == positive_90_right_movement - - # -90 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=-90)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == negative_90_left_movement - - # +180 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=180)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == positive_180_right_movement - - # -180 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=-180)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == negative_180_left_movement - - # 270 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=270)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == positive_270_right_movement - - # -270 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=-270)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == negative_270_left_movement - - # +360 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=360)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == positive_360_right_movement - - # -360 Degrees - goal = {} - steps = [SidestepsConfig(begin=1, object_label='object', degrees=-360)] - ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == negative_360_left_movement + for i in range(100): + for i in range(4): + # Setup scene and object + x = minimum_distance if i == 0 else -minimum_distance if \ + i == 1 else 0 + z = minimum_distance if i == 2 else -minimum_distance if \ + i == 3 else 0 + scene = create_test_obj_scene(x, z) + + # +90 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=90)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == positive_90_right_movement + + # -90 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=-90)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == negative_90_left_movement + + # +180 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=180)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == positive_180_right_movement + + # -180 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=-180)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == negative_180_left_movement + + # 270 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=270)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == positive_270_right_movement + + # -270 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=-270)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == negative_270_left_movement + + # +360 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=360)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == positive_360_right_movement + + # -360 Degrees + goal = Goal() + steps = [ + SidestepsConfig( + begin=1, + object_label='object', + degrees=-360)] + ActionService.add_sidesteps(goal, steps, scene) + assert goal.action_list == negative_360_left_movement def test_action_sidesteps_multiple(): # Setup scene and object scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -735,7 +779,7 @@ def test_action_sidesteps_multiple(): object_label='object', degrees=-90)] ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == ( + assert goal.action_list == ( [['MoveRight']] * 8 + [['RotateLeft']] * 1 + [['MoveRight']] * 2 + @@ -798,7 +842,7 @@ def test_action_sidesteps_multiple(): def test_action_sidesteps_begin_later(): # Setup scene and object scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=10, @@ -809,7 +853,7 @@ def test_action_sidesteps_begin_later(): object_label='object', degrees=-90)] ActionService.add_sidesteps(goal, steps, scene) - assert goal['action_list'] == ( + assert goal.action_list == ( [[]] * 9 + [['MoveRight']] * 8 + [['RotateLeft']] * 1 + @@ -874,10 +918,10 @@ def test_action_sidesteps_begin_later(): def test_action_sidesteps_overlap_freeze_error(): scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [StepBeginEnd(1, 3)] ActionService.add_freezes(goal, steps) - assert goal['action_list'] == [['Pass'], ['Pass']] + assert goal.action_list == [['Pass'], ['Pass']] steps = [SidestepsConfig(begin=1, object_label='object', degrees=90)] with pytest.raises(ILEException): ActionService.add_sidesteps(goal, steps, scene) @@ -888,7 +932,7 @@ def test_action_sidesteps_overlap_freeze_error(): def test_action_sidesteps_overlap_error(): scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -904,7 +948,7 @@ def test_action_sidesteps_overlap_error(): def test_action_sidesteps_too_short_distance_error(): scene = create_test_obj_scene(0, 2.9) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -916,7 +960,7 @@ def test_action_sidesteps_too_short_distance_error(): def test_action_sidesteps_wrong_labels_error(): scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -932,7 +976,7 @@ def test_action_sidesteps_wrong_labels_error(): def test_action_sidesteps_bad_angle(): scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -940,7 +984,7 @@ def test_action_sidesteps_bad_angle(): degrees=91)] with pytest.raises(ILEException): ActionService.add_sidesteps(goal, steps, scene) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, @@ -952,14 +996,14 @@ def test_action_sidesteps_bad_angle(): def test_action_sidesteps_no_degrees(): scene = create_test_obj_scene(0, 3) - goal = {} + goal = Goal() steps = [ SidestepsConfig( begin=1, object_label='object') ] ActionService.add_sidesteps(goal, steps, scene) - for action in goal['action_list']: + for action in goal.action_list: assert action in [ ["MoveRight"], ["MoveLeft"], @@ -969,25 +1013,123 @@ def test_action_sidesteps_no_degrees(): def test_action_freeze_while_moving_empty_array(): - goal = {} + goal = Goal() ActionService.add_freeze_while_moving(goal, []) - assert goal['action_list'] == [] + assert goal.action_list == [] -def test_action_freeze_while_moving(): +def test_action_freeze_while_moving_moves_rotates(): scene = create_placers_turntables_scene(20, 1) - goal = {} + goal = Goal() freeze_while_moving = ['placers', 'turntables'] ActionService.add_freeze_while_moving(goal, freeze_while_moving) moves = \ max(scene.objects[1:-1], key=lambda x: 0 if not x.get('moves') else x['moves'][-1]['stepEnd'])['moves'][-1]['stepEnd'] - assert len(goal['action_list']) == moves + assert len(goal.action_list) == moves scene = create_placers_turntables_scene(1, 20) - goal = {} + goal = Goal() freeze_while_moving = ['placers', 'turntables'] ActionService.add_freeze_while_moving(goal, freeze_while_moving) assert \ - len(goal['action_list']) == scene.objects[-1]['rotates'][-1]['stepEnd'] + len(goal.action_list) == scene.objects[-1]['rotates'][-1]['stepEnd'] + + +def test_action_freeze_while_moving_actions(): + scene = create_agent_scene() + goal = Goal() + freeze_while_moving = ['agent'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + actions = scene.objects[0]['actions'] + actions_max_step_end = \ + max(item['stepEnd'] for item in actions if not item.get( + 'isLoopAnimation', False)) + assert len(goal.action_list) == actions_max_step_end + + +def test_action_freeze_while_moving_actions_multiple_agents(): + number_of_agents = random.randint(2, 10) + scene = create_agent_scene(number_of_agents) + goal = Goal() + freeze_while_moving = ['agent'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + action_lists = [agent['actions'] for agent in scene.objects] + actions_max_step_end = max( + item['stepEnd'] for action_list in action_lists + for item in action_list if not item.get('isLoopAnimation', False)) + assert len(goal.action_list) == actions_max_step_end + + +def test_action_freeze_while_moving_agents_placers_and_turntables_random(): + scene = create_random_agent_placer_turntable_scene() + goal = Goal() + freeze_while_moving = ['agent', 'placers', 'turntables'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + + action_lists = [ + obj.get('actions') for obj in scene.objects if + obj.get('actions') is not None] + moves_lists = [ + obj.get('moves') for obj in scene.objects if + obj.get('moves') is not None] + rotates_lists = [ + obj.get('rotates') for obj in scene.objects if + obj.get('rotates') is not None] # noqa + + actions_max_step_end = max( + item['stepEnd'] for action_list in action_lists + for item in action_list if not item.get('isLoopAnimation', False)) + moves_max_step_end = max( + move['stepEnd'] for moves in moves_lists for move in moves) + rotates_max_step_end = max( + rotate['stepEnd'] for rotates in rotates_lists for rotate in rotates) + + assert len( + goal.action_list) == max( + actions_max_step_end, + moves_max_step_end, + rotates_max_step_end) + + +def test_action_freeze_while_moving_agents_placers_and_turntables_agents(): + scene = create_specific_agent_placer_turntable_scene( + agent_start=100, placer_start=50, turntable_start=10) + goal = Goal() + freeze_while_moving = ['agent', 'placers', 'turntables'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + action_lists = [ + obj.get('actions') for obj in scene.objects if + obj.get('actions') is not None] + actions_max_step_end = max( + item['stepEnd'] for action_list in action_lists + for item in action_list if not item.get('isLoopAnimation', False)) + assert len(goal.action_list) == actions_max_step_end + + +def test_action_freeze_while_moving_agents_placers_and_turntables_placers(): + scene = create_specific_agent_placer_turntable_scene( + agent_start=10, placer_start=100, turntable_start=50) + goal = Goal() + freeze_while_moving = ['agent', 'placers', 'turntables'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + moves_lists = [obj.get('moves') + for obj in scene.objects if obj.get('moves') is not None] + moves_max_step_end = max( + move['stepEnd'] for moves in moves_lists for move in moves) + assert len(goal.action_list) == moves_max_step_end + + +def test_action_freeze_while_moving_agents_placers_and_turntables_turntables(): + scene = create_specific_agent_placer_turntable_scene( + agent_start=10, placer_start=50, turntable_start=100) + goal = Goal() + freeze_while_moving = ['agent', 'placers', 'turntables'] + ActionService.add_freeze_while_moving(goal, freeze_while_moving) + rotates_lists = [ + obj.get('rotates') for obj in scene.objects if + obj.get('rotates') is not None] + rotates_max_step_end = max( + rotate['stepEnd'] for rotates in rotates_lists for rotate in rotates) + assert len(goal.action_list) == rotates_max_step_end diff --git a/tests/ile_agent_service_test.py b/tests/ile_agent_service_test.py index 27d9c89..677e5c2 100644 --- a/tests/ile_agent_service_test.py +++ b/tests/ile_agent_service_test.py @@ -12,8 +12,7 @@ AgentCreationService, AgentMovementConfig, AgentPointingConfig, - AgentSettings, - get_default_agent_settings + AgentSettings ) from ideal_learning_env.defs import ILEConfigurationException, ILEException from ideal_learning_env.numerics import MinMaxFloat, VectorFloatConfig @@ -50,8 +49,8 @@ def test_agent_service_reconcile(): template = AgentConfig( 1, type=[ - 'test_type', - 'test_type2'], + 'agent_male_01', + 'agent_female_01'], agent_settings=AgentSettings( chest=[ 2, @@ -62,7 +61,7 @@ def test_agent_service_reconcile(): srv = AgentCreationService() reconciled: AgentConfig = srv.reconcile(scene, template) assert reconciled.num == 1 - assert reconciled.type in ['test_type', 'test_type2'] + assert reconciled.type in ['agent_male_01', 'agent_female_01'] assert reconciled.agent_settings.chest in [2, 4] assert reconciled.position.x in [1, 2] assert 0.5 <= reconciled.position.y <= 0.6 @@ -72,6 +71,39 @@ def test_agent_service_reconcile(): assert reconciled.actions == [] +def test_agent_labels(): + scene = Scene() + template = AgentConfig( + 1, + type=[ + 'agent_male_01', + 'agent_female_01'], + agent_settings=AgentSettings( + chest=[ + 2, + 4]), + position=VectorFloatConfig([1, 2], MinMaxFloat(0.5, 0.6), + [1, MinMaxFloat(4.4, 4.5)]), + rotation_y=[56, 57], + labels=[ + 'label1', + 'label2' + ] + ) + srv = AgentCreationService() + agents, _ = srv.add_to_scene(scene, template, []) + agent = agents[0] + + object_repo = ObjectRepository.get_instance() + agents_label1 = object_repo.get_all_from_labeled_objects('label1') or [] + agents_label2 = object_repo.get_all_from_labeled_objects('label2') or [] + assert (len(agents_label1) == 1) or (len(agents_label2) == 1) + if len(agents_label1): + assert agents_label1[0].instance == agent + if len(agents_label2): + assert agents_label2[0].instance == agent + + def test_agent_service_reconcile_default(): scene = Scene() template = AgentConfig(1) @@ -90,15 +122,16 @@ def test_agent_service_reconcile_default(): def test_agent_service_create(): scene = Scene() template = AgentConfig( - 1, - type='test_type', + num=1, + type='agent_female_01', agent_settings=AgentSettings(), position=VectorFloatConfig(1, 0.5, 1), - rotation_y=90) + rotation_y=90 + ) srv = AgentCreationService() agent = srv.create_feature_from_specific_values( - scene, template, template) - assert agent['type'] == 'test_type' + scene, srv.reconcile(scene, template), template) + assert agent['type'] == 'agent_female_01' assert agent['id'].startswith('agent') assert agent['agentSettings'] assert agent['shows'][0]['position']['x'] == 1 @@ -125,14 +158,14 @@ def test_agent_service_add(): scene = Scene() template = AgentConfig( 1, - type='test_type', + type='agent_male_01', agent_settings=AgentSettings(), position=VectorFloatConfig(1, 0.5, 1), rotation_y=90) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) agent = agents[0] - assert agent['type'] == 'test_type' + assert agent['type'] == 'agent_male_01' assert agent['id'].startswith('agent') assert agent['agentSettings'] assert agent['shows'][0]['position']['x'] == 1 @@ -163,7 +196,7 @@ def test_agent_service_add_fail(): scene = Scene() template = AgentConfig( 2, - type='test_type', + type='agent_male_01', agent_settings=AgentSettings(), position=VectorFloatConfig(1, 0.5, 1), rotation_y=90) @@ -183,7 +216,7 @@ def test_agent_service_add_actions(): ] template = AgentConfig( 1, - type='test_type', + type='agent_male_01', agent_settings=AgentSettings(), position=VectorFloatConfig(0, 0, 2), rotation_y=180, @@ -191,7 +224,7 @@ def test_agent_service_add_actions(): srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) agent = agents[0] - assert agent['type'] == 'test_type' + assert agent['type'] == 'agent_male_01' assert agent['id'].startswith('agent') assert agent['agentSettings'] assert agent['shows'][0]['position']['x'] == 0 @@ -249,7 +282,7 @@ def test_agent_service_add_actions_fail(): ] template = AgentConfig( 1, - type='test_type', + type='agent_male_01', agent_settings=AgentSettings(), position=VectorFloatConfig(0, 0, 2), rotation_y=180, @@ -323,8 +356,11 @@ def test_agent_service_reconcile_movement_empty(): def test_agent_service_create_movement_bounds_and_points(): scene = Scene() template = AgentConfig( - 1, position=Vector3d(), rotation_y=0, - agent_settings=get_default_agent_settings()) + num=1, + position=Vector3d(), + rotation_y=0, + agent_settings=None + ) template.movement = AgentMovementConfig( animation='anim1', step_begin=3, points=[Vector3d(x=1, y=0, z=3), @@ -337,9 +373,13 @@ def test_agent_service_create_movement_bounds_and_points(): srv = AgentCreationService() agent = srv.create_feature_from_specific_values( - scene, template, template) + scene, srv.reconcile(scene, template), template) move = agent['agentMovement'] assert agent + assert ( + agent['type'].startswith('agent_male_') or + agent['type'].startswith('agent_female_') + ) assert move assert move['repeat'] is True assert move['stepBegin'] == 3 @@ -353,8 +393,11 @@ def test_agent_service_create_movement_bounds_and_points(): def test_agent_service_create_movement_bounds(): scene = Scene() template = AgentConfig( - 1, position=Vector3d(), rotation_y=0, - agent_settings=get_default_agent_settings()) + num=1, + position=Vector3d(), + rotation_y=0, + agent_settings=None + ) template.movement = AgentMovementConfig( animation='anim2', step_begin=1, bounds=[Vector3d(x=0, y=0, z=1.0), @@ -366,9 +409,13 @@ def test_agent_service_create_movement_bounds(): srv = AgentCreationService() agent = srv.create_feature_from_specific_values( - scene, template, template) + scene, srv.reconcile(scene, template), template) move = agent['agentMovement'] assert agent + assert ( + agent['type'].startswith('agent_male_') or + agent['type'].startswith('agent_female_') + ) assert move assert move['repeat'] is True assert move['stepBegin'] == 1 @@ -383,8 +430,11 @@ def test_agent_service_create_movement_bounds(): def test_agent_service_create_no_bounds(): scene = prior_scene_custom_size(2, 10) template = AgentConfig( - 1, position=Vector3d(), rotation_y=0, - agent_settings=get_default_agent_settings()) + num=1, + position=Vector3d(), + rotation_y=0, + agent_settings=None + ) template.movement = AgentMovementConfig( animation='anim3', step_begin=3, num_points=10, @@ -392,9 +442,13 @@ def test_agent_service_create_no_bounds(): srv = AgentCreationService() agent = srv.create_feature_from_specific_values( - scene, template, template) + scene, srv.reconcile(scene, template), template) move = agent['agentMovement'] assert agent + assert ( + agent['type'].startswith('agent_male_') or + agent['type'].startswith('agent_female_') + ) assert move assert move['repeat'] is False assert move['stepBegin'] == 3 @@ -409,15 +463,22 @@ def test_agent_service_create_no_bounds(): def test_agent_service_create_no_data(): scene = prior_scene_custom_size(2, 10) template = AgentConfig( - 1, position=Vector3d(), rotation_y=0, - agent_settings=get_default_agent_settings()) + num=1, + position=Vector3d(), + rotation_y=0, + agent_settings=None + ) template.movement = AgentMovementConfig() srv = AgentCreationService() agent = srv.create_feature_from_specific_values( - scene, template, template) + scene, srv.reconcile(scene, template), template) move = agent['agentMovement'] assert agent + assert ( + agent['type'].startswith('agent_male_') or + agent['type'].startswith('agent_female_') + ) assert move assert move['repeat'] in [True, False] assert move['stepBegin'] > -1 @@ -435,7 +496,7 @@ def test_agent_opposite_x(): keyword=KeywordLocation.OPPOSITE_X, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -453,7 +514,7 @@ def test_agent_opposite_z(): keyword=KeywordLocation.OPPOSITE_Z, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -471,7 +532,7 @@ def test_agent_adjacent_to_object(): keyword=KeywordLocation.ADJACENT_TO_OBJECT, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -490,7 +551,7 @@ def test_agent_behind_object_from_performer(): keyword=KeywordLocation.BEHIND_OBJECT_FROM_PERFORMER, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -508,7 +569,7 @@ def test_agent_front_of_performer(): keyword=KeywordLocation.FRONT_OF_PERFORMER, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -531,7 +592,7 @@ def test_agent_on_object(): keyword=KeywordLocation.ON_OBJECT, relative_object_label='platforms'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, bounds) @@ -554,7 +615,7 @@ def test_agent_on_object_centered(): keyword=KeywordLocation.ON_OBJECT, relative_object_label='platforms'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -572,7 +633,7 @@ def test_agent_keyword_location_random(): keyword=KeywordLocation.RANDOM, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() agents, _ = srv.add_to_scene(scene, template, []) @@ -591,7 +652,7 @@ def test_agent_keyword_location_and_position_fail(): relative_object_label='target'), position=VectorFloatConfig(1, 2, 3), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() with pytest.raises(ILEException): @@ -605,7 +666,7 @@ def test_agent_opposite_z_fail_overlap(): keyword=KeywordLocation.OPPOSITE_Z, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() bounds = [] @@ -621,7 +682,7 @@ def test_agent_associate_with_agent_fail(): keyword=KeywordLocation.ASSOCIATED_WITH_AGENT, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() with pytest.raises(ILEConfigurationException): @@ -636,7 +697,7 @@ def test_agent_in_fail(): relative_object_label='target', container_label="test"), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() with pytest.raises(ILEConfigurationException): @@ -651,7 +712,7 @@ def test_agent_in_with_fail(): relative_object_label='target', container_label="test"), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() with pytest.raises(ILEConfigurationException): @@ -665,7 +726,7 @@ def test_agent_occlude_fail(): keyword=KeywordLocation.OCCLUDE_OBJECT, relative_object_label='target'), rotation_y=0, - agent_settings=get_default_agent_settings()) + agent_settings=None) srv = AgentCreationService() with pytest.raises(ILEConfigurationException): @@ -874,7 +935,7 @@ def test_agent_pointing_walk_distance(): True ) # Agent starts turned around - rotation_y = (rotation_y + 180) % 360 + rotation_y = round(rotation_y + 180, 2) % 360 assert agent['shows'][0]['rotation'] == {'x': 0, 'y': rotation_y, 'z': 0} assert agent['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} assert agent['agentMovement'] == { @@ -925,7 +986,7 @@ def test_agent_pointing_walk_distance_custom_step(): True ) # Agent starts turned around - rotation_y = (rotation_y + 180) % 360 + rotation_y = round(rotation_y + 180, 2) % 360 assert agent['shows'][0]['rotation'] == {'x': 0, 'y': rotation_y, 'z': 0} assert agent['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} assert agent['agentMovement'] == { diff --git a/tests/ile_choosers_test.py b/tests/ile_choosers_test.py index 4d6f7a9..b09ba68 100644 --- a/tests/ile_choosers_test.py +++ b/tests/ile_choosers_test.py @@ -555,7 +555,7 @@ def test_choose_shape_material_neither_none_non_restricted(): shape, mat = choose_shape_material(shape_input, mat_input) assert shape == "sphere" assert mat.material == "AI2-THOR/Materials/Fabrics/Carpet2" - assert mat.color == ["brown"] + assert mat.color == ["brown", "grey"] def test_choose_shape_material_neither_none_non_restricted_category(): @@ -668,7 +668,7 @@ def test_choose_shape_material_neither_none_non_restricted_with_prohibited(): shape, mat = choose_shape_material(shape_input, mat_input, prohibited) assert shape == "sphere" assert mat.material == "AI2-THOR/Materials/Fabrics/Carpet2" - assert mat.color == ["brown"] + assert mat.color == ["brown", "grey"] def test_choose_shape_material_prohibited_colors(): @@ -696,14 +696,14 @@ def test_choose_shape_material_category_prohibited_colors(): def test_choose_shape_material_no_shape_or_material_with_excluded_colors(): - ILESharedConfiguration.get_instance().set_excluded_colors(['brown']) + ILESharedConfiguration.get_instance().set_excluded_colors(['white']) # Really just make sure this does not throw an error shape, mat = choose_shape_material(None, None) assert shape # Some shapes do not have an assigned material if mat: assert mat.material - assert 'brown' not in mat.color + assert 'white' not in mat.color def test_choose_shape_material_with_excluded_colors(): @@ -793,7 +793,7 @@ def test_choose_shape_material_with_excluded_colors_override(): shape, mat = choose_shape_material(shape_input, mat_input) assert shape == 'sphere' assert mat.material == 'Custom/Materials/BrownWoodMCS' - assert mat.color == ['brown'] + assert mat.color == ['brown', 'orange'] def test_choose_shape_material_no_shape_with_excluded_colors_override(): @@ -802,4 +802,4 @@ def test_choose_shape_material_no_shape_with_excluded_colors_override(): shape, mat = choose_shape_material(None, mat_input) assert shape assert mat.material == 'Custom/Materials/BrownWoodMCS' - assert mat.color == ['brown'] + assert mat.color == ['brown', 'orange'] diff --git a/tests/ile_defs_test.py b/tests/ile_defs_test.py index 9dd472f..2b9114d 100644 --- a/tests/ile_defs_test.py +++ b/tests/ile_defs_test.py @@ -138,18 +138,24 @@ def test_find_bounds(): ], max_y=100, min_y=0) # Case 4: 1 hole - scene = Scene(holes=[{'x': 3, 'z': 3}], objects=[]) + scene = Scene(holes=[Vector2dInt(x=3, z=3)], objects=[]) assert find_bounds(scene) == [bounds_3] # Case 5: 2 holes - scene = Scene(holes=[{'x': 3, 'z': 3}, {'x': -3, 'z': -3}], objects=[]) + scene = Scene( + holes=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[] + ) assert find_bounds(scene) == [bounds_3, bounds_4] # Case 6: holes and objects - scene = Scene(holes=[{'x': 3, 'z': 3}, {'x': -3, 'z': -3}], objects=[ - {'shows': [{'boundingBox': bounds_1}]}, - {'shows': [{'boundingBox': bounds_2}]} - ]) + scene = Scene( + holes=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) assert find_bounds(scene) == [bounds_3, bounds_4, bounds_1, bounds_2] # Case 7: floor textures @@ -160,28 +166,37 @@ def test_find_bounds(): assert find_bounds(scene) == [] # Case 8: 1 lava area - scene = Scene(lava=[{'x': 3, 'z': 3}], objects=[]) + scene = Scene(lava=[Vector2dInt(x=3, z=3)], objects=[]) assert find_bounds(scene) == [bounds_3] # Case 9: 2 lava areas - scene = Scene(lava=[{'x': 3, 'z': 3}, {'x': -3, 'z': -3}], objects=[]) + scene = Scene( + lava=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[] + ) assert find_bounds(scene) == [bounds_3, bounds_4] # Case 10: lava areas and objects - scene = Scene(lava=[{'x': 3, 'z': 3}, {'x': -3, 'z': -3}], objects=[ - {'shows': [{'boundingBox': bounds_1}]}, - {'shows': [{'boundingBox': bounds_2}]} - ]) + scene = Scene( + lava=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) assert find_bounds(scene) == [bounds_3, bounds_4, bounds_1, bounds_2] # Case 11: everything scene = Scene(floor_textures=FloorTexturesConfig( material='blue', positions=[Vector2dInt(x=0, z=0)]), - lava=[{'x': 3, 'z': 3}], holes=[{'x': -3, 'z': -3}], objects=[ - {'shows': [{'boundingBox': bounds_1}]}, - {'shows': [{'boundingBox': bounds_2}]} - ]) + lava=[Vector2dInt(x=3, z=3)], + holes=[Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) assert find_bounds(scene) == [bounds_4, bounds_3, bounds_1, bounds_2] diff --git a/tests/ile_global_settings_component_test.py b/tests/ile_global_settings_component_test.py index 0d48617..1e45f17 100644 --- a/tests/ile_global_settings_component_test.py +++ b/tests/ile_global_settings_component_test.py @@ -1,7 +1,7 @@ import math import pytest -from machine_common_sense.config_manager import PerformerStart, Vector3d +from machine_common_sense.config_manager import Goal, PerformerStart, Vector3d from numpy import arange from shapely.geometry import Point, Polygon @@ -79,9 +79,9 @@ def test_global_settings(): is None assert isinstance(scene.floor_material, str) assert scene.floor_properties is None - assert scene.goal.get('last_step') is None - assert scene.goal.get('category') is None - assert scene.goal.get('metadata') == {} + assert scene.goal.last_step is None + assert scene.goal.category is None + assert scene.goal.metadata == {} dimensions = scene.room_dimensions assert dimensions.x >= 2 and dimensions.x <= 100 assert dimensions.y >= 2 and dimensions.y <= 10 @@ -101,11 +101,11 @@ def test_global_settings(): assert rotation.x == 0 assert rotation.y >= 0 and rotation.y <= 360 assert rotation.z == 0 - wall_material = scene.room_materials['back'] + wall_material = scene.room_materials.back assert isinstance(wall_material, str) - assert scene.room_materials['front'] == wall_material - assert scene.room_materials['left'] == wall_material - assert scene.room_materials['right'] == wall_material + assert scene.room_materials.front == wall_material + assert scene.room_materials.left == wall_material + assert scene.room_materials.right == wall_material assert scene.debug['ceilingColors'] assert scene.debug['floorColors'] assert scene.debug['wallColors'] @@ -278,7 +278,7 @@ def test_global_settings_configured(): assert component.occluder_gap == 1.0 assert component.occluder_gap_viewport == 1.0 assert component.performer_start_position == VectorFloatConfig(-1, 0, 1) - assert component.performer_start_rotation == VectorIntConfig(-10, 90, 0) + assert component.performer_start_rotation == VectorFloatConfig(-10, 90, 0) assert component.restrict_open_doors assert component.restrict_open_objects assert component.room_dimensions == VectorIntConfig(5, 3, 10) @@ -296,14 +296,14 @@ def test_global_settings_configured(): assert shared_config.get_excluded_shapes() == ['ball', 'pacifier'] assert scene.floor_material == 'Custom/Materials/GreyCarpetMCS' assert scene.floor_properties is None - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] in [ + assert scene.goal.category == 'retrieval' + assert scene.goal.description in [ 'Find and pick up the tiny light black white rubber ball.', 'Find and pick up the medium light black white rubber ball.', 'Find and pick up the small light black white rubber ball.' ] - assert scene.goal['last_step'] == 1000 - assert scene.goal['metadata']['target']['id'] + assert scene.goal.last_step == 1000 + assert scene.goal.metadata['target']['id'] assert ILESharedConfiguration.get_instance().get_occluder_gap() == 1.0 assert ILESharedConfiguration.get_instance().get_occluder_gap_viewport() \ == 1.0 @@ -312,12 +312,10 @@ def test_global_settings_configured(): assert scene.restrict_open_doors is True assert scene.restrict_open_objects is True assert scene.room_dimensions == Vector3d(x=5, y=3, z=10) - assert scene.room_materials == { - 'back': 'Custom/Materials/WhiteDrywallMCS', - 'front': 'Custom/Materials/BlueDrywallMCS', - 'left': 'Custom/Materials/GreenDrywallMCS', - 'right': 'Custom/Materials/RedDrywallMCS' - } + assert scene.room_materials.front == 'Custom/Materials/BlueDrywallMCS' + assert scene.room_materials.back == 'Custom/Materials/WhiteDrywallMCS' + assert scene.room_materials.left == 'Custom/Materials/GreenDrywallMCS' + assert scene.room_materials.right == 'Custom/Materials/RedDrywallMCS' assert not scene.intuitive_physics assert len(scene.objects) == 1 assert scene.objects[0]['type'] == 'soccer_ball' @@ -371,7 +369,7 @@ def test_global_settings_passive_physics_scene(): assert component.occluder_gap_viewport == 1.0 assert component.passive_physics_scene is True assert component.performer_start_position == VectorFloatConfig(-1, 0, 1) - assert component.performer_start_rotation == VectorIntConfig(-10, 90, 0) + assert component.performer_start_rotation == VectorFloatConfig(-10, 90, 0) assert component.room_dimensions == VectorIntConfig(5, 3, 10) assert component.room_shape == 'square' assert component.wall_back_material == 'Custom/Materials/WhiteDrywallMCS' @@ -396,21 +394,19 @@ def test_global_settings_passive_physics_scene(): assert scene.floor_properties['drag'] == 0 assert scene.floor_properties['dynamicFriction'] == 0.1 assert scene.floor_properties['staticFriction'] == 0.1 - assert scene.goal['category'] == 'intuitive physics' - assert scene.goal['description'] == '' - assert scene.goal['last_step'] == 1000 - assert scene.goal['metadata'] == {} + assert scene.goal.category == 'intuitive physics' + assert scene.goal.description == '' + assert scene.goal.last_step == 1000 + assert scene.goal.metadata == {} assert scene.performer_start == PerformerStart( position=Vector3d(x=0, y=0, z=-4.5), rotation=Vector3d(x=0, y=0, z=0) ) assert scene.room_dimensions == Vector3d(x=20, y=10, z=20) - assert scene.room_materials == { - 'back': 'Custom/Materials/WhiteDrywallMCS', - 'front': 'Custom/Materials/BlueDrywallMCS', - 'left': 'Custom/Materials/GreenDrywallMCS', - 'right': 'Custom/Materials/RedDrywallMCS' - } + assert scene.room_materials.front == 'Custom/Materials/BlueDrywallMCS' + assert scene.room_materials.back == 'Custom/Materials/WhiteDrywallMCS' + assert scene.room_materials.left == 'Custom/Materials/GreenDrywallMCS' + assert scene.room_materials.right == 'Custom/Materials/RedDrywallMCS' # Cleanup shared_config.set_excluded_colors([]) @@ -426,17 +422,17 @@ def test_global_settings_side_wall_opposite_colors(): assert component.side_wall_opposite_colors scene = component.update_ile_scene(prior_scene()) - options = [item.material for item in materials.OPPOSITE_MATERIALS] - wall_material = scene.room_materials['back'] + options = [item.material for item in materials.WALL_OPPOSITE_MATERIALS] + wall_material = scene.room_materials.back assert wall_material in options opposite_material = materials.OPPOSITE_SETS[wall_material].material - assert scene.room_materials['front'] == wall_material + assert scene.room_materials.front == wall_material assert ( - scene.room_materials['left'] == wall_material and - scene.room_materials['right'] == opposite_material + scene.room_materials.left == wall_material and + scene.room_materials.right == opposite_material ) or ( - scene.room_materials['right'] == wall_material and - scene.room_materials['left'] == opposite_material + scene.room_materials.right == wall_material and + scene.room_materials.left == opposite_material ) @@ -474,20 +470,18 @@ def test_global_settings_trapezoidal_room(): assert scene.ceiling_material == 'Custom/Materials/GreyDrywallMCS' assert scene.floor_material == 'Custom/Materials/GreyCarpetMCS' assert scene.floor_properties is None - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] in [ + assert scene.goal.category == 'retrieval' + assert scene.goal.description in [ 'Find and pick up the tiny light black white rubber ball.', 'Find and pick up the medium light black white rubber ball.', 'Find and pick up the small light black white rubber ball.' ] - assert scene.goal['metadata']['target']['id'] + assert scene.goal.metadata['target']['id'] assert scene.room_dimensions == Vector3d(x=12, y=3, z=16) - assert scene.room_materials == { - 'back': 'Custom/Materials/WhiteDrywallMCS', - 'front': 'Custom/Materials/BlueDrywallMCS', - 'left': 'Custom/Materials/GreenDrywallMCS', - 'right': 'Custom/Materials/RedDrywallMCS' - } + assert scene.room_materials.front == 'Custom/Materials/BlueDrywallMCS' + assert scene.room_materials.back == 'Custom/Materials/WhiteDrywallMCS' + assert scene.room_materials.left == 'Custom/Materials/GreenDrywallMCS' + assert scene.room_materials.right == 'Custom/Materials/RedDrywallMCS' assert not scene.intuitive_physics assert len(scene.objects) == 3 wall_left = scene.objects[0] @@ -509,10 +503,10 @@ def test_global_settings_wall_material(): assert component.wall_material == "Custom/Materials/BrownDrywallMCS" scene = component.update_ile_scene(prior_scene()) - assert scene.room_materials['back'] == "Custom/Materials/BrownDrywallMCS" - assert scene.room_materials['front'] == "Custom/Materials/BrownDrywallMCS" - assert scene.room_materials['left'] == "Custom/Materials/BrownDrywallMCS" - assert scene.room_materials['right'] == "Custom/Materials/BrownDrywallMCS" + assert scene.room_materials.back == "Custom/Materials/BrownDrywallMCS" + assert scene.room_materials.front == "Custom/Materials/BrownDrywallMCS" + assert scene.room_materials.left == "Custom/Materials/BrownDrywallMCS" + assert scene.room_materials.right == "Custom/Materials/BrownDrywallMCS" def test_auto_last_step(): @@ -521,7 +515,7 @@ def test_auto_last_step(): 'room_dimensions': {'x': 5, 'y': 3, 'z': 10}, }) scene: Scene = component.update_ile_scene(prior_scene()) - assert scene.goal['last_step'] == 2000 + assert scene.goal.last_step == 2000 def test_last_step_both_methods(): @@ -531,7 +525,7 @@ def test_last_step_both_methods(): 'room_dimensions': {'x': 5, 'y': 3, 'z': 10}, }) scene: Scene = component.update_ile_scene(prior_scene()) - assert scene.goal['last_step'] == 987 + assert scene.goal.last_step == 987 def test_global_settings_fail_ceiling_material(): @@ -1086,6 +1080,9 @@ def test_global_settings_performer_starts_near(): scene = component.update_ile_scene( prior_scene_custom_start(10, 10)) assert component.get_num_delayed_actions() == 1 + room_dimensions = scene.room_dimensions + assert scene.performer_start.position.x == room_dimensions.x + 1 + assert scene.performer_start.position.z == room_dimensions.z + 1 object_comp = SpecificInteractableObjectsComponent(data) scene = object_comp.update_ile_scene(scene) @@ -1209,12 +1206,12 @@ def test_global_settings_forced_choice_multi_retrieval_target_left(): 'forced_choice_multi_retrieval_target': 'soccer_ball' }) scene = component.update_ile_scene(scene) - assert scene.goal == {'metadata': {}} + assert scene.goal == Goal(metadata={}) scene = component.run_actions_at_end_of_scene_generation(scene) assert len(scene.objects) == 1 - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] - assert scene.goal['metadata'] == {'targets': [ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description + assert scene.goal.metadata == {'targets': [ {'id': scene.objects[0]['id']} ]} @@ -1238,12 +1235,12 @@ def test_global_settings_forced_choice_multi_retrieval_target_right(): 'forced_choice_multi_retrieval_target': 'soccer_ball' }) scene = component.update_ile_scene(scene) - assert scene.goal == {'metadata': {}} + assert scene.goal == Goal(metadata={}) scene = component.run_actions_at_end_of_scene_generation(scene) assert len(scene.objects) == 3 - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] - assert scene.goal['metadata'] == {'targets': [ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description + assert scene.goal.metadata == {'targets': [ {'id': scene.objects[0]['id']}, {'id': scene.objects[2]['id']} ]} @@ -1270,12 +1267,12 @@ def test_global_settings_forced_choice_multi_retrieval_target_pickup(): 'forced_choice_multi_retrieval_target': 'soccer_ball' }) scene = component.update_ile_scene(scene) - assert scene.goal == {'metadata': {}} + assert scene.goal == Goal(metadata={}) scene = component.run_actions_at_end_of_scene_generation(scene) assert len(scene.objects) == 3 - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] - assert scene.goal['metadata'] == {'targets': [ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description + assert scene.goal.metadata == {'targets': [ {'id': scene.objects[1]['id']} ]} diff --git a/tests/ile_goal_services_test.py b/tests/ile_goal_services_test.py index 1f3438a..8822a8e 100644 --- a/tests/ile_goal_services_test.py +++ b/tests/ile_goal_services_test.py @@ -21,14 +21,14 @@ def test_attempt_goal_retrieval(): ) ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] == 'Find and pick up the small light ' + \ + assert scene.goal.category == 'retrieval' + assert scene.goal.description == 'Find and pick up the small light ' + \ 'black white rubber ball.' - assert scene.goal['metadata']['target'] - assert scene.goal['metadata']['target']['id'] + assert scene.goal.metadata['target'] + assert scene.goal.metadata['target']['id'] objs = scene.objects assert len(objs) == 1 - assert objs[0]['id'] == scene.goal['metadata']['target']['id'] + assert objs[0]['id'] == scene.goal.metadata['target']['id'] assert objs[0]['type'] == 'soccer_ball' assert objs[0]['pickupable'] assert objs[0]['moveable'] @@ -45,14 +45,14 @@ def test_attempt_goal_retrieval_ignore_num(): target=InteractableObjectConfig(num=2, scale=1.5, shape='soccer_ball') ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] == 'Find and pick up the small light ' + \ + assert scene.goal.category == 'retrieval' + assert scene.goal.description == 'Find and pick up the small light ' + \ 'black white rubber ball.' - assert scene.goal['metadata']['target'] - assert scene.goal['metadata']['target']['id'] + assert scene.goal.metadata['target'] + assert scene.goal.metadata['target']['id'] objs = scene.objects assert len(objs) == 1 - assert objs[0]['id'] == scene.goal['metadata']['target']['id'] + assert objs[0]['id'] == scene.goal.metadata['target']['id'] assert objs[0]['type'] == 'soccer_ball' @@ -62,14 +62,14 @@ def test_attempt_goal_retrieval_no_category(): target=InteractableObjectConfig(scale=1, shape='soccer_ball') ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] == 'Find and pick up the tiny light ' + \ + assert scene.goal.category == 'retrieval' + assert scene.goal.description == 'Find and pick up the tiny light ' + \ 'black white rubber ball.' - assert scene.goal['metadata']['target'] - assert scene.goal['metadata']['target']['id'] + assert scene.goal.metadata['target'] + assert scene.goal.metadata['target']['id'] objs = scene.objects assert len(objs) == 1 - assert objs[0]['id'] == scene.goal['metadata']['target']['id'] + assert objs[0]['id'] == scene.goal.metadata['target']['id'] assert objs[0]['type'] == 'soccer_ball' @@ -82,14 +82,14 @@ def test_attempt_goal_multi_retrieval(): ] ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] == 'Find and pick up as many objects ' + \ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description == 'Find and pick up as many objects ' + \ 'as possible of type: tiny light black white rubber ball.' - assert len(scene.goal['metadata']['targets']) == 1 - assert scene.goal['metadata']['targets'][0]['id'] + assert len(scene.goal.metadata['targets']) == 1 + assert scene.goal.metadata['targets'][0]['id'] objs = scene.objects assert len(objs) == 1 - assert objs[0]['id'] == scene.goal['metadata']['targets'][0]['id'] + assert objs[0]['id'] == scene.goal.metadata['targets'][0]['id'] assert objs[0]['type'] == 'soccer_ball' @@ -102,17 +102,17 @@ def test_attempt_goal_multi_retrieval_with_num(): ] ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] == 'Find and pick up as many objects ' + \ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description == 'Find and pick up as many objects ' + \ 'as possible of type: tiny light black white rubber ball.' - assert len(scene.goal['metadata']['targets']) == 2 - assert scene.goal['metadata']['targets'][0]['id'] - assert scene.goal['metadata']['targets'][1]['id'] + assert len(scene.goal.metadata['targets']) == 2 + assert scene.goal.metadata['targets'][0]['id'] + assert scene.goal.metadata['targets'][1]['id'] objs = scene.objects assert len(objs) == 2 - assert objs[0]['id'] == scene.goal['metadata']['targets'][0]['id'] + assert objs[0]['id'] == scene.goal.metadata['targets'][0]['id'] assert objs[0]['type'] == 'soccer_ball' - assert objs[1]['id'] == scene.goal['metadata']['targets'][1]['id'] + assert objs[1]['id'] == scene.goal.metadata['targets'][1]['id'] assert objs[1]['type'] == 'soccer_ball' @@ -131,23 +131,23 @@ def test_attempt_goal_multi_retrieval_multiple_shapes(): ] ) GoalServices.attempt_to_add_goal(scene, goal_template) - assert scene.goal['category'] == 'multi retrieval' - assert scene.goal['description'] == 'Find and pick up as many objects ' + \ + assert scene.goal.category == 'multi retrieval' + assert scene.goal.description == 'Find and pick up as many objects ' + \ 'as possible of type: small light black white rubber ball; ' + \ 'and tiny light grey metal trophy.' - assert len(scene.goal['metadata']['targets']) == 3 - assert scene.goal['metadata']['targets'][0]['id'] - assert scene.goal['metadata']['targets'][1]['id'] - assert scene.goal['metadata']['targets'][2]['id'] + assert len(scene.goal.metadata['targets']) == 3 + assert scene.goal.metadata['targets'][0]['id'] + assert scene.goal.metadata['targets'][1]['id'] + assert scene.goal.metadata['targets'][2]['id'] objs = scene.objects assert len(objs) == 3 - assert objs[0]['id'] == scene.goal['metadata']['targets'][0]['id'] + assert objs[0]['id'] == scene.goal.metadata['targets'][0]['id'] assert objs[0]['type'] == 'soccer_ball' - assert objs[1]['id'] == scene.goal['metadata']['targets'][1]['id'] + assert objs[1]['id'] == scene.goal.metadata['targets'][1]['id'] assert objs[1]['id'] != objs[0]['id'] assert objs[1]['type'] == 'soccer_ball' assert objs[1]['shows'][0]['scale'] == objs[0]['shows'][0]['scale'] - assert objs[2]['id'] == scene.goal['metadata']['targets'][2]['id'] + assert objs[2]['id'] == scene.goal.metadata['targets'][2]['id'] assert objs[2]['type'] == 'trophy' assert objs[2]['id'] != objs[0]['id'] assert objs[2]['id'] != objs[1]['id'] diff --git a/tests/ile_helper.py b/tests/ile_helper.py index 8c3f9e0..571ec3b 100644 --- a/tests/ile_helper.py +++ b/tests/ile_helper.py @@ -1,10 +1,12 @@ +import random from os import getuid -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d from generator import ObjectBounds, Scene from generator.base_objects import create_soccer_ball from generator.instances import instantiate_object +from ideal_learning_env.agent_component import SpecificAgentComponent from ideal_learning_env.defs import TARGET_LABEL from ideal_learning_env.interactable_objects_component import ( SpecificInteractableObjectsComponent @@ -21,7 +23,7 @@ def prior_scene(last_step: int = None): scene = Scene() if last_step: - scene.goal['last_step'] = last_step + scene.goal.last_step = last_step return scene @@ -52,7 +54,7 @@ def prior_passive_scene(last_step: int = None): scene.set_performer_start_rotation(0, 0) scene.set_room_dimensions(20, 10, 20) if last_step: - scene.goal['last_step'] = last_step + scene.goal.last_step = last_step return scene @@ -97,15 +99,15 @@ def prior_scene_with_target( 'materials': []} scene.objects = [target_object] - goal = { - "metadata": { + goal = Goal( + metadata={ "target": { "id": "743a91ad-fa2a-42a6-bf6b-2ac737ab7f8f" } }, - "last_step": 1000, - "category": "retrieval" - } + last_step=1000, + category="retrieval" + ) scene.goal = goal ObjectRepository.get_instance().clear() if add_to_repo: @@ -128,15 +130,15 @@ def prior_scene_with_targets(): {'position': {'x': 3, 'y': 0.11, 'z': 4}} ) scene.objects = [target_1, target_2] - scene.goal = { - 'category': 'multi retrieval', - 'metadata': { + scene.goal = Goal( + category='multi retrieval', + metadata={ 'targets': [ {'id': target_1['id']}, {'id': target_2['id']} ] } - } + ) return scene @@ -199,8 +201,10 @@ def create_test_obj_scene( perf_start_x=0, perf_start_z=3, object_start_x=0, object_start_z=0): """This creates a scene with a small object in the room. - This is mostly useful for `sidesteps` testing which requires - an object already being in the scene for the performer to sidestep. + The object shapes here are not irregular in shape (ex. hooked_tool) + to keep things simple. This is mostly useful for `sidesteps` + testing which requires an object already being in the scene for + the performer to sidestep. """ scene = prior_scene_custom_start( start_x=perf_start_x, start_z=perf_start_z) @@ -209,6 +213,7 @@ def create_test_obj_scene( "num": 1, "labels": "object", "scale": 0.1, + "shape": ["block_blue_letter_s", "apple_1", "truck_3", "chest_6"], "position": { "x": object_start_x, "z": object_start_z @@ -250,3 +255,108 @@ def create_placers_turntables_scene( ObjectRepository.get_instance().clear() scene = structural_component.update_ile_scene(scene) return scene + + +def create_agent_scene(number_of_agents=1): + """This creates a scene with an agent with random actions useful for + `freeze_while_moving` testing. + """ + scene = prior_scene_custom(10, 10, 0, 4) + step_begin_options = list(range(1, 9999)) + agent_component = SpecificAgentComponent({ + 'specific_agents': { + 'num': number_of_agents, + 'pointing': { + 'step_begin': step_begin_options, + 'walk_distance': 0.6 + } + } + }) + ObjectRepository.get_instance().clear() + scene = agent_component.update_ile_scene(scene) + return scene + + +def create_random_agent_placer_turntable_scene(): + """This creates a scene with agent, placers, and turntables for + `freeze_while_moving` testing with random setup. + """ + number_of_agents = random.randint(1, 5) + number_of_placers = random.randint(1, 5) + number_of_turntables = random.randint(1, 5) + structural_step_begin_options = list(range(1, 1000)) + structural_step_end_options = list(range(1000, 2000)) + agent_step_begin_options = list(range(1, 2000)) + + scene = prior_scene_custom(100, 100, 0, 4) + + agent_component = SpecificAgentComponent({ + 'specific_agents': { + 'num': number_of_agents, + 'pointing': { + 'step_begin': agent_step_begin_options, + 'walk_distance': 0.6 + } + } + }) + structural_component = SpecificStructuralObjectsComponent({ + 'structural_turntables': [{ + 'num': number_of_turntables, + 'rotation_y': 0, + 'turntable_radius': 2, + 'turntable_movement': { + 'step_begin': structural_step_begin_options, + 'step_end': structural_step_end_options, + 'rotation_y': 5, + } + }], + 'placers': [{ + 'num': number_of_placers, + 'activation_step': structural_step_begin_options, + "deactivation_step": structural_step_end_options, + 'end_height': 0 + }] + }) + ObjectRepository.get_instance().clear() + scene = structural_component.update_ile_scene(scene) + scene = agent_component.update_ile_scene(scene) + return scene + + +def create_specific_agent_placer_turntable_scene( + agent_start, placer_start, turntable_start): + """This creates a scene with agent, placers, and turntables for + `freeze_while_moving` testing with specific setup. + """ + scene = prior_scene_custom(20, 20, 0, 4) + agent_component = SpecificAgentComponent({ + 'specific_agents': { + 'num': 1, + 'pointing': { + 'step_begin': agent_start, + 'walk_distance': 0.6 + } + } + }) + structural_component = SpecificStructuralObjectsComponent({ + 'structural_turntables': [{ + 'num': 1, + 'rotation_y': 0, + 'turntable_radius': 2, + 'turntable_movement': { + 'step_begin': turntable_start, + 'step_end': turntable_start + 10, + 'rotation_y': 5, + } + }], + 'placers': [{ + 'num': 1, + 'activation_step': placer_start, + "deactivation_step": placer_start + 10, + 'end_height': 0 + }] + }) + ObjectRepository.get_instance().clear() + scene = structural_component.update_ile_scene(scene) + scene = agent_component.update_ile_scene(scene) + return scene diff --git a/tests/ile_interactable_object_service_test.py b/tests/ile_interactable_object_service_test.py index 94900d1..f803f59 100644 --- a/tests/ile_interactable_object_service_test.py +++ b/tests/ile_interactable_object_service_test.py @@ -3,24 +3,32 @@ import pytest from machine_common_sense.config_manager import Vector3d -from generator import base_objects, materials +from generator import base_objects, instances, materials, tools from ideal_learning_env import ( ILEException, ILESharedConfiguration, + InstanceDefinitionLocationTuple, InteractableObjectConfig, KeywordLocation, KeywordLocationConfig, + MinMaxFloat, ObjectRepository, VectorFloatConfig, VectorIntConfig ) from ideal_learning_env.interactable_object_service import ( InteractableObjectCreationService, - TargetCreationService + TargetCreationService, + ToolConfig, + ToolCreationService ) from ideal_learning_env.object_services import DEBUG_FINAL_POSITION_KEY -from .ile_helper import prior_scene, prior_scene_custom_size +from .ile_helper import ( + prior_scene, + prior_scene_custom_size, + prior_scene_with_target +) @pytest.fixture(autouse=True) @@ -824,6 +832,531 @@ def test_interactable_object_service_create_keyword_location_adjacent_failed(): ) +def test_interactable_object_service_create_keyword_adjacent_corner(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + abs(x_pos) == pytest.approx(4.45, 0.1) and + abs(z_pos) == pytest.approx(4.45, 0.1) + ) + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_interactable_object_service_create_keyword_adjacent_corner_with_label(): # noqa: E501 + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, relative_object_label="back_right") + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + x_pos == pytest.approx(4.45, 0.1) and + z_pos == pytest.approx(-4.45, 0.1) + ) + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_interactable_object_service_create_keyword_adjacent_corner_with_label_and_distance(): # noqa: E501 + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + relative_object_label="front_right", + adjacent_distance=VectorFloatConfig(2, 0, 1.5)) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + x_pos == pytest.approx(2.5, 0.1) and + z_pos == pytest.approx(3.0, 0.1) + ) + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_interactable_object_service_create_keyword_adjacent_corner_with_distance(): # noqa: E501 + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(1.5, 0, 2.0)) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + abs(x_pos) == pytest.approx(3.0, 0.1) and + abs(z_pos) == pytest.approx(2.5, 0.1) + ) + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_interactable_object_service_create_keyword_adjacent_corner_with_distance_list(): # noqa: E501 + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=[ + VectorFloatConfig(3, 0, 0), + VectorFloatConfig(0, 0, 4) + ] + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert (( + abs(x_pos) == pytest.approx(1.5, 0.1) and + abs(z_pos) == pytest.approx(4.5, 0.1) + ) or + ( + abs(x_pos) == pytest.approx(4.5, 0.1) and + abs(z_pos) == pytest.approx(0.5, 0.1) + )) + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_interactable_object_service_create_keyword_adjacent_corner_failed(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(10, 0, 10), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball' + ) + reconciled = srv.reconcile(scene, config) + with pytest.raises(ILEException): + srv.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + + +def test_interactable_object_service_surrounded_by_lava(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(2, 0, 2), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=0.5, + shape='ball', + surrounded_by_lava=True, + surrounding_lava_size=2 + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.25 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + x_pos == pytest.approx(-3.0, 0.1) and + z_pos == pytest.approx(-3.0, 0.1) + ) + + assert instance['shows'][0]['scale'] == {'x': 0.5, 'y': 0.5, 'z': 0.5} + + # Make sure lava isn't officially added until add_to_scene is called + assert not scene.lava + assert instance['debug']['surroundingLava'] + assert len(instance['debug']['surroundingLava']) == 24 + for square in instance['debug']['surroundingLava']: + assert square['x'] in [-5, -4, -3, -2, -1] + assert square['z'] in [-5, -4, -3, -2, -1] + assert not (square['x'] == -3 and square['z'] == -3) + + instances, _ = srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + instance = instances[0] + assert scene.lava + assert len(scene.lava) == 24 + # check that 'surroundingLava' array was deleted + # after lava was placed + assert 'surroundingLava' not in instance['debug'] + + for square in scene.lava: + assert square.x in [-5, -4, -3, -2, -1] + assert square.z in [-5, -4, -3, -2, -1] + assert not (square.x == -3 and square.z == -3) + + +def test_interactable_object_service_larger_object_surrounded_by_lava(): + srv = InteractableObjectCreationService() + scene = prior_scene() + + # move performer out of the way of object and lava so there are no errors + scene.performer_start.position.x = 3 + scene.performer_start.position.z = 3 + + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(3, 0, 3), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=2.0, + shape='ball', + surrounded_by_lava=True, + surrounding_lava_size=1 + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 1.0 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + + assert ( + x_pos == pytest.approx(-1.0, 0.1) and + z_pos == pytest.approx(-1.0, 0.1) + ) + + assert instance['shows'][0]['scale'] == {'x': 2.0, 'y': 2.0, 'z': 2.0} + + assert not scene.lava + assert instance['debug']['surroundingLava'] + assert len(instance['debug']['surroundingLava']) == 16 + for square in instance['debug']['surroundingLava']: + assert square['x'] in [-3, -2, -1, 0, 1] + assert square['z'] in [-3, -2, -1, 0, 1] + assert not (square['x'] == -2 and square['z'] == -2) + assert not (square['x'] == -2 and square['z'] == -1) + assert not (square['x'] == -2 and square['z'] == 0) + assert not (square['x'] == -1 and square['z'] == -2) + assert not (square['x'] == -1 and square['z'] == -1) + assert not (square['x'] == -1 and square['z'] == 0) + assert not (square['x'] == 0 and square['z'] == -2) + assert not (square['x'] == 0 and square['z'] == -1) + assert not (square['x'] == 0 and square['z'] == 0) + + instances, _ = srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + instance = instances[0] + assert scene.lava + assert len(scene.lava) == 16 + assert 'surroundingLava' not in instance['debug'] + + for square in scene.lava: + assert square.x in [-3, -2, -1, 0, 1] + assert square.z in [-3, -2, -1, 0, 1] + assert not (square.x == -2 and square.z == -2) + assert not (square.x == -2 and square.z == -1) + assert not (square.x == -2 and square.z == 0) + assert not (square.x == -1 and square.z == -2) + assert not (square.x == -1 and square.z == -1) + assert not (square.x == -1 and square.z == 0) + assert not (square.x == 0 and square.z == -2) + assert not (square.x == 0 and square.z == -1) + assert not (square.x == 0 and square.z == 0) + + +def test_interactable_object_service_surrounded_by_lava_fail_perf_start(): + srv = InteractableObjectCreationService() + scene = prior_scene() + + # should overlap with performer_start, so should fail + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(3, 0, 3), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=2.0, + shape='ball', + surrounded_by_lava=True, + surrounding_lava_size=1 + ) + reconciled = srv.reconcile(scene, config) + + with pytest.raises(ILEException): + srv.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + + +def test_interactable_object_service_surrounded_by_lava_fail_object_overlap(): + srv = InteractableObjectCreationService() + # target will overlap with new keyword object, should throw error + scene = prior_scene_with_target() + + # move performer out of the way of objects + scene.performer_start.position.x = 3 + scene.performer_start.position.z = 3 + + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(3, 0, 2), + relative_object_label="front_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=2.0, + shape='ball', + surrounded_by_lava=True, + surrounding_lava_size=1 + ) + reconciled = srv.reconcile(scene, config) + + with pytest.raises(ILEException): + srv.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + +def test_interactable_object_service_surrounded_by_lava_default_lava_size(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(2, 0, 2), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=0.5, + shape='ball', + surrounded_by_lava=True + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.25 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + x_pos == pytest.approx(-3.0, 0.1) and + z_pos == pytest.approx(-3.0, 0.1) + ) + + assert instance['shows'][0]['scale'] == {'x': 0.5, 'y': 0.5, 'z': 0.5} + + assert not scene.lava + assert instance['debug']['surroundingLava'] + assert len(instance['debug']['surroundingLava']) == 8 + for square in scene.lava: + assert square['x'] in [-4, -3, -2] + assert square['z'] in [-4, -3, -2] + assert not (square['x'] == -3 and square['z'] == -3) + + instances, _ = srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + instance = instances[0] + assert scene.lava + assert len(scene.lava) == 8 + + assert 'surroundingLava' not in instance['debug'] + for square in scene.lava: + assert square.x in [-4, -3, -2] + assert square.z in [-4, -3, -2] + assert not (square.x == -3 and square.z == -3) + + +def test_interactable_object_service_surrounded_by_lava_lava_size_list(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(2, 0, 2), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=0.5, + shape='ball', + surrounded_by_lava=True, + surrounding_lava_size=[1, 2] + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.25 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + + assert ( + x_pos == pytest.approx(-3.0, 1) and + z_pos == pytest.approx(-3.0, 1) + ) + + assert instance['shows'][0]['scale'] == {'x': 0.5, 'y': 0.5, 'z': 0.5} + + assert not scene.lava + assert instance['debug']['surroundingLava'] + assert len(instance['debug']['surroundingLava']) in [8, 24] + + instances, _ = srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + instance = instances[0] + assert scene.lava + assert len(scene.lava) in [8, 24] + assert 'surroundingLava' not in instance['debug'] + + +def test_interactable_object_service_surrounded_by_lava_default_false(): + srv = InteractableObjectCreationService() + scene = prior_scene() + klc = KeywordLocationConfig( + KeywordLocation.ADJACENT_TO_CORNER, + adjacent_distance=VectorFloatConfig(2, 0, 2), + relative_object_label="back_left" + ) + config = InteractableObjectConfig( + keyword_location=klc, + material='AI2-THOR/Materials/Plastics/OrangePlastic', + rotation=VectorFloatConfig(0, 0, 0), + scale=1, + shape='ball', + surrounded_by_lava=False + ) + reconciled = srv.reconcile(scene, config) + instance = srv.create_feature_from_specific_values( + scene=scene, reconciled=reconciled, source_template=config) + assert instance['type'] == 'ball' + assert instance['debug'][DEBUG_FINAL_POSITION_KEY] + assert instance['materials'] == [ + 'AI2-THOR/Materials/Plastics/OrangePlastic'] + assert instance['shows'][0]['position']['y'] == 0.5 + x_pos = instance['shows'][0]['position']['x'] + z_pos = instance['shows'][0]['position']['z'] + assert ( + x_pos == pytest.approx(-2.5, 0.1) and + z_pos == pytest.approx(-2.5, 0.1) + ) + + assert instance['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + assert not scene.lava + assert 'surroundingLava' not in instance['debug'] + + srv.add_to_scene( + scene=scene, source_template=config, bounds=[]) + + assert not scene.lava + + def test_interactable_object_service_create_keyword_location_in(): chest_config = InteractableObjectConfig( material='AI2-THOR/Materials/Plastics/WhitePlastic', @@ -1273,3 +1806,717 @@ def test_target_creation_service(): assert instance['shows'][0]['scale'] == {'x': 3, 'y': 3, 'z': 3} assert ObjectRepository.get_instance().has_label('target') assert ObjectRepository.get_instance().has_label('test_label') + + +def test_tool_creation_reconcile(): + scene = prior_scene() + srv = ToolCreationService() + tmp = ToolConfig(1) + r1: ToolConfig = srv.reconcile(scene, tmp) + assert r1.num == 1 + assert -5 < r1.position.x < 5 + assert r1.position.y == 0 + assert -5 < r1.position.z < 5 + assert 0 <= r1.rotation_y < 360 + assert r1.shape in base_objects.ALL_LARGE_BLOCK_TOOLS + + tmp2 = ToolConfig( + num=[2, 3], + position=VectorFloatConfig([3, 2], MinMaxFloat(0, 2), 3), + rotation_y=[90, 180], + shape=['tool_rect_1_00_x_9_00', 'tool_hooked_0_50_x_4_00'] + ) + srv = ToolCreationService() + r2: ToolConfig = srv.reconcile(scene, tmp2) + + assert r2.num in [2, 3] + assert r2.position.x in [2, 3] + assert 0 <= r2.position.y <= 2 + assert r2.position.z == 3 + assert r2.rotation_y in [90, 180] + assert r2.shape in ["tool_rect_1_00_x_9_00", "tool_hooked_0_50_x_4_00"] + + +def test_tool_creation_reconcile_by_size(): + scene = prior_scene() + template = ToolConfig(num=1, width=0.75, length=6) + srv = ToolCreationService() + r1: ToolConfig = srv.reconcile(scene, template) + + assert r1.num == 1 + assert r1.shape in ['tool_rect_0_75_x_6_00', 'tool_hooked_0_75_x_6_00'] + + template2 = ToolConfig(num=1, length=6) + srv = ToolCreationService() + r2: ToolConfig = srv.reconcile(scene, template2) + + assert r2.num == 1 + assert r2.shape in [ + 'tool_rect_0_50_x_6_00', + 'tool_rect_0_75_x_6_00', + 'tool_rect_1_00_x_6_00', + 'tool_hooked_0_50_x_6_00', + 'tool_hooked_0_75_x_6_00', + 'tool_hooked_1_00_x_6_00'] + + +def test_tool_creation_reconcile_by_size_error(): + scene = prior_scene() + template = ToolConfig(num=1, width=0.76, length=6) + srv = ToolCreationService() + with pytest.raises(ILEException): + srv.reconcile(scene, template) + + +def test_tool_create(): + temp = ToolConfig( + position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, + shape='tool_rect_0_75_x_4_00', material=materials.TOOL_MATERIALS[0]) + tool = ToolCreationService().create_feature_from_specific_values( + prior_scene(), temp, None) + + assert tool + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['materials'] == [materials.TOOL_MATERIALS[0].material] + show = tool['shows'][0] + pos = show['position'] + rot = show['rotation'] + scale = show['scale'] + assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} + assert rot == {'x': 0, 'y': 34, 'z': 0} + assert scale == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_hooked(): + temp = ToolConfig( + num=1, width=0.76, length=6, + position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, + shape='tool_hooked_0_75_x_4_00') + temp.material = materials.TOOL_MATERIALS[1] + tool = ToolCreationService().create_feature_from_specific_values( + prior_scene(), temp, None) + + assert tool + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_hooked_0_75_x_4_00' + show = tool['shows'][0] + pos = show['position'] + rot = show['rotation'] + scale = show['scale'] + assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} + assert rot == {'x': 0, 'y': 34, 'z': 0} + assert scale == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_short(): + temp = ToolConfig( + position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, + shape='tool_rect_0_75_x_1_00', material=materials.TOOL_MATERIALS[0]) + tool = ToolCreationService().create_feature_from_specific_values( + prior_scene(), temp, None) + + assert tool + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_1_00' + show = tool['shows'][0] + pos = show['position'] + rot = show['rotation'] + scale = show['scale'] + assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} + assert rot == {'x': 0, 'y': 34, 'z': 0} + assert scale == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_rect_tool(): + config = ToolConfig( + align_distance=2, + shape='tool_rect_0_75_x_4_00', + # Should override configured position and rotation. + position=VectorFloatConfig(1.1, 1.2, 1.3), + rotation_y=34 + ) + service = ToolCreationService() + scene = prior_scene() + + instance_1 = tools.create_tool('tool_rect_0_75_x_6_00', 0, 0, 0) + assert instance_1['shows'][0]['rotation']['y'] == 0 + assert instance_1['debug'].get('originalRotation', {'y': 0})['y'] == 0 + location_1 = instance_1['shows'][0] + scene.objects.append(instance_1) + idl_1 = InstanceDefinitionLocationTuple(instance_1, None, location_1) + ObjectRepository.get_instance().add_to_labeled_objects(idl_1, 'tool_1') + + config.align_with = 'tool_1' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == 0 + assert reconciled.position.z == -7 + assert reconciled.rotation_y == 0 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': 0, 'y': 0.15, 'z': -7} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_2 = tools.create_tool('tool_rect_0_75_x_6_00', 0, 0, 45) + location_2 = instance_2['shows'][0] + scene.objects.append(instance_2) + idl_2 = InstanceDefinitionLocationTuple(instance_2, None, location_2) + ObjectRepository.get_instance().add_to_labeled_objects(idl_2, 'tool_2') + + config.align_with = 'tool_2' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -4.9497 + assert reconciled.position.z == -4.9497 + assert reconciled.rotation_y == 45 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == { + 'x': -4.9497, + 'y': 0.15, + 'z': -4.9497 + } + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 45, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_3 = tools.create_tool('tool_rect_0_75_x_6_00', 0, 0, 90) + location_3 = instance_3['shows'][0] + scene.objects.append(instance_3) + idl_3 = InstanceDefinitionLocationTuple(instance_3, None, location_3) + ObjectRepository.get_instance().add_to_labeled_objects(idl_3, 'tool_3') + + config.align_with = 'tool_3' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -7 + assert reconciled.position.z == 0 + assert reconciled.rotation_y == 90 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': -7, 'y': 0.15, 'z': 0} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 90, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_hooked_tool(): + config = ToolConfig( + align_distance=2, + shape='tool_rect_0_75_x_4_00', + # Should override configured position and rotation. + position=VectorFloatConfig(1.1, 1.2, 1.3), + rotation_y=34 + ) + service = ToolCreationService() + scene = prior_scene() + + instance_1 = tools.create_tool('tool_hooked_0_75_x_6_00', 0, 0, 0) + assert instance_1['shows'][0]['rotation']['y'] == 0 + assert instance_1['debug'].get('originalRotation', {'y': 0})['y'] == 0 + location_1 = instance_1['shows'][0] + scene.objects.append(instance_1) + idl_1 = InstanceDefinitionLocationTuple(instance_1, None, location_1) + ObjectRepository.get_instance().add_to_labeled_objects(idl_1, 'tool_1') + + config.align_with = 'tool_1' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == 0.75 + assert reconciled.position.z == -7 + assert reconciled.rotation_y == 0 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': 0.75, 'y': 0.15, 'z': -7} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_2 = tools.create_tool('tool_hooked_0_75_x_6_00', 0, 0, 45) + location_2 = instance_2['shows'][0] + scene.objects.append(instance_2) + idl_2 = InstanceDefinitionLocationTuple(instance_2, None, location_2) + ObjectRepository.get_instance().add_to_labeled_objects(idl_2, 'tool_2') + + config.align_with = 'tool_2' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -4.4194 + assert reconciled.position.z == -5.4801 + assert reconciled.rotation_y == 45 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == { + 'x': -4.4194, + 'y': 0.15, + 'z': -5.4801 + } + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 45, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_3 = tools.create_tool('tool_hooked_0_75_x_6_00', 0, 0, 90) + location_3 = instance_3['shows'][0] + scene.objects.append(instance_3) + idl_3 = InstanceDefinitionLocationTuple(instance_3, None, location_3) + ObjectRepository.get_instance().add_to_labeled_objects(idl_3, 'tool_3') + + config.align_with = 'tool_3' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -7 + assert reconciled.position.z == -0.75 + assert reconciled.rotation_y == 90 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': -7, 'y': 0.15, 'z': -0.75} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 90, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_isosceles_tool(): + config = ToolConfig( + align_distance=2, + shape='tool_rect_0_75_x_4_00', + # Should override configured position and rotation. + position=VectorFloatConfig(1.1, 1.2, 1.3), + rotation_y=34 + ) + service = ToolCreationService() + scene = prior_scene() + + instance_1 = tools.create_tool('tool_isosceles_0_75_x_6_00', 0, 0, 0) + assert instance_1['shows'][0]['rotation']['y'] == 0 + assert instance_1['debug'].get('originalRotation', {'y': 0})['y'] == 0 + location_1 = instance_1['shows'][0] + scene.objects.append(instance_1) + idl_1 = InstanceDefinitionLocationTuple(instance_1, None, location_1) + ObjectRepository.get_instance().add_to_labeled_objects(idl_1, 'tool_1') + + config.align_with = 'tool_1' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == 2.625 + assert reconciled.position.z == -7 + assert reconciled.rotation_y == 0 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': 2.625, 'y': 0.15, 'z': -7} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_2 = tools.create_tool('tool_isosceles_0_75_x_6_00', 0, 0, 45) + location_2 = instance_2['shows'][0] + scene.objects.append(instance_2) + idl_2 = InstanceDefinitionLocationTuple(instance_2, None, location_2) + ObjectRepository.get_instance().add_to_labeled_objects(idl_2, 'tool_2') + + config.align_with = 'tool_2' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -3.0936 + assert reconciled.position.z == -6.8059 + assert reconciled.rotation_y == 45 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == { + 'x': -3.0936, + 'y': 0.15, + 'z': -6.8059 + } + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 45, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + instance_3 = tools.create_tool('tool_isosceles_0_75_x_6_00', 0, 0, 90) + location_3 = instance_3['shows'][0] + scene.objects.append(instance_3) + idl_3 = InstanceDefinitionLocationTuple(instance_3, None, location_3) + ObjectRepository.get_instance().add_to_labeled_objects(idl_3, 'tool_3') + + config.align_with = 'tool_3' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -7 + assert reconciled.position.z == -2.625 + assert reconciled.rotation_y == 90 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': -7, 'y': 0.15, 'z': -2.625} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 90, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_normal_toy(): + config = ToolConfig( + align_distance=2, + shape='tool_rect_0_75_x_4_00', + # Should override configured position and rotation. + position=VectorFloatConfig(1.1, 1.2, 1.3), + rotation_y=34 + ) + service = ToolCreationService() + scene = prior_scene() + material_tuple = materials.ORANGE_PLASTIC + definition = base_objects.create_specific_definition_from_base( + 'ball', + material_tuple.color, + [material_tuple.material], + None, + 1 + ) + + location_1 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 0, 'z': 0} + } + instance_1 = instances.instantiate_object(definition, location_1) + assert instance_1['shows'][0]['rotation']['y'] == 0 + assert instance_1['debug']['originalRotation']['y'] == 0 + scene.objects.append(instance_1) + idl_1 = InstanceDefinitionLocationTuple(instance_1, definition, location_1) + ObjectRepository.get_instance().add_to_labeled_objects(idl_1, 'toy_1') + + config.align_with = 'toy_1' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -4.5 + assert reconciled.position.z == 0 + assert reconciled.rotation_y == 90 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': -4.5, 'y': 0.15, 'z': 0} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 90, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + location_2 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 45, 'z': 0} + } + instance_2 = instances.instantiate_object(definition, location_2) + scene.objects.append(instance_2) + idl_2 = InstanceDefinitionLocationTuple(instance_2, definition, location_2) + ObjectRepository.get_instance().add_to_labeled_objects(idl_2, 'toy_2') + + config.align_with = 'toy_2' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -3.182 + assert reconciled.position.z == 3.182 + assert reconciled.rotation_y == 135 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == { + 'x': -3.182, + 'y': 0.15, + 'z': 3.182 + } + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 135, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + location_3 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 90, 'z': 0} + } + instance_3 = instances.instantiate_object(definition, location_3) + scene.objects.append(instance_3) + idl_3 = InstanceDefinitionLocationTuple(instance_3, definition, location_3) + ObjectRepository.get_instance().add_to_labeled_objects(idl_3, 'toy_3') + + config.align_with = 'toy_3' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == 0 + assert reconciled.position.z == 4.5 + assert reconciled.rotation_y == 180 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': 0, 'y': 0.15, 'z': 4.5} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 180, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_sideways_toy(): + config = ToolConfig( + align_distance=2, + shape='tool_rect_0_75_x_4_00', + # Should override configured position and rotation. + position=VectorFloatConfig(1.1, 1.2, 1.3), + rotation_y=34 + ) + service = ToolCreationService() + scene = prior_scene() + material_tuple = materials.ORANGE_PLASTIC + definition = base_objects.create_specific_definition_from_base( + 'car_1', + material_tuple.color, + [material_tuple.material], + None, + 1 + ) + + location_1 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 0, 'z': 0} + } + instance_1 = instances.instantiate_object(definition, location_1) + assert instance_1['shows'][0]['rotation']['y'] == 90 + assert instance_1['debug']['originalRotation']['y'] == 90 + scene.objects.append(instance_1) + idl_1 = InstanceDefinitionLocationTuple(instance_1, definition, location_1) + ObjectRepository.get_instance().add_to_labeled_objects(idl_1, 'toy_1') + + config.align_with = 'toy_1' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -4.0375 + assert reconciled.position.z == 0 + assert reconciled.rotation_y == 90 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': -4.0375, 'y': 0.15, 'z': 0} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 90, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + location_2 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 45, 'z': 0} + } + instance_2 = instances.instantiate_object(definition, location_2) + scene.objects.append(instance_2) + idl_2 = InstanceDefinitionLocationTuple(instance_2, definition, location_2) + ObjectRepository.get_instance().add_to_labeled_objects(idl_2, 'toy_2') + + config.align_with = 'toy_2' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == -2.8549 + assert reconciled.position.z == 2.8549 + assert reconciled.rotation_y == 135 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == { + 'x': -2.8549, + 'y': 0.15, + 'z': 2.8549 + } + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 135, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + location_3 = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 90, 'z': 0} + } + instance_3 = instances.instantiate_object(definition, location_3) + scene.objects.append(instance_3) + idl_3 = InstanceDefinitionLocationTuple(instance_3, definition, location_3) + ObjectRepository.get_instance().add_to_labeled_objects(idl_3, 'toy_3') + + config.align_with = 'toy_3' + reconciled = service.reconcile(scene, config) + assert reconciled.position.x == 0 + assert reconciled.position.z == 4.0375 + assert reconciled.rotation_y == 180 + + tool = service.create_feature_from_specific_values( + scene=scene, + reconciled=reconciled, + source_template=config + ) + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_rect_0_75_x_4_00' + assert tool['shows'][0]['position'] == {'x': 0, 'y': 0.15, 'z': 4.0375} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 180, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + + +def test_tool_create_aligned_with_error_invalid_distance(): + config = ToolConfig( + align_distance=20, + align_with='toy', + shape='tool_rect_0_75_x_4_00' + ) + service = ToolCreationService() + scene = prior_scene() + material_tuple = materials.ORANGE_PLASTIC + definition = base_objects.create_specific_definition_from_base( + 'ball', + material_tuple.color, + [material_tuple.material], + None, + 1 + ) + location = { + 'position': {'x': 0, 'y': definition.positionY, 'z': 0}, + 'rotation': {'x': 0, 'y': 0, 'z': 0} + } + instance = instances.instantiate_object(definition, location) + scene.objects.append(instance) + idl = InstanceDefinitionLocationTuple(instance, definition, location) + ObjectRepository.get_instance().add_to_labeled_objects(idl, 'toy') + + with pytest.raises(Exception): + service.add_to_scene( + scene=scene, + source_template=config, + bounds=[] + ) + + +def test_tool_create_with_lava_surrounding(): + scene = prior_scene() + # move performer out of the way of tool and lava so there are no errors + scene.performer_start.position.x = -4 + scene.performer_start.position.z = -4 + temp = ToolConfig( + surrounded_by_lava=True, + shape='tool_hooked_0_50_x_5_00', + rotation_y=0, + position=VectorFloatConfig(1, 0, 1) + ) + temp.material = materials.TOOL_MATERIALS[1] + tool = ToolCreationService().create_feature_from_specific_values( + scene, temp, None) + + assert tool + assert tool['id'].startswith('tool_') + assert tool['type'] == 'tool_hooked_0_50_x_5_00' + show = tool['shows'][0] + pos = show['position'] + rot = show['rotation'] + scale = show['scale'] + assert pos == {'x': 1, 'y': 0.15, 'z': 1} + assert rot == {'x': 0, 'y': 0, 'z': 0} + assert scale == {'x': 1, 'y': 1, 'z': 1} + + # Make sure lava isn't officially added until add_to_scene is called + assert not scene.lava + assert tool['debug']['surroundingLava'] + assert len(tool['debug']['surroundingLava']) == 20 + for square in tool['debug']['surroundingLava']: + assert square['x'] in [-1, 0, 1, 2, 3] + assert square['z'] in [-2, -1, 0, 1, 2, 3, 4] + assert not (square['x'] in [0, 1, 2] and + square['z'] in [-1, 0, 1, 2, 3]) + + instances, _ = ToolCreationService().add_to_scene( + scene=scene, source_template=temp, bounds=[]) + + tool = instances[0] + assert scene.lava + assert len(scene.lava) == 20 + # check that 'surroundingLava' array was deleted + # after lava was placed + assert 'surroundingLava' not in tool['debug'] + + for square in scene.lava: + assert square.x in [-1, 0, 1, 2, 3] + assert square.z in [-2, -1, 0, 1, 2, 3, 4] + assert not (square.x in [0, 1, 2] and square.z in [-1, 0, 1, 2, 3]) + + +def test_tool_create_with_lava_surrounding_fail_perf_start(): + scene = prior_scene() + + temp = ToolConfig( + surrounded_by_lava=True, + shape='tool_hooked_0_50_x_5_00', + rotation_y=0, + position=VectorFloatConfig(1, 0, 1) + ) + temp.material = materials.TOOL_MATERIALS[1] + + with pytest.raises(ILEException): + ToolCreationService().create_feature_from_specific_values( + scene, temp, None + ) + + +def test_tool_create_with_lava_surrounding_fail_object_overlap(): + scene = prior_scene_with_target() + + # move performer out of the way of tool and lava so there are no errors + scene.performer_start.position.x = -4 + scene.performer_start.position.z = -4 + + temp = ToolConfig( + surrounded_by_lava=True, + shape='tool_hooked_0_50_x_5_00', + rotation_y=0, + position=VectorFloatConfig(1, 0, 1) + ) + temp.material = materials.TOOL_MATERIALS[1] + + with pytest.raises(ILEException): + ToolCreationService().create_feature_from_specific_values( + scene=scene, reconciled=temp, source_template=None + ) + ToolCreationService().add_to_scene( + scene=scene, source_template=temp, bounds=[]) diff --git a/tests/ile_interactable_objects_component_test.py b/tests/ile_interactable_objects_component_test.py index a233bc4..aa4c456 100644 --- a/tests/ile_interactable_objects_component_test.py +++ b/tests/ile_interactable_objects_component_test.py @@ -1,6 +1,13 @@ import pytest - -from generator import FULL_TYPE_LIST, definitions, geometry, materials +from machine_common_sense.config_manager import Vector2dInt + +from generator import ( + FULL_TYPE_LIST, + base_objects, + definitions, + geometry, + materials +) from ideal_learning_env import ( ILEException, ILESharedConfiguration, @@ -10,7 +17,8 @@ ObjectRepository, RandomInteractableObjectsComponent, RandomKeywordObjectsComponent, - SpecificInteractableObjectsComponent + SpecificInteractableObjectsComponent, + ToolConfig ) from ideal_learning_env.numerics import VectorFloatConfig @@ -85,7 +93,7 @@ def test_specific_objects_on_lava_fail(): } }) scene = prior_scene() - scene.lava = [{'x': 2, 'z': 2}] + scene.lava = [Vector2dInt(x=2, z=2)] with pytest.raises(ILEException): component.update_ile_scene(scene) @@ -101,7 +109,7 @@ def test_specific_objects_on_holes_fail(): } }) scene = prior_scene() - scene.holes = [{'x': 1, 'z': -3}] + scene.holes = [Vector2dInt(x=1, z=-3)] with pytest.raises(ILEException): component.update_ile_scene(scene) @@ -1180,7 +1188,10 @@ def test_random_keyword_objects_types_bin_containers(): assert len(objs) == 6 for obj in objs: assert ( - obj['type'].startswith('bowl_') or obj['type'].startswith('cup_') + obj['type'].startswith('bin_') or + obj['type'].startswith('bowl_') or + obj['type'].startswith('crate_open_topped_') or + obj['type'].startswith('cup_') ) assert obj['receptacle'] assert obj['debug']['random_position'] @@ -1210,7 +1221,9 @@ def test_random_keyword_objects_types_open_topped_containers(): assert len(objs) == 6 for obj in objs: assert ( + obj['type'].startswith('bin_') or obj['type'].startswith('bowl_') or + obj['type'].startswith('crate_open_topped_') or obj['type'].startswith('cup_') or obj['type'].startswith('container_asymmetric_') or obj['type'].startswith('container_symmetric_') @@ -1767,6 +1780,7 @@ def test_specific_objects_delayed_action(): assert len(objects) == 0 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 def test_specific_objects_delayed_action_adjacent(): @@ -1787,6 +1801,7 @@ def test_specific_objects_delayed_action_adjacent(): assert len(objects) == 1 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 scene = component.run_delayed_actions(scene) objects = scene.objects @@ -1794,6 +1809,7 @@ def test_specific_objects_delayed_action_adjacent(): assert len(objects) == 2 assert len(component._delayed_templates) == 0 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 def test_specific_objects_delayed_action_in(): @@ -1816,6 +1832,7 @@ def test_specific_objects_delayed_action_in(): assert len(objects) == 1 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 scene = component.run_delayed_actions(scene) objects = scene.objects @@ -1823,6 +1840,7 @@ def test_specific_objects_delayed_action_in(): assert len(objects) == 2 assert len(component._delayed_templates) == 0 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 assert objects[1]['locationParent'] == objects[0]['id'] @@ -1837,6 +1855,7 @@ def test_specific_objects_delayed_action_identical_to(): assert len(scene.objects) == 0 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 def test_specific_objects_delayed_action_identical_except_color(): @@ -1850,6 +1869,7 @@ def test_specific_objects_delayed_action_identical_except_color(): assert len(scene.objects) == 0 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 def test_specific_objects_delayed_action_position_relative(): @@ -1865,6 +1885,7 @@ def test_specific_objects_delayed_action_position_relative(): assert len(scene.objects) == 0 assert len(component._delayed_templates) == 1 assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 0 def test_specific_objects_separate_container_no_lid(): @@ -2021,6 +2042,7 @@ def test_specific_objects_separate_container_lid_placer_delay(): assert scene.objects[0]['type'] == 'separate_container' assert len(component._delayed_templates) == 0 assert len(component._delayed_separate_lids) == 1 + assert len(component._delayed_tools) == 0 def test_run_actions_at_end_of_scene_generation_separate_container_with_lid_placer_and_moves(): # noqa @@ -2111,3 +2133,104 @@ def test_run_actions_at_end_of_scene_generation_separate_container_with_lid_plac assert lid['shows'][0]['position']['z'] == pytest.approx(end_z, 1e-2) assert placer['shows'][0]['position']['z'] == pytest.approx(end_z, 1e-2) assert placer['shows'][0]['position']['z'] == pytest.approx(end_z, 1e-2) + + +def test_tools_random(): + component = SpecificInteractableObjectsComponent({ + 'tools': [{ + 'num': 1 + }] + }) + + assert isinstance(component.tools, list) + pre = component.tools[0] + assert isinstance(pre, ToolConfig) + assert pre.num == 1 + assert pre.labels is None + assert pre.position is None + assert pre.rotation_y is None + assert pre.shape is None + + scene = component.update_ile_scene(prior_scene()) + assert isinstance(scene.objects, list) + + objs = scene.objects + assert isinstance(objs, list) + obj = objs[0] + assert obj['id'].startswith('tool_') + assert obj['type'] in base_objects.ALL_LARGE_BLOCK_TOOLS + assert not obj.get('kinematic') + assert not obj.get('structure') + assert not obj.get('mass') + show = obj['shows'][0] + scale = show['scale'] + assert scale['x'] == 1 + assert scale['y'] == 1 + assert scale['z'] == 1 + + assert ObjectRepository.get_instance().has_label('tools') + assert not ObjectRepository.get_instance().has_label('test_label') + + +def test_tools_full(): + component = SpecificInteractableObjectsComponent({ + 'tools': [{ + 'num': 1, + 'labels': 'test_label', + 'position': { + 'x': -1.5, + 'y': 0, + 'z': 1 + }, + 'rotation_y': 67, + 'shape': 'tool_rect_1_00_x_4_00' + }] + }) + + assert isinstance(component.tools, list) + pre = component.tools[0] + assert isinstance(pre, ToolConfig) + assert pre.num == 1 + assert pre.position == VectorFloatConfig(-1.5, 0, 1) + assert pre.rotation_y == 67 + assert pre.shape == 'tool_rect_1_00_x_4_00' + + scene = component.update_ile_scene(prior_scene()) + assert isinstance(scene.objects, list) + + objs = scene.objects + assert isinstance(objs, list) + obj = objs[0] + assert obj['id'].startswith('tool_') + assert obj['type'] in base_objects.ALL_LARGE_BLOCK_TOOLS + assert not obj.get('kinematic') + assert not obj.get('structure') + assert not obj.get('mass') + show = obj['shows'][0] + pos = show['position'] + scale = show['scale'] + assert pos['x'] == -1.5 + assert pos['y'] == 0.15 + assert pos['z'] == 1 + assert scale['x'] == 1 + assert scale['y'] == 1 + assert scale['z'] == 1 + assert show['rotation']['y'] == 67 + + assert ObjectRepository.get_instance().has_label('tools') + assert ObjectRepository.get_instance().has_label('test_label') + + +def test_tools_delay_align_with(): + component = SpecificInteractableObjectsComponent({ + 'tools': [{ + 'num': 1, + 'align_with': 'object_does_not_exist' + }] + }) + + scene = component.update_ile_scene(prior_scene()) + assert len(scene.objects) == 0 + assert len(component._delayed_templates) == 0 + assert len(component._delayed_separate_lids) == 0 + assert len(component._delayed_tools) == 1 diff --git a/tests/ile_multiple_components_test.py b/tests/ile_multiple_components_test.py new file mode 100644 index 0000000..958f559 --- /dev/null +++ b/tests/ile_multiple_components_test.py @@ -0,0 +1,101 @@ +import pytest + +from ideal_learning_env import ( + GlobalSettingsComponent, + ObjectRepository, + ShortcutComponent, + SpecificInteractableObjectsComponent, + SpecificStructuralObjectsComponent +) +from ile import generate_ile_scene + + +@pytest.fixture(autouse=True) +def run_around_test(): + # Prepare test + ObjectRepository.get_instance().clear() + + # Run test + yield + + # Cleanup + ObjectRepository.get_instance().clear() + + +def test_start_on_platform_and_lava_partition(): + for _ in range(50): + shortcut_data = { + 'shortcut_lava_room': True, + 'shortcut_start_on_platform': True + } + platform_data = { + 'structural_platforms': { + 'num': 1, + 'position': { + 'x': 0, + 'y': 0, + 'z': -5.5 + }, + 'rotation_y': 0, + 'scale': 1, + 'labels': 'start_structure' + } + } + global_data = { + 'performer_look_at': 'target', + "room_dimensions": { + "x": 14, + "y": 4, + "z": 12 + } + } + soccer_ball_data = { + "specific_interactable_objects": { + "num": 1, + "shape": "soccer_ball", + "labels": "target", + 'position': { + 'x': 2, + 'y': 0, + 'z': 4 + }, + 'scale': 1 + } + } + + shortcut_component = ShortcutComponent(shortcut_data) + platform_component = SpecificStructuralObjectsComponent(platform_data) + global_settings_component = GlobalSettingsComponent(global_data) + interactable_objects_component = SpecificInteractableObjectsComponent( + soccer_ball_data + ) + component_list = [ + global_settings_component, + shortcut_component, + interactable_objects_component, + platform_component + ] + + scene = generate_ile_scene( + component_list=component_list, scene_index=0) + objects = scene.objects + assert len(scene.objects) == 2 + assert objects[0]['type'] == 'soccer_ball' + assert objects[0]['shows'][0]["position"] == { + 'x': 2.0, 'y': 0.11, 'z': 4.0} + assert objects[0]['shows'][0]["scale"] == { + 'x': 1.0, 'y': 1.0, 'z': 1.0} + assert objects[1]['type'] == 'cube' + assert objects[1]['shows'][0]["position"] == { + 'x': 0.0, 'y': 0.5, 'z': -5.5} + assert objects[1]['shows'][0]["scale"] == { + 'x': 1.0, 'y': 1.0, 'z': 1.0} + assert scene.performer_start.rotation.x == 10 + assert scene.performer_start.rotation.y == 10 + assert pytest.approx( + scene.performer_start.position.x, abs=0.175) == 0 + assert scene.performer_start.position.y == 1 + assert pytest.approx( + scene.performer_start.position.z, abs=0.175) == -5.5 + assert pytest.approx(scene.partition_floor.leftHalf, 0.015) == 0.66 + assert pytest.approx(scene.partition_floor.rightHalf, 0.015) == 0.66 diff --git a/tests/ile_object_services_test.py b/tests/ile_object_services_test.py index 2a2ebfc..b9be542 100644 --- a/tests/ile_object_services_test.py +++ b/tests/ile_object_services_test.py @@ -1,5 +1,6 @@ import pytest +from generator import SceneException, geometry from ideal_learning_env import ( ILEDelayException, ILEException, @@ -8,10 +9,13 @@ ObjectRepository ) from ideal_learning_env.object_services import ( + calculate_rotated_position, get_step_after_movement, get_step_after_movement_or_start ) +from .ile_helper import prior_scene + @pytest.fixture(autouse=True) def run_around_test(): @@ -311,3 +315,380 @@ def test_get_step_after_movement_or_start_delay(): with pytest.raises(ILEDelayException): # Error because no objects with this label are in the ObjectRepository get_step_after_movement_or_start(['label_1']) + + +def test_calculate_rotated_position(): + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': 'turntable_5678', + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + mock_turntable = { + 'id': 'turntable_5678', + 'rotates': [{ + 'stepBegin': 1, + 'stepEnd': None, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }], + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene = prior_scene() + scene.objects.append(mock_turntable) + + # Test: clockwise rotation, varying degrees. + for step_end, expected_x, expected_z in [ + (9, 0.70710678, -0.70710678), (18, 0, -1), + (27, -0.70710678, -0.70710678), (36, -1, 0), + (45, -0.70710678, 0.70710678), (54, 0, 1), + (63, 0.70710678, 0.70710678), (72, 1, 0) + ]: + mock_turntable['rotates'][0]['stepEnd'] = step_end + actual = calculate_rotated_position( + scene, + step_end + 10, + mock_container + ) + expected = {'x': expected_x, 'y': 0.5, 'z': expected_z} + if actual != pytest.approx(expected): + print(f'{step_end=} {expected_x=} {expected_z=}') + assert actual == pytest.approx(expected) + + # Adjustments for next test... + mock_turntable['rotates'][0]['vector']['y'] = -5 + + # Test: counterclockwise rotation, varying degrees. + for step_end, expected_x, expected_z in [ + (9, 0.70710678, 0.70710678), (18, 0, 1), + (27, -0.70710678, 0.70710678), (36, -1, 0), + (45, -0.70710678, -0.70710678), (54, 0, -1), + (63, 0.70710678, -0.70710678), (72, 1, 0) + ]: + mock_turntable['rotates'][0]['stepEnd'] = step_end + actual = calculate_rotated_position( + scene, + step_end + 10, + mock_container + ) + expected = {'x': expected_x, 'y': 0.5, 'z': expected_z} + if actual != pytest.approx(expected): + print(f'{step_end=} {expected_x=} {expected_z=}') + assert actual == pytest.approx(expected) + + # Adjustments for next test... + container_position = {'x': 2, 'y': 0.5, 'z': 1} + mock_container['shows'][0]['position'] = container_position + mock_container['shows'][0]['boundingBox'] = geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ) + mock_turntable['shows'][0]['position'] = {'x': 1, 'y': 0, 'z': 1} + mock_turntable['rotates'][0]['vector']['y'] = 5 + + # Test: non-zero turntable position. + for step_end, expected_x, expected_z in [ + (9, 1.70710678, 0.29289322), (18, 1, 0), + (27, 0.29289322, 0.29289322), (36, 0, 1), + (45, 0.29289322, 1.70710678), (54, 1, 2), + (63, 1.70710678, 1.70710678), (72, 2, 1) + ]: + mock_turntable['rotates'][0]['stepEnd'] = step_end + actual = calculate_rotated_position( + scene, + step_end + 10, + mock_container + ) + expected = {'x': expected_x, 'y': 0.5, 'z': expected_z} + if actual != pytest.approx(expected): + print(f'{step_end=} {expected_x=} {expected_z=}') + assert actual == pytest.approx(expected) + + +def test_calculate_rotated_position_return_none(): + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': None, + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + scene = prior_scene() + + # Test: container does not have isRotatedBy property. + actual = calculate_rotated_position( + scene, + 20, + mock_container + ) + assert actual is None + + mock_container['debug']['isRotatedBy'] = 'turntable_5678' + mock_turntable = { + 'id': 'turntable_5678', + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene.objects.append(mock_turntable) + + # Test: turntable does not rotate. + actual = calculate_rotated_position( + scene, + 20, + mock_container + ) + assert actual is None + + mock_turntable['rotates'] = [{ + 'stepBegin': 1, + 'stepEnd': 9, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }] + + # Test: container starts with lid already on placed it (lid_step_begin=0). + actual = calculate_rotated_position( + scene, + 0, + mock_container + ) + assert actual is None + + mock_turntable['rotates'] = [{ + 'stepBegin': 21, + 'stepEnd': 29, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }] + + # Test: container lid is placed before turntable starts rotating. + actual = calculate_rotated_position( + scene, + 1, + mock_container + ) + assert actual is None + + +def test_calculate_rotated_position_error_placed_during_rotation(): + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': 'turntable_5678', + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + mock_turntable = { + 'id': 'turntable_5678', + 'rotates': [{ + 'stepBegin': 2, + 'stepEnd': 100, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }], + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene = prior_scene() + scene.objects.append(mock_turntable) + + # Test: container lid is placed as turntable is rotating. + with pytest.raises(SceneException): + calculate_rotated_position( + scene, + 1, + mock_container + ) + + +def test_calculate_rotated_position_error_placed_step_begin(): + # Assume our function will resolve its placer_steps to to following value: + placer_steps = 6 + + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': 'turntable_5678', + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + mock_turntable = { + 'id': 'turntable_5678', + 'rotates': [{ + 'stepBegin': 1 + placer_steps, + 'stepEnd': 18 + placer_steps, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }], + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene = prior_scene() + scene.objects.append(mock_turntable) + + # Test: container lid is placed on same step turntable starts rotating. + with pytest.raises(SceneException): + calculate_rotated_position( + scene, + 1, + mock_container + ) + + +def test_calculate_rotated_position_error_placed_step_end(): + # Assume our function will resolve its placer_steps to to following value: + placer_steps = 6 + + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': 'turntable_5678', + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + mock_turntable = { + 'id': 'turntable_5678', + 'rotates': [{ + 'stepBegin': 1, + 'stepEnd': 18, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }], + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene = prior_scene() + scene.objects.append(mock_turntable) + + # Test: container lid is placed on same step turntable finishes rotating. + with pytest.raises(SceneException): + calculate_rotated_position( + scene, + 18 - placer_steps, + mock_container + ) + + +def test_calculate_rotated_position_edge_case(): + container_position = {'x': 1, 'y': 0.5, 'z': 0} + container_dimensions = {'x': 1, 'y': 1, 'z': 1} + mock_container = { + 'id': 'container_1234', + 'debug': { + 'dimensions': container_dimensions, + 'isRotatedBy': 'turntable_5678', + 'positionY': 0 + }, + 'shows': [{ + 'boundingBox': geometry.create_bounds( + container_dimensions, + None, + container_position, + {'x': 0, 'y': 0, 'z': 0}, + 0 + ), + 'position': container_position + }] + } + mock_turntable = { + 'id': 'turntable_5678', + 'rotates': [{ + 'stepBegin': 1, + 'stepEnd': 18, + 'vector': {'x': 0, 'y': 5, 'z': 0} + }], + 'shows': [{ + 'position': {'x': 0, 'y': 0, 'z': 0} + }] + } + scene = prior_scene() + scene.objects.append(mock_turntable) + + # Assume our function will resolve its placer_steps to to following value: + placer_steps = 6 + + # Test: container lid is placed on step turntable finishes rotating. + actual = calculate_rotated_position( + scene, + 19 - placer_steps, + mock_container + ) + assert actual == pytest.approx({'x': 0, 'y': 0.5, 'z': -1}) + + # Test: container places its lid the step the turntable starts rotating. + mock_turntable['rotates'][0]['stepEnd'] = placer_steps + actual = calculate_rotated_position( + scene, + 1, + mock_container + ) + assert actual == pytest.approx({'x': 0.866025, 'y': 0.5, 'z': -0.5}) diff --git a/tests/ile_shortcut_component_test.py b/tests/ile_shortcut_component_test.py index 8aefda7..02875e1 100644 --- a/tests/ile_shortcut_component_test.py +++ b/tests/ile_shortcut_component_test.py @@ -3,17 +3,35 @@ from typing import List import pytest -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Vector2dInt, Vector3d from numpy import arange from shapely.geometry import Point, Polygon -from generator import agents, base_objects, instances, materials, structures +from generator import ( + agents, + base_objects, + instances, + materials, + structures, + tools +) from generator.geometry import ( PERFORMER_HALF_WIDTH, PERFORMER_WIDTH, calculate_rotations, + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector, get_normalized_vector_from_two_points ) +from generator.imitation import ( + IMITATION_AGENT_END_X, + IMITATION_AGENT_START_X, + IMITATION_CONTAINER_SEPARATION, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE, + IMITATION_CONTAINER_TELEPORT_X_POS_RANGE, + IMITATION_TASK_TARGET_SEPARATION +) from generator.scene import PartitionFloor, Scene from ideal_learning_env import ( InstanceDefinitionLocationTuple, @@ -23,9 +41,6 @@ ) from ideal_learning_env.defs import ILEException from ideal_learning_env.shortcut_component import ( - IMITATION_AGENT_END_X, - IMITATION_AGENT_START_X, - IMITATION_TASK_TARGET_SEPARATION, LARGE_BLOCK_TOOLS_TO_DIMENSIONS, DoubleDoorConfig, TripleDoorConfig @@ -110,7 +125,7 @@ def test_shortcut_bisecting_platform_on(): assert show['scale'] == {'x': 1, 'y': 1, 'z': sizez} obj = objs[1] - assert obj['id'].startswith('platform') + assert obj['id'].startswith('blocking_wall') assert obj['type'] == 'cube' assert obj['kinematic'] assert obj['structure'] @@ -148,7 +163,7 @@ def test_shortcut_bisecting_platform_on_with_blocking_wall_prop(): assert show['scale'] == {'x': 1, 'y': 1, 'z': sizez} obj = objs[1] - assert obj['id'].startswith('platform') + assert obj['id'].startswith('blocking_wall') assert obj['type'] == 'cube' assert obj['kinematic'] assert obj['structure'] @@ -189,6 +204,23 @@ def test_shortcut_bisecting_platform_on_no_blocking_wall(): assert perf_pos == Vector3d(x=0, y=1, z=-sizez / 2.0 + 0.5) +def test_shortcut_bisecting_platform_position_z(): + component = ShortcutComponent({ + 'shortcut_bisecting_platform': { + 'position_z': -4.25 + } + }) + assert component.shortcut_bisecting_platform + assert component.get_shortcut_bisecting_platform() + scene = component.update_ile_scene(prior_scene()) + assert scene != prior_scene() + objs = scene.objects + assert isinstance(objs, List) + assert len(objs) == 2 + perf_pos = scene.performer_start.position + assert perf_pos == Vector3d(x=0, y=1, z=-4.25) + + def test_shortcut_bisecting_platform_on_with_long_blocking_wall(): component = ShortcutComponent({ 'shortcut_bisecting_platform': { @@ -214,7 +246,7 @@ def test_shortcut_bisecting_platform_on_with_long_blocking_wall(): assert show['scale'] == {'x': 1, 'y': 1, 'z': sizez} obj = objs[1] - assert obj['id'].startswith('platform') + assert obj['id'].startswith('blocking_wall') assert obj['type'] == 'cube' assert obj['kinematic'] assert obj['structure'] @@ -227,6 +259,54 @@ def test_shortcut_bisecting_platform_on_with_long_blocking_wall(): assert perf_pos == Vector3d(x=0, y=1, z=-sizez / 2.0 + 0.5) +def test_shortcut_bisecting_platform_on_with_double_blocking_wall(): + component = ShortcutComponent({ + 'shortcut_bisecting_platform': { + 'has_double_blocking_wall': True + } + }) + assert component.shortcut_bisecting_platform + assert component.get_shortcut_bisecting_platform() + scene = component.update_ile_scene(prior_scene()) + assert scene != prior_scene() + objs = scene.objects + sizez = scene.room_dimensions.z + assert isinstance(objs, List) + assert len(objs) == 3 + obj = objs[0] + assert obj['id'].startswith('platform') + assert obj['type'] == 'cube' + assert obj['kinematic'] + assert obj['structure'] + show = obj['shows'][0] + assert show['position'] == {'x': 0, 'y': 0.5, 'z': 0} + assert show['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert show['scale'] == {'x': 1, 'y': 1, 'z': sizez} + + obj = objs[1] + assert obj['id'].startswith('blocking_wall') + assert obj['type'] == 'cube' + assert obj['kinematic'] + assert obj['structure'] + show = obj['shows'][0] + assert show['position'] == {'x': 0, 'y': 0.625, 'z': -(sizez / 2.0) + 3} + assert show['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert show['scale'] == {'x': 0.99, 'y': 1.25, 'z': 0.25} + + obj = objs[2] + assert obj['id'].startswith('blocking_wall') + assert obj['type'] == 'cube' + assert obj['kinematic'] + assert obj['structure'] + show = obj['shows'][0] + assert show['position'] == {'x': 0, 'y': 0.625, 'z': (sizez / 2.0) - 3} + assert show['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert show['scale'] == {'x': 0.99, 'y': 1.25, 'z': 0.25} + + perf_pos = scene.performer_start.position + assert perf_pos == Vector3d(x=0, y=1, z=-sizez / 2.0 + 0.5) + + def test_shortcut_bisecting_platform_on_and_is_short(): component = ShortcutComponent({ 'shortcut_bisecting_platform': { @@ -324,7 +404,7 @@ def test_shortcut_triple_door_on(): scene = component.update_ile_scene(prior_scene()) assert scene != prior_scene() assert scene.room_dimensions.y == 5 - assert not scene.goal.get('action_list') + assert not scene.goal.action_list assert scene.restrict_open_doors objs = scene.objects sizez = scene.room_dimensions.z @@ -508,7 +588,7 @@ def test_shortcut_triple_door_drop_on_step_1(): sizez = scene.room_dimensions.z assert scene != prior_scene() assert scene.room_dimensions.y == 5 - assert scene.goal['action_list'] == ( + assert scene.goal.action_list == ( [['Pass']] * (scene.room_dimensions.y * 4) ) assert scene.restrict_open_doors @@ -603,7 +683,7 @@ def test_shortcut_triple_door_config(): sizez = scene.room_dimensions.z assert scene != prior_scene() assert scene.room_dimensions.y == 5 - assert not scene.goal.get('action_list') + assert not scene.goal.action_list assert not scene.restrict_open_doors objs = scene.objects assert isinstance(objs, List) @@ -729,7 +809,7 @@ def test_shortcut_triple_door_with_extension(): scene = component.update_ile_scene(prior_scene()) assert scene != prior_scene() assert scene.room_dimensions.y == 5 - assert scene.goal['action_list'] == ( + assert scene.goal.action_list == ( [['Pass']] * (scene.room_dimensions.y * 4 + 29) ) assert scene.restrict_open_doors @@ -806,7 +886,7 @@ def test_shortcut_triple_door_with_bigger_far_end(): scene = component.update_ile_scene(prior_scene()) assert scene != prior_scene() assert scene.room_dimensions.y == 5 - assert scene.goal['action_list'] == ( + assert scene.goal.action_list == ( [['Pass']] * (scene.room_dimensions.y * 4 + 29) ) assert scene.restrict_open_doors @@ -848,6 +928,48 @@ def test_shortcut_triple_door_with_bigger_far_end(): assert show['scale'] == {'x': 1, 'y': 2, 'z': scene.room_dimensions.z} +def test_shortcut_triple_door_colors(): + configurations = [ + (None, None, "None"), + ('AI2-THOR/Materials/Metals/Brass 1', None, "Door"), + (None, 'AI2-THOR/Materials/Metals/Brass 1', "Wall"), + ('AI2-THOR/Materials/Metals/Brass 1', + 'AI2-THOR/Materials/Metals/Brass 1', "Both") + ] + + for door_material, wall_material, label in configurations: + shortcut_triple_door_choice = { + 'start_drop_step': {'min': 1, 'max': 10} + } + if isinstance(door_material, str): + shortcut_triple_door_choice['door_material'] = door_material + if isinstance(wall_material, str): + shortcut_triple_door_choice['wall_material'] = wall_material + + for _ in range(10): + component = ShortcutComponent( + {'shortcut_triple_door_choice': shortcut_triple_door_choice}) + assert component.shortcut_triple_door_choice + scene = component.update_ile_scene(prior_scene()) + + door_colors = [ + obj['debug']['color'] for obj in scene.objects if + obj["id"].startswith("door")] + wall_colors = [ + obj['debug']['color'] for obj in scene.objects if + obj["id"].startswith("wall")] + door_colors_set = set( + x for sublist in door_colors for x in sublist) + wall_colors_set = set( + x for sublist in wall_colors for x in sublist) + + intersection = door_colors_set.intersection(wall_colors_set) + if label == "Both": + assert len(intersection) == 2 + else: + assert len(intersection) == 0 + + def test_shortcut_double_door(): component = ShortcutComponent({ 'shortcut_double_door_choice': True @@ -881,57 +1003,7 @@ def test_shortcut_double_door(): objs = scene.objects - assert scene.lava - assert scene.lava == [{'x': 0, - 'z': -12}, - {'x': 0, - 'z': -11}, - {'x': 0, - 'z': -10}, - {'x': 0, - 'z': -9}, - {'x': 0, - 'z': -8}, - {'x': 0, - 'z': -7}, - {'x': 0, - 'z': -6}, - {'x': 0, - 'z': -5}, - {'x': 0, - 'z': -4}, - {'x': 0, - 'z': -3}, - {'x': 0, - 'z': -2}, - {'x': 0, - 'z': -1}, - {'x': 0, - 'z': 0}, - {'x': 0, - 'z': 1}, - {'x': 0, - 'z': 2}, - {'x': 0, - 'z': 3}, - {'x': 0, - 'z': 4}, - {'x': 0, - 'z': 5}, - {'x': 0, - 'z': 6}, - {'x': 0, - 'z': 7}, - {'x': 0, - 'z': 8}, - {'x': 0, - 'z': 9}, - {'x': 0, - 'z': 10}, - {'x': 0, - 'z': 11}, - {'x': 0, - 'z': 12}] + assert scene.lava == [Vector2dInt(x=0, z=z) for z in range(-12, 13)] sizez = scene.room_dimensions.z assert isinstance(objs, List) @@ -1378,57 +1450,7 @@ def test_shortcut_double_door_other_configs(): objs = scene.objects - assert scene.lava - assert scene.lava == [{'x': 0, - 'z': -12}, - {'x': 0, - 'z': -11}, - {'x': 0, - 'z': -10}, - {'x': 0, - 'z': -9}, - {'x': 0, - 'z': -8}, - {'x': 0, - 'z': -7}, - {'x': 0, - 'z': -6}, - {'x': 0, - 'z': -5}, - {'x': 0, - 'z': -4}, - {'x': 0, - 'z': -3}, - {'x': 0, - 'z': -2}, - {'x': 0, - 'z': -1}, - {'x': 0, - 'z': 0}, - {'x': 0, - 'z': 1}, - {'x': 0, - 'z': 2}, - {'x': 0, - 'z': 3}, - {'x': 0, - 'z': 4}, - {'x': 0, - 'z': 5}, - {'x': 0, - 'z': 6}, - {'x': 0, - 'z': 7}, - {'x': 0, - 'z': 8}, - {'x': 0, - 'z': 9}, - {'x': 0, - 'z': 10}, - {'x': 0, - 'z': 11}, - {'x': 0, - 'z': 12}] + assert scene.lava == [Vector2dInt(x=0, z=z) for z in range(-12, 13)] sizez = scene.room_dimensions.z assert isinstance(objs, List) @@ -1889,6 +1911,49 @@ def test_shortcut_double_door_occluder_distance_from_performer(): assert perf_pos == Vector3d(x=0, y=2, z=-sizez / 2.0 + 0.5) +def test_shortcut_double_door_colors(): + configurations = [ + (None, None, "None"), + ('AI2-THOR/Materials/Metals/Brass 1', None, "Door"), + (None, 'AI2-THOR/Materials/Metals/Brass 1', "Wall"), + ('AI2-THOR/Materials/Metals/Brass 1', + 'AI2-THOR/Materials/Metals/Brass 1', "Both") + ] + + for door_material, wall_material, label in configurations: + shortcut_double_door_choice = { + 'start_drop_step': {'min': 1, 'max': 10}, + 'add_lava': False + } + if isinstance(door_material, str): + shortcut_double_door_choice['door_material'] = door_material + if isinstance(wall_material, str): + shortcut_double_door_choice['wall_material'] = wall_material + + for _ in range(10): + component = ShortcutComponent( + {'shortcut_double_door_choice': shortcut_double_door_choice}) + assert component.shortcut_double_door_choice + scene = component.update_ile_scene(prior_scene()) + + door_colors = [ + obj['debug']['color'] for obj in scene.objects if + obj["id"].startswith("door")] + wall_colors = [ + obj['debug']['color'] for obj in scene.objects if + obj["id"].startswith("wall")] + door_colors_set = set( + x for sublist in door_colors for x in sublist) + wall_colors_set = set( + x for sublist in wall_colors for x in sublist) + + intersection = door_colors_set.intersection(wall_colors_set) + if label == "Both": + assert len(intersection) == 2 + else: + assert len(intersection) == 0 + + def test_shortcut_start_on_platform_off(): component = ShortcutComponent({ 'shortcut_start_on_platform': False @@ -2195,8 +2260,8 @@ def test_shortcut_lava_island_min_size(): for x in range(-2, 3): for z in range(1, 6): if x != 0 and z != 4: - assert {'x': x, 'z': z} in lavas - assert {'x': z, 'z': x} in lavas_switched + assert Vector2dInt(x=x, z=z) in lavas + assert Vector2dInt(x=z, z=x) in lavas_switched objs = scene.objects assert len(objs) == 2 @@ -2238,8 +2303,8 @@ def test_shortcut_lava_island_min_size_plus_one(): for x in range(-2, 3): for z in range(1, 6): if x != 0 and z != 4: - assert {'x': x, 'z': z} in lavas - assert {'x': z, 'z': x} in lavas_switched + assert Vector2dInt(x=x, z=z) in lavas + assert Vector2dInt(x=z, z=x) in lavas_switched objs = scene.objects assert len(objs) == 2 @@ -2316,8 +2381,8 @@ def test_shortcut_lava_island_guide_rail(): for x in range(-2, 3): for z in range(1, 6): if x != 0 and z != 4: - assert {'x': x, 'z': z} in lavas - assert {'x': z, 'z': x} in lavas_switched + assert Vector2dInt(x=x, z=z) in lavas + assert Vector2dInt(x=z, z=x) in lavas_switched objs = scene.objects assert len(objs) == 4 @@ -2530,8 +2595,8 @@ def test_shortcut_lava_island_random_target_position(): for x in range(-2, 3): for z in range(1, 6): if x != 0 and z != 4: - assert {'x': x, 'z': z} in lavas - assert {'x': z, 'z': x} in lavas_switched + assert Vector2dInt(x=x, z=z) in lavas + assert Vector2dInt(x=z, z=x) in lavas_switched objs = scene.objects assert len(objs) == 2 @@ -2571,16 +2636,16 @@ def test_shortcut_lava_island_left_right(): scene = component.update_ile_scene(scene) lavas = scene.lava - lava_island_x_min = lavas[0]['x'] + 2 - lava_island_x_max = lavas[-1]['x'] - 3 + lava_island_x_min = lavas[0].x + 2 + lava_island_x_max = lavas[-1].x - 3 left_count = 0 right_count = 0 - min_z = lavas[0]['z'] - max_z = lavas[-1]['z'] + min_z = lavas[0].z + max_z = lavas[-1].z for lava in lavas: - if lava['x'] < lava_island_x_min: + if lava.x < lava_island_x_min: left_count += 1 - if lava['x'] > lava_island_x_max: + if lava.x > lava_island_x_max: right_count += 1 front_rear_width = abs(max_z - min_z) + 1 left_count /= front_rear_width @@ -2706,16 +2771,16 @@ def test_shortcut_lava_island_front_rear(): scene = component.update_ile_scene(scene) lavas = scene.lava - lava_island_z_min = lavas[0]['z'] + 3 - lava_island_z_max = lavas[-1]['z'] - 2 + lava_island_z_min = lavas[0].z + 3 + lava_island_z_max = lavas[-1].z - 2 front_count = 0 rear_count = 0 - min_x = lavas[0]['x'] - max_x = lavas[-1]['x'] + min_x = lavas[0].x + max_x = lavas[-1].x for lava in lavas: - if lava['z'] < lava_island_z_min: + if lava.z < lava_island_z_min: front_count += 1 - if lava['z'] > lava_island_z_max: + if lava.z > lava_island_z_max: rear_count += 1 left_right_width = abs(max_x - min_x) + 1 front_count /= left_right_width @@ -2743,29 +2808,30 @@ def test_shortcut_lava_island_front_rear_left_right(): scene = component.update_ile_scene(scene) lavas = scene.lava - lava_island_front_min = lavas[0]['z'] + 3 - lava_island_rear_max = lavas[-1]['z'] - 5 - lava_island_left_min = lavas[0]['x'] + 2 - lava_island_right_max = lavas[-1]['x'] - 4 + lava_island_front_min = lavas[0].z + 3 + lava_island_rear_max = lavas[-1].z - 5 + lava_island_left_min = lavas[0].x + 2 + lava_island_right_max = lavas[-1].x - 4 front_count = 0 rear_count = 0 left_count = 0 right_count = 0 - min_left = lavas[0]['x'] - max_right = lavas[-1]['x'] - min_front = lavas[0]['z'] - max_rear = lavas[-1]['z'] + min_left = lavas[0].x + max_right = lavas[-1].x + min_front = lavas[0].z + max_rear = lavas[-1].z for lava in lavas: - if lava['z'] < lava_island_front_min: + if lava.z < lava_island_front_min: front_count += 1 - if lava['z'] > lava_island_rear_max: + if lava.z > lava_island_rear_max: rear_count += 1 - if lava['x'] < lava_island_left_min: + if lava.x < lava_island_left_min: left_count += 1 - if lava['x'] > lava_island_right_max: + if lava.x > lava_island_right_max: right_count += 1 assert front_count / (abs(max_right - min_left) + 1) == 3 - assert rear_count / (abs(max_right - min_left) + 1) == 5 + assert rear_count / (abs(max_right - min_left) + + 1) == 5 assert left_count / (abs(max_rear - min_front) + 1) == 2 assert right_count / (abs(max_rear - min_front) + 1) == 4 @@ -2791,31 +2857,32 @@ def test_shortcut_lava_island_front_rear_left_right_long_x_dimension(): lavas = scene.lava # everything needs to rotate 90 degrees # so x is z - lava_island_front_min = lavas[0]['x'] + 3 - lava_island_rear_max = lavas[-1]['x'] - 5 - lava_island_left_min = lavas[0]['z'] + 2 - lava_island_right_max = lavas[-1]['z'] - 4 + lava_island_front_min = lavas[0].x + 3 + lava_island_rear_max = lavas[-1].x - 5 + lava_island_left_min = lavas[0].z + 2 + lava_island_right_max = lavas[-1].z - 4 front_count = 0 rear_count = 0 left_count = 0 right_count = 0 - min_left = lavas[0]['z'] - max_right = lavas[-1]['z'] - min_front = lavas[0]['x'] - max_rear = lavas[-1]['x'] + min_left = lavas[0].z + max_right = lavas[-1].z + min_front = lavas[0].x + max_rear = lavas[-1].x for lava in lavas: - if lava['x'] < lava_island_front_min: + if lava.x < lava_island_front_min: front_count += 1 - if lava['x'] > lava_island_rear_max: + if lava.x > lava_island_rear_max: rear_count += 1 - if lava['z'] < lava_island_left_min: + if lava.z < lava_island_left_min: left_count += 1 - if lava['z'] > lava_island_right_max: + if lava.z > lava_island_right_max: right_count += 1 # not sure why this is missing 1, generating this scene works as expected right_count += 1 assert front_count / (abs(max_right - min_left) + 1) == 3 - assert rear_count / (abs(max_right - min_left) + 1) == 5 + assert rear_count / (abs(max_right - min_left) + + 1) == 5 assert left_count / (abs(max_rear - min_front) + 1) == 2 assert right_count / (abs(max_rear - min_front) + 1) == 4 @@ -2923,7 +2990,7 @@ def test_shortcut_lava_island_hooked_tool(): for x in range(-9, 10): for z in range(7, 10): if x != 0 and z != 8: - assert {'x': x, 'z': z} in lavas + assert Vector2dInt(x=x, z=z) in lavas objs = scene.objects assert len(objs) == 2 @@ -2966,7 +3033,7 @@ def test_shortcut_lava_island_hooked_tool_even_room_dim(): for x in range(-10, 11): for z in range(7, 11): if x != 0 and z != 8: - assert {'x': x, 'z': z} in lavas + assert Vector2dInt(x=x, z=z) in lavas objs = scene.objects assert len(objs) == 2 @@ -3488,7 +3555,7 @@ def setup_turntables_with_agent_and_non_agent(agent_point_step=46): step_end=0, movement_rotation=0, material_tuple=materials.GREY - )[0] + ) scene.objects.append(turntable_left) object_repository.add_to_labeled_objects(InstanceDefinitionLocationTuple( turntable_left, @@ -3507,7 +3574,7 @@ def setup_turntables_with_agent_and_non_agent(agent_point_step=46): step_end=0, movement_rotation=0, material_tuple=materials.GREY - )[0] + ) scene.objects.append(turntable_right) object_repository.add_to_labeled_objects(InstanceDefinitionLocationTuple( turntable_right, @@ -3555,7 +3622,7 @@ def test_turntables_with_agent_and_non_agent(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 1, 'stepEnd': 36, @@ -3598,7 +3665,7 @@ def test_turntables_with_agent_and_non_agent_angled(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 1, 'stepEnd': 28, @@ -3637,7 +3704,7 @@ def test_turntables_with_agent_and_non_agent_with_random_directions(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 1, 'stepEnd': 36, @@ -3684,7 +3751,7 @@ def test_turntables_with_agent_and_non_agent_with_label_lists(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 1, 'stepEnd': 36, @@ -3735,7 +3802,7 @@ def test_turntables_with_agent_and_non_agent_with_mirrored_rotation(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 1, 'stepEnd': 36, @@ -3783,7 +3850,7 @@ def test_turntables_with_agent_and_non_agent_with_point_step_begin_before_46(): assert ball == original_ball assert cube == original_cube assert turntable_left != original_turntable_left - assert turntable_right != original_turntable_right + assert turntable_right == original_turntable_right assert turntable_left['rotates'] == [{ 'stepBegin': 51, 'stepEnd': 86, @@ -3841,15 +3908,15 @@ def test_shortcut_tool_choice_default(): assert len(lavas) % 2 == 0 for square in lavas: - assert(square['x'] > 1 or square['x'] < -1) - assert(square['x'] < 9 or square['x'] > -9) - assert(square['z'] > 0) - - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 36 - for action in scene.goal['action_list']: + assert (square.x > 1 or square.x < -1) + assert (square.x < 9 or square.x > -9) + assert (square.z > 0) + + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.action_list + assert len(scene.goal.action_list) == 36 + for action in scene.goal.action_list: assert action == ['RotateRight'] objs = scene.objects @@ -3860,13 +3927,13 @@ def test_shortcut_tool_choice_default(): assert len(soccer_balls) == 2 ball_1 = soccer_balls[0] ball_2 = soccer_balls[1] - tools = list( + tool_list = list( filter( lambda obj: obj['type'].startswith('tool_rect'), objs)) - assert len(tools) == 2 - tool_1 = tools[0] - tool_2 = tools[1] if len(tools) == 2 else None + assert len(tool_list) == 2 + tool_1 = tool_list[0] + tool_2 = tool_list[1] if len(tool_list) == 2 else None # check platform properties and all object types assert platform['type'] == 'cube' @@ -3900,15 +3967,15 @@ def test_shortcut_tool_choice_too_short(): assert len(lavas) % 2 == 0 for square in lavas: - assert(square['x'] > 1 or square['x'] < -1) - assert(square['x'] < 9 or square['x'] > -9) - assert(square['z'] > 0) - - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 36 - for action in scene.goal['action_list']: + assert (square.x > 1 or square.x < -1) + assert (square.x < 9 or square.x > -9) + assert (square.z > 0) + + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.action_list + assert len(scene.goal.action_list) == 36 + for action in scene.goal.action_list: assert action == ['RotateRight'] objs = scene.objects @@ -3919,13 +3986,13 @@ def test_shortcut_tool_choice_too_short(): assert len(soccer_balls) == 2 ball_1 = soccer_balls[0] ball_2 = soccer_balls[1] - tools = list( + tool_list = list( filter( lambda obj: obj['type'].startswith('tool_rect'), objs)) - assert len(tools) == 2 - tool_1 = tools[0] - tool_2 = tools[1] + assert len(tool_list) == 2 + tool_1 = tool_list[0] + tool_2 = tool_list[1] # check platform properties and all object types assert platform['type'] == 'cube' @@ -3970,15 +4037,15 @@ def test_shortcut_tool_choice_no_tool(): assert len(lavas) % 2 == 0 for square in lavas: - assert(square['x'] > 1 or square['x'] < -1) - assert(square['x'] < 9 or square['x'] > -9) - assert(square['z'] > 0) - - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 36 - for action in scene.goal['action_list']: + assert (square.x > 1 or square.x < -1) + assert (square.x < 9 or square.x > -9) + assert (square.z > 0) + + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.action_list + assert len(scene.goal.action_list) == 36 + for action in scene.goal.action_list: assert action == ['RotateRight'] objs = scene.objects @@ -4015,14 +4082,13 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): 'shortcut_imitation_task': { 'trigger_order': 'left_right', 'containers_on_right_side': False, - 'kidnap_option': 'containers_rotate' + 'kidnap_option': 'containers' } }) assert component.shortcut_imitation_task assert component.shortcut_imitation_task.containers_on_right_side is False - assert component.shortcut_imitation_task.kidnap_option == \ - 'containers_rotate' + assert component.shortcut_imitation_task.kidnap_option == 'containers' assert component.shortcut_imitation_task.trigger_order == 'left_right' scene = component.update_ile_scene(prior_scene()) @@ -4030,20 +4096,20 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 2 + assert len(scene.goal.triggered_by_target_sequence) == 2 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[0]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] # right container - assert scene.goal['triggeredByTargetSequence'][1] == scene.objects[2]['id'] + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[2]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -4068,6 +4134,19 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): colors_used = [] containers = scene.objects[0:3] end_container = containers[0]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_CONTAINER_SEPARATION, 0) + start_x += x + start_z += z + teleport_positions.append((start_x, start_z)) for i in range(3): colors_used.append(containers[i]['debug']['color'][0]) assert containers[i]['shows'][0]['position']['z'] == (i - 1) @@ -4078,13 +4157,10 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): assert containers[i]['shows'][0]['scale']['z'] == 1 teleport = containers[i]['shows'][1] - assert teleport['rotation']['y'] == 180 - if i < 1: - # make sure next chest is in a straight line - assert teleport['position']['x'] < \ - containers[i + 1]['shows'][1]['position']['x'] - assert teleport['position']['z'] == \ - containers[i + 1]['shows'][1]['position']['z'] + assert teleport['position']['x'] == teleport_positions[i][0] + assert teleport['position']['z'] == teleport_positions[i][1] + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step # no duplicate colors assert len(colors_used) == len(set(colors_used)) @@ -4100,12 +4176,21 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): assert containers[2]['openClose'][1]['open'] is False # left container is in view after rotation, it tells us if the other # containers are in view because they are in a straight line - assert -2.5 <= scene.objects[0]['shows'][1]['position']['x'] <= 0.5 + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] agent_stand_behind_buffer = 1 assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ scene.room_dimensions.z / 2 - agent_stand_behind_buffer # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_TASK_TARGET_SEPARATION, 0) + x = first_container_teleport_position['x'] - x + z = first_container_teleport_position['z'] - z target = scene.objects[3] assert target['triggeredBy'] assert target['type'] == 'soccer_ball' @@ -4113,10 +4198,8 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): end_container[0]['position']['x'] assert target['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z assert target['shows'][1]['stepBegin'] == kidnapp_step assert target['kinematic'] assert len(target['moves']) == 1 @@ -4134,10 +4217,8 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): end_container[0]['position']['x'] assert placer['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z assert placer['shows'][1]['stepBegin'] == kidnapp_step assert len(placer['moves']) == 2 assert placer['moves'][0]['stepBegin'] == 0 @@ -4158,19 +4239,20 @@ def test_shortcut_imitation_left_side_teleport_containers_rotation_left_right(): assert start['position']['z'] == -1 # left chest assert start['rotation']['y'] == -90 # face right teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step - teleport['rotation']['y'] == 180 # face performer - end_container_pos = containers[0]['shows'][1]['position']['z'] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer # behind containers separation = 1 - teleport['position']['z'] == end_container_pos + separation - # right or the left since the containers were rotated 90 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + containers[0]['shows'][1]['rotation']['y']) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] assert ( - teleport['position']['x'] == - containers[0]['shows'][1]['position']['x'] - separation or - containers[-1]['shows'][1]['position']['x'] + separation) - teleport['position']['x'] == 0 - teleport['position']['z'] == 0 + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) assert len(agent['rotates']) == 1 assert agent['rotates'][0]['stepBegin'] == 83 # left rotation because agent is walking torward perfromer @@ -4208,14 +4290,14 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() 'shortcut_imitation_task': { 'trigger_order': 'left_right', 'containers_on_right_side': True, - 'kidnap_option': 'containers_rotate' + 'kidnap_option': 'containers' } }) assert component.shortcut_imitation_task assert component.shortcut_imitation_task.containers_on_right_side is True assert component.shortcut_imitation_task.kidnap_option == \ - 'containers_rotate' + 'containers' assert component.shortcut_imitation_task.trigger_order == 'left_right' scene = component.update_ile_scene(prior_scene()) @@ -4223,20 +4305,20 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 2 + assert len(scene.goal.triggered_by_target_sequence) == 2 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[0]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] # right container - assert scene.goal['triggeredByTargetSequence'][1] == scene.objects[2]['id'] + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[2]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -4252,6 +4334,7 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() assert scene.debug['endHabituationTeleportPositionX'] == 0 assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step # objects assert len(scene.objects) == 6 @@ -4260,6 +4343,19 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() colors_used = [] containers = scene.objects[0:3] end_container = containers[-1]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_CONTAINER_SEPARATION, 0) + start_x -= x + start_z -= z + teleport_positions.append((start_x, start_z)) for i in range(3): colors_used.append(containers[i]['debug']['color'][0]) assert containers[i]['shows'][0]['position']['z'] == -(i - 1) @@ -4270,13 +4366,14 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() assert containers[i]['shows'][0]['scale']['z'] == 1 teleport = containers[i]['shows'][1] - assert teleport['rotation']['y'] == 180 - if i < 1: - # make sure next chest is in a straight line - assert teleport['position']['x'] < \ - containers[i + 1]['shows'][1]['position']['x'] - assert teleport['position']['z'] == \ - containers[i + 1]['shows'][1]['position']['z'] + assert round( + teleport['position']['x'], 3) == round( + teleport_positions[i][0], 3) + assert round( + teleport['position']['z'], 3) == round( + teleport_positions[i][1], 3) + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step # no duplicate colors assert len(colors_used) == len(set(colors_used)) @@ -4292,12 +4389,21 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() assert containers[2]['openClose'][1]['open'] is False # left container is in view after rotation, it tells us if the other # containers are in view because they are in a straight line - assert -2.5 <= scene.objects[0]['shows'][1]['position']['x'] <= 0.5 + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] agent_stand_behind_buffer = 1 assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ scene.room_dimensions.z / 2 - agent_stand_behind_buffer # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_TASK_TARGET_SEPARATION, 0) + x = end_container[1]['position']['x'] - x + z = end_container[1]['position']['z'] - z target = scene.objects[3] assert target['triggeredBy'] assert target['type'] == 'soccer_ball' @@ -4305,10 +4411,8 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() end_container[0]['position']['x'] assert target['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] + IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z assert target['shows'][1]['stepBegin'] == kidnapp_step assert target['kinematic'] assert len(target['moves']) == 1 @@ -4326,10 +4430,8 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() end_container[0]['position']['x'] assert placer['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] + IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z assert placer['shows'][1]['stepBegin'] == kidnapp_step assert len(placer['moves']) == 2 assert placer['moves'][0]['stepBegin'] == 0 @@ -4350,19 +4452,20 @@ def test_shortcut_imitation_right_side_teleport_containers_rotation_left_right() assert start['position']['z'] == 1 # left chest assert start['rotation']['y'] == 90 # face right teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step - teleport['rotation']['y'] == 180 # face performer - end_container_pos = containers[-1]['shows'][1]['position']['z'] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer # behind containers separation = 1 - teleport['position']['z'] == end_container_pos + separation - # of to the right or the left since the containers were rotate 90 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] assert ( - teleport['position']['x'] == - containers[0]['shows'][1]['position']['x'] - separation or - containers[-1]['shows'][1]['position']['x'] + separation) - teleport['position']['x'] == 0 - teleport['position']['z'] == 0 + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) assert len(agent['rotates']) == 1 assert agent['rotates'][0]['stepBegin'] == 82 # left rotation because agent is walking torward perfromer @@ -4415,20 +4518,20 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 2 + assert len(scene.goal.triggered_by_target_sequence) == 2 # right container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[2]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] # middle container - assert scene.goal['triggeredByTargetSequence'][1] == scene.objects[1]['id'] + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[1]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -4453,6 +4556,19 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): colors_used = [] containers = scene.objects[0:3] end_container = containers[0]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_CONTAINER_SEPARATION, 0) + start_x += x + start_z += z + teleport_positions.append((start_x, start_z)) for i in range(3): colors_used.append(containers[i]['debug']['color'][0]) assert containers[i]['shows'][0]['position']['z'] == (i - 1) @@ -4463,39 +4579,40 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): assert containers[i]['shows'][0]['scale']['z'] == 1 teleport = containers[i]['shows'][1] - assert teleport['rotation']['y'] == 90 - if i < 1: - # make sure next chest is in a straight line on z axis - assert teleport['position']['z'] < \ - containers[i + 1]['shows'][1]['position']['z'] - assert teleport['position']['x'] == \ - containers[i + 1]['shows'][1]['position']['x'] + assert teleport['position']['x'] == teleport_positions[i][0] + assert teleport['position']['z'] == teleport_positions[i][1] + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step # no duplicate colors assert len(colors_used) == len(set(colors_used)) # open close - assert containers[0].get('openClose') is None # left container assert containers[2]['openClose'][0]['step'] == 22 assert containers[2]['openClose'][0]['open'] is True assert containers[2]['openClose'][1]['step'] == kidnapp_step assert containers[2]['openClose'][1]['open'] is False + assert containers[0].get('openClose') is None # left container assert containers[1]['openClose'][0]['step'] == 71 assert containers[1]['openClose'][0]['open'] is True assert containers[1]['openClose'][1]['step'] == kidnapp_step assert containers[1]['openClose'][1]['open'] is False - - # in view after teleport - buffer_for_all_containers_to_fit = 2 - buffer_for_agent_to_stand_behind = 1 - minimum = 0 - maximum = scene.room_dimensions.z / 2 - \ - buffer_for_all_containers_to_fit - buffer_for_agent_to_stand_behind - assert -2.5 <= scene.objects[0]['shows'][1]['position']['x'] <= 2.5 - assert -2.5 <= scene.objects[1]['shows'][1]['position']['x'] <= 2.5 - assert -2.5 <= scene.objects[2]['shows'][1]['position']['x'] <= 2.5 - assert minimum <= containers[0]['shows'][1]['position']['z'] <= maximum + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_TASK_TARGET_SEPARATION, 0) + x = first_container_teleport_position['x'] - x + z = first_container_teleport_position['z'] - z target = scene.objects[3] assert target['triggeredBy'] assert target['type'] == 'soccer_ball' @@ -4503,10 +4620,8 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): end_container[0]['position']['x'] assert target['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - assert target['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z assert target['shows'][1]['stepBegin'] == kidnapp_step assert target['kinematic'] assert len(target['moves']) == 1 @@ -4524,10 +4639,8 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): end_container[0]['position']['x'] assert placer['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - assert placer['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z assert placer['shows'][1]['stepBegin'] == kidnapp_step assert len(placer['moves']) == 2 assert placer['moves'][0]['stepBegin'] == 0 @@ -4548,22 +4661,23 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): assert start['position']['z'] == 1 # right chest assert start['rotation']['y'] == -90 # face left teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step - teleport['rotation']['y'] == 180 # face performer - end_container_pos = containers[0]['shows'][1]['position']['z'] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer # behind containers separation = 1 - teleport['position']['z'] == end_container_pos + separation - # of to the right or the left since the containers were rotated 90 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] assert ( - teleport['position']['x'] == - containers[0]['shows'][1]['position']['x'] - separation or - containers[-1]['shows'][1]['position']['x'] + separation) - teleport['position']['x'] == 0 - teleport['position']['z'] == 0 + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) assert len(agent['rotates']) == 1 assert agent['rotates'][0]['stepBegin'] == 57 - # right rotation because agent is walking torward perfromer + # left rotation because agent is walking torward perfromer assert agent['rotates'][0]['vector']['y'] == 9 # opening containers sequence animations = agent['actions'] @@ -4588,8 +4702,8 @@ def test_shortcut_imitation_left_side_teleport_containers_right_middle(): assert (movement['sequence'][0]['endPoint']['x'] == movement['sequence'][1]['endPoint']['x'] == movement['sequence'][2]['endPoint']['x'] == -IMITATION_AGENT_END_X) - assert movement['sequence'][0]['endPoint']['z'] == 1 # right - assert movement['sequence'][1]['endPoint']['z'] == 0 # middle + assert movement['sequence'][0]['endPoint']['z'] == 1.0 # right + assert movement['sequence'][1]['endPoint']['z'] == 0.0 # middle assert movement['sequence'][2]['endPoint']['z'] == -0.15 # face performer @@ -4613,21 +4727,20 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 2 + assert len(scene.goal.triggered_by_target_sequence) == 2 # right container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[2]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] # middle container - assert scene.goal['triggeredByTargetSequence'][1] == scene.objects[1]['id'] + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[1]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') - + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) @@ -4651,6 +4764,19 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): colors_used = [] containers = scene.objects[0:3] end_container = containers[-1]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_CONTAINER_SEPARATION, 0) + start_x -= x + start_z -= z + teleport_positions.append((start_x, start_z)) for i in range(3): colors_used.append(containers[i]['debug']['color'][0]) assert containers[i]['shows'][0]['position']['z'] == -(i - 1) @@ -4661,38 +4787,44 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): assert containers[i]['shows'][0]['scale']['z'] == 1 teleport = containers[i]['shows'][1] - assert teleport['rotation']['y'] == -90 - if i < 1: - # make sure next chest is in a straight line on z axis - assert teleport['position']['z'] > \ - containers[i + 1]['shows'][1]['position']['z'] - assert teleport['position']['x'] == \ - containers[i + 1]['shows'][1]['position']['x'] + assert round( + teleport['position']['x'], 3) == round( + teleport_positions[i][0], 3) + assert round( + teleport['position']['z'], 3) == round( + teleport_positions[i][1], 3) + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step # no duplicate colors assert len(colors_used) == len(set(colors_used)) # open close - assert containers[0].get('openClose') is None # left container assert containers[2]['openClose'][0]['step'] == 22 assert containers[2]['openClose'][0]['open'] is True assert containers[2]['openClose'][1]['step'] == kidnapp_step assert containers[2]['openClose'][1]['open'] is False + assert containers[0].get('openClose') is None # left container assert containers[1]['openClose'][0]['step'] == 71 assert containers[1]['openClose'][0]['open'] is True assert containers[1]['openClose'][1]['step'] == kidnapp_step assert containers[1]['openClose'][1]['open'] is False - - # in view after teleport - buffer_for_agent_to_stand_behind = 1 - minimum = 2 - maximum = scene.room_dimensions.z / 2 - \ - buffer_for_agent_to_stand_behind - assert -2.5 <= scene.objects[0]['shows'][1]['position']['x'] <= 2.5 - assert -2.5 <= scene.objects[1]['shows'][1]['position']['x'] <= 2.5 - assert -2.5 <= scene.objects[2]['shows'][1]['position']['x'] <= 2.5 - assert minimum <= containers[0]['shows'][1]['position']['z'] <= maximum + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_TASK_TARGET_SEPARATION, 0) + x = end_container[1]['position']['x'] - x + z = end_container[1]['position']['z'] - z target = scene.objects[3] assert target['triggeredBy'] assert target['type'] == 'soccer_ball' @@ -4700,10 +4832,8 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): end_container[0]['position']['x'] assert target['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert target['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - assert target['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z assert target['shows'][1]['stepBegin'] == kidnapp_step assert target['kinematic'] assert len(target['moves']) == 1 @@ -4721,10 +4851,8 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): end_container[0]['position']['x'] assert placer['shows'][0]['position']['z'] == \ end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION - assert placer['shows'][1]['position']['x'] == \ - end_container[1]['position']['x'] - assert placer['shows'][1]['position']['z'] == \ - end_container[1]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z assert placer['shows'][1]['stepBegin'] == kidnapp_step assert len(placer['moves']) == 2 assert placer['moves'][0]['stepBegin'] == 0 @@ -4742,25 +4870,26 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): # positions start = agent['shows'][0] assert start['position']['x'] == -IMITATION_AGENT_START_X - assert start['position']['z'] == -1 # right chest + assert start['position']['z'] == -1 # left chest assert start['rotation']['y'] == 90 # face right teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step - teleport['rotation']['y'] == 180 # face performer - end_container_pos = containers[0]['shows'][1]['position']['z'] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer # behind containers separation = 1 - teleport['position']['z'] == end_container_pos + separation - # of to the right or the left since the containers were rotate 90 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] assert ( - teleport['position']['x'] == - containers[0]['shows'][1]['position']['x'] - separation or - containers[-1]['shows'][1]['position']['x'] + separation) - teleport['position']['x'] == 0 - teleport['position']['z'] == 0 + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) assert len(agent['rotates']) == 1 assert agent['rotates'][0]['stepBegin'] == 57 - # right rotation because agent is walking torward perfromer + # left rotation because agent is walking torward perfromer assert agent['rotates'][0]['vector']['y'] == 9 # opening containers sequence animations = agent['actions'] @@ -4785,8 +4914,8 @@ def test_shortcut_imitation_right_side_teleport_containers_right_middle(): assert (movement['sequence'][0]['endPoint']['x'] == movement['sequence'][1]['endPoint']['x'] == movement['sequence'][2]['endPoint']['x'] == IMITATION_AGENT_END_X) - assert movement['sequence'][0]['endPoint']['z'] == -1 # right - assert movement['sequence'][1]['endPoint']['z'] == 0 # middle + assert movement['sequence'][0]['endPoint']['z'] == -1.0 # right + assert movement['sequence'][1]['endPoint']['z'] == 0.0 # middle assert movement['sequence'][2]['endPoint']['z'] == -0.15 # face performer @@ -4810,18 +4939,18 @@ def test_shortcut_imitation_left_side_teleport_performer_left(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 1 + assert len(scene.goal.triggered_by_target_sequence) == 1 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[0]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -4940,7 +5069,7 @@ def test_shortcut_imitation_left_side_teleport_performer_left(): assert start['rotation']['y'] == -90 # face right # move agent after kidnapp teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step + assert teleport['stepBegin'] == kidnapp_step agent_z_choices = (-2, 2) agent_x_range = (-2, 2) assert teleport['rotation']['y'] == \ @@ -4989,18 +5118,18 @@ def test_shortcut_imitation_right_side_teleport_performer_right(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 1 + assert len(scene.goal.triggered_by_target_sequence) == 1 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[2]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -5119,7 +5248,7 @@ def test_shortcut_imitation_right_side_teleport_performer_right(): assert start['rotation']['y'] == 90 # face right # move agent after kidnapp teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step + assert teleport['stepBegin'] == kidnapp_step agent_z_choices = (-2, 2) agent_x_range = (-2, 2) assert teleport['rotation']['y'] == \ @@ -5154,18 +5283,18 @@ def test_shortcut_imitation_left_side_middle(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 1 + assert len(scene.goal.triggered_by_target_sequence) == 1 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[1]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[1]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -5262,7 +5391,7 @@ def test_shortcut_imitation_left_side_middle(): assert start['rotation']['y'] == -90 # face left # move agent after kidnapp teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step + assert teleport['stepBegin'] == kidnapp_step agent_x_range = (-2, 2) assert teleport['rotation']['y'] == 180 assert teleport['position']['z'] == 2 @@ -5288,7 +5417,7 @@ def test_shortcut_imitation_left_side_middle(): assert movement['sequence'][1]['endPoint']['z'] == -0.15 # face performer -def test_shortcut_imitation_right_side_teleport_containers_middle_left(): +def test_shortcut_imitation_right_side_middle_left(): component = ShortcutComponent({ 'shortcut_imitation_task': { 'trigger_order': 'middle_left', @@ -5308,20 +5437,18 @@ def test_shortcut_imitation_right_side_teleport_containers_middle_left(): kidnapp_step = scene.debug['endHabituationStep'] # goal - assert scene.goal['category'] == 'imitation' - assert scene.goal['description'] == ( + assert scene.goal.category == 'imitation' + assert scene.goal.description == ( 'Open the containers in the correct order for ' 'the tiny light black white rubber ball to be placed.') - assert len(scene.goal['triggeredByTargetSequence']) == 2 + assert len(scene.goal.triggered_by_target_sequence) == 2 # left container - assert scene.goal['triggeredByTargetSequence'][0] == scene.objects[1]['id'] - # right container - assert scene.goal['triggeredByTargetSequence'][1] == scene.objects[0]['id'] + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[1]['id'] # action list - assert len(scene.goal['action_list']) == kidnapp_step + assert len(scene.goal.action_list) == kidnapp_step for i in range(kidnapp_step): - scene.goal['action_list'][i][0] == 'Pass' - assert scene.goal['action_list'][-1][0].startswith('EndHabituation') + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') # rectangular room with always 3 ceiling height assert (scene.room_dimensions.x == scene.room_dimensions.z / @@ -5420,8 +5547,8 @@ def test_shortcut_imitation_right_side_teleport_containers_middle_left(): assert start['position']['z'] == 0 # middle chest assert start['rotation']['y'] == 90 # face right teleport = agent['shows'][1] - teleport['stepBegin'] == kidnapp_step - teleport['rotation']['y'] == 180 # face performer + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer # behind containers # of to the right or the left since the containers were rotate 90 assert -2 <= teleport['position']['x'] <= 2 @@ -5458,6 +5585,173 @@ def test_shortcut_imitation_right_side_teleport_containers_middle_left(): assert movement['sequence'][2]['endPoint']['z'] == 0.85 # face performer +def test_shortcut_imitation_container_rotations(): + right_side_check = (True, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE) + left_side_check = (False, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE) + for (on_right_side, global_rotations, relative_rotations) in \ + [right_side_check, left_side_check]: + rotation_amount_with_rotation_types = ( + [(rot, 'global') for rot in global_rotations] + + [(rot, 'relative') for rot in relative_rotations]) + for rot, rotation_type in rotation_amount_with_rotation_types: + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'containers_on_right_side': on_right_side, + 'kidnap_option': 'containers', + f'{rotation_type}_container_rotation': rot + } + }) + assert ( + component.shortcut_imitation_task.containers_on_right_side == + on_right_side) + assert ( + component.shortcut_imitation_task.kidnap_option == + 'containers') + scene = component.update_ile_scene(prior_scene()) + containers = scene.objects[0:3] + teleport_rotation = (rot if rotation_type == 'global' else + ((270 if on_right_side else 90) + rot) % 360) + assert all( + c['shows'][1]['rotation']['y'] == teleport_rotation + for c in containers) + + +def test_shortcut_imitation_container_rotations_global_override(): + right_side_check = (True, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE) + left_side_check = (False, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE) + for (on_right_side, global_rotations, relative_rotations) in \ + [right_side_check, left_side_check]: + for global_rotation in global_rotations: + for relative_rotation in relative_rotations: + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'containers_on_right_side': on_right_side, + 'kidnap_option': 'containers', + 'global_container_rotation': global_rotation, + 'relative_container_rotation': relative_rotation + } + }) + assert ( + component.shortcut_imitation_task.containers_on_right_side == # noqa + on_right_side) + assert ( + component.shortcut_imitation_task.kidnap_option == + 'containers') + scene = component.update_ile_scene(prior_scene()) + containers = scene.objects[0:3] + # Expecting global rotation to override relative rotation + assert all( + c['shows'][1]['rotation']['y'] == global_rotation + for c in containers) + + +def test_shortcut_imitation_container_rotations_multiple_choices(): + right_side_check = (True, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE) + left_side_check = (False, IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE) + for (on_right_side, global_rotations, relative_rotations) in \ + [right_side_check, left_side_check]: + rotation_amounts_with_rotation_types = \ + [('global', global_rotations), ('relative', relative_rotations)] + for rotation_type, rotations in rotation_amounts_with_rotation_types: + # make sure all rotations are randomly ran + for i in range(25): + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'containers_on_right_side': on_right_side, + 'kidnap_option': 'containers', + f'{rotation_type}_container_rotation': rotations + } + }) + assert ( + component.shortcut_imitation_task.containers_on_right_side == # noqa + on_right_side) + assert ( + component.shortcut_imitation_task.kidnap_option == + 'containers') + scene = component.update_ile_scene(prior_scene()) + containers = scene.objects[0:3] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + global_rot = teleport_rotation + right_side_rot = (teleport_rotation - 270) % 360 + left_side_rot = (teleport_rotation - 90) % 360 + assert ( + global_rot in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL if + rotation_type == "global" else + right_side_rot in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE if + on_right_side else left_side_rot in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE) + assert all( + c['shows'][1]['rotation']['y'] == teleport_rotation + for c in containers) + + +def test_shortcut_imitation_container_rotations_only_on_container_kidnap(): + for on_right_side in [True, False]: + for kidnap_option in ['agent_only', 'performer']: + for rotation_type in ['global', 'relative']: + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'containers_on_right_side': on_right_side, + 'kidnap_option': kidnap_option, + f'{rotation_type}_container_rotation': 45 + } + }) + assert ( + component.shortcut_imitation_task.kidnap_option == + kidnap_option + ) + scene = component.update_ile_scene(prior_scene()) + containers = scene.objects[0:3] + same_start_rotation = -90 if on_right_side else 90 + assert all(len(c['shows']) == 1 for c in containers) + assert all( + c['shows'][0]['rotation']['y'] == same_start_rotation + for c in containers) + + +def test_shortcut_imitation_container_rotations_errors(): + global_rotations = [ + i for i in range(361) if i not in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL] + right_side_relative_rotations = [ + i for i in range(361) if i not in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_RIGHT_SIDE] + left_side_relative_rotations = [ + i for i in range(361) if i not in + IMITATION_CONTAINER_TELEPORT_ROTATIONS_LEFT_SIDE] + rotation_type_lists = [ + ('global', global_rotations), + ('relative_right', right_side_relative_rotations), + ('relative_left', left_side_relative_rotations)] + + for rotation_type, rotation_list in rotation_type_lists: + side_conditions = ( + [True, False] if rotation_type == 'global' else + [True] if rotation_type.endswith("right") else [False] + ) + component_rotation_type = \ + "global" if rotation_type == "global" else "relative" + for on_right_side in side_conditions: + for rot in rotation_list: + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'containers_on_right_side': on_right_side, + 'kidnap_option': 'containers', + f'{component_rotation_type}_container_rotation': rot + } + }) + with pytest.raises(ILEException): + component.update_ile_scene(prior_scene()) + + def test_shortcut_imitation_true(): component = ShortcutComponent({ 'shortcut_imitation_task': True @@ -5475,35 +5769,47 @@ def test_shortcut_imitation_true(): assert scene.objects[5]['type'].startswith('agent') -def test_shortcut_imitation_trigger_order_failures(): - options = ['error', 'left_middle_right', 'left_right_middle', - 'middle_right_left', 'middle_left_right', 'right_middle_left', - 'right_left_middle'] - for option in options: +def test_shortcut_imitation_failures(): + trigger_order_options = [ + 'error', 'left_middle_right', 'left_right_middle', 'middle_right_left', + 'middle_left_right', 'right_middle_left', 'right_left_middle'] + for option in trigger_order_options: with pytest.raises(ILEException): ShortcutComponent({ 'shortcut_imitation_task': { 'trigger_order': option, } }) - - -def test_shortcut_imitation_trigger_order_options(): - options = ['left', 'middle', 'right', 'left_middle', 'left_right', - 'middle_left', 'middle_right', 'right_middle', 'right_left'] with pytest.raises(ILEException): ShortcutComponent({ 'shortcut_imitation_task': { - 'trigger_order': 'error', + 'kidnap_option': 'error' } }) - for option in options: + + +def test_shortcut_imitation_options(): + trigger_order_options = [ + 'left', 'middle', 'right', 'left_middle', 'left_right', + 'middle_left', 'middle_right', 'right_middle', 'right_left'] + for option in trigger_order_options: component = ShortcutComponent({ 'shortcut_imitation_task': { 'trigger_order': option, } }) assert component.shortcut_imitation_task + assert component.shortcut_imitation_task.trigger_order == option + + kidnap_options = ['agent_only', 'containers', 'performer'] + for option in kidnap_options: + component = ShortcutComponent({ + 'shortcut_imitation_task': { + 'kidnap_option': option, + } + }) + assert component.shortcut_imitation_task + assert component.shortcut_imitation_task.kidnap_option == option def test_shortcut_lava_target_tool_offset_rectangular(): @@ -5798,23 +6104,23 @@ def test_shortcut_lava_target_tool_broken(): 'tool_type': 'broken' } }) - minimum = structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN - maximum = structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + minimum = tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN + maximum = tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX assert component.shortcut_lava_target_tool assert component.shortcut_lava_target_tool.tool_type == 'broken' assert component.get_shortcut_lava_target_tool() scene = prior_scene_custom_size(15, 20) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['z'] == 3 assert last_tool_pos['z'] == round( first_tool_pos['z'] - - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) - for tool in tools: + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_x = tool['shows'][0]['position']['x'] @@ -5824,15 +6130,15 @@ def test_shortcut_lava_target_tool_broken(): scene = prior_scene_custom_size(20, 15) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['x'] == 3 assert last_tool_pos['x'] == round( first_tool_pos['x'] - - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) - for tool in tools: + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_z = tool['shows'][0]['position']['z'] @@ -5858,20 +6164,20 @@ def test_shortcut_lava_target_tool_broken_rotated(): scene = prior_scene_custom_size(15, 20) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['x'] == 2 assert last_tool_pos['x'] == round( first_tool_pos['x'] - - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) center_of_broken_tools = 1 positive_x_maximum = center_of_broken_tools + \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX negative_x_maximum = center_of_broken_tools - \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX - for tool in tools: + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_z = tool['shows'][0]['position']['z'] @@ -5880,20 +6186,20 @@ def test_shortcut_lava_target_tool_broken_rotated(): scene = prior_scene_custom_size(20, 15) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['z'] == -2 assert last_tool_pos['z'] == round( first_tool_pos['z'] + - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) center_of_broken_tools = 1 positive_x_maximum = center_of_broken_tools + \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX negative_x_maximum = center_of_broken_tools - \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX - for tool in tools: + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_x = tool['shows'][0]['position']['x'] @@ -6298,28 +6604,28 @@ def test_shortcut_lava_target_tool_offset_broken(): } }) minimum_positive = \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN + horizontal + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN + horizontal maximum_positive = \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + horizontal + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX + horizontal minimum_negative = \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN - horizontal + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MIN - horizontal maximum_negative = \ - structures.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX - horizontal + tools.BROKEN_TOOL_HORIZONTAL_SEPARATION_MAX - horizontal assert component.shortcut_lava_target_tool assert component.shortcut_lava_target_tool.tool_type == 'broken' assert component.get_shortcut_lava_target_tool() scene = prior_scene_custom_size(15, 20) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['z'] == 3 - backward assert last_tool_pos['z'] == round( first_tool_pos['z'] - - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) - for tool in tools: + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_x = tool['shows'][0]['position']['x'] @@ -6331,15 +6637,15 @@ def test_shortcut_lava_target_tool_offset_broken(): scene = prior_scene_custom_size(20, 15) scene = component.update_ile_scene(scene) objs = scene.objects - tools = objs[1:] - assert len(tools) == 5 - first_tool_pos = tools[0]['shows'][0]['position'] - last_tool_pos = tools[-1]['shows'][0]['position'] + tool_list = objs[1:] + assert len(tool_list) == 5 + first_tool_pos = tool_list[0]['shows'][0]['position'] + last_tool_pos = tool_list[-1]['shows'][0]['position'] assert first_tool_pos['x'] == 3 - backward assert last_tool_pos['x'] == round( first_tool_pos['x'] - - structures.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tools) - 1), 2) - for tool in tools: + tools.BROKEN_TOOL_VERTICAL_SEPARATION * (len(tool_list) - 1), 2) + for tool in tool_list: assert tool['type'].startswith('tool_rect') assert tool['debug']['length'] == 1 pos_z = tool['shows'][0]['position']['z'] @@ -6370,9 +6676,9 @@ def test_shortcut_lava_target_tool_distance_between_performer_broken(): performer_start = Point( scene.performer_start.position.x, scene.performer_start.position.z) - tools = scene.objects[1:] + tool_list = scene.objects[1:] valid = False - for tool in tools: + for tool in tool_list: bb_boxes = tool['shows'][0]['boundingBox'].box_xz top_right = Point(bb_boxes[0].x, bb_boxes[0].z) bottom_right = Point(bb_boxes[1].x, bb_boxes[1].z) @@ -6439,14 +6745,14 @@ def test_shortcut_seeing_leads_to_knowing_default(): # target and goal info assert scene.goal - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['category'] == 'passive' - assert scene.goal['answer']['choice'] == 'plausible' - assert scene.goal['last_step'] == 105 - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 105 - for action in scene.goal['action_list']: + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.category == 'passive' + assert scene.goal.answer['choice'] == 'plausible' + assert scene.goal.last_step == 105 + assert scene.goal.action_list + assert len(scene.goal.action_list) == 105 + for action in scene.goal.action_list: assert action == ['Pass'] assert target['debug']['dimensions'] == { @@ -6504,14 +6810,14 @@ def test_shortcut_seeing_leads_to_knowing_target_behind_agent(): # target and goal info assert scene.goal - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['category'] == 'passive' - assert scene.goal['answer']['choice'] == 'plausible' - assert scene.goal['last_step'] == 105 - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 105 - for action in scene.goal['action_list']: + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.category == 'passive' + assert scene.goal.answer['choice'] == 'plausible' + assert scene.goal.last_step == 105 + assert scene.goal.action_list + assert len(scene.goal.action_list) == 105 + for action in scene.goal.action_list: assert action == ['Pass'] assert target['debug']['dimensions'] == { @@ -6537,7 +6843,7 @@ def test_shortcut_seeing_leads_to_knowing_target_behind_agent(): agent_start_on_left = agent['shows'][0]['position']['x'] == -2 # ensure target is behind agent's path - if(agent_start_on_left): + if (agent_start_on_left): assert target_pos['x'] == -1 else: assert target_pos['x'] == 1 @@ -6576,14 +6882,14 @@ def test_shortcut_seeing_leads_to_knowing_target_ahead_agent(): # target and goal info assert scene.goal - assert scene.goal['metadata']['target'] - assert 'targets' not in scene.goal['metadata'] - assert scene.goal['category'] == 'passive' - assert scene.goal['answer']['choice'] == 'plausible' - assert scene.goal['last_step'] == 105 - assert scene.goal['action_list'] - assert len(scene.goal['action_list']) == 105 - for action in scene.goal['action_list']: + assert scene.goal.metadata['target'] + assert 'targets' not in scene.goal.metadata + assert scene.goal.category == 'passive' + assert scene.goal.answer['choice'] == 'plausible' + assert scene.goal.last_step == 105 + assert scene.goal.action_list + assert len(scene.goal.action_list) == 105 + for action in scene.goal.action_list: assert action == ['Pass'] assert target['debug']['dimensions'] == { @@ -6610,7 +6916,7 @@ def test_shortcut_seeing_leads_to_knowing_target_ahead_agent(): agent_start_on_left = agent['shows'][0]['position']['x'] == -2 # ensure target is ahead of agent's path - if(agent_start_on_left): + if (agent_start_on_left): assert target_pos['x'] == 1 else: assert target_pos['x'] == -1 diff --git a/tests/ile_structural_object_service_test.py b/tests/ile_structural_object_service_test.py index c73a1fc..ecd66db 100644 --- a/tests/ile_structural_object_service_test.py +++ b/tests/ile_structural_object_service_test.py @@ -3,13 +3,12 @@ from generator import materials from generator.base_objects import ( - ALL_LARGE_BLOCK_TOOLS, create_soccer_ball, create_specific_definition_from_base ) from generator.geometry import MAX_TRIES, ORIGIN_LOCATION from generator.instances import instantiate_object -from ideal_learning_env.defs import ILEDelayException, ILEException +from ideal_learning_env.defs import ILEDelayException from ideal_learning_env.numerics import ( MinMaxFloat, MinMaxInt, @@ -58,12 +57,12 @@ StructuralRampCreationService, StructuralThrowerConfig, StructuralThrowerCreationService, - StructuralToolsCreationService, + StructuralTubeOccluderConfig, + StructuralTubeOccluderCreationService, StructuralTurntableConfig, StructuralTurntableCreationService, StructuralWallConfig, StructuralWallCreationService, - ToolConfig, WallSide, is_wall_too_close ) @@ -605,6 +604,14 @@ def test_door_creation_reconcile(): assert r2.wall_material == ( "AI2-THOR/Materials/Metals/BrushedAluminum_Blue") + for i in range(50): + tmp3 = StructuralDoorConfig(num=[1, 2, 3]) + srv = StructuralDoorsCreationService() + r3: StructuralDoorConfig = srv.reconcile(scene, tmp3) + door_color = materials.find_colors(r3.material) + wall_color = materials.find_colors(r3.wall_material) + assert not any(item in door_color for item in wall_color) + def test_dropper_creation_reconcile(): scene = prior_scene() @@ -1239,68 +1246,6 @@ def test_thrower_creation_reconcile(): assert r2.projectile_scale == 1 -def test_tool_creation_reconcile(): - scene = prior_scene() - srv = StructuralToolsCreationService() - tmp = ToolConfig(1) - r1: ToolConfig = srv.reconcile(scene, tmp) - assert r1.num == 1 - assert -5 < r1.position.x < 5 - assert r1.position.y == 0 - assert -5 < r1.position.z < 5 - assert 0 <= r1.rotation_y < 360 - assert r1.shape in ALL_LARGE_BLOCK_TOOLS - assert r1.guide_rails is False - - tmp2 = ToolConfig( - num=[2, 3], - position=VectorFloatConfig([3, 2], MinMaxFloat(0, 2), 3), - rotation_y=[90, 180], - shape=['imaginary_tool_1', 'imaginary_tool_2'], guide_rails=True) - srv = StructuralToolsCreationService() - r2: ToolConfig = srv.reconcile(scene, tmp2) - - assert r2.num in [2, 3] - assert r2.position.x in [2, 3] - assert 0 <= r2.position.y <= 2 - assert r2.position.z == 3 - assert r2.rotation_y in [90, 180] - assert r2.shape in ["imaginary_tool_1", - "imaginary_tool_2"] - assert r2.guide_rails is True - - -def test_tool_creation_reconcile_by_size(): - scene = prior_scene() - template = ToolConfig(num=1, width=0.75, length=6) - srv = StructuralToolsCreationService() - r1: ToolConfig = srv.reconcile(scene, template) - - assert r1.num == 1 - assert r1.shape == 'tool_rect_0_75_x_6_00' - - template2 = ToolConfig(num=1, length=6) - srv = StructuralToolsCreationService() - r2: ToolConfig = srv.reconcile(scene, template2) - - assert r2.num == 1 - assert r2.shape in [ - 'tool_rect_0_50_x_6_00', - 'tool_rect_0_75_x_6_00', - 'tool_rect_1_00_x_6_00', - 'tool_hooked_0_50_x_6_00', - 'tool_hooked_0_75_x_6_00', - 'tool_hooked_1_00_x_6_00'] - - -def test_tool_creation_reconcile_by_size_error(): - scene = prior_scene() - template = ToolConfig(num=1, width=0.76, length=6) - srv = StructuralToolsCreationService() - with pytest.raises(ILEException): - srv.reconcile(scene, template) - - def test_wall_creation_reconcile(): scene = prior_scene() rd = scene.room_dimensions @@ -1428,6 +1373,39 @@ def test_dropper_create(): show['scale'] == {'x': 1, 'y': 1.1, 'z': 1.2} +def test_dropper_create_with_projectile_dimensions(): + config = StructuralDropperConfig( + projectile_shape=DROPPER_SHAPES, + projectile_dimensions=0.5 + ) + scene = prior_scene() + service = StructuralDropperCreationService() + service.add_to_scene(scene, config, []) + + dropper = scene.objects[0] + assert dropper + assert dropper['id'].startswith('dropping_device_') + assert dropper['type'] == 'tube_wide' + + projectile = scene.objects[1] + assert projectile + assert projectile['debug']['dimensions'] == pytest.approx( + {'x': 0.5, 'y': 0.5, 'z': 0.5} + ) + + +def test_dropper_no_projectile(): + config = StructuralDropperConfig( + num=1, + no_projectile=True, + ) + scene = prior_scene() + service = StructuralDropperCreationService() + service.add_to_scene(scene, config, []) + + assert len(scene.objects) == 1 + + def test_floor_material_create(): temp = FloorMaterialConfig( material="AI2-THOR/Materials/Walls/DrywallRed", @@ -1442,8 +1420,9 @@ def test_floor_material_create(): ).add_to_scene(scene, temp, []) assert scene.floor_textures text = scene.floor_textures[0] - assert text['material'] == "AI2-THOR/Materials/Walls/DrywallRed" - assert text['positions'] == [{'x': 3, 'z': -3}] + assert text.material == "AI2-THOR/Materials/Walls/DrywallRed" + assert text.positions == [{'x': 3, 'z': -3}] + assert len(scene.floor_textures) == 1 def test_holes_create(): @@ -2168,6 +2147,27 @@ def test_thrower_create(): # Note, the ball hasn't been positioned yet +def test_thrower_create_with_projectile_dimensions(): + config = StructuralThrowerConfig( + projectile_shape=THROWER_SHAPES, + projectile_dimensions=0.5 + ) + scene = prior_scene() + service = StructuralThrowerCreationService() + service.add_to_scene(scene, config, []) + + thrower = scene.objects[0] + assert thrower + assert thrower['id'].startswith('throwing_device_') + assert thrower['type'] == 'tube_wide' + + projectile = scene.objects[1] + assert projectile + assert projectile['debug']['dimensions'] == pytest.approx( + {'x': 0.5, 'y': 0.5, 'z': 0.5} + ) + + def test_wall_create(): temp = StructuralWallConfig( position=VectorFloatConfig(1.1, 0, 1.3), rotation_y=34, @@ -2194,63 +2194,6 @@ def test_wall_create(): assert scale == {'x': 2.2, 'y': 3, 'z': 0.1} -def test_tool_create(): - temp = ToolConfig( - position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, - guide_rails=True, shape='tool_rect_0_75_x_4_00') - tool = StructuralToolsCreationService( - ).create_feature_from_specific_values(prior_scene(), temp, None) - - assert tool - assert tool['id'].startswith('tool_') - assert tool['type'] == 'tool_rect_0_75_x_4_00' - show = tool['shows'][0] - pos = show['position'] - rot = show['rotation'] - scale = show['scale'] - assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} - assert rot == {'x': 0, 'y': 34, 'z': 0} - assert scale == {'x': 1, 'y': 1, 'z': 1} - - -def test_tool_create_hooked(): - temp = ToolConfig( - position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, - guide_rails=False, shape='tool_hooked_0_75_x_4_00') - tool = StructuralToolsCreationService( - ).create_feature_from_specific_values(prior_scene(), temp, None) - - assert tool - assert tool['id'].startswith('tool_') - assert tool['type'] == 'tool_hooked_0_75_x_4_00' - show = tool['shows'][0] - pos = show['position'] - rot = show['rotation'] - scale = show['scale'] - assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} - assert rot == {'x': 0, 'y': 34, 'z': 0} - assert scale == {'x': 1, 'y': 1, 'z': 1} - - -def test_tool_create_short(): - temp = ToolConfig( - position=VectorFloatConfig(1.1, 1.2, 1.3), rotation_y=34, - guide_rails=False, shape='tool_rect_0_75_x_1_00') - tool = StructuralToolsCreationService( - ).create_feature_from_specific_values(prior_scene(), temp, None) - - assert tool - assert tool['id'].startswith('tool_') - assert tool['type'] == 'tool_rect_0_75_x_1_00' - show = tool['shows'][0] - pos = show['position'] - rot = show['rotation'] - scale = show['scale'] - assert pos == {'x': 1.1, 'y': 0.15, 'z': 1.3} - assert rot == {'x': 0, 'y': 34, 'z': 0} - assert scale == {'x': 1, 'y': 1, 'z': 1} - - def test_turntable_creation_reconcile(): scene = prior_scene() rd = scene.room_dimensions @@ -2424,9 +2367,9 @@ def test_turntable_rotation_y_zero_end_after_rotation(): def test_platform_long_with_two_ramps(): scene = prior_scene() - scene.room_dimensions.x = 10 + scene.room_dimensions.x = 20 scene.room_dimensions.y = 8 - scene.room_dimensions.z = 10 + scene.room_dimensions.z = 20 # lips, attached_ramps, and platform_underneath_attached_ramps # will be overrided temp = StructuralPlatformConfig( @@ -2606,61 +2549,236 @@ def test_platform_adjacent_to_wall(): def test_platform_stacked_adjacent_to_wall(): + for i in range(10): + scene = prior_scene() + x = 10 + z = 8 + scene.room_dimensions.x = x + scene.room_dimensions.y = 4 + scene.room_dimensions.z = z + pos_x = 1 + pos_z = 3 + scale = 1 + for side in WALL_SIDES: + temp = StructuralPlatformConfig( + num=1, + position=VectorFloatConfig(pos_x, 2, pos_z), + rotation_y=0, + scale=scale, + material="AI2-THOR/Materials/Ceramics/BrownMarbleFake 1", + lips=StructuralPlatformLipsConfig(False, False, False, False), + attached_ramps=1, + platform_underneath=True, + platform_underneath_attached_ramps=1, + adjacent_to_wall=[side] + ) + platform_adjacent_to_wall = StructuralPlatformCreationService( + ).create_feature_from_specific_values(scene, temp, None) + side_x = -1 if 'left' in side else 1 if 'right' in side else 0 + side_z = -1 if 'back' in side else 1 if 'front' in side else 0 + for i in range(2): + plat = platform_adjacent_to_wall[i] + ramp = platform_adjacent_to_wall[i + 2] + plat_pos = plat['shows'][0]['position'] + ramp_pos = ramp['shows'][0]['position'] + half_scale_x = plat['shows'][0]['scale']['x'] / 2 + half_scale_z = plat['shows'][0]['scale']['z'] / 2 + # Check a different scale if the platform is rotated + # 90 or 270 degrees + scale_check_x = half_scale_z if abs( + plat['shows'][0]['rotation']['y']) % 180 != 0 else \ + half_scale_x + scale_check_z = half_scale_x if abs( + plat['shows'][0]['rotation']['y']) % 180 != 0 else \ + half_scale_z + if side_x: + side_x_check = round(side_x * (x / 2 - scale_check_x), 3) + assert abs(round(plat_pos['x'], 3) - side_x_check) < 0.0011 + assert (ramp_pos['x'] > side_x * (x / 2 + scale_check_x) if + side_x == -1 else + ramp_pos['x'] < side_x * (x / 2 + scale_check_x)) + assert ramp['shows'][0]['rotation']['y'] != -side_x * 90 + else: + plat_pos['x'] == pos_x + if side_z: + side_z_check = round(side_z * (z / 2 - scale_check_z), 3) + assert abs(round(plat_pos['z'], 3) - side_z_check) < 0.0011 + assert (ramp_pos['z'] > side_z * (z / 2 + scale_check_z) if + side_z == -1 else + ramp_pos['z'] < side_z * (z / 2 + scale_check_z)) + assert abs(ramp['shows'][0]['rotation']['y']) != ( + 0 if side_z == -1 else 180) + else: + plat_pos['z'] == pos_z + + +def test_tube_occluder_creation_reconcile(): + scene = prior_scene() + limit_x = scene.room_dimensions.x / 2.0 + limit_z = scene.room_dimensions.z / 2.0 + service = StructuralTubeOccluderCreationService() + + config = StructuralTubeOccluderConfig(num=1) + reconciled = service.reconcile(scene, config) + assert reconciled.material in material_tuple_group_to_string_list( + materials.ROOM_WALL_MATERIALS + ) + assert -limit_x <= reconciled.position_x <= limit_x + assert -limit_z <= reconciled.position_z <= limit_z + assert 0.5 <= reconciled.radius <= 5 + assert 1 <= reconciled.down_step <= 10 + assert 51 <= reconciled.up_step <= 60 + assert reconciled.down_after is None + assert reconciled.up_after is None + + config = StructuralTubeOccluderConfig( + num=1, + down_step=20, + material='AI2-THOR/Materials/Plastics/BlackPlastic', + position_x=-1, + position_z=-2, + radius=6, + up_step=80 + ) + reconciled = service.reconcile(scene, config) + assert reconciled.material == 'AI2-THOR/Materials/Plastics/BlackPlastic' + assert reconciled.position_x == -1 + assert reconciled.position_z == -2 + assert reconciled.radius == 6 + assert reconciled.down_step == 20 + assert reconciled.up_step == 80 + assert reconciled.down_after is None + assert reconciled.up_after is None + + +def test_tube_occluder_create(): scene = prior_scene() - x = 10 - z = 8 - scene.room_dimensions.x = x scene.room_dimensions.y = 4 - scene.room_dimensions.z = z - pos_x = 1 - pos_z = 3 - scale = 1 - for side in WALL_SIDES: - temp = StructuralPlatformConfig( - num=1, - position=VectorFloatConfig(pos_x, 2, pos_z), - rotation_y=0, - scale=scale, - material="AI2-THOR/Materials/Ceramics/BrownMarbleFake 1", - lips=StructuralPlatformLipsConfig(False, False, False, False), - attached_ramps=1, - platform_underneath=True, - platform_underneath_attached_ramps=1, - adjacent_to_wall=[side] - ) - platform_adjacent_to_wall = StructuralPlatformCreationService( - ).create_feature_from_specific_values(scene, temp, None) - side_x = -1 if 'left' in side else 1 if 'right' in side else 0 - side_z = -1 if 'back' in side else 1 if 'front' in side else 0 - for i in range(2): - plat = platform_adjacent_to_wall[i] - ramp = platform_adjacent_to_wall[i + 2] - plat_pos = plat['shows'][0]['position'] - ramp_pos = ramp['shows'][0]['position'] - half_scale_x = plat['shows'][0]['scale']['x'] / 2 - half_scale_z = plat['shows'][0]['scale']['z'] / 2 - # Check a different scale if the platform is rotated - # 90 or 270 degrees - scale_check_x = half_scale_z if abs( - plat['shows'][0]['rotation']['y']) % 180 != 0 else half_scale_x - scale_check_z = half_scale_x if abs( - plat['shows'][0]['rotation']['y']) % 180 != 0 else half_scale_z - if side_x: - assert round(plat_pos['x'], 3) == round( - side_x * (x / 2 - scale_check_x), 3) - assert (ramp_pos['x'] > side_x * (x / 2 + scale_check_x) if - side_x == -1 else - ramp_pos['x'] < side_x * (x / 2 + scale_check_x)) - assert ramp['shows'][0]['rotation']['y'] != -side_x * 90 - else: - plat_pos['x'] == pos_x - if side_z: - assert round(plat_pos['z'], 2) == round( - side_z * (z / 2 - scale_check_z), 2) - assert (ramp_pos['z'] > side_z * (z / 2 + scale_check_z) if - side_z == -1 else - ramp_pos['z'] < side_z * (z / 2 + scale_check_z)) - assert abs(ramp['shows'][0]['rotation']['y']) != ( - 0 if side_z == -1 else 180) - else: - plat_pos['z'] == pos_z + service = StructuralTubeOccluderCreationService() + + config = StructuralTubeOccluderConfig( + num=1, + down_step=6, + material='AI2-THOR/Materials/Plastics/BlackPlastic', + position_x=1, + position_z=2, + radius=3, + up_step=61 + ) + output = service.create_feature_from_specific_values(scene, config, None) + assert len(output) == 1 + tube = output[0] + + assert tube['id'].startswith('tube_occluder_') + assert tube['type'] == 'tube_wide' + assert tube.get('kinematic', True) + assert tube.get('structure', True) + assert not tube.get('moveable') + assert not tube.get('pickupable') + assert tube['materials'] == ['AI2-THOR/Materials/Plastics/BlackPlastic'] + + assert len(tube['shows']) == 1 + assert tube['shows'][0]['stepBegin'] == 0 + assert tube['shows'][0]['position'] == {'x': 1, 'y': 5.75, 'z': 2} + assert tube['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert tube['shows'][0]['scale'] == {'x': 3, 'y': 4, 'z': 3} + tube_bounds = tube['shows'][0]['boundingBox'] + assert vars(tube_bounds.box_xz[0]) == pytest.approx( + {'x': 2.5, 'y': 0.0, 'z': 3.5} + ) + assert vars(tube_bounds.box_xz[1]) == pytest.approx( + {'x': 2.5, 'y': 0.0, 'z': 0.5} + ) + assert vars(tube_bounds.box_xz[2]) == pytest.approx( + {'x': -0.5, 'y': 0.0, 'z': 0.5} + ) + assert vars(tube_bounds.box_xz[3]) == pytest.approx( + {'x': -0.5, 'y': 0.0, 'z': 3.5} + ) + assert tube_bounds.min_y == -4 + assert tube_bounds.max_y == -0.25 + + assert len(tube['moves']) == 2 + assert tube['moves'][0]['stepBegin'] == 6 + assert tube['moves'][0]['stepEnd'] == 20 + assert tube['moves'][0]['vector'] == {'x': 0, 'y': -0.25, 'z': 0} + assert tube['moves'][1]['stepBegin'] == 61 + assert tube['moves'][1]['stepEnd'] == 75 + assert tube['moves'][1]['vector'] == {'x': 0, 'y': 0.25, 'z': 0} + + +def test_tube_occluder_creation_reconcile_down_after(): + object_repository = ObjectRepository.get_instance() + mock_moving_object = { + 'id': 'object_1', + 'moves': [{'stepBegin': 41, 'stepEnd': 50}] + } + mock_idl = InstanceDefinitionLocationTuple(mock_moving_object, {}, {}) + object_repository.add_to_labeled_objects(mock_idl, 'label_1') + + scene = prior_scene() + service = StructuralTubeOccluderCreationService() + config = StructuralTubeOccluderConfig(num=1, down_after='label_1') + reconciled = service.reconcile(scene, config) + assert reconciled.down_step == 51 + + +def test_tube_occluder_creation_reconcile_down_after_delay(): + scene = prior_scene() + service = StructuralTubeOccluderCreationService() + config = StructuralTubeOccluderConfig(num=1, down_after='label_1') + with pytest.raises(ILEDelayException): + # Error because no objects with this label are in the ObjectRepository + service.reconcile(scene, config) + + +def test_tube_occluder_creation_reconcile_up_after(): + object_repository = ObjectRepository.get_instance() + mock_moving_object = { + 'id': 'object_1', + 'moves': [{'stepBegin': 41, 'stepEnd': 50}] + } + mock_idl = InstanceDefinitionLocationTuple(mock_moving_object, {}, {}) + object_repository.add_to_labeled_objects(mock_idl, 'label_1') + + scene = prior_scene() + service = StructuralTubeOccluderCreationService() + config = StructuralTubeOccluderConfig(num=1, up_after='label_1') + reconciled = service.reconcile(scene, config) + assert reconciled.up_step == 51 + + +def test_tube_occluder_creation_reconcile_up_after_delay(): + scene = prior_scene() + service = StructuralTubeOccluderCreationService() + config = StructuralTubeOccluderConfig(num=1, up_after='label_1') + with pytest.raises(ILEDelayException): + # Error because no objects with this label are in the ObjectRepository + service.reconcile(scene, config) + + +def test_tube_occluder_creation_reconcile_down_after_and_up_after(): + object_repository = ObjectRepository.get_instance() + mock_moving_object_1 = { + 'id': 'object_1', + 'moves': [{'stepBegin': 1, 'stepEnd': 10}] + } + mock_idl_1 = InstanceDefinitionLocationTuple(mock_moving_object_1, {}, {}) + object_repository.add_to_labeled_objects(mock_idl_1, 'label_1') + mock_moving_object_2 = { + 'id': 'object_2', + 'moves': [{'stepBegin': 41, 'stepEnd': 50}] + } + mock_idl_2 = InstanceDefinitionLocationTuple(mock_moving_object_2, {}, {}) + object_repository.add_to_labeled_objects(mock_idl_2, 'label_2') + + scene = prior_scene() + service = StructuralTubeOccluderCreationService() + config = StructuralTubeOccluderConfig( + num=1, + down_after='label_1', + up_after='label_2' + ) + reconciled = service.reconcile(scene, config) + assert reconciled.down_step == 11 + assert reconciled.up_step == 51 diff --git a/tests/ile_structural_objects_component_test.py b/tests/ile_structural_objects_component_test.py index 9e79b80..3dae71d 100644 --- a/tests/ile_structural_objects_component_test.py +++ b/tests/ile_structural_objects_component_test.py @@ -2,9 +2,13 @@ from typing import List import pytest +from machine_common_sense.config_manager import ( + FloorTexturesConfig, + Vector2dInt +) from generator import materials -from generator.base_objects import ALL_LARGE_BLOCK_TOOLS, create_soccer_ball +from generator.base_objects import create_soccer_ball from generator.geometry import MAX_TRIES from generator.instances import instantiate_object from ideal_learning_env import ( @@ -31,8 +35,7 @@ PLATFORM_SCALE_MIN, WALL_SIDES, StopPositionConfig, - StructuralObjectMovementConfig, - ToolConfig + StructuralObjectMovementConfig ) from ideal_learning_env.structural_objects_component import ( FloorAreaConfig, @@ -48,6 +51,7 @@ StructuralPlatformConfig, StructuralRampConfig, StructuralThrowerConfig, + StructuralTubeOccluderConfig, StructuralTurntableConfig, StructuralWallConfig, WallSide @@ -92,13 +96,14 @@ def verify_object_counts(scene, min_count, max_count): for o in objs) - door_walls platforms = sum(bool(o['id'].startswith('platform')) for o in objs) ramps = sum(bool(o['id'].startswith('ramp')) for o in objs) - tools = sum(bool(o['id'].startswith('tool')) for o in objs) + tool_list = sum(bool(o['id'].startswith('tool')) for o in objs) + tubes = sum(bool(o['id'].startswith('tube_occluder_')) for o in objs) turntables = sum(bool(o['id'].startswith('turntable')) for o in objs) holes = len(scene.holes) lava = len(scene.lava) floor_textures = 0 for text in scene.floor_textures: - floor_textures += len(text['positions']) + floor_textures += len(text.positions) structural = 0 non_structural = 0 @@ -118,8 +123,8 @@ def verify_object_counts(scene, min_count, max_count): actual_count = ( (occluders / 2) + throwers + droppers + placers - additional_placers + - three_part_occluding_walls + doors + platforms + ramps + tools + - interior_walls + holes + lava + floor_textures + turntables + three_part_occluding_walls + doors + platforms + ramps + tool_list + + interior_walls + holes + lava + floor_textures + tubes + turntables ) # Assert the number of structural objects generated by this component is @@ -130,8 +135,8 @@ def verify_object_counts(scene, min_count, max_count): # Assert the number of structural objects generated by this component. assert structural == ( occluders + throwers + droppers + placers + doors + door_walls + - (three_part_occluding_walls * 3) + platforms + ramps + tools + - interior_walls + turntables + (three_part_occluding_walls * 3) + platforms + ramps + tool_list + + interior_walls + tubes + turntables ) # Assert the number of non-structural objects generated by this component. @@ -174,7 +179,7 @@ def test_random_structural_objects_min_max(): # Test a bunch of different randomly-generated scenes. for _ in range(100): - scene = component.update_ile_scene(prior_scene()) + scene = component.update_ile_scene(prior_scene_custom_size(50, 50)) verify_object_counts(scene, 1, 3) @@ -486,8 +491,8 @@ def test_random_structural_objects_holes(): assert len(holes) == 30 # moving occluders create 2 objects each for hole in holes: - assert -6 <= hole['x'] <= 6 - assert -6 <= hole['z'] <= 6 + assert -6 <= hole.x <= 6 + assert -6 <= hole.z <= 6 def test_random_structural_objects_hole_fail_under_performer(): @@ -531,10 +536,9 @@ def test_random_structural_objects_floor_textures(): # moving occluders create 2 objects each num_floor_mats = 0 for text in ft: - loc_list = text['positions'] - for loc in loc_list: - assert -6 <= loc['x'] <= 6 - assert -6 <= loc['z'] <= 6 + for area in text.positions: + assert -6 <= area.x <= 6 + assert -6 <= area.z <= 6 num_floor_mats += 1 assert num_floor_mats == 50 @@ -556,10 +560,10 @@ def test_random_structural_objects_floor_materials_under_performer(): scene = component.update_ile_scene(scene) ft = scene.floor_textures assert len(ft) == 1 - pos = ft[0]['positions'] + pos = ft[0].positions assert len(pos) == 1 - assert pos[0]['x'] == 0 - assert pos[0]['z'] == 0 + assert pos[0].x == 0 + assert pos[0].z == 0 def test_random_structural_objects_lava(): @@ -572,8 +576,8 @@ def test_random_structural_objects_lava(): scene = component.update_ile_scene(prior_scene()) assert len(scene.objects) == 0 for area in scene.lava: - assert -5 <= area['x'] <= 5 - assert -5 <= area['z'] <= 5 + assert -5 <= area.x <= 5 + assert -5 <= area.z <= 5 assert len(scene.lava) == 10 @@ -685,36 +689,6 @@ def test_random_structural_objects_doors(): assert ObjectRepository.get_instance().has_label('test_label') -def test_random_structural_objects_tools(): - component = RandomStructuralObjectsComponent({ - 'random_structural_objects': { - 'type': 'tools', - 'num': 2, - 'labels': 'test_label' - } - }) - assert isinstance( - component.random_structural_objects, - RandomStructuralObjectConfig) - assert component.random_structural_objects.type == 'tools' - assert component.random_structural_objects.num == 2 - - scene = prior_scene() - scene = component.update_ile_scene(scene) - objects = scene.objects - assert len(objects) == 2 - for obj in objects: - assert obj['id'].startswith('tool_') - assert obj['type'] in ALL_LARGE_BLOCK_TOOLS - assert not obj.get('kinematic') - assert not obj.get('openable') - assert not obj.get('structure') - assert not obj.get('mass') - - assert ObjectRepository.get_instance().has_label('tools') - assert ObjectRepository.get_instance().has_label('test_label') - - def test_random_structural_objects_turntables(): component = RandomStructuralObjectsComponent({ 'random_structural_objects': { @@ -736,6 +710,7 @@ def test_random_structural_objects_turntables(): for obj in objects: assert obj['id'].startswith('turntable_') assert obj['type'] == 'rotating_cog' + assert obj['materials'] == ['Custom/Materials/GreyWoodMCS'] assert obj.get('kinematic') assert obj.get('structure') assert obj.get('mass') @@ -1471,9 +1446,9 @@ def test_structural_objects_platforms_with_under_platform_with_ramps(): bscale = show['scale'] bottom_poly = show['boundingBox'].polygon_xz assert brot['y'] == 37 - assert pytest.approx(bpos['y'], 0.5) + assert 0.5 == pytest.approx(bpos['y']) assert bscale['x'] > tscale['x'] - assert pytest.approx(bscale['y'], 1) + assert 1 == pytest.approx(bscale['y']) assert bscale['z'] > tscale['z'] assert bottom_poly.contains(top_poly) @@ -1620,13 +1595,12 @@ def test_structural_objects_platforms_with_too_many_ramps(): 'z': -2 }, 'rotation_y': 37, - 'scale': { - 'x': 1, - 'y': 1, - 'z': 1 + 'x': 0.5, + 'y': 0.5, + 'z': 0.5 }, - 'attached_ramps': 30, + 'attached_ramps': 10 } }) with pytest.raises(ILEException): @@ -2389,6 +2363,29 @@ def test_structural_objects_thrower(): assert ObjectRepository.get_instance().has_label('test_label') +def test_thrower_no_projectile(): + component = SpecificStructuralObjectsComponent({ + 'structural_throwers': { + 'num': 1, + 'labels': 'test_label', + 'wall': 'front', + 'position_wall': 0, + 'height': 1, + 'rotation_y': 35, + 'rotation_z': 7, + 'throw_step': 3, + 'throw_force': 15, + 'no_projectile': True + } + }) + + pre_thrower = component.structural_throwers + assert isinstance(pre_thrower, StructuralThrowerConfig) + + scene = component.update_ile_scene(prior_scene()) + assert len(scene.objects) == 1 + + def test_structural_objects_thrower_non_impulse(): component = SpecificStructuralObjectsComponent({ 'structural_throwers': { @@ -4924,8 +4921,8 @@ def test_floor_features_holes_variables(): holes = scene.holes assert len(holes) in [5, 6] for hole in holes: - assert hole['x'] in [1, 2, -3, -2, 4, -4, 5, -5] - assert -5 <= hole['z'] <= -1 + assert hole.x in [1, 2, -3, -2, 4, -4, 5, -5] + assert -5 <= hole.z <= -1 def test_floor_features_holes_variables_one_missing(): @@ -4957,10 +4954,10 @@ def test_floor_features_holes_variables_one_missing(): assert len(holes) == 7 for i, hole in enumerate(holes): if i < 3: - assert hole['x'] == 3 + assert hole.x == 3 else: - assert hole['x'] in [-2, -4] - assert -6 <= hole['z'] <= 6 + assert hole.x in [-2, -4] + assert -6 <= hole.z <= 6 def test_floor_features_holes_under_object_fail(): @@ -5080,20 +5077,21 @@ def test_floor_features_materials_specific(): mats = scene.floor_textures assert len(mats) == 2 mat = mats[0] - assert mat['material'] == 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue' - pos = mat['positions'] + assert mat.material == 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue' + pos = mat.positions assert isinstance(pos, list) assert len(pos) == 1 - assert pos[0] == {'x': 3, 'z': 3} + assert pos[0].x == 3 + assert pos[0].z == 3 mat = mats[1] plastic = [m[0] for m in materials.PLASTIC_MATERIALS] - assert mat['material'] in plastic - pos = mat['positions'] + assert mat.material in plastic + pos = mat.positions assert isinstance(pos, list) assert len(pos) == 1 - assert pos[0]['x'] in [-2, -4] - assert -7 <= pos[0]['z'] <= 7 + assert pos[0].x in [-2, -4] + assert -7 <= pos[0].z <= 7 def test_floor_features_materials_missing_material(): @@ -5118,10 +5116,11 @@ def test_floor_features_materials_missing_material(): num = 0 mats = scene.floor_textures for mat in mats: - assert mat['material'] in materials.ALL_UNRESTRICTED_MATERIAL_STRINGS - for pos in mat['positions']: + assert mat.material in materials.ALL_UNRESTRICTED_MATERIAL_STRINGS + for pos in mat.positions: num += 1 - assert pos in [{'x': 5, 'z': 6}, {'x': 6, 'z': 6}] + assert pos.x in [5, 6] + assert pos.z == 6 assert num == 2 @@ -5148,11 +5147,11 @@ def test_floor_features_materials_missing_location(): num = 0 mats = scene.floor_textures for mat in mats: - assert mat['material'] in ceramic - for pos in mat['positions']: + assert mat.material in ceramic + for pos in mat.positions: num += 1 - assert -7 <= pos['x'] <= 7 - assert -7 <= pos['z'] <= 7 + assert -7 <= pos.x <= 7 + assert -7 <= pos.z <= 7 assert num == 5 @@ -5178,7 +5177,7 @@ def test_floor_features_lava_specific(): assert component.lava[1].position_z == -3 scene = component.update_ile_scene(prior_scene()) - assert scene.lava == [{'x': 2, 'z': 3}, {'x': -2, 'z': -3}] + assert scene.lava == [Vector2dInt(x=2, z=3), Vector2dInt(x=-2, z=-3)] def test_floor_features_lava_variable(): @@ -5203,8 +5202,8 @@ def test_floor_features_lava_variable(): scene = component.update_ile_scene(prior_scene()) assert len(scene.lava) in [5, 6] for area in scene.lava: - assert area['x'] in [1, 2] - assert -4 <= area['z'] <= -1 + assert area.x in [1, 2] + assert -4 <= area.z <= -1 def test_floor_features_lava_variable_restricted(): @@ -5222,9 +5221,9 @@ def test_floor_features_lava_variable_restricted(): scene = component.update_ile_scene(prior_scene()) assert ( - scene.lava == [{'x': 1, 'z': 3}, {'x': 2, 'z': 3}] + scene.lava == [Vector2dInt(x=1, z=3), Vector2dInt(x=2, z=3)] ) or ( - scene.lava == [{'x': 2, 'z': 3}, {'x': 1, 'z': 3}] + scene.lava == [Vector2dInt(x=2, z=3), Vector2dInt(x=1, z=3)] ) @@ -5272,14 +5271,14 @@ def test_floor_features_lava_with_floor_materials(): assert component.lava[1].position_z == -1 scene = component.update_ile_scene(prior_scene()) - assert scene.floor_textures == [{ - 'material': 'Custom/Materials/Blue', - 'positions': [{'x': 1, 'z': 1}] - }, { - 'material': 'Custom/Materials/Yellow', - 'positions': [{'x': 0, 'z': 1}] - }] - assert scene.lava == [{'x': 0, 'z': -1}, {'x': -1, 'z': -1}] + assert scene.floor_textures == [FloorTexturesConfig( + material='Custom/Materials/Blue', + positions=[Vector2dInt(x=1, z=1)] + ), FloorTexturesConfig( + material='Custom/Materials/Yellow', + positions=[Vector2dInt(x=0, z=1)] + )] + assert scene.lava == [Vector2dInt(x=0, z=-1), Vector2dInt(x=-1, z=-1)] def test_floor_features_lava_fail_duplicate(): @@ -7056,15 +7055,25 @@ def test_door_random_values(): assert isinstance(scene.objects, list) objs = scene.objects + door_materials = None + wall_materials = None for obj in objs: if obj['id'].startswith('door'): assert obj['type'] == 'door_4' assert obj['openable'] assert not obj.get('structure') + # Assume each door is always before all of its walls. + door_materials = obj['materials'] + wall_materials = None else: assert obj['id'].startswith("wall_") assert obj['type'] == 'cube' assert obj['structure'] + # Assume each door is always before all of its walls. + if wall_materials is None: + wall_materials = obj['materials'] + assert obj['materials'] == wall_materials + assert obj['materials'] != door_materials assert obj['kinematic'] assert obj['debug']['random_position'] assert ObjectRepository.get_instance().has_label('doors') @@ -7485,6 +7494,10 @@ def test_door_fully_defined(): assert show['rotation']['y'] == 90 assert not obj['debug']['random_position'] + assert objs[0]['materials'] != objs[1]['materials'] + assert objs[1]['materials'] == objs[2]['materials'] + assert objs[1]['materials'] == objs[3]['materials'] + def test_door_template_test(): # Testing 2 to verify we don't corrupt the template and force the same @@ -7678,92 +7691,6 @@ def test_door_fail_common_location(): component.update_ile_scene(prior_scene()) -def test_tool_random(): - component = SpecificStructuralObjectsComponent({ - 'tools': [{ - 'num': 1 - }] - }) - - assert isinstance(component.tools, List) - pre = component.tools[0] - assert isinstance(pre, ToolConfig) - assert pre.num == 1 - assert pre.labels is None - assert pre.position is None - assert pre.rotation_y is None - assert pre.shape is None - - scene = component.update_ile_scene(prior_scene()) - assert isinstance(scene.objects, list) - - objs = scene.objects - assert isinstance(objs, List) - obj = objs[0] - assert obj['id'].startswith('tool_') - assert obj['type'] in ALL_LARGE_BLOCK_TOOLS - assert not obj.get('kinematic') - assert not obj.get('structure') - assert not obj.get('mass') - show = obj['shows'][0] - scale = show['scale'] - assert scale['x'] == 1 - assert scale['y'] == 1 - assert scale['z'] == 1 - - assert ObjectRepository.get_instance().has_label('tools') - assert not ObjectRepository.get_instance().has_label('test_label') - - -def test_tool_full(): - component = SpecificStructuralObjectsComponent({ - 'tools': [{ - 'num': 1, - 'labels': 'test_label', - 'position': { - 'x': -1.5, - 'y': 0, - 'z': 1 - }, - 'rotation_y': 67, - 'shape': 'tool_rect_1_00_x_4_00' - }] - }) - - assert isinstance(component.tools, List) - pre = component.tools[0] - assert isinstance(pre, ToolConfig) - assert pre.num == 1 - assert pre.position == VectorFloatConfig(-1.5, 0, 1) - assert pre.rotation_y == 67 - assert pre.shape == 'tool_rect_1_00_x_4_00' - - scene = component.update_ile_scene(prior_scene()) - assert isinstance(scene.objects, list) - - objs = scene.objects - assert isinstance(objs, List) - obj = objs[0] - assert obj['id'].startswith('tool_') - assert obj['type'] in ALL_LARGE_BLOCK_TOOLS - assert not obj.get('kinematic') - assert not obj.get('structure') - assert not obj.get('mass') - show = obj['shows'][0] - pos = show['position'] - scale = show['scale'] - assert pos['x'] == -1.5 - assert pos['y'] == 0.15 - assert pos['z'] == 1 - assert scale['x'] == 1 - assert scale['y'] == 1 - assert scale['z'] == 1 - assert show['rotation']['y'] == 67 - - assert ObjectRepository.get_instance().has_label('tools') - assert ObjectRepository.get_instance().has_label('test_label') - - def test_turntable_random(): component = SpecificStructuralObjectsComponent({ 'structural_turntables': [{ @@ -7791,6 +7718,7 @@ def test_turntable_random(): obj = objs[0] assert obj['id'].startswith('turntable_') assert obj['type'] == 'rotating_cog' + assert obj['materials'] == ['Custom/Materials/GreyWoodMCS'] assert obj.get('kinematic') assert obj.get('structure') assert obj.get('mass') @@ -7843,6 +7771,9 @@ def test_turntable_full(): obj = objs[0] assert obj['id'].startswith('turntable_') assert obj['type'] == 'rotating_cog' + assert obj['materials'] == [ + 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue' + ] assert obj.get('kinematic') assert obj.get('structure') assert obj.get('mass') @@ -7959,6 +7890,8 @@ def test_door_material_group(): doors = 0 walls = 0 + door_materials = None + wall_materials = None for obj in objs: if obj['id'].startswith('door'): assert obj['type'] == 'door_4' @@ -7966,11 +7899,19 @@ def test_door_material_group(): assert obj['materials'][0] in wood_mats assert not obj.get('structure') doors += 1 + # Assume each door is always before all of its walls. + door_materials = obj['materials'] + wall_materials = None else: assert obj['id'].startswith('wall_') assert obj['type'] == 'cube' assert obj['structure'] walls += 1 + # Assume each door is always before all of its walls. + if wall_materials is None: + wall_materials = obj['materials'] + assert obj['materials'] == wall_materials + assert obj['materials'] != door_materials assert obj['kinematic'] assert doors == 2 @@ -8304,10 +8245,12 @@ def test_structural_objects_platform_adjacent_to_wall_errors(): def test_structural_objects_platform_adjacent_to_wall(): for side in WALL_SIDES: - ramp_count = randint(2, 4) + ramp_count = 4 component = SpecificStructuralObjectsComponent({ 'structural_platforms': { 'num': 1, + # Ensure the platform is big enough for all the ramps. + 'scale': {'x': 3, 'y': 1, 'z': 3}, 'adjacent_to_wall': side, 'attached_ramps': ramp_count } @@ -8418,8 +8361,8 @@ def test_multiple_platforms_adjacent_to_same_wall(): def test_multiple_platforms_adjacent_to_all_walls(): platform_count = 8 - ramp_count = randint(2, 3) - scale = randint(1, 2) + ramp_count = 2 + scale = 1 component = SpecificStructuralObjectsComponent({ 'structural_platforms': { 'num': platform_count, @@ -8434,8 +8377,8 @@ def test_multiple_platforms_adjacent_to_all_walls(): assert pre_plat.attached_ramps == ramp_count assert pre_plat.adjacent_to_wall == WALL_SIDES - x = randint(25, 30) - z = randint(25, 30) + x = 30 + z = 30 scene = component.update_ile_scene(prior_scene_custom_size(x, z)) assert ObjectRepository.get_instance().has_label('platforms') @@ -8629,3 +8572,136 @@ def test_multiple_platforms_stacked_adjacent_to_all_walls(): ramp_pos['z'] < side_z * (z / 2 + scale_check_z)) assert abs(ramp['shows'][0]['rotation']['y']) != ( 0 if side_z == -1 else 180) + + +def test_random_structural_objects_tube_occluders(): + component = RandomStructuralObjectsComponent({ + 'random_structural_objects': { + 'type': 'tube_occluders', + 'num': 2, + 'labels': 'test_label' + } + }) + assert isinstance( + component.random_structural_objects, + RandomStructuralObjectConfig + ) + assert component.random_structural_objects.type == 'tube_occluders' + assert component.random_structural_objects.num == 2 + + scene = prior_scene() + scene = component.update_ile_scene(scene) + objects = scene.objects + assert len(objects) == 2 + for obj in objects: + assert obj['id'].startswith('tube_occluder_') + assert obj['type'] == 'tube_wide' + assert obj['mass'] + assert obj['materials'] + assert obj.get('kinematic') + assert obj.get('structure') + assert not obj.get('openable') + assert not obj.get('pickupable') + + assert ObjectRepository.get_instance().has_label('tube_occluders') + assert ObjectRepository.get_instance().has_label('test_label') + + +def test_tube_occluder_random(): + component = SpecificStructuralObjectsComponent({ + 'structural_tube_occluders': [{ + 'num': 1 + }] + }) + + assert isinstance(component.structural_tube_occluders, List) + pre = component.structural_tube_occluders[0] + assert isinstance(pre, StructuralTubeOccluderConfig) + assert pre.num == 1 + assert pre.labels is None + assert pre.material is None + assert pre.position_x is None + assert pre.position_z is None + assert pre.radius is None + assert pre.down_after is None + assert pre.down_step is None + assert pre.up_after is None + assert pre.up_step is None + + scene = component.update_ile_scene(prior_scene()) + assert isinstance(scene.objects, list) + + objs = scene.objects + assert isinstance(objs, List) + obj = objs[0] + assert obj['id'].startswith('tube_occluder_') + assert obj['type'] == 'tube_wide' + assert obj['mass'] + assert obj['materials'] + assert obj.get('kinematic') + assert obj.get('structure') + assert obj['debug']['random_position'] + + assert ObjectRepository.get_instance().has_label('tube_occluders') + assert not ObjectRepository.get_instance().has_label('test_label') + + +def test_tube_occluder_configured(): + component = SpecificStructuralObjectsComponent({ + 'structural_tube_occluders': [{ + 'num': 1, + 'labels': 'test_label', + 'material': 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue', + 'position_x': 1, + 'position_z': 2, + 'radius': 3, + 'down_step': 6, + 'up_step': 61 + }] + }) + + assert isinstance(component.structural_tube_occluders, List) + pre = component.structural_tube_occluders[0] + assert isinstance(pre, StructuralTubeOccluderConfig) + assert pre.num == 1 + assert pre.labels == 'test_label' + assert pre.material == 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue' + assert pre.position_x == 1 + assert pre.position_z == 2 + assert pre.radius == 3 + assert pre.down_after is None + assert pre.down_step == 6 + assert pre.up_after is None + assert pre.up_step == 61 + + scene = prior_scene() + scene.room_dimensions.y = 4 + scene = component.update_ile_scene(scene) + assert isinstance(scene.objects, list) + + objs = scene.objects + assert isinstance(objs, List) + obj = objs[0] + assert obj['id'].startswith('tube_occluder_') + assert obj['type'] == 'tube_wide' + assert obj['mass'] + assert obj['materials'] == [ + 'AI2-THOR/Materials/Metals/BrushedAluminum_Blue' + ] + assert obj.get('kinematic') + assert obj.get('structure') + + assert obj['shows'][0]['position'] == {'x': 1, 'y': 5.75, 'z': 2} + assert obj['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert obj['shows'][0]['scale'] == {'x': 3, 'y': 4, 'z': 3} + + assert len(obj['moves']) == 2 + assert obj['moves'][0]['stepBegin'] == 6 + assert obj['moves'][0]['stepEnd'] == 20 + assert obj['moves'][0]['vector'] == {'x': 0, 'y': -0.25, 'z': 0} + assert obj['moves'][1]['stepBegin'] == 61 + assert obj['moves'][1]['stepEnd'] == 75 + assert obj['moves'][1]['vector'] == {'x': 0, 'y': 0.25, 'z': 0} + + assert ObjectRepository.get_instance().has_label('tube_occluders') + assert ObjectRepository.get_instance().has_label('test_label') diff --git a/tests/ile_validation_component_test.py b/tests/ile_validation_component_test.py index 641c835..39ca4cd 100644 --- a/tests/ile_validation_component_test.py +++ b/tests/ile_validation_component_test.py @@ -1,5 +1,5 @@ import pytest -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Vector2dInt, Vector3d from generator import ObjectBounds from ideal_learning_env.defs import ILEException @@ -71,7 +71,7 @@ def test_valid_path_blocked_by_holes(): # create blocked by holes holes = scene.holes for i in range(11): - holes.append({'x': i - 5, 'z': 2}) + holes.append(Vector2dInt(x=(i - 5), z=2)) with pytest.raises(ILEException): component.update_ile_scene(scene) @@ -82,7 +82,7 @@ def test_valid_path_blocked_by_lava(): scene = prior_scene_with_target() # create blocked by lava - scene.lava = [{'x': i - 5, 'z': 2} for i in range(11)] + scene.lava = [Vector2dInt(x=(i - 5), z=2) for i in range(11)] with pytest.raises(ILEException): component.update_ile_scene(scene) @@ -515,9 +515,9 @@ def test_valid_path_with_lava_and_holes(): # create blocked by holes holes = scene.holes for i in range(8): - holes.append({'x': i - 5, 'z': 3}) + holes.append(Vector2dInt(x=(i - 5), z=3)) - scene.lava = [{'x': i - 3, 'z': 0} for i in range(8)] + scene.lava = [Vector2dInt(x=(i - 3), z=0) for i in range(8)] component.update_ile_scene(scene) assert component.last_distance == pytest.approx(15, 0.1) diff --git a/tests/imitation_test.py b/tests/imitation_test.py new file mode 100644 index 0000000..ac18476 --- /dev/null +++ b/tests/imitation_test.py @@ -0,0 +1,1409 @@ +from machine_common_sense.config_manager import Vector3d + +from generator import base_objects, instances +from generator.geometry import ( + ORIGIN_LOCATION, + PERFORMER_WIDTH, + calculate_rotations, + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector +) +from generator.imitation import ( + IMITATION_AGENT_END_X, + IMITATION_AGENT_START_X, + IMITATION_CONTAINER_SEPARATION, + IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL, + IMITATION_CONTAINER_TELEPORT_X_POS_RANGE, + IMITATION_TASK_TARGET_SEPARATION, + add_imitation_task +) +from generator.scene import Scene + + +def test_shortcut_imitation_left_side_teleport_containers_left_right(): # noqa + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'left_right', False, 'containers') + + assert len(scene.goal.triggered_by_target_sequence) == 2 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] + # right container + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[2]['id'] + # action list + kidnapp_step = scene.debug['endHabituationStep'] + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[0]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_CONTAINER_SEPARATION, 0) + start_x += x + start_z += z + teleport_positions.append((start_x, start_z)) + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == (i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == 90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + + teleport = containers[i]['shows'][1] + assert teleport['position']['x'] == teleport_positions[i][0] + assert teleport['position']['z'] == teleport_positions[i][1] + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation + assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[0]['openClose'][0]['step'] == 22 + assert containers[0]['openClose'][0]['open'] is True + assert containers[0]['openClose'][1]['step'] == kidnapp_step + assert containers[0]['openClose'][1]['open'] is False + assert containers[1].get('openClose') is None # middle container + assert containers[2]['openClose'][0]['step'] == 97 + assert containers[2]['openClose'][0]['open'] is True + assert containers[2]['openClose'][1]['step'] == kidnapp_step + assert containers[2]['openClose'][1]['open'] is False + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer + + # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_TASK_TARGET_SEPARATION, 0) + x = first_container_teleport_position['x'] - x + z = first_container_teleport_position['z'] - z + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == IMITATION_AGENT_START_X + assert start['position']['z'] == -1 # left chest + assert start['rotation']['y'] == -90 # face right + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer + # behind containers + separation = 1 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + containers[0]['shows'][1]['rotation']['y']) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert ( + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) + assert len(agent['rotates']) == 1 + assert agent['rotates'][0]['stepBegin'] == 83 + # left rotation because agent is walking torward perfromer + assert agent['rotates'][0]['vector']['y'] == -9 + # opening containers sequence + animations = agent['actions'] + assert len(animations) == 3 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + assert animations[1]['id'] == 'TPM_turnL45' + assert animations[1]['stepBegin'] == 83 + assert animations[1]['stepEnd'] == 93 + assert animations[2]['id'] == 'TPE_jump' + assert animations[2]['stepBegin'] == 93 + assert animations[2]['stepEnd'] == 103 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 3 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == + movement['sequence'][2]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == + movement['sequence'][2]['endPoint']['x'] == -IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == -1.0 # left + assert movement['sequence'][1]['endPoint']['z'] == 1.0 # right + assert movement['sequence'][2]['endPoint']['z'] == 0.85 # face performer + + +def test_shortcut_imitation_right_side_teleport_containers_left_right(): # noqa + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'left_right', True, 'containers') + + assert len(scene.goal.triggered_by_target_sequence) == 2 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] + # right container + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[2]['id'] + # action list + kidnapp_step = scene.debug['endHabituationStep'] + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[-1]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_CONTAINER_SEPARATION, 0) + start_x -= x + start_z -= z + teleport_positions.append((start_x, start_z)) + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == -(i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == -90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + + teleport = containers[i]['shows'][1] + assert round( + teleport['position']['x'], 3) == round( + teleport_positions[i][0], 3) + assert round( + teleport['position']['z'], 3) == round( + teleport_positions[i][1], 3) + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation + assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[0]['openClose'][0]['step'] == 22 + assert containers[0]['openClose'][0]['open'] is True + assert containers[0]['openClose'][1]['step'] == kidnapp_step + assert containers[0]['openClose'][1]['open'] is False + assert containers[1].get('openClose') is None # middle container + assert containers[2]['openClose'][0]['step'] == 96 + assert containers[2]['openClose'][0]['open'] is True + assert containers[2]['openClose'][1]['step'] == kidnapp_step + assert containers[2]['openClose'][1]['open'] is False + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer + + # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_TASK_TARGET_SEPARATION, 0) + x = end_container[1]['position']['x'] - x + z = end_container[1]['position']['z'] - z + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == -IMITATION_AGENT_START_X + assert start['position']['z'] == 1 # left chest + assert start['rotation']['y'] == 90 # face right + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer + # behind containers + separation = 1 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert ( + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) + assert len(agent['rotates']) == 1 + assert agent['rotates'][0]['stepBegin'] == 82 + # left rotation because agent is walking torward perfromer + assert agent['rotates'][0]['vector']['y'] == -9 + # opening containers sequence + animations = agent['actions'] + assert len(animations) == 3 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + assert animations[1]['id'] == 'TPM_turnL45' + assert animations[1]['stepBegin'] == 82 + assert animations[1]['stepEnd'] == 92 + assert animations[2]['id'] == 'TPE_jump' + assert animations[2]['stepBegin'] == 92 + assert animations[2]['stepEnd'] == 102 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 3 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == + movement['sequence'][2]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == + movement['sequence'][2]['endPoint']['x'] == IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == 1.0 # left + assert movement['sequence'][1]['endPoint']['z'] == -1.0 # right + assert movement['sequence'][2]['endPoint']['z'] == -1.15 # face performer + + +def test_shortcut_imitation_left_side_teleport_containers_right_middle(): + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'right_middle', False, 'containers') + + assert len(scene.goal.triggered_by_target_sequence) == 2 + # right container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] + # middle container + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[1]['id'] + # action list + kidnapp_step = scene.debug['endHabituationStep'] + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[0]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_CONTAINER_SEPARATION, 0) + start_x += x + start_z += z + teleport_positions.append((start_x, start_z)) + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == (i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == 90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + + teleport = containers[i]['shows'][1] + assert teleport['position']['x'] == teleport_positions[i][0] + assert teleport['position']['z'] == teleport_positions[i][1] + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation + assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[2]['openClose'][0]['step'] == 22 + assert containers[2]['openClose'][0]['open'] is True + assert containers[2]['openClose'][1]['step'] == kidnapp_step + assert containers[2]['openClose'][1]['open'] is False + assert containers[0].get('openClose') is None # left container + assert containers[1]['openClose'][0]['step'] == 71 + assert containers[1]['openClose'][0]['open'] is True + assert containers[1]['openClose'][1]['step'] == kidnapp_step + assert containers[1]['openClose'][1]['open'] is False + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer + + # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, -IMITATION_TASK_TARGET_SEPARATION, 0) + x = first_container_teleport_position['x'] - x + z = first_container_teleport_position['z'] - z + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == IMITATION_AGENT_START_X + assert start['position']['z'] == 1 # right chest + assert start['rotation']['y'] == -90 # face left + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer + # behind containers + separation = 1 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert ( + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) + assert len(agent['rotates']) == 1 + assert agent['rotates'][0]['stepBegin'] == 57 + # left rotation because agent is walking torward perfromer + assert agent['rotates'][0]['vector']['y'] == 9 + # opening containers sequence + animations = agent['actions'] + assert len(animations) == 3 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + assert animations[1]['id'] == 'TPM_turnR45' + assert animations[1]['stepBegin'] == 57 + assert animations[1]['stepEnd'] == 67 + assert animations[2]['id'] == 'TPE_jump' + assert animations[2]['stepBegin'] == 67 + assert animations[2]['stepEnd'] == 77 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 3 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == + movement['sequence'][2]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == + movement['sequence'][2]['endPoint']['x'] == -IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == 1.0 # right + assert movement['sequence'][1]['endPoint']['z'] == 0.0 # middle + assert movement['sequence'][2]['endPoint']['z'] == -0.15 # face performer + + +def test_shortcut_imitation_right_side_teleport_containers_right_middle(): + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'right_middle', True, 'containers') + + assert len(scene.goal.triggered_by_target_sequence) == 2 + # right container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] + # middle container + assert scene.goal.triggered_by_target_sequence[1] == scene.objects[1]['id'] + # action list + kidnapp_step = scene.debug['endHabituationStep'] + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[-1]['shows'] + teleport_rotation = containers[0]['shows'][1]['rotation']['y'] + first_container_teleport_position = containers[0]['shows'][1]['position'] + start_x = first_container_teleport_position['x'] + start_z = first_container_teleport_position['z'] + teleport_positions = [] + teleport_positions.append((start_x, start_z)) + for _ in range(1, 3): + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_CONTAINER_SEPARATION, 0) + start_x -= x + start_z -= z + teleport_positions.append((start_x, start_z)) + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == -(i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == -90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + + teleport = containers[i]['shows'][1] + assert round( + teleport['position']['x'], 3) == round( + teleport_positions[i][0], 3) + assert round( + teleport['position']['z'], 3) == round( + teleport_positions[i][1], 3) + assert teleport['rotation']['y'] in IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL # noqa + assert teleport['rotation']['y'] == teleport_rotation + assert containers[i]['shows'][1]['stepBegin'] == kidnapp_step + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[2]['openClose'][0]['step'] == 22 + assert containers[2]['openClose'][0]['open'] is True + assert containers[2]['openClose'][1]['step'] == kidnapp_step + assert containers[2]['openClose'][1]['open'] is False + assert containers[0].get('openClose') is None # left container + assert containers[1]['openClose'][0]['step'] == 71 + assert containers[1]['openClose'][0]['open'] is True + assert containers[1]['openClose'][1]['step'] == kidnapp_step + assert containers[1]['openClose'][1]['open'] is False + # left container is in view after rotation, it tells us if the other + # containers are in view because they are in a straight line + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert min_max[0] <= scene.objects[0]['shows'][1]['position']['x'] \ + <= min_max[1] + agent_stand_behind_buffer = 1 + assert 0 <= scene.objects[0]['shows'][1]['position']['z'] <= \ + scene.room_dimensions.z / 2 - agent_stand_behind_buffer + + # target + x, z = \ + get_magnitudes_of_x_z_dirs_for_rotation_and_move_vector( + teleport_rotation, IMITATION_TASK_TARGET_SEPARATION, 0) + x = end_container[1]['position']['x'] - x + z = end_container[1]['position']['z'] - z + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == x + assert target['shows'][1]['position']['z'] == z + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == x + assert placer['shows'][1]['position']['z'] == z + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == -IMITATION_AGENT_START_X + assert start['position']['z'] == -1 # left chest + assert start['rotation']['y'] == 90 # face right + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer + # behind containers + separation = 1 + max_container_z = \ + max(containers, key=lambda c: c['shows'][1]['position']['z'])[ + 'shows'][1]['position']['z'] + assert teleport['position']['z'] == max_container_z + separation + index = IMITATION_CONTAINER_TELEPORT_ROTATIONS_GLOBAL.index( + teleport_rotation) + min_max = IMITATION_CONTAINER_TELEPORT_X_POS_RANGE[index] + assert ( + teleport['position']['x'] == min_max[0] - separation or + teleport['position']['x'] == min_max[1] + separation) + assert len(agent['rotates']) == 1 + assert agent['rotates'][0]['stepBegin'] == 57 + # left rotation because agent is walking torward perfromer + assert agent['rotates'][0]['vector']['y'] == 9 + # opening containers sequence + animations = agent['actions'] + assert len(animations) == 3 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + assert animations[1]['id'] == 'TPM_turnR45' + assert animations[1]['stepBegin'] == 57 + assert animations[1]['stepEnd'] == 67 + assert animations[2]['id'] == 'TPE_jump' + assert animations[2]['stepBegin'] == 67 + assert animations[2]['stepEnd'] == 77 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 3 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == + movement['sequence'][2]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == + movement['sequence'][2]['endPoint']['x'] == IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == -1.0 # right + assert movement['sequence'][1]['endPoint']['z'] == 0.0 # middle + assert movement['sequence'][2]['endPoint']['z'] == -0.15 # face performer + + +def test_shortcut_imitation_left_side_teleport_performer_left(): + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'left', False, 'performer') + + kidnapp_step = scene.debug['endHabituationStep'] + + # goal + assert len(scene.goal.triggered_by_target_sequence) == 1 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[0]['id'] + # action list + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + + shift = 2.5 + neg_range_x = (-scene.room_dimensions.x / 2 - PERFORMER_WIDTH, -shift) + pos_range_x = (shift, scene.room_dimensions.x / 2 - PERFORMER_WIDTH) + neg_range_z = (-scene.room_dimensions.z / 2 - PERFORMER_WIDTH, -shift) + pos_range_z = (shift, scene.room_dimensions.z / 2 - PERFORMER_WIDTH) + assert ( + (neg_range_x[0] <= scene.debug['endHabituationTeleportPositionX'] <= + neg_range_x[1]) or + (pos_range_x[0] <= scene.debug['endHabituationTeleportPositionX'] <= + pos_range_x[1]) + ) + assert ( + (neg_range_z[0] <= scene.debug['endHabituationTeleportPositionZ'] <= + neg_range_z[1]) or + (pos_range_z[0] <= scene.debug['endHabituationTeleportPositionZ'] <= + pos_range_z[1]) + ) + _, rotation_y = calculate_rotations( + Vector3d( + x=scene.debug['endHabituationTeleportPositionX'], + y=0, + z=scene.debug['endHabituationTeleportPositionZ']), + Vector3d(x=0, y=0, z=0) + ) + assert scene.debug['endHabituationTeleportRotationY'] == rotation_y + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[0]['shows'] + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == (i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == 90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[1].get('openClose') is None # middle container + assert containers[2].get('openClose') is None # right container + assert containers[0]['openClose'][0]['step'] == 22 + assert containers[0]['openClose'][0]['open'] is True + assert containers[0]['openClose'][1]['step'] == kidnapp_step + assert containers[0]['openClose'][1]['open'] is False + + # target + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == IMITATION_AGENT_START_X + assert start['position']['z'] == -1 # left chest + assert start['rotation']['y'] == -90 # face right + # move agent after kidnapp + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + agent_z_choices = (-2, 2) + agent_x_range = (-2, 2) + assert teleport['rotation']['y'] == \ + (180 if teleport['position']['z'] > 0 else 0) + assert teleport['position']['z'] == \ + agent_z_choices[0] or teleport['position']['z'] == agent_z_choices[1] + assert agent_x_range[0] <= teleport['position']['x'] <= agent_x_range[1] + + # opening containers sequence + assert agent.get('rotates') is None + animations = agent['actions'] + assert len(animations) == 1 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 2 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == -IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == -1 # left + assert movement['sequence'][1]['endPoint']['z'] == -1.15 # face performer + + +def test_shortcut_imitation_right_side_teleport_performer_right(): + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'right', True, 'performer') + + kidnapp_step = scene.debug['endHabituationStep'] + + # goal + assert len(scene.goal.triggered_by_target_sequence) == 1 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[2]['id'] + # action list + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + + shift = 2.5 + neg_range_x = (-scene.room_dimensions.x / 2 - PERFORMER_WIDTH, -shift) + pos_range_x = (shift, scene.room_dimensions.x / 2 - PERFORMER_WIDTH) + neg_range_z = (-scene.room_dimensions.z / 2 - PERFORMER_WIDTH, -shift) + pos_range_z = (shift, scene.room_dimensions.z / 2 - PERFORMER_WIDTH) + assert ( + (neg_range_x[0] <= scene.debug['endHabituationTeleportPositionX'] <= + neg_range_x[1]) or + (pos_range_x[0] <= scene.debug['endHabituationTeleportPositionX'] <= + pos_range_x[1]) + ) + assert ( + (neg_range_z[0] <= scene.debug['endHabituationTeleportPositionZ'] <= + neg_range_z[1]) or + (pos_range_z[0] <= scene.debug['endHabituationTeleportPositionZ'] <= + pos_range_z[1]) + ) + _, rotation_y = calculate_rotations( + Vector3d( + x=scene.debug['endHabituationTeleportPositionX'], + y=0, + z=scene.debug['endHabituationTeleportPositionZ']), + Vector3d(x=0, y=0, z=0) + ) + assert scene.debug['endHabituationTeleportRotationY'] == rotation_y + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[-1]['shows'] + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == -(i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == -90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[0].get('openClose') is None # left container + assert containers[1].get('openClose') is None # middle container + assert containers[2]['openClose'][0]['step'] == 22 + assert containers[2]['openClose'][0]['open'] is True + assert containers[2]['openClose'][1]['step'] == kidnapp_step + assert containers[2]['openClose'][1]['open'] is False + + # target + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == -IMITATION_AGENT_START_X + assert start['position']['z'] == -1 # right chest + assert start['rotation']['y'] == 90 # face right + # move agent after kidnapp + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + agent_z_choices = (-2, 2) + agent_x_range = (-2, 2) + assert teleport['rotation']['y'] == \ + (180 if teleport['position']['z'] > 0 else 0) + assert teleport['position']['z'] == \ + agent_z_choices[0] or teleport['position']['z'] == agent_z_choices[1] + assert agent_x_range[0] <= teleport['position']['x'] <= agent_x_range[1] + + # opening containers sequence + assert agent.get('rotates') is None + animations = agent['actions'] + assert len(animations) == 1 + + +def test_shortcut_imitation_left_side_middle_agent_only(): + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'middle', False, 'agent_only') + + kidnapp_step = scene.debug['endHabituationStep'] + + # goal + assert len(scene.goal.triggered_by_target_sequence) == 1 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[1]['id'] + # action list + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[0]['shows'] + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == (i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == 90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[0].get('openClose') is None # left container + assert containers[2].get('openClose') is None # right container + assert containers[1]['openClose'][0]['step'] == 22 + assert containers[1]['openClose'][0]['open'] is True + assert containers[1]['openClose'][1]['step'] == kidnapp_step + assert containers[1]['openClose'][1]['open'] is False + + # target + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == IMITATION_AGENT_START_X + assert start['position']['z'] == 0 # middle chest + assert start['rotation']['y'] == -90 # face left + # move agent after kidnapp + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + agent_x_range = (-2, 2) + assert teleport['rotation']['y'] == 180 + assert teleport['position']['z'] == 2 + assert agent_x_range[0] <= teleport['position']['x'] <= agent_x_range[1] + + # opening containers sequence + assert agent.get('rotates') is None + animations = agent['actions'] + assert len(animations) == 1 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 2 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == -IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == 0 # middle + assert movement['sequence'][1]['endPoint']['z'] == -0.15 # face performer + + +def test_shortcut_imitation_right_side_teleport_containers_middle_left_agent_only(): # noqa + scene = Scene() + target_definition = base_objects.create_soccer_ball() + target = instances.instantiate_object(target_definition, ORIGIN_LOCATION) + scene.objects.append(target) + scene = add_imitation_task(scene, 'middle_left', True, 'agent_only') + + kidnapp_step = scene.debug['endHabituationStep'] + + # goal + assert len(scene.goal.triggered_by_target_sequence) == 2 + # left container + assert scene.goal.triggered_by_target_sequence[0] == scene.objects[1]['id'] + # action list + assert len(scene.goal.action_list) == kidnapp_step + for i in range(kidnapp_step): + scene.goal.action_list[i][0] == 'Pass' + assert scene.goal.action_list[-1][0].startswith('EndHabituation') + + # rectangular room with always 3 ceiling height + assert (scene.room_dimensions.x == scene.room_dimensions.z / + 2 or scene.room_dimensions.x == scene.room_dimensions.z * 2) + assert 8 <= min(scene.room_dimensions.x, scene.room_dimensions.z) <= 10 + assert scene.room_dimensions.y == 3 + + # performer + assert scene.performer_start.position.x == 0 + assert scene.performer_start.position.z == -3.75 + assert scene.performer_start.rotation.y == 0 + # do NOT teleport performer for this config + assert scene.debug['endHabituationTeleportPositionX'] == 0 + assert scene.debug['endHabituationTeleportPositionZ'] == -3.75 + assert scene.debug['endHabituationTeleportRotationY'] == 0 + assert scene.debug['endHabituationStep'] == kidnapp_step + + # objects + assert len(scene.objects) == 6 + + # containers + colors_used = [] + containers = scene.objects[0:3] + end_container = containers[-1]['shows'] + for i in range(3): + colors_used.append(containers[i]['debug']['color'][0]) + assert containers[i]['shows'][0]['position']['z'] == -(i - 1) + assert containers[i]['shows'][0]['rotation']['y'] == -90 + assert containers[i]['type'] == 'chest_1' + assert containers[i]['shows'][0]['scale']['x'] == 1 + assert containers[i]['shows'][0]['scale']['y'] == 1 + assert containers[i]['shows'][0]['scale']['z'] == 1 + # no duplicate colors + assert len(colors_used) == len(set(colors_used)) + # open close + assert containers[2].get('openClose') is None # left container + assert containers[1]['openClose'][0]['step'] == 22 + assert containers[1]['openClose'][0]['open'] is True + assert containers[1]['openClose'][1]['step'] == kidnapp_step + assert containers[1]['openClose'][1]['open'] is False + assert containers[0]['openClose'][0]['step'] == 71 + assert containers[0]['openClose'][0]['open'] is True + assert containers[0]['openClose'][1]['step'] == kidnapp_step + assert containers[0]['openClose'][1]['open'] is False + + # target + target = scene.objects[3] + assert target['triggeredBy'] + assert target['type'] == 'soccer_ball' + assert target['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert target['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert target['shows'][1]['stepBegin'] == kidnapp_step + assert target['kinematic'] + assert len(target['moves']) == 1 + assert target['moves'][0]['vector']['y'] == -0.25 + assert target['moves'][0]['stepBegin'] == 0 + assert target['moves'][0]['stepEnd'] == 8 + assert len(target['togglePhysics']) == 1 + assert target['togglePhysics'][0]['stepBegin'] == 14 + + # placer + placer = scene.objects[4] + assert placer['triggeredBy'] + assert placer['type'] == 'cylinder' + assert placer['shows'][0]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][0]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['position']['x'] == \ + end_container[0]['position']['x'] + assert placer['shows'][1]['position']['z'] == \ + end_container[0]['position']['z'] - IMITATION_TASK_TARGET_SEPARATION + assert placer['shows'][1]['stepBegin'] == kidnapp_step + assert len(placer['moves']) == 2 + assert placer['moves'][0]['stepBegin'] == 0 + assert placer['moves'][0]['stepEnd'] == 8 + assert placer['moves'][0]['vector']['y'] == -0.25 + assert placer['moves'][1]['stepBegin'] == 19 + assert placer['moves'][1]['stepEnd'] == 26 + assert placer['moves'][1]['vector']['y'] == 0.25 + assert len(placer['changeMaterials']) == 1 + assert placer['changeMaterials'][0]['stepBegin'] == 14 + + # agent + agent = scene.objects[5] + assert agent['type'].startswith('agent') + # positions + start = agent['shows'][0] + assert start['position']['x'] == -IMITATION_AGENT_START_X + assert start['position']['z'] == 0 # middle chest + assert start['rotation']['y'] == 90 # face right + teleport = agent['shows'][1] + assert teleport['stepBegin'] == kidnapp_step + assert teleport['rotation']['y'] == 180 # face performer + # behind containers + # of to the right or the left since the containers were rotate 90 + assert -2 <= teleport['position']['x'] <= 2 + assert teleport['position']['z'] == 2 + assert len(agent['rotates']) == 1 + assert agent['rotates'][0]['stepBegin'] == 57 + # right rotation because agent is walking torward perfromer + assert agent['rotates'][0]['vector']['y'] == 9 + # opening containers sequence + animations = agent['actions'] + assert len(animations) == 3 + assert animations[0]['id'] == 'TPE_jump' + assert animations[0]['stepBegin'] == 18 + assert animations[0]['stepEnd'] == 28 + assert animations[1]['id'] == 'TPM_turnR45' + assert animations[1]['stepBegin'] == 57 + assert animations[1]['stepEnd'] == 67 + assert animations[2]['id'] == 'TPE_jump' + assert animations[2]['stepBegin'] == 67 + assert animations[2]['stepEnd'] == 77 + # movement + movement = agent['agentMovement'] + assert len(movement['sequence']) == 3 + assert movement['repeat'] is False + assert movement['stepBegin'] == 1 + assert (movement['sequence'][0]['animation'] == + movement['sequence'][1]['animation'] == + movement['sequence'][2]['animation'] == 'TPM_walk') + assert (movement['sequence'][0]['endPoint']['x'] == + movement['sequence'][1]['endPoint']['x'] == + movement['sequence'][2]['endPoint']['x'] == IMITATION_AGENT_END_X) + assert movement['sequence'][0]['endPoint']['z'] == 0 # middle + assert movement['sequence'][1]['endPoint']['z'] == 1 # left + assert movement['sequence'][2]['endPoint']['z'] == 0.85 # face performer diff --git a/tests/interactive_goals_test.py b/tests/interactive_goals_test.py index e080f06..363b0c1 100644 --- a/tests/interactive_goals_test.py +++ b/tests/interactive_goals_test.py @@ -1,6 +1,7 @@ import uuid import pytest +from machine_common_sense.config_manager import Goal from generator import RetrievalGoal, TransferralGoal, TraversalGoal, geometry @@ -49,11 +50,11 @@ def test_RetrievalGoal_update_goal_template(): } goal = RetrievalGoal('') - config = goal.update_goal_template({}, [target]) + config = goal.update_goal_template(Goal(), [target]) - assert config['description'] == 'Find and pick up the blue rubber ball.' - assert config['metadata']['target']['id'] == target['id'] - assert config['metadata']['target']['info'] == [ + assert config.description == 'Find and pick up the blue rubber ball.' + assert config.metadata['target']['id'] == target['id'] + assert config.metadata['target']['info'] == [ 'blue', 'rubber', 'ball' ] @@ -106,22 +107,22 @@ def test_TransferralGoal_update_goal_template(): } goal = TransferralGoal('') - config = goal.update_goal_template({}, [target_1, target_2]) + config = goal.update_goal_template(Goal(), [target_1, target_2]) - relationship = config['metadata']['relationship'] + relationship = config.metadata['relationship'] relationship_type = relationship[1] assert relationship_type in [ item.value for item in TransferralGoal.RelationshipType ] - assert config['description'] == 'Find and pick up the blue rubber ' + \ + assert config.description == 'Find and pick up the blue rubber ' + \ 'ball and move it ' + relationship_type + ' the grey fabric sofa.' - assert config['metadata']['target_1']['id'] == target_1['id'] - assert config['metadata']['target_1']['info'] == [ + assert config.metadata['target_1']['id'] == target_1['id'] + assert config.metadata['target_1']['info'] == [ 'blue', 'rubber', 'ball' ] - assert config['metadata']['target_2']['id'] == target_2['id'] - assert config['metadata']['target_2']['info'] == [ + assert config.metadata['target_2']['id'] == target_2['id'] + assert config.metadata['target_2']['info'] == [ 'grey', 'fabric', 'sofa' ] @@ -231,12 +232,12 @@ def test_TraversalGoal_update_goal_template(): } goal = TraversalGoal('') - config = goal.update_goal_template({}, [target]) + config = goal.update_goal_template(Goal(), [target]) - assert config['description'] == 'Find the blue rubber ball and move ' + \ + assert config.description == 'Find the blue rubber ball and move ' + \ 'near it.' - assert config['metadata']['target']['id'] == target['id'] - assert config['metadata']['target']['info'] == [ + assert config.metadata['target']['id'] == target['id'] + assert config.metadata['target']['info'] == [ 'blue', 'rubber', 'ball' ] diff --git a/tests/interactive_hypercube_test_util.py b/tests/interactive_hypercube_test_util.py index 9ac205f..0c0e7f6 100644 --- a/tests/interactive_hypercube_test_util.py +++ b/tests/interactive_hypercube_test_util.py @@ -1,13 +1,22 @@ -from generator import Scene +from generator import Scene, tags from hypercube import scene_generator STARTER_SCENE = scene_generator.STARTER_SCENE +def find_object(object_list, prefix): + results = list(filter(lambda x: x['type'].startswith(prefix), object_list)) + return results[0] if len(results) else None + + +def find_objects(object_list, prefix): + return list(filter(lambda x: x['type'].startswith(prefix), object_list)) + + def map_id_to_scene(scenes): scene_dict = {} for index, scene in enumerate(scenes): - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = (scene, index) return scene_dict @@ -21,13 +30,16 @@ def verify_scene( lava=False, partition_floor=False, position=None, - rotation=None + rotation=None, + category='retrieval', + passive=False ): assert scene assert scene.version == 2 - assert scene.goal['category'] == 'retrieval' - assert scene.goal['description'] - assert scene.goal['last_step'] + assert (scene.goal.category == category) + assert not scene.goal.description if passive else \ + scene.goal.description + assert scene.goal.last_step if dimensions: assert scene.room_dimensions.x == dimensions[0] @@ -62,12 +74,23 @@ def verify_scene( assert bool(scene.holes) == holes assert bool(scene.lava) == lava assert bool(scene.partition_floor) == partition_floor - assert len(scene.goal['sceneInfo']['slices']) == slice_count + assert len(scene.goal.scene_info['slices']) == slice_count - assert scene.goal['sceneInfo']['primaryType'] == 'interactive' - assert scene.goal['sceneInfo']['secondaryType'] == 'retrieval' - assert scene.goal['sceneInfo']['tertiaryType'] == task_type - assert scene.goal['sceneInfo']['quaternaryType'] == ( - 'action variable' if bool(scene.goal.get('action_list')) else + assert (scene.goal.scene_info['primaryType'] == 'interactive' or + (scene.goal.scene_info['primaryType'] == 'passive' and passive)) + # TODO: MCS-1683: verify secondaryType/newer categories within ingest/UI + assert (scene.goal.scene_info['secondaryType'] == category or + scene.goal.scene_info['secondaryType'] == 'retrieval' or + scene.goal.scene_info['secondaryType'] == 'passive' and passive) + assert scene.goal.scene_info['tertiaryType'] == task_type + assert scene.goal.scene_info['quaternaryType'] == ( + 'action none' if ( + passive and scene.goal.scene_info['primaryType'] == 'passive') + else + 'action variable' if bool(scene.goal.action_list) else 'action full' ) + + assert scene.goal.scene_info['domainType'] == ( + tags.get_domain_type(task_type) + ) diff --git a/tests/interactive_hypercubes_test.py b/tests/interactive_hypercubes_test.py index 9d1a051..1543f77 100644 --- a/tests/interactive_hypercubes_test.py +++ b/tests/interactive_hypercubes_test.py @@ -1,3 +1,4 @@ +import math import os import pytest @@ -11,6 +12,7 @@ InteractiveOccluderEvaluationHypercubeFactory, InteractiveSingleSceneFactory ) +from hypercube.interactive_hypercubes import ROOM_SIZE_XZ_MAX from tests.interactive_hypercube_test_util import ( STARTER_SCENE, map_id_to_scene, @@ -165,7 +167,8 @@ def verify_immediately_visible( if 'locationParent' in target else target target_poly = geometry.get_bounding_polygon(target_or_parent) - view_line = shapely.geometry.LineString([[0, 0], [0, 10]]) + max_diagonal = int(math.sqrt(2 * (ROOM_SIZE_XZ_MAX**2)) + 1) + view_line = shapely.geometry.LineString([[0, 0], [0, max_diagonal]]) view_line = affinity.rotate( view_line, -performer_start['rotation']['y'], @@ -415,15 +418,15 @@ def verify_object_list(scene, index, tag, object_list): # Verify object instance info is in goal info. for info in expected['debug']['info']: - if info not in scene.goal['objectsInfo']['all']: + if info not in scene.goal.objects_info['all']: return False if info != tag: - if info not in scene.goal['objectsInfo'][tag]: + if info not in scene.goal.objects_info[tag]: return False - if scene.goal['sceneInfo']['count'][tag] != len(object_list): + if scene.goal.scene_info['count'][tag] != len(object_list): return False - if scene.goal['sceneInfo']['present'][tag] != (len(object_list) > 0): + if scene.goal.scene_info['present'][tag] != (len(object_list) > 0): return False return True @@ -470,15 +473,15 @@ def verify_object_data_list(scene, index, tag, object_data_list, ignore_list): # Verify object instance info is in goal info. for info in expected['debug']['info']: - if info not in scene.goal['objectsInfo']['all']: + if info not in scene.goal.objects_info['all']: return False if info != tag: - if info not in scene.goal['objectsInfo'][tag]: + if info not in scene.goal.objects_info[tag]: return False - if scene.goal['sceneInfo']['count'][tag] != count: + if scene.goal.scene_info['count'][tag] != count: return False - if scene.goal['sceneInfo']['present'][tag] != (count > 0): + if scene.goal.scene_info['present'][tag] != (count > 0): return False return True @@ -599,12 +602,9 @@ def verify_scene_properties( slice_count = 0 verify_scene(scene, task, slice_count) - assert 10 <= scene.room_dimensions.x <= 15 + assert 10 <= scene.room_dimensions.x <= ROOM_SIZE_XZ_MAX assert 3 <= scene.room_dimensions.y <= 5 - assert 10 <= scene.room_dimensions.z <= 15 - - # Floor should not have distracting patterns. - assert len(scene.debug['floorColors']) == 1 + assert 10 <= scene.room_dimensions.z <= ROOM_SIZE_XZ_MAX # Floor should not have the same color as critical (non-context) objects. for role in [ @@ -740,15 +740,15 @@ def verify_scene_properties( performer_start ) - assert scene.goal['category'] == tags.tag_to_label(tags.SCENE.RETRIEVAL) - assert scene.goal['description'] - assert scene.goal['last_step'] >= 2500 - assert scene.goal['sceneInfo']['id'] - assert 'slices' in scene.goal['sceneInfo'] + assert scene.goal.category == tags.tag_to_label(tags.SCENE.RETRIEVAL) + assert scene.goal.description + assert scene.goal.last_step >= 2500 + assert scene.goal.scene_info['id'] + assert 'slices' in scene.goal.scene_info - target_metadata = scene.goal['metadata'].get( + target_metadata = scene.goal.metadata.get( 'target', - scene.goal['metadata'].get('target_1') + scene.goal.metadata.get('target_1') ) assert target_metadata['id'] == ( @@ -817,16 +817,13 @@ def verify_target_trophy(tag, instance): def test_single_scene(): hypercube_factory = InteractiveSingleSceneFactory(RetrievalGoal('')) hypercube = hypercube_factory._build(STARTER_SCENE) - + scenes = hypercube.generate_scenes() + assert len(scenes) == 1 assert len(hypercube._data['large_container']) == 0 assert len(hypercube._data['small_container']) == 0 assert len(hypercube._data['obstacle']) == 0 assert len(hypercube._data['occluder']) == 0 - assert 0 <= len(hypercube._small_context_object_list) <= 10 - - scenes = hypercube.get_scenes() - assert len(scenes) == 1 assert verify_scene_properties(hypercube, scenes[0], 0) @@ -835,6 +832,7 @@ def test_eval_4_container_hypercube(): RetrievalGoal('container') ) hypercube = hypercube_factory._build(STARTER_SCENE) + scene_dict = map_id_to_scene(hypercube.generate_scenes()) assert len(hypercube._data['large_container']) == 3 assert len(hypercube._data['small_container']) == 0 @@ -860,7 +858,6 @@ def test_eval_4_container_hypercube(): large_container_data_3 ], large_container_data_1) - scene_dict = map_id_to_scene(hypercube.get_scenes()) base_filename = save_scene_debug_files(scene_dict, 'container') for i in ['a', 'd', 'g', 'j', 'm', 'p']: @@ -915,8 +912,8 @@ def test_eval_4_container_hypercube(): hypercube._target_data.instance_list[index], target ) - assert scene.goal['sceneInfo']['contained']['target'] - assert not scene.goal['sceneInfo']['uncontained']['target'] + assert scene.goal.scene_info['contained']['target'] + assert not scene.goal.scene_info['uncontained']['target'] # Verify target is close to 1st large container. if i in ['d', 'e', 'f', 'j', 'k', 'l', 'p', 'q', 'r']: @@ -937,8 +934,8 @@ def test_eval_4_container_hypercube(): hypercube._target_data.instance_list[index], target ) - assert not scene.goal['sceneInfo']['contained']['target'] - assert scene.goal['sceneInfo']['uncontained']['target'] + assert not scene.goal.scene_info['contained']['target'] + assert scene.goal.scene_info['uncontained']['target'] # Verify large container 2 location same across all scenes. if i in [ @@ -1013,6 +1010,7 @@ def test_obstacle_hypercube(): RetrievalGoal('obstacle') ) hypercube = hypercube_factory._build(STARTER_SCENE) + scene_dict = map_id_to_scene(hypercube.generate_scenes()) assert len(hypercube._data['obstacle']) == 1 @@ -1033,7 +1031,6 @@ def test_obstacle_hypercube(): target_data, obstacle_data_1 ]) - scene_dict = map_id_to_scene(hypercube.get_scenes()) base_filename = save_scene_debug_files(scene_dict, 'obstacle') for i in ['a', 'b', 'c', 'd']: @@ -1153,6 +1150,7 @@ def test_occluder_hypercube(): RetrievalGoal('occluder') ) hypercube = hypercube_factory._build(STARTER_SCENE) + scene_dict = map_id_to_scene(hypercube.generate_scenes()) assert len(hypercube._data['occluder']) == 3 @@ -1177,7 +1175,6 @@ def test_occluder_hypercube(): target_data, occluder_data_1, occluder_data_2, occluder_data_3 ]) - scene_dict = map_id_to_scene(hypercube.get_scenes()) base_filename = save_scene_debug_files(scene_dict, 'occluder') for i in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']: diff --git a/tests/intuitive_physics_hypercubes_test.py b/tests/intuitive_physics_hypercubes_test.py index 7e3cf51..33a7d36 100644 --- a/tests/intuitive_physics_hypercubes_test.py +++ b/tests/intuitive_physics_hypercubes_test.py @@ -65,7 +65,7 @@ def test_CollisionsHypercube_default_scene_move_across(): )) scene = hypercube._create_default_scene(STARTER_SCENE, goal_template) verify_scene(scene, hypercube.is_move_across()) - assert 'collisions' == scene.goal['sceneInfo']['tertiaryType'] + assert 'collisions' == scene.goal.scene_info['tertiaryType'] def test_CollisionsHypercube_scenes_move_across(): @@ -75,7 +75,7 @@ def test_CollisionsHypercube_scenes_move_across(): ) assert hypercube.is_move_across() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} assert len(scene_list) == 3 @@ -83,9 +83,9 @@ def test_CollisionsHypercube_scenes_move_across(): print(f'NON_TARGET={hypercube._distractor_list[0]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('COLL_') for i in ['a', 'c', 'h']: @@ -233,7 +233,7 @@ def test_ObjectPermanenceHypercube_default_scene_fall_down(): )) scene = hypercube._create_default_scene(STARTER_SCENE, goal_template) verify_scene(scene, hypercube.is_move_across()) - assert 'object permanence' == scene.goal['sceneInfo']['tertiaryType'] + assert 'object permanence' == scene.goal.scene_info['tertiaryType'] def test_ShapeConstancyHypercube_default_objects_fall_down(): @@ -274,7 +274,7 @@ def test_ShapeConstancyHypercube_default_scene_fall_down(): )) scene = hypercube._create_default_scene(STARTER_SCENE, goal_template) verify_scene(scene, hypercube.is_move_across()) - assert 'shape constancy' == scene.goal['sceneInfo']['tertiaryType'] + assert 'shape constancy' == scene.goal.scene_info['tertiaryType'] @pytest.mark.skip(reason='Eval 3 version not used; please see Eval 4 version') @@ -320,7 +320,7 @@ def test_SpatioTemporalContinuityHypercube_default_scene_move_across(): verify_scene(scene, hypercube.is_move_across()) assert ( 'spatio temporal continuity' == - scene.goal['sceneInfo']['tertiaryType'] + scene.goal.scene_info['tertiaryType'] ) @@ -384,7 +384,7 @@ def test_GravitySupportHypercube_default_scene_fall_down(): )) scene = hypercube._create_default_scene(STARTER_SCENE, goal_template) verify_scene(scene, hypercube.is_move_across(), last_step=100) - assert 'gravity support' == scene.goal['sceneInfo']['tertiaryType'] + assert 'gravity support' == scene.goal.scene_info['tertiaryType'] @pytest.mark.skip(reason='Eval 3 version not used; please see Eval 4 version') @@ -396,7 +396,7 @@ def test_ObjectPermanenceHypercube_scenes_fall_down(): ) assert hypercube.is_fall_down() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} target_dict = {} assert len(scene_list) == 90 @@ -405,10 +405,10 @@ def test_ObjectPermanenceHypercube_scenes_fall_down(): print(f'TARGET_2={hypercube._target_list[1]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene target_dict[scene_id] = get_object_list(scene, 'target') - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('OBJP_') for i in [ @@ -574,7 +574,7 @@ def test_ShapeConstancyHypercube_scenes_fall_down(): ) assert hypercube.is_fall_down() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} target_dict = {} assert len(scene_list) == 10 @@ -583,11 +583,17 @@ def test_ShapeConstancyHypercube_scenes_fall_down(): print(f'TARGET_2={hypercube._target_list[1]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene target_dict[scene_id] = get_object_list(scene, 'target') - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('SHAP_') + assert scene.goal.scene_info[tags.SCENE.TIPSY] is False + for obj in scene.objects: + if obj['type'] in intuitive_physics_hypercubes.TIPSY_OBJECT_TYPES: + assert scene.goal.scene_info[tags.SCENE.TIPSY] is True + else: + assert scene.goal.scene_info[tags.SCENE.TIPSY] is False cell_list = ['a1', 'a2', 'e1', 'e2', 'e3', 'b1', 'd2', 'j1', 'l2', 'l4'] @@ -791,7 +797,7 @@ def test_SpatioTemporalContinuityHypercube_scenes_move_across(): ) assert hypercube.is_move_across() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} target_dict = {} non_target_dict = {} @@ -802,7 +808,7 @@ def test_SpatioTemporalContinuityHypercube_scenes_move_across(): print(f'NON_TARGET={hypercube._distractor_list[0]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene target_dict[scene_id] = get_object_list(scene, 'target') non_target_dict[scene_id] = get_object_list(scene, 'non target') @@ -810,7 +816,7 @@ def test_SpatioTemporalContinuityHypercube_scenes_move_across(): scene, 'intuitive physics occluder' ) - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('STC_') for i in [ @@ -938,7 +944,7 @@ def test_GravitySupportHypercube_scenes_fall_down(): ) assert hypercube.is_fall_down() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} assert len(scene_list) == 16 @@ -956,9 +962,9 @@ def test_GravitySupportHypercube_scenes_fall_down(): )['shows'][0]['position']['z'] for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('GRAV_') for i in [ @@ -968,7 +974,7 @@ def test_GravitySupportHypercube_scenes_fall_down(): for j in [i + '1']: scene = scene_dict[j] is_positive = ( - scene.goal['sceneInfo']['direction'] == 'right' + scene.goal.scene_info['direction'] == 'right' ) print(f'SCENE_ID={j} IS_POSITIVE_DIRECTION={is_positive}') @@ -987,7 +993,7 @@ def test_GravitySupportHypercube_scenes_fall_down(): if i in []: assert invisible_support y_difference -= invisible_support['debug']['dimensions']['y'] - move_step_count = int(y_difference / 0.25) - 1 + move_step_count = round(y_difference / 0.25) - 1 # Verify scene-agnostic target properties. assert verify_object_fall_down_position(target, 'TARGET', True) @@ -1262,7 +1268,7 @@ def test_ObjectPermanenceHypercubeEval4_default_scene_move_across(): )) scene = hypercube._create_default_scene(STARTER_SCENE, goal_template) verify_scene(scene, hypercube.is_move_across(), last_step=240) - assert 'object permanence' == scene.goal['sceneInfo']['tertiaryType'] + assert 'object permanence' == scene.goal.scene_info['tertiaryType'] def test_ObjectPermanenceHypercubeEval4_scenes_move_across(): @@ -1273,7 +1279,7 @@ def test_ObjectPermanenceHypercubeEval4_scenes_move_across(): ) assert hypercube.is_move_across() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} target_dict = {} assert len(scene_list) == 2 @@ -1281,10 +1287,10 @@ def test_ObjectPermanenceHypercubeEval4_scenes_move_across(): print(f'TARGET={hypercube._target_list[0]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene target_dict[scene_id] = get_object_list(scene, 'target') - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('OBJP_') for i in ['j']: @@ -1389,7 +1395,7 @@ def test_SpatioTemporalContinuityHypercubeEval4_default_scene_move_across(): verify_scene(scene, hypercube.is_move_across()) assert ( 'spatio temporal continuity' == - scene.goal['sceneInfo']['tertiaryType'] + scene.goal.scene_info['tertiaryType'] ) @@ -1401,7 +1407,7 @@ def test_SpatioTemporalContinuityHypercubeEval4_scenes_move_across(): ) assert hypercube.is_move_across() - scene_list = hypercube.get_scenes() + scene_list = hypercube.generate_scenes() scene_dict = {} target_dict = {} non_target_dict = {} @@ -1411,7 +1417,7 @@ def test_SpatioTemporalContinuityHypercubeEval4_scenes_move_across(): print(f'TARGET={hypercube._target_list[0]}') for scene in scene_list: - scene_id = scene.goal['sceneInfo']['id'][0].lower() + scene_id = scene.goal.scene_info['id'][0].lower() scene_dict[scene_id] = scene target_dict[scene_id] = get_object_list(scene, 'target') non_target_dict[scene_id] = get_object_list(scene, 'non target') @@ -1419,7 +1425,7 @@ def test_SpatioTemporalContinuityHypercubeEval4_scenes_move_across(): scene, 'intuitive physics occluder' ) - scene_name = scene.goal['sceneInfo']['name'] + scene_name = scene.goal.scene_info['name'] assert scene_name.startswith('STC_') for i in ['a', 'e']: diff --git a/tests/intuitive_physics_objects_test.py b/tests/intuitive_physics_objects_test.py index de27e12..767d034 100644 --- a/tests/intuitive_physics_objects_test.py +++ b/tests/intuitive_physics_objects_test.py @@ -10,109 +10,83 @@ SIDEWAYS_SHAPES = [ 'car_1', 'racecar_red', 'train_1', 'trolley_1', 'bus_1', 'car_2', 'cart_2', 'dog_on_wheels', 'truck_1', 'truck_2', - 'dog_on_wheels_2', 'skateboard', 'jeep', 'tank_1', 'tank_2', + 'dog_on_wheels_2', 'skateboard', 'jeep', 'tank_1', 'tank_2', 'trike', 'truck_3', 'truck_4' ] def test_getters_reuse_immutable_dataset(): - dataset_1 = intuitive_physics_objects.get_fall_down_definition_dataset( - unshuffled=True - ) - dataset_2 = intuitive_physics_objects.get_fall_down_definition_dataset( - unshuffled=True - ) + dataset_1 = intuitive_physics_objects.get_fall_down_definition_dataset(unshuffled=True) # noqa: E501 + dataset_2 = intuitive_physics_objects.get_fall_down_definition_dataset(unshuffled=True) # noqa: E501 assert dataset_1 is dataset_2 assert isinstance(dataset_1, DefinitionDataset) def test_does_have_definitions(): - dataset = intuitive_physics_objects.get_fall_down_definition_dataset( - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_fall_down_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_move_across_definition_dataset( - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) - dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: assert isinstance(definition, ObjectDefinition) def test_does_have_sideways_definitions(): - dataset = intuitive_physics_objects.get_move_across_definition_dataset( - unshuffled=True - ) - definitions = dataset.definitions() + dataset = intuitive_physics_objects.get_move_across_definition_dataset(unshuffled=True) # noqa: E501 + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 for definition in definitions: if definition.type in SIDEWAYS_SHAPES: @@ -123,356 +97,316 @@ def test_does_have_sideways_definitions(): @pytest.mark.slow def test_intuitive_physics_move_across_all_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_move_across_definition_dataset( - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 @pytest.mark.slow def test_intuitive_physics_fall_down_all_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_fall_down_definition_dataset( - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_move_across_basic_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_fall_down_basic_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_move_across_complex_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_fall_down_complex_objects_untrained_shapes(): - dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SHAPE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_shape( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_move_across_all_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_move_across_definition_dataset( - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_fall_down_all_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_fall_down_definition_dataset( - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_move_across_basic_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_fall_down_basic_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_basic_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_basic_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_move_across_complex_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_move_across_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 def test_intuitive_physics_fall_down_complex_objects_untrained_sizes(): - dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_complex_shape_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 - dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset( # noqa: E501 - unshuffled=True - ) + dataset = intuitive_physics_objects.get_fall_down_complex_shape_opposite_colors_definition_dataset(unshuffled=True) # noqa: E501 trained_dataset = dataset.filter_on_trained() untrained_dataset = dataset.filter_on_untrained( tags.SCENE.UNTRAINED_SIZE ) - for definition_1 in trained_dataset.definitions(unshuffled=True): + for definition_1 in trained_dataset.definitions_unique_shape_scale(): filtered_dataset = untrained_dataset.filter_on_similar_except_size( definition_1, only_diagonal_size=True ) print(f'{definition_1.type} {definition_1.scale}') # We want at least two possible untrained objects. - assert len(filtered_dataset.definitions()) >= 2 + assert len(filtered_dataset.definitions_unique_shape_scale()) >= 2 diff --git a/tests/occluders_test.py b/tests/occluders_test.py index 109344c..0c4e189 100644 --- a/tests/occluders_test.py +++ b/tests/occluders_test.py @@ -2,6 +2,7 @@ from generator.geometry import ObjectBounds from generator.materials import MaterialTuple +from generator.objects import SceneObject from generator.occluders import ( DEFAULT_INTUITIVE_PHYSICS_ROOM_DIMENSIONS, OCCLUDER_HEIGHT, @@ -64,7 +65,7 @@ def verify_bounds( def verify_pole( - pole: dict, + pole: SceneObject, position_x: float, position_y: float = ((DEFAULT_ROOM_Y * 0.5) + (OCCLUDER_HEIGHT * 0.5)), position_z: float = 1, @@ -115,7 +116,7 @@ def verify_pole( scale_z ) - if(not move_down_only): + if (not move_down_only): assert len(pole['moves']) == (2 if no_last_step else 3) assert pole['moves'][0]['stepBegin'] == 1 assert pole['moves'][0]['stepEnd'] == 6 @@ -134,7 +135,7 @@ def verify_pole( def verify_pole_sideways( - pole: dict, + pole: SceneObject, position_x: float, position_y: float = (OCCLUDER_HEIGHT * 0.5), position_z: float = 1, @@ -186,7 +187,7 @@ def verify_pole_sideways( scale_z if rotation_y % 180 == 0 else (scale_y * 2) ) - if(not move_down_only): + if (not move_down_only): assert len(pole['moves']) == (2 if no_last_step else 3) assert pole['moves'][0]['stepBegin'] == 1 assert pole['moves'][0]['stepEnd'] == 6 @@ -205,7 +206,7 @@ def verify_pole_sideways( def verify_wall( - wall: dict, + wall: SceneObject, position_x: float, position_y: float = (OCCLUDER_HEIGHT * 0.5), position_z: float = 1, @@ -222,7 +223,7 @@ def verify_wall( move_down_only: bool = False ): - if(move_down_only): + if (move_down_only): position_y = room_dim_y - (OCCLUDER_HEIGHT * 0.5) move_down_dist = ( wall['shows'][0]['position']['y'] - @@ -260,7 +261,7 @@ def verify_wall( move_2_step_end = move_2_step_begin + 5 move_3_step_end = move_3_step_begin + 5 - if(not move_down_only): + if (not move_down_only): assert len(wall['moves']) == (2 if no_last_step else 3) assert wall['moves'][0]['stepBegin'] == 1 assert wall['moves'][0]['stepEnd'] == 6 diff --git a/tests/optimal_path_test.py b/tests/optimal_path_test.py index 80b6731..a5d64fd 100644 --- a/tests/optimal_path_test.py +++ b/tests/optimal_path_test.py @@ -87,7 +87,7 @@ def test_dilate_and_unify_object_bounds_multiple_poly(): ) assert len(output) == 3 assert_array_almost_equal_nulp(numpy.array(output[0]), numpy.array([ - (-4.0, -1.5), (-4.5, -1.0), (-4.5, 1.0), (-4.0, 1.5), (-3.0, 1.5), + (-4, -1.5), (-4.5, -1.0), (-4.5, 1.0), (-4.0, 1.5), (-3.0, 1.5), (-2.5, 1.0), (-2.5, -1.0), (-3.0, -1.5) ])) assert_array_almost_equal_nulp(numpy.array(output[1]), numpy.array([ diff --git a/tests/scene_saver_test.py b/tests/scene_saver_test.py index e126c63..5132e4d 100644 --- a/tests/scene_saver_test.py +++ b/tests/scene_saver_test.py @@ -1,8 +1,8 @@ import copy -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import Goal, Vector3d -from generator import ObjectBounds, Scene +from generator import ObjectBounds, Scene, SceneObject from generator.scene_saver import ( _convert_non_serializable_data, _strip_debug_data, @@ -15,7 +15,7 @@ def create_test_object(): - return { + return SceneObject({ 'id': 'thing1', 'type': 'thing_1', 'debug': { @@ -50,7 +50,7 @@ def create_test_object(): Vector3d(x=2.5, y=0, z=3.5), Vector3d(x=2, y=0, z=3.5) ], max_y=1, min_y=0) }] - } + }) def test_find_next_filename(): @@ -94,7 +94,7 @@ def test_find_next_filename(): def test_convert_non_serializable_data(): scene = Scene(objects=[create_test_object()]) - expected_object = create_test_object() + expected_object = create_test_object().data expected_object['shows'][0]['boundingBox'] = [ {'x': 2, 'y': 0, 'z': 3}, {'x': 2.5, 'y': 0, 'z': 3}, @@ -117,50 +117,50 @@ def test_strip_debug_data(): 'wallColors': ['blue'] }, objects=[create_test_object()], - goal={ - 'category': 'test', - 'domainsInfo': { + goal=Goal( + category='test', + domains_info={ 'domainsTag': True }, - 'objectsInfo': { + objects_info={ 'objectsTag': True }, - 'sceneInfo': { + scene_info={ 'sceneTag': True }, - 'metadata': { + metadata={ 'target': { 'id': 'golden_idol', 'info': ['gold', 'idol'] } } - } - ) + ) + ).to_dict() expected = Scene( debug=None, - objects=[{ + objects=[SceneObject({ 'id': 'thing1', 'type': 'thing_1', 'shows': [{ 'stepBegin': 0 }] - }], - goal={ - 'category': 'test', - 'metadata': { + })], + goal=Goal( + category='test', + metadata={ 'target': { 'id': 'golden_idol' } } - } - ) - _strip_debug_data(scene) - assert scene == expected + ) + ).to_dict() + actual = _strip_debug_data(scene) + assert actual == expected def test_strip_debug_misleading_data(): obj = create_test_object() - expected = copy.deepcopy(obj) + expected = copy.deepcopy(obj.data) expected['debug']['movement'] = { 'deepExit': { 'key': 'value' diff --git a/tests/scene_test.py b/tests/scene_test.py index 4865738..6f29425 100644 --- a/tests/scene_test.py +++ b/tests/scene_test.py @@ -1,5 +1,11 @@ -from machine_common_sense.config_manager import Vector3d +from machine_common_sense.config_manager import ( + FloorTexturesConfig, + Goal, + Vector2dInt, + Vector3d +) +from generator import ObjectBounds, SceneObject, geometry from generator.scene import Scene, get_step_limit_from_dimensions from .ile_helper import prior_scene_with_target, prior_scene_with_targets @@ -55,7 +61,7 @@ def test_scene_default(): assert scene.floor_material is None assert scene.floor_properties is None assert scene.floor_textures == [] - assert scene.goal == {"metadata": {}} + assert scene.goal == Goal(metadata={}) assert scene.holes == [] assert not scene.intuitive_physics assert not scene.isometric @@ -147,15 +153,13 @@ def test_to_dict(): scene.set_performer_start_position(1, 2, 3) scene.set_room_dimensions(4, 5, 6) scene.name = "test" - scene.objects.append({"name": "pretend object"}) - scene.lava.append({ - "x": 0, - "z": -1 - }) - scene.holes.append({ - "x": 3, - "z": 4 - }) + scene.objects.append(SceneObject({"id": "pretend object"})) + scene.lava.append(Vector2dInt(x=0, z=-1)) + scene.holes.append(Vector2dInt(x=3, z=4)) + scene.floor_textures.append(FloorTexturesConfig( + material='floor_texture_a', + positions=[Vector2dInt(x=-2, z=-3)] + )) d = scene.to_dict() assert d == { "version": 2, @@ -172,9 +176,12 @@ def test_to_dict(): "z": -1 }], "name": "test", - "objects": [{"name": "pretend object"}], + "objects": [{"id": "pretend object"}], "screenshot": False, - "floorTextures": [], + "floorTextures": [{ + 'material': 'floor_texture_a', + 'positions': [{'x': -2, 'z': -3}] + }], "intuitivePhysics": False, "performerStart": { "position": { @@ -204,3 +211,151 @@ def test_get_last_step(): assert get_step_limit_from_dimensions(None, None) == 2500 assert get_step_limit_from_dimensions(None, 10) == 2500 assert get_step_limit_from_dimensions(10, None) == 2500 + + +def test_find_bounds(): + # Case 1: No objects + scene = Scene(objects=[]) + assert scene.find_bounds() == [] + + bounds_1 = ObjectBounds(box_xz=[ + Vector3d(x=1, y=0, z=1), + Vector3d(x=2, y=0, z=1), + Vector3d(x=2, y=0, z=2), + Vector3d(x=1, y=0, z=2) + ], max_y=1, min_y=0) + bounds_2 = ObjectBounds(box_xz=[ + Vector3d(x=-1, y=0, z=-1), + Vector3d(x=-2, y=0, z=-1), + Vector3d(x=-2, y=0, z=-2), + Vector3d(x=-1, y=0, z=-2) + ], max_y=1, min_y=0) + + # Case 2: 1 object + scene = Scene(objects=[ + {'shows': [{'boundingBox': bounds_1}]} + ]) + assert scene.find_bounds() == [bounds_1] + + # Case 3: 2 objects + scene = Scene(objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ]) + assert scene.find_bounds() == [bounds_1, bounds_2] + buffer = geometry.FLOOR_FEATURE_BOUNDS_BUFFER + bounds_3 = ObjectBounds(box_xz=[ + Vector3d(x=2.5 + buffer, y=0, z=2.5 + buffer), + Vector3d(x=3.5 - buffer, y=0, z=2.5 + buffer), + Vector3d(x=3.5 - buffer, y=0, z=3.5 - buffer), + Vector3d(x=2.5 + buffer, y=0, z=3.5 - buffer) + ], max_y=100, min_y=0) + bounds_4 = ObjectBounds(box_xz=[ + Vector3d(x=-3.5 + buffer, y=0, z=-3.5 + buffer), + Vector3d(x=-2.5 - buffer, y=0, z=-3.5 + buffer), + Vector3d(x=-2.5 - buffer, y=0, z=-2.5 - buffer), + Vector3d(x=-3.5 + buffer, y=0, z=-2.5 - buffer) + ], max_y=100, min_y=0) + + # Case 4: 1 hole + scene = Scene(holes=[Vector2dInt(x=3, z=3)], objects=[]) + assert scene.find_bounds() == [bounds_3] + + # Case 5: 2 holes + scene = Scene( + holes=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[] + ) + assert scene.find_bounds() == [bounds_3, bounds_4] + + # Case 6: holes and objects + scene = Scene( + holes=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) + assert scene.find_bounds() == [bounds_3, bounds_4, bounds_1, bounds_2] + + # Case 7: floor textures + scene = Scene(floor_textures=[FloorTexturesConfig( + material='blue', + positions=[Vector2dInt(x=0, z=0)] + )], objects=[]) + assert scene.find_bounds() == [] + + # Case 8: 1 lava area + scene = Scene(lava=[Vector2dInt(x=3, z=3)], objects=[]) + assert scene.find_bounds() == [bounds_3] + + # Case 9: 2 lava areas + scene = Scene( + lava=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[] + ) + assert scene.find_bounds() == [bounds_3, bounds_4] + + # Case 10: lava areas and objects + scene = Scene( + lava=[Vector2dInt(x=3, z=3), Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) + assert scene.find_bounds() == [bounds_3, bounds_4, bounds_1, bounds_2] + + # Case 11: everything + scene = Scene(floor_textures=FloorTexturesConfig( + material='blue', + positions=[Vector2dInt(x=0, z=0)]), + lava=[Vector2dInt(x=3, z=3)], + holes=[Vector2dInt(x=-3, z=-3)], + objects=[ + {'shows': [{'boundingBox': bounds_1}]}, + {'shows': [{'boundingBox': bounds_2}]} + ] + ) + assert scene.find_bounds() == [bounds_4, bounds_3, bounds_1, bounds_2] + + +def test_find_bounds_ignore_id(): + bounds_1 = ObjectBounds(box_xz=[ + Vector3d(x=1, y=0, z=1), + Vector3d(x=2, y=0, z=1), + Vector3d(x=2, y=0, z=2), + Vector3d(x=1, y=0, z=2) + ], max_y=1, min_y=0) + bounds_2 = ObjectBounds(box_xz=[ + Vector3d(x=-1, y=0, z=-1), + Vector3d(x=-2, y=0, z=-1), + Vector3d(x=-2, y=0, z=-2), + Vector3d(x=-1, y=0, z=-2) + ], max_y=1, min_y=0) + + # Case 1: 1 object, ignore 0 + scene = Scene(objects=[ + {'id': 'id_1', 'shows': [{'boundingBox': bounds_1}]} + ]) + assert scene.find_bounds(ignore_ids='absent_id') == [bounds_1] + + # Case 2: 1 object, ignore 1 + scene = Scene(objects=[ + {'id': 'id_1', 'shows': [{'boundingBox': bounds_1}]} + ]) + assert scene.find_bounds(ignore_ids='id_1') == [] + + # Case 3: 2 objects, ignore 1 + scene = Scene(objects=[ + {'id': 'id_1', 'shows': [{'boundingBox': bounds_1}]}, + {'id': 'id_2', 'shows': [{'boundingBox': bounds_2}]} + ]) + assert scene.find_bounds(ignore_ids='id_1') == [bounds_2] + + # Case 4: 2 objects, ignore 2 + scene = Scene(objects=[ + {'id': 'id_1', 'shows': [{'boundingBox': bounds_1}]}, + {'id': 'id_2', 'shows': [{'boundingBox': bounds_2}]} + ]) + assert scene.find_bounds(ignore_ids=['id_1', 'id_2']) == [] diff --git a/tests/similarity_test.py b/tests/similarity_test.py index 46f685b..fdee691 100644 --- a/tests/similarity_test.py +++ b/tests/similarity_test.py @@ -3,12 +3,7 @@ import pytest from machine_common_sense.config_manager import Vector3d -from generator import ( - DefinitionDataset, - ObjectDefinition, - base_objects, - specific_objects -) +from generator import ObjectDefinition, base_objects, specific_objects from generator.definitions import ( create_dataset, do_materials_match, @@ -19,22 +14,17 @@ ) DATASET = specific_objects.get_interactable_definition_dataset(unshuffled=True) -ALL_DEFINITIONS = [ - # Just use a few variations (colors) of each object for faster testing. - definition_variations[:2] - for definition_selections in DATASET._definition_groups - for definition_variations in definition_selections -] -# Reassign the dataset to use the filtered definition list for faster testing. -DATASET = DefinitionDataset([ALL_DEFINITIONS]) +DATASET = DATASET.dataset_unique_shape_scale(keep=2) DEFINITIONS = DATASET.definitions(unshuffled=True) # TODO MCS-1012 Add new shapes/textures that are similar to these objects. SIMILARITY_EXCEPTIONS = [ - 'sofa_4', 'sofa_5', 'sofa_6', 'sofa_7', 'sofa_8', 'sofa_9', - 'sofa_chair_4', 'sofa_chair_5', 'sofa_chair_6', 'sofa_chair_7', - 'sofa_chair_8', 'sofa_chair_9', 'skateboard', 'antique_armchair_1' + 'sofa_4', 'sofa_5', 'sofa_6', 'sofa_7', 'sofa_8', 'sofa_9', 'sofa_11', + 'sofa_12', 'sofa_chair_4', 'sofa_chair_5', 'sofa_chair_6', 'sofa_chair_7', + 'sofa_chair_8', 'sofa_chair_9', 'skateboard', 'antique_armchair_1', + 'antique_armchair_2', 'antique_armchair_3', 'antique_chair_2', + 'antique_chair_3' ] diff --git a/tests/specific_objects_test.py b/tests/specific_objects_test.py index 30d6070..3cfc3fe 100644 --- a/tests/specific_objects_test.py +++ b/tests/specific_objects_test.py @@ -15,14 +15,14 @@ def test_getters_reuse_immutable_dataset(): def validate_datasets(dataset, no_untrained_shape=False): - definitions = dataset.definitions() + definitions = dataset.definitions_unique_shape_scale() assert len(definitions) > 0 assert isinstance(definitions[0], ObjectDefinition) trained_dataset = dataset.filter_on_trained() - assert len(trained_dataset.definitions()) > 0 + assert len(trained_dataset.definitions_unique_shape_scale()) > 0 if not no_untrained_shape: untrained_dataset = dataset.filter_on_untrained('untrainedShape') - assert len(untrained_dataset.definitions()) > 0 + assert len(untrained_dataset.definitions_unique_shape_scale()) > 0 @pytest.mark.slow diff --git a/tests/structures_test.py b/tests/structures_test.py index e81423b..3628e37 100644 --- a/tests/structures_test.py +++ b/tests/structures_test.py @@ -1,12 +1,7 @@ import pytest from machine_common_sense.config_manager import Vector3d -from generator import ( - ALL_LARGE_BLOCK_TOOLS, - MaterialTuple, - ObjectBounds, - structures -) +from generator import MaterialTuple, ObjectBounds, structures def test_create_interior_wall(): @@ -1213,51 +1208,6 @@ def test_create_guilde_rail_around(): assert rail1['materials'] == rail2['materials'] == [mat[0]] -def test_create_tool(): - tool_type = 'tool_rect_1_00_x_4_00' - assert tool_type in ALL_LARGE_BLOCK_TOOLS - tool = structures.create_tool( - object_type=tool_type, - position_x=1, - position_z=2, - rotation_y=180 - ) - - assert tool['id'].startswith('tool_') - assert tool['type'] == tool_type - assert not tool.get('kinematic') - assert not tool.get('structure') - assert tool.get('moveable', True) - assert 'mass' not in tool - assert 'materials' not in tool - assert tool['debug']['color'] == ['grey', 'black'] - assert tool['debug']['info'] == [ - 'grey', 'black', 'tool', 'grey tool', 'black tool', - 'grey black tool' - ] - - assert len(tool['shows']) == 1 - assert tool['shows'][0]['stepBegin'] == 0 - assert tool['shows'][0]['position'] == {'x': 1, 'y': 0.15, 'z': 2} - assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 180, 'z': 0} - assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} - tool_bounds = tool['shows'][0]['boundingBox'] - assert vars(tool_bounds.box_xz[0]) == pytest.approx( - {'x': 0.5, 'y': 0, 'z': 0} - ) - assert vars(tool_bounds.box_xz[1]) == pytest.approx( - {'x': 0.5, 'y': 0, 'z': 4} - ) - assert vars(tool_bounds.box_xz[2]) == pytest.approx( - {'x': 1.5, 'y': 0, 'z': 4} - ) - assert vars(tool_bounds.box_xz[3]) == pytest.approx( - {'x': 1.5, 'y': 0, 'z': 0} - ) - assert tool_bounds.max_y == 0.3 - assert tool_bounds.min_y == 0 - - def test_create_turntable(): turntable_type = 'rotating_cog' mat = MaterialTuple("test_material", ["brown", "blue"]) @@ -1273,7 +1223,7 @@ def test_create_turntable(): step_end=12, movement_rotation=15, material_tuple=mat - )[0] + ) assert turntable['id'].startswith('turntable_') assert turntable['type'] == turntable_type @@ -1314,3 +1264,59 @@ def test_create_turntable(): assert turntable['rotates'][0]['stepBegin'] == 2 assert turntable['rotates'][0]['stepEnd'] == 12 assert turntable['rotates'][0]['vector'] == {'x': 0, 'y': 15, 'z': 0} + + +def test_tube_occluder(): + material_tuple = MaterialTuple("test_material", ["brown", "blue"]) + + tube = structures.create_tube_occluder( + position_x=1, + position_z=2, + radius=3, + room_height=4, + material_tuple=material_tuple, + down_step=6, + up_step=61 + ) + + assert tube['id'].startswith('tube_occluder_') + assert tube['type'] == 'tube_wide' + assert tube.get('kinematic', True) + assert tube.get('structure', True) + assert not tube.get('moveable') + assert not tube.get('pickupable') + assert tube.get('materials', material_tuple) + assert tube['debug']['color'] == ["brown", "blue"] + assert tube['debug']['info'] == [ + 'brown', 'blue', 'tube_occluder', 'brown tube_occluder', + 'blue tube_occluder', 'brown blue tube_occluder' + ] + + assert len(tube['shows']) == 1 + assert tube['shows'][0]['stepBegin'] == 0 + assert tube['shows'][0]['position'] == {'x': 1, 'y': 5.75, 'z': 2} + assert tube['shows'][0]['rotation'] == {'x': 0, 'y': 0, 'z': 0} + assert tube['shows'][0]['scale'] == {'x': 3, 'y': 4.0, 'z': 3} + tube_bounds = tube['shows'][0]['boundingBox'] + assert vars(tube_bounds.box_xz[0]) == pytest.approx( + {'x': 2.5, 'y': 0.0, 'z': 3.5} + ) + assert vars(tube_bounds.box_xz[1]) == pytest.approx( + {'x': 2.5, 'y': 0.0, 'z': 0.5} + ) + assert vars(tube_bounds.box_xz[2]) == pytest.approx( + {'x': -0.5, 'y': 0.0, 'z': 0.5} + ) + assert vars(tube_bounds.box_xz[3]) == pytest.approx( + {'x': -0.5, 'y': 0.0, 'z': 3.5} + ) + assert tube_bounds.min_y == -4 + assert tube_bounds.max_y == -0.25 + + assert len(tube['moves']) == 2 + assert tube['moves'][0]['stepBegin'] == 6 + assert tube['moves'][0]['stepEnd'] == 20 + assert tube['moves'][0]['vector'] == {'x': 0, 'y': -0.25, 'z': 0} + assert tube['moves'][1]['stepBegin'] == 61 + assert tube['moves'][1]['stepEnd'] == 75 + assert tube['moves'][1]['vector'] == {'x': 0, 'y': 0.25, 'z': 0} diff --git a/tests/tools_test.py b/tests/tools_test.py new file mode 100644 index 0000000..13f725d --- /dev/null +++ b/tests/tools_test.py @@ -0,0 +1,129 @@ +import pytest + +from generator import ALL_LARGE_BLOCK_TOOLS, materials, tools + + +def test_get_tool_shape(): + # With length + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.RECT) + assert shape in [ + 'tool_rect_0_50_x_4_00', + 'tool_rect_0_75_x_4_00', + 'tool_rect_1_00_x_4_00' + ] + + # With length and width + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.RECT, 0.5) + assert shape == 'tool_rect_0_50_x_4_00' + + # No length + shape = tools.get_tool_shape(None, tools.TOOL_TYPES.RECT) + assert shape.startswith('tool_rect_') + assert not shape.endswith('1_00') + + # Hooked tools + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.HOOKED) + assert shape in [ + 'tool_hooked_0_50_x_4_00', + 'tool_hooked_0_75_x_4_00', + 'tool_hooked_1_00_x_4_00' + ] + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.HOOKED, 1) + assert shape in ['tool_hooked_1_00_x_4_00'] + + # Isosceles tools + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.ISOSCELES) + assert shape in [ + 'tool_isosceles_0_50_x_4_00', + 'tool_isosceles_0_75_x_4_00', + 'tool_isosceles_1_00_x_4_00' + ] + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.ISOSCELES, 1) + assert shape in ['tool_isosceles_1_00_x_4_00'] + + # Other tools + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.SMALL) + assert shape in [ + 'tool_rect_0_50_x_1_00', + 'tool_rect_0_75_x_1_00', + 'tool_rect_1_00_x_1_00' + ] + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.BROKEN) + assert shape in [ + 'tool_rect_0_50_x_1_00', + 'tool_rect_0_75_x_1_00', + 'tool_rect_1_00_x_1_00' + ] + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.INACCESSIBLE) + assert shape in [ + 'tool_rect_0_50_x_4_00', + 'tool_rect_0_75_x_4_00', + 'tool_rect_1_00_x_4_00' + ] + + # No matching tool shapes + shape = tools.get_tool_shape(20, tools.TOOL_TYPES.RECT) + assert shape is None + shape = tools.get_tool_shape(4, tools.TOOL_TYPES.NO_TOOL) + assert shape is None + + +def test_get_tool_width_from_type(): + assert tools.get_tool_width_from_type('tool_rect_0_50_x_4_00') == 0.5 + assert tools.get_tool_width_from_type('tool_rect_0_63_x_4_00') == 0.63 + assert tools.get_tool_width_from_type('tool_rect_0_75_x_4_00') == 0.75 + assert tools.get_tool_width_from_type('tool_rect_0_88_x_4_00') == 0.88 + assert tools.get_tool_width_from_type('tool_rect_1_00_x_4_00') == 1 + assert tools.get_tool_width_from_type('tool_rect_1_13_x_4_00') == 1.13 + assert tools.get_tool_width_from_type('tool_hooked_0_50_x_4_00') == 0.5 + assert tools.get_tool_width_from_type('tool_hooked_0_75_x_4_00') == 0.75 + assert tools.get_tool_width_from_type('tool_hooked_1_00_x_4_00') == 1 + assert tools.get_tool_width_from_type('tool_isosceles_0_50_x_4_00') == 0.5 + assert tools.get_tool_width_from_type('tool_isosceles_0_75_x_4_00') == 0.75 + assert tools.get_tool_width_from_type('tool_isosceles_1_00_x_4_00') == 1 + + +def test_create_tool(): + tool_type = 'tool_rect_1_00_x_4_00' + assert tool_type in ALL_LARGE_BLOCK_TOOLS + tool = tools.create_tool( + object_type=tool_type, + position_x=1, + position_z=2, + rotation_y=180, + material_tuple=materials.AZURE + ) + + assert tool['id'].startswith('tool_') + assert tool['type'] == tool_type + assert not tool.get('kinematic') + assert not tool.get('structure') + assert tool.get('moveable', True) + assert 'mass' not in tool + assert 'materials' in tool + assert tool['materials'] == ['Custom/Materials/Azure'] + assert tool['debug']['info'] == [ + 'azure', 'blue', 'tool', 'azure tool', 'blue tool', + 'azure blue tool' + ] + + assert len(tool['shows']) == 1 + assert tool['shows'][0]['stepBegin'] == 0 + assert tool['shows'][0]['position'] == {'x': 1, 'y': 0.15, 'z': 2} + assert tool['shows'][0]['rotation'] == {'x': 0, 'y': 180, 'z': 0} + assert tool['shows'][0]['scale'] == {'x': 1, 'y': 1, 'z': 1} + tool_bounds = tool['shows'][0]['boundingBox'] + assert vars(tool_bounds.box_xz[0]) == pytest.approx( + {'x': 0.5, 'y': 0, 'z': 0} + ) + assert vars(tool_bounds.box_xz[1]) == pytest.approx( + {'x': 0.5, 'y': 0, 'z': 4} + ) + assert vars(tool_bounds.box_xz[2]) == pytest.approx( + {'x': 1.5, 'y': 0, 'z': 4} + ) + assert vars(tool_bounds.box_xz[3]) == pytest.approx( + {'x': 1.5, 'y': 0, 'z': 0} + ) + assert tool_bounds.max_y == 0.3 + assert tool_bounds.min_y == 0