MultiTalk-Code / mesh /bin /meshviewer
ameerazam08's picture
Upload folder using huggingface_hub
6931c7b verified
#!/usr/bin/env python3
import textwrap
__doc__ = textwrap.dedent(
"""
`meshviewer` is a program that allows you to display polygonal
meshes produced by `mesh` package.
Viewing a mesh on a local machine
---------------------------------
The most straightforward use-case is viewing the mesh on the same
machine where it is stored. To do this simply run
```
$ meshviewer view sphere.obj
```
This will create an interactive window with your mesh rendering.
You can render more than one mesh in the same window by passing
several paths to `view` command
```
$ meshviewer view sphere.obj cylinder.obj
```
This will arrange the subplots horizontally in a row. If you want
a grid arrangement, you can specify the grid parameters explicitly
```
$ meshviewer view -nx 2 -ny 2 *.obj
```
Viewing a mesh from a remote machine
------------------------------------
It is also possible to view a mesh stored on a remote machine. To
do this you need mesh to be installed on both the local and the
remote machines. You start by opening an empty viewer window
listening on a network port
```
(local) $ meshviewer open --port 3000
```
To stream a shape to this viewer you have to either pick a port
that is visible from the remote machine or by manually exposing
the port when connecting. For example, through SSH port
forwarding
```
(local) $ ssh -R 3000:127.0.0.1:3000 user@host
```
Then on a remote machine you use `view` command pointing to the
locally forwarded port
```
(remote) $ meshviewer view -p 3000 sphere.obj
```
This should display the remote mesh on your local viewer. In case it
does not it might be caused by the network connection being closed
before the mesh could be sent. To work around this one can try
increasing the timeout up to 1 second
```
(remote) $ meshviewer view -p 3000 --timeout 1 sphere.obj
```
To take a snapshot you should locally run a `snap` command
```
(local) $ meshviewer snap -p 3000 sphere.png
```
""")
import argparse
import logging
import sys
import time
from psbody.mesh.mesh import Mesh
from psbody.mesh.meshviewer import (
MESH_VIEWER_DEFAULT_TITLE,
MESH_VIEWER_DEFAULT_SHAPE,
MESH_VIEWER_DEFAULT_WIDTH,
MESH_VIEWER_DEFAULT_HEIGHT,
ZMQ_HOST,
MeshViewerLocal,
MeshViewerRemote)
logging.basicConfig(level=logging.INFO)
parser_root = argparse.ArgumentParser(
add_help=False,
description="View the polygonal meshes, locally and across the network",
epilog=__doc__,
formatter_class=argparse.RawTextHelpFormatter)
subparsers = parser_root.add_subparsers(dest="command")
subparsers.required = True
parser_open = subparsers.add_parser("open", add_help=False)
parser_open.add_argument(
"-p", "--port",
help="local port to listen for incoming commands",
type=int)
parser_view = subparsers.add_parser("view", add_help=False)
parser_view.add_argument(
"-h", "--host",
help="remote host",
metavar="HOSTNAME",
type=str)
parser_view.add_argument(
"-p", "--port",
help="remote port",
type=int)
parser_view.add_argument(
"-ix", "--subwindow-index-horizontal",
help="horizontal index of the target subwindow",
metavar="INDEX",
type=int)
parser_view.add_argument(
"-iy", "--subwindow-index-vertical",
help="vertical index of the target subwindow",
metavar="INDEX",
type=int)
parser_view.add_argument(
"--timeout",
help="wait for some time after sending the mesh to let it render",
metavar="SECONDS",
type=float,
default=0.5)
parser_view.add_argument(
"filename",
help="path to the mesh file",
type=str,
nargs="+")
for parser in parser_open, parser_view:
window_options = parser.add_argument_group("window options")
window_options.add_argument(
"-t", "--title",
help="window title",
type=str)
window_options.add_argument(
"-ww", "-wx", "--window-width",
help="window width in pixels",
metavar="PIXELS",
type=int)
window_options.add_argument(
"-wh", "-wy", "--window-height",
help="window height in pixels",
metavar="PIXELS",
type=int)
window_options.add_argument(
"-nx", "--subwindow-number-horizontal",
help="number of horizontal subwindows",
metavar="NUMBER",
type=int)
window_options.add_argument(
"-ny", "--subwindow-number-vertical",
help="number of vertical subwindows",
metavar="NUMBER",
type=int)
parser_snap = subparsers.add_parser("snap", add_help=False)
parser_snap.add_argument(
"-h", "--host",
help="remote host",
type=str)
parser_snap.add_argument(
"-p", "--port",
help="remote port",
type=int,
required=True)
parser_snap.add_argument(
"filename",
help="path to the output snapshot",
type=str)
for p in parser_root, parser_open, parser_view, parser_snap:
p.add_argument("--help", action="help")
def dispatch_command(args):
"""
Performs a sanity check of the passed arguments and then
dispatches the appropriate command.
"""
if args.command == "open":
start_server(args)
return
if not args.port:
client = start_local_client(args)
else:
client = start_remote_client(args)
if args.command == "snap":
take_snapshot(client, args)
if args.command == "view":
if args.port is not None:
# Below is a list of contradicting settings: it futile to
# try to change the parameters of a mesh viewer already
# running on a remote machine.
if args.title is not None:
logging.warning(
"--title is ignored when working with remote viewer")
if args.window_width is not None:
logging.warning(
"--window-width is ignored when working with remote viewer")
if args.window_height is not None:
logging.warning(
"--window-height is ignored when working with remote viewer")
if args.subwindow_number_horizontal is not None:
logging.warning(
"--subwindow-number-horizontal is ignored when working "
"with remote viewer")
if args.subwindow_number_vertical is not None:
logging.warning(
"--subwindow-number-vertical is ignored when working "
"with remote viewer")
# This one is a bit different: while it should be
# technically possible to stream the mesh in a specific
# subwindow, we currently don't support that.
if (
args.subwindow_index_horizontal is not None or
args.subwindow_index_vertical is not None
):
logging.warning(
"unfortunately, drawing to a specific subwindow is not "
"supported when working with remote viewer and the first "
"subwindow is going to be used instead")
if (
args.subwindow_index_horizontal is not None and
args.subwindow_index_vertical is None
) or (
args.subwindow_index_horizontal is None and
args.subwindow_index_vertical is not None
):
logging.fatal(
"you have to specify both horizontal "
"and vertical subwindow incides")
return
if (
args.subwindow_index_horizontal is not None and
args.subwindow_index_vertical is not None
):
display_single_subwindow(client, args)
else:
display_multi_subwindows(client, args)
# Basically, wait for send_pyobj() to actually send everything
# before terminating.
time.sleep(args.timeout)
def start_server(args):
"""
Starts a meshviewer window on a local machine.
This function opens a mesh viewer window that listens for command
on a given port.
"""
server = MeshViewerRemote(
titlebar=args.title or MESH_VIEWER_DEFAULT_TITLE,
subwins_vert=args.subwindow_number_vertical or MESH_VIEWER_DEFAULT_SHAPE[1],
subwins_horz=args.subwindow_number_horizontal or MESH_VIEWER_DEFAULT_SHAPE[0],
width=args.window_width or MESH_VIEWER_DEFAULT_WIDTH,
height=args.window_height or MESH_VIEWER_DEFAULT_HEIGHT,
port=args.port)
return server
def start_local_client(args):
"""
Starts a local meshviewer not connected to anywhere.
This function internally opens a mesh viewer window listening on a
random port.
"""
client = MeshViewerLocal(
titlebar=args.title or MESH_VIEWER_DEFAULT_TITLE,
window_width=args.window_width or MESH_VIEWER_DEFAULT_WIDTH,
window_height=args.window_height or MESH_VIEWER_DEFAULT_HEIGHT,
shape=(
args.subwindow_number_vertical or 1,
args.subwindow_number_horizontal or len(args.filename),
),
keepalive=True)
return client
def start_remote_client(args):
"""
Starts a meshviewer client connected to a remote machine.
This function does not create a new window, but is necessary to
stream the mesh to a remote viewer.
"""
client = MeshViewerLocal(
host=args.host or ZMQ_HOST,
port=args.port)
return client
def display_single_subwindow(client, args):
"""
Displays a single mesh in a given subwindow.
"""
ix = args.subwindow_index_horizontal
iy = args.subwindow_index_vertical
try:
subwindow = client.get_subwindows()[iy][ix]
except IndexError:
logging.fatal(
"cannot find subwindow ({}, {}). "
"The current viewer shape is {}x{} subwindows, "
"indexing is zero-based."
.format(ix, iy, *client.shape))
return
meshes = [Mesh(filename=filename) for filename in args.filename]
subwindow.set_static_meshes(meshes)
def display_multi_subwindows(client, args):
"""
Displays a list of meshes. One mesh per subwindow.
"""
grid = client.get_subwindows()
subwindows = [
subwindow
for row in grid
for subwindow in row
]
if len(subwindows) < len(args.filename):
logging.warning(
"cannot display {0} meshes in {1} subwindows. "
"Taking the first {1}.".format(
len(args.filename), len(subwindows)))
for subwindow, filename in zip(subwindows, args.filename):
mesh = Mesh(filename=filename)
subwindow.set_static_meshes([mesh])
def take_snapshot(client, args):
"""
Take snapshot and dump it into a file.
"""
client.save_snapshot(args.filename)
if __name__ == "__main__":
args = parser_root.parse_args()
dispatch_command(args)
sys.exit(0)