Skip to content

add interfacing to javascript side 2d text rendering #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
69 changes: 69 additions & 0 deletions demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,75 @@
"vis.delete()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"MeshCat supports simple 2d texts rendering. For example, to write 2d texts onto a geometry or object:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vis.set_object(g.Box([1, 1, 2]),texts='Hello, world!')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It is also possible to simple write 'floating' texts onto a scene without attached to an object (e.g., for scene description):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vis.delete()\n",
"vis.set_object(g.SceneText('Hello, world!',font_size=100))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and just like the usual geometry/object, the texts can be rotated:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Rz = tf.rotation_matrix(np.pi/2, [0, 0, 1])\n",
"Ry = tf.rotation_matrix(np.pi/2, [0, 1, 0])\n",
"vis.set_transform(Ry.dot(Rz))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Under the hood, the SceneTexts are written onto a `Plane()` geometry, and the plane size can be specified with width and height. These two parameters affect the texts size when the font_size itself is set too large; they would force a font resizing when rendering so as to fit all the texts within the specified plane."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for i in np.linspace(8,2,10):\n",
" vis.set_object(g.SceneText('Hello, world!',width=2*i,height=2*i,font_size=300))\n",
" time.sleep(0.05)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
8 changes: 6 additions & 2 deletions src/meshcat/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
if sys.version_info >= (3, 0):
unicode = str

from .geometry import Geometry, Object, Mesh, MeshPhongMaterial, PointsMaterial, Points
from .geometry import (Geometry, Plane, Object, Mesh,
MeshPhongMaterial, PointsMaterial, Points, TextTexture)

class SetObject:
__slots__ = ["object", "path"]
def __init__(self, geometry_or_object, material=None, path=[]):
def __init__(self, geometry_or_object, material=None, texts=None, path=[]):
if isinstance(geometry_or_object, Object):
if material is not None:
raise(ArgumentError("Please supply either an Object OR a Geometry and a Material"))
Expand All @@ -19,6 +20,9 @@ def __init__(self, geometry_or_object, material=None, path=[]):
if isinstance(material, PointsMaterial):
self.object = Points(geometry_or_object, material)
else:
if texts is not None:
material.map = TextTexture(texts)
material.needsUpdate = True
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, they only serve to simplify the call a little, but it's not too verbose otherwise either. Removed them.

self.object = Mesh(geometry_or_object, material)
self.path = path

Expand Down
52 changes: 52 additions & 0 deletions src/meshcat/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,25 @@ def intrinsic_transform(self):
return np.diag(np.hstack((self.radii, 1.0)))


class Plane(Geometry):

def __init__(self, width=1, height=1, widthSegments=1, heightSegments=1):
super(Plane, self).__init__()
self.width = width
self.height = height
self.widthSegments = widthSegments
self.heightSegments = heightSegments

def lower(self, object_data):
return {
u"uuid": self.uuid,
u"type": u"PlaneGeometry",
u"width": self.width,
u"height": self.height,
u"widthSegments": self.widthSegments,
u"heightSegments": self.heightSegments,
}

"""
A cylinder of the given height and radius. By Three.js convention, the axis of
rotational symmetry is aligned with the y-axis.
Expand Down Expand Up @@ -184,6 +203,26 @@ def lower(self, object_data):
}


class TextTexture(Texture):

def __init__(self, text, font_size=100, font_face='sans-serif',
width=200, height=100, position=[10, 10]):
super(TextTexture, self).__init__()
self.text = text
# font_size will be passed to the JS side as is; however if the
# text width exceeds canvas width, font_size will be reduced.
self.font_size = font_size
self.font_face = font_face

def lower(self, object_data):
return {
u"uuid": self.uuid,
u"type": u"_text",
u"text": unicode(self.text),
u"font_size": self.font_size,
u"font_face": self.font_face,
}

class GenericTexture(Texture):
def __init__(self, properties):
super(GenericTexture, self).__init__()
Expand Down Expand Up @@ -388,3 +427,16 @@ def PointCloud(position, color, **kwargs):
PointsGeometry(position, color),
PointsMaterial(**kwargs)
)


def SceneText(text, **kwargs):
if 'width' in kwargs and 'height' in kwargs:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is quite right. If the user only provides width or height then that argument will be ignored. Can't we just do:

def SceneText(text, width=10, height=10, **kwargs): 
  ...

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed. Changed it.

plane = Plane(kwargs.pop('width'), kwargs.pop('height'))
else:
plane = Plane(width=10,height=10)
return Mesh(
plane,
MeshPhongMaterial(map=TextTexture(text,**kwargs),transparent=True,
needsUpdate=True)
)

5 changes: 3 additions & 2 deletions src/meshcat/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ def jupyter_cell(self):
def __getitem__(self, path):
return Visualizer.view_into(self.window, self.path.append(path))

def set_object(self, geometry, material=None):
return self.window.send(SetObject(geometry, material, self.path))
def set_object(self, geometry, material=None, texts=None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of the texts argument here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return self.window.send(SetObject(geometry, material, texts,
self.path))

def set_transform(self, matrix=np.eye(4)):
return self.window.send(SetTransform(matrix, self.path))
Expand Down