Skip to content

Commit

Permalink
Update ball_bin example to use a blend file (#12)
Browse files Browse the repository at this point in the history
Improve shutdown and tmpdir cleanup logic.

Also generally tidy up the README and etc.
  • Loading branch information
jwnimmer-tri authored May 11, 2023
1 parent c125ef5 commit 1c09e63
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 20 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
atop
[Blender](https://www.blender.org/).

**This repository is currently a WORK IN PROGRESS and still has many bugs.
Please check back later once the code has been battle-tested.**
**This is a relatively new project and may still have bugs.
Please share your issues and improvements on GitHub.**

## Compatibility

Expand All @@ -17,16 +17,24 @@ work with any Python interpreter that supports our `requirements.txt`.

## Building and testing

From a git checkout of `drake-blender`:

```sh
./bazel test //...
```

## Running the render server

From a git checkout of `drake-blender`:

```sh
./bazel run :server
```

Note that `server.py` is self-contained. Instead of using Bazel, you can also
run it as a standalone Python program so long as the `requirements.in` packages
are available in your Python runtime environment.

## Examples

See [examples](examples/README.md).
Expand Down
21 changes: 20 additions & 1 deletion WORKSPACE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# SPDX-License-Identifier: BSD-2-Clause

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load(
"@bazel_tools//tools/build_defs/repo:http.bzl",
"http_archive",
"http_file",
)

http_archive(
name = "rules_python",
Expand Down Expand Up @@ -41,3 +45,18 @@ pip_parse(
load("@examples_requirements//:requirements.bzl", "install_deps")

install_deps()

# This is a sample file from https://www.blender.org/download/demo-files/,
# licensed under CC0. Credit goes to Ramil Roosileht for creating it,
# https://twitter.com/limarest_art.
http_file(
name = "color_attribute_painting",
urls = [
base + "/demo/sculpt_mode/color_attribute_painting.blend"
for base in [
"https://mirrors.ocf.berkeley.edu/blender",
"https://mirror.clarkson.edu/blender",
]
],
sha256 = "443b213229a4c863b2015beff623a700886c14928707a2fb24a6dd85fd80a207",
)
6 changes: 5 additions & 1 deletion examples/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ py_binary(
data = [
":ball_bin.yaml",
"//:server",
"@color_attribute_painting//file",
],
deps = [
"@bazel_tools//tools/python/runfiles",
Expand All @@ -23,7 +24,10 @@ py_binary(
py_test(
name = "ball_bin_test",
srcs = ["test/ball_bin_test.py"],
data = [":ball_bin"],
data = [
":ball_bin",
":test/bpy_use_cycles.py",
],
)

pycodestyle_test(
Expand Down
13 changes: 12 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ TODO(jwnimmer-tri) Add more examples!

## Ball Bin example

This (incomplete) example is still a "work in progress".
This shows how to use the Drake Simulator to create videos of a dynamic scene,
where moving objects (some balls) and a fixed object (a bin) are simulated by
Drake and the static visual background (a room with custom lighting) is provided
by a Blender file.

https://github.com/RobotLocomotion/drake-blender/assets/17596505/c0f5f6ae-db51-42cb-9a86-09fa1c9ae18e

From the root directory of the drake-blender source checkout, run:

Expand All @@ -17,9 +22,15 @@ $ ./bazel run //examples:ball_bin
```

This will create a videos named blender_camera.mp4 and vtk_camera.mp4.
The blender-rendered video will show the balls, bin, and room in the background.
The VTK-rendered video will show only the balls and bin.
Expect the video rendering to take 5 minutes or longer.

To create a single photo instead of a movie, use `--still`:

```sh
$ ./bazel run //examples:ball_bin -- --still
```

Thanks to Ramil Roosileht (https://twitter.com/limarest_art) for creating
the blender scene we use (from https://www.blender.org/download/demo-files/).
62 changes: 56 additions & 6 deletions examples/ball_bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@
"""
Demonstrates combining Drake with the Blender render server to create a
simulation video (or still image).
In this demo, moving objects (some balls) and a fixed object (a bin) are
simulated by Drake and the static visual background (a room with custom
lighting) is provided by a Blender file.
"""

import argparse
import dataclasses as dc
import logging
import os
from pathlib import Path
import time
import typing
import signal
import socket
import subprocess
import tempfile

from bazel_tools.tools.python.runfiles import runfiles
import tqdm

from pydrake.common import configure_logging
from pydrake.common.yaml import yaml_load_typed
from pydrake.geometry.render import (
RenderEngineGltfClientParams,
Expand Down Expand Up @@ -147,8 +156,10 @@ def _run(args):

# Simulate.
if args.still:
logging.info("Creating still image(s)")
simulator.AdvanceTo(1e-3)
else:
logging.info("Creating video(s)")
simulator.set_monitor(_ProgressBar(scenario.simulation_duration))
simulator.AdvanceTo(scenario.simulation_duration)
for writer in video_writers:
Expand All @@ -164,18 +175,42 @@ def main():
parser.add_argument(
"--no-server", dest="server", action="store_false",
help="Don't automatically launch the blender server.")
parser.add_argument(
"--bpy_settings_file", metavar="FILE",
help="This flag is forward along to the server, unchanged. "
"Refer to its documentation for details.")
args = parser.parse_args()

scenario_file = _find_resource("__main__/examples/ball_bin.yaml")
setattr(args, "scenario_file", scenario_file)

# Launch the server (if requested).
if args.server:
logging.info("Starting drake-blender server")
server = _find_resource("__main__/server")
server_process = subprocess.Popen(server)
# TODO(jwnimmer-tri) Wait until the server is ready.
time.sleep(1)
assert server_process.poll() is None
blend_file = _find_resource("color_attribute_painting/file/downloaded")
log_file = open(os.environ["TMPDIR"] + "/server-log.txt", "w")
# TODO(jwnimmer-tri) Echo the log file to the console.
command = [
server,
f"--blend_file={blend_file}",
]
if args.bpy_settings_file:
command.append(f"--bpy_settings_file={args.bpy_settings_file}")
server_process = subprocess.Popen(command,
stdout=log_file,
stderr=subprocess.STDOUT)
# Wait until the server is ready.
while True:
with socket.socket() as s:
try:
s.connect(("127.0.0.1", 8000))
# Success!
break
except ConnectionRefusedError as e:
time.sleep(0.1)
assert server_process.poll() is None
logging.info("The drake-blender server is ready")
else:
server_process = None

Expand All @@ -184,12 +219,26 @@ def main():
_run(args)
finally:
if server_process is not None:
server_process.terminate()
server_process.send_signal(signal.SIGINT)
try:
server_process.wait(1.0)
except subprocess.TimeoutExpired:
server_process.terminate()
log_file.flush()


def _wrapped_main():
# Do our best to clean up after ourselves, by advising Drake code to use
# a directory other than /tmp.
with tempfile.TemporaryDirectory(prefix="ball_bin_") as temp_dir:
os.environ["TMPDIR"] = temp_dir
main()


if __name__ == "__main__":
# Tell Drake it that even though it's running from Bazel it's not a source
# build so it needs to use resources from the wheel file not Bazel.
# TODO(jwnimmer-tri) As of Drake >= v1.16.0, this is no longer necessary.
os.environ["DRAKE_RESOURCE_ROOT"] = str(_find_resource(
"examples_requirements_drake/site-packages/"
"pydrake/share/drake/package.xml").parent.parent)
Expand All @@ -198,4 +247,5 @@ def main():
if "BUILD_WORKING_DIRECTORY" in os.environ:
os.chdir(os.environ["BUILD_WORKING_DIRECTORY"])

main()
configure_logging()
_wrapped_main()
22 changes: 14 additions & 8 deletions examples/ball_bin.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# SPDX-License-Identifier: MIT-0
simulation_duration: 3.0
simulation_duration: 2.0
directives:
- add_model:
name: bin
file: package://drake/examples/manipulation_station/models/bin.sdf
- add_weld:
parent: world
child: bin::bin_base
X_PC:
translation: [0, 0, 0.20]
- add_model:
name: ball_1
file: package://drake/examples/manipulation_station/models/sphere.sdf
Expand All @@ -30,15 +32,19 @@ directives:
- add_model:
name: ball_6
file: package://drake/examples/manipulation_station/models/sphere.sdf
default_free_body_pose: { base_link: { translation: [0.03, 0.06, 1.5] } }
default_free_body_pose: { base_link: { translation: [0.03, 0.06, 1.8] } }
- add_model:
name: ball_7
file: package://drake/examples/manipulation_station/models/sphere.sdf
default_free_body_pose: { base_link: { translation: [0.02, 0.07, 1.5] } }
default_free_body_pose: { base_link: { translation: [0.02, 0.07, 2.1] } }
- add_model:
name: ball_8
file: package://drake/examples/manipulation_station/models/sphere.sdf
default_free_body_pose: { base_link: { translation: [0.01, 0.08, 1.5] } }
default_free_body_pose: { base_link: { translation: [0.01, 0.08, 2.4] } }
- add_model:
name: ball_9
file: package://drake/examples/manipulation_station/models/sphere.sdf
default_free_body_pose: { base_link: { translation: [0.00, 0.09, 2.7] } }
cameras:
blender_camera:
name: blender_camera
Expand All @@ -47,8 +53,8 @@ cameras:
height: 1024
fps: 8.0
X_PB:
translation: [1.0, 0.8, 1.25]
rotation: !Rpy { deg: [-130, 5, 125] }
translation: [-4.0, -3.2, 4.8]
rotation: !Rpy { deg: [-130, 5, -55] }
vtk_camera:
name: vtk_camera
renderer_name: vtk
Expand All @@ -57,5 +63,5 @@ cameras:
height: 1024
fps: 8.0
X_PB:
translation: [1.0, 0.8, 1.25]
rotation: !Rpy { deg: [-130, 5, 125] }
translation: [-4.0, -3.2, 4.8]
rotation: !Rpy { deg: [-130, 5, -55] }
8 changes: 7 additions & 1 deletion examples/test/ball_bin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ def test_still_images(self):
# Find the server.
demo_path = Path("examples/ball_bin").absolute().resolve()
self.assertTrue(demo_path.exists(), demo_path)
settings = Path("examples/test/bpy_use_cycles.py").absolute().resolve()
self.assertTrue(settings.exists(), settings)
# Run it.
result = subprocess.run([demo_path, "--still"], cwd=tmpdir)
result = subprocess.run([
demo_path,
"--still",
f"--bpy_settings_file={settings}"
], cwd=tmpdir)
result.check_returncode()
self.assertTrue((tmpdir / "vtk_camera.png").exists())
self.assertTrue((tmpdir / "blender_camera.png").exists())
Expand Down
1 change: 1 addition & 0 deletions examples/test/bpy_use_cycles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bpy.context.scene.render.engine = "CYCLES"

0 comments on commit 1c09e63

Please sign in to comment.