Skip to content
This repository was archived by the owner on Feb 1, 2023. It is now read-only.

Commit 6f95bfa

Browse files
author
Release Manager
committed
Trac #29227: Plotting single text3d results in empty scene with Three.js viewer
Attempting to plot a single piece of text using the three.js viewer results in an empty scene (besides the coordinate frame): {{{ text3d("Hello world!", (1, 2, 3)) }}} Inspecting the javascript in the generated HTML file, the `texts` array is empty: `var texts = []` Adding any other Graphics3d object (even an empty one) to it fixes the problem: {{{ from sage.plot.plot3d.base import Graphics3d text3d("Hello world!", (1, 2, 3)) + Graphics3d() }}} I encountered this in my Ubuntu install based on 9.1.beta3 while working on #29194. I also tried it on my Windows install which is still 8.9, and the bug is present there as well. URL: https://trac.sagemath.org/29227 Reported by: gh-jcamp0x2a Ticket author(s): Joshua Campbell Reviewer(s): Paul Masson, Eric Gourgoulhon, Travis Scrimshaw
2 parents 9591a58 + e76dbdd commit 6f95bfa

File tree

6 files changed

+421
-54
lines changed

6 files changed

+421
-54
lines changed

src/sage/plot/plot3d/base.pyx

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -400,44 +400,11 @@ cdef class Graphics3d(SageObject):
400400
ambient = '{{"color":"{}"}}'.format(Color(.5,.5,.5).html_color())
401401

402402
import json
403-
from sage.plot.plot3d.shapes import arrow3d
404-
points, lines, texts = [], [], []
405-
406-
if not hasattr(self, 'all'):
407-
self += Graphics3d()
408-
for p in self.flatten().all:
409-
if hasattr(p, 'loc'):
410-
color = p._extra_kwds.get('color', 'blue')
411-
opacity = float(p._extra_kwds.get('opacity', 1))
412-
points.append('{{"point":{}, "size":{}, "color":"{}", "opacity":{}}}'.format(
413-
json.dumps(p.loc), p.size, color, opacity))
414-
if hasattr(p, 'points'):
415-
color = p._extra_kwds.get('color', 'blue')
416-
opacity = float(p._extra_kwds.get('opacity', 1))
417-
thickness = p._extra_kwds.get('thickness', 1)
418-
lines.append('{{"points":{}, "color":"{}", "opacity":{}, "linewidth":{}}}'.format(
419-
json.dumps(p.points), color, opacity, thickness))
420-
if hasattr(p, '_trans'):
421-
m = p.get_transformation().get_matrix()
422-
t = (m[0,3], m[1,3], m[2,3])
423-
if hasattr(p.all[0], 'points'):
424-
translated = [[sum(x) for x in zip(t,u)] for u in p.all[0].points]
425-
width = .5 * p.all[0].thickness
426-
color = p.all[0].texture.color
427-
opacity = p.all[0].texture.opacity
428-
self += arrow3d(translated[0], translated[1], width=width, color=color, opacity=opacity)
429-
if hasattr(p.all[0], 'string'):
430-
color = '#' + p.all[0].texture.hex_rgb();
431-
texts.append('{{"text":"{}", "x":{}, "y":{}, "z":{}, "color":"{}"}}'.format(
432-
p.all[0].string, t[0], t[1], t[2], color))
433-
434-
points = '[' + ','.join(points) + ']'
435-
lines = '[' + ','.join(lines) + ']'
436-
texts = '[' + ','.join(texts) + ']'
437-
438-
surfaces = self.json_repr(self.default_render_params())
439-
surfaces = flatten_list(surfaces)
440-
surfaces = '[' + ','.join(surfaces) + ']'
403+
404+
reprs = {'point': [], 'line': [], 'text': [], 'surface': []}
405+
for kind, desc in self.threejs_repr(self.default_render_params()):
406+
reprs[kind].append(desc)
407+
reprs = {kind: json.dumps(descs) for kind, descs in reprs.items()}
441408

442409
from sage.env import SAGE_EXTCODE
443410
with open(os.path.join(
@@ -449,10 +416,10 @@ cdef class Graphics3d(SageObject):
449416
html = html.replace('SAGE_BOUNDS', bounds)
450417
html = html.replace('SAGE_LIGHTS', lights)
451418
html = html.replace('SAGE_AMBIENT', ambient)
452-
html = html.replace('SAGE_TEXTS', str(texts))
453-
html = html.replace('SAGE_POINTS', str(points))
454-
html = html.replace('SAGE_LINES', str(lines))
455-
html = html.replace('SAGE_SURFACES', str(surfaces))
419+
html = html.replace('SAGE_TEXTS', str(reprs['text']))
420+
html = html.replace('SAGE_POINTS', str(reprs['point']))
421+
html = html.replace('SAGE_LINES', str(reprs['line']))
422+
html = html.replace('SAGE_SURFACES', str(reprs['surface']))
456423

457424
from sage.repl.rich_output.output_catalog import OutputSceneThreejs
458425
return OutputSceneThreejs(html);
@@ -1164,6 +1131,21 @@ end_scene""" % (render_params.antialiasing,
11641131
"""
11651132
return []
11661133

1134+
def threejs_repr(self, render_params):
1135+
"""
1136+
A flat list of ``(kind, desc)`` tuples where ``kind`` is one of:
1137+
'point', 'line', 'text', or 'surface'; and where ``desc`` is a dictionary
1138+
describing a point, line, text, or surface.
1139+
1140+
EXAMPLES::
1141+
1142+
sage: G = sage.plot.plot3d.base.Graphics3d()
1143+
sage: G.threejs_repr(G.default_render_params())
1144+
[]
1145+
1146+
"""
1147+
return []
1148+
11671149
def texture_set(self):
11681150
"""
11691151
Often the textures of a 3d file format are kept separate from the
@@ -2149,6 +2131,31 @@ class Graphics3dGroup(Graphics3d):
21492131
"""
21502132
return [g.jmol_repr(render_params) for g in self.all]
21512133

2134+
def threejs_repr(self, render_params):
2135+
r"""
2136+
The three.js representation of a group is the concatenation of the
2137+
representations of its objects.
2138+
2139+
EXAMPLES::
2140+
2141+
sage: G = point3d((1,2,3)) + point3d((4,5,6)) + line3d([(1,2,3), (4,5,6)])
2142+
sage: G.threejs_repr(G.default_render_params())
2143+
[('point',
2144+
{'color': '#6666ff', 'opacity': 1.0, 'point': (1.0, 2.0, 3.0), 'size': 5.0}),
2145+
('point',
2146+
{'color': '#6666ff', 'opacity': 1.0, 'point': (4.0, 5.0, 6.0), 'size': 5.0}),
2147+
('line',
2148+
{'color': '#6666ff',
2149+
'linewidth': 1.0,
2150+
'opacity': 1.0,
2151+
'points': [(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)]})]
2152+
2153+
"""
2154+
reprs = []
2155+
for g in self.all:
2156+
reprs += g.threejs_repr(render_params)
2157+
return reprs
2158+
21522159
def stl_binary_repr(self, render_params):
21532160
r"""
21542161
The stl binary representation of a group is simply the
@@ -2405,6 +2412,29 @@ class TransformGroup(Graphics3dGroup):
24052412
render_params.pop_transform()
24062413
return rep
24072414

2415+
def threejs_repr(self, render_params):
2416+
r"""
2417+
Transformations for three.js are applied at the leaf nodes.
2418+
2419+
EXAMPLES::
2420+
2421+
sage: G = point3d((1,2,3)) + point3d((4,5,6))
2422+
sage: G = G.translate(-1, -2, -3).scale(10)
2423+
sage: G.threejs_repr(G.default_render_params())
2424+
[('point',
2425+
{'color': '#6666ff', 'opacity': 1.0, 'point': (0.0, 0.0, 0.0), 'size': 5.0}),
2426+
('point',
2427+
{'color': '#6666ff',
2428+
'opacity': 1.0,
2429+
'point': (30.0, 30.0, 30.0),
2430+
'size': 5.0})]
2431+
2432+
"""
2433+
render_params.push_transform(self.get_transformation())
2434+
rep = Graphics3dGroup.threejs_repr(self, render_params)
2435+
render_params.pop_transform()
2436+
return rep
2437+
24082438
def get_transformation(self):
24092439
"""
24102440
Return the actual transformation object associated with ``self``.
@@ -2600,6 +2630,28 @@ cdef class PrimitiveObject(Graphics3d):
26002630
"""
26012631
return self.triangulation().jmol_repr(render_params)
26022632

2633+
def threejs_repr(self, render_params):
2634+
r"""
2635+
Default behavior is to render the triangulation.
2636+
2637+
EXAMPLES::
2638+
2639+
sage: from sage.plot.plot3d.base import PrimitiveObject
2640+
sage: class SimpleTriangle(PrimitiveObject):
2641+
....: def triangulation(self):
2642+
....: return polygon3d([(0,0,0), (1,0,0), (0,1,0)])
2643+
sage: G = SimpleTriangle()
2644+
sage: G.threejs_repr(G.default_render_params())
2645+
[('surface',
2646+
{'color': '#0000ff',
2647+
'faces': [[0, 1, 2]],
2648+
'opacity': 1.0,
2649+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': 0.0},
2650+
{'x': 1.0, 'y': 0.0, 'z': 0.0},
2651+
{'x': 0.0, 'y': 1.0, 'z': 0.0}]})]
2652+
2653+
"""
2654+
return self.triangulation().threejs_repr(render_params)
26032655

26042656

26052657
class BoundingSphere(SageObject):

src/sage/plot/plot3d/implicit_surface.pyx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,30 @@ cdef class ImplicitSurface(IndexFaceSet):
11441144
self.triangulate()
11451145
return IndexFaceSet.json_repr(self, render_params)
11461146

1147+
def threejs_repr(self, render_params):
1148+
r"""
1149+
Return a represention of the surface suitable for plotting with three.js.
1150+
1151+
EXAMPLES::
1152+
1153+
sage: from sage.plot.plot3d.implicit_surface import ImplicitSurface
1154+
sage: _ = var('x,y,z')
1155+
sage: G = ImplicitSurface(x + y + z, (x,-1, 1), (y,-1, 1), (z,-1, 1))
1156+
sage: G.threejs_repr(G.default_render_params())
1157+
[('surface',
1158+
{'color': '#6666ff',
1159+
'faces': [[0, 1, 2],
1160+
...
1161+
'opacity': 1.0,
1162+
'vertices': [{'x': ...,
1163+
'y': -0.9743589743589...,
1164+
'z': -0.02564102564102...},
1165+
...
1166+
{'x': -1.0, 'y': 0.9487179487179..., 'z': 0.05128205128205...}]})]
1167+
"""
1168+
self.triangulate()
1169+
return IndexFaceSet.threejs_repr(self, render_params)
1170+
11471171
def triangulate(self, force=False):
11481172
"""
11491173
The IndexFaceSet will be empty until you call this method,

src/sage/plot/plot3d/index_face_set.pyx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,124 @@ cdef class IndexFaceSet(PrimitiveObject):
12841284

12851285
return json
12861286

1287+
def threejs_repr(self, render_params):
1288+
r"""
1289+
Return representation of the surface suitable for plotting with three.js.
1290+
1291+
EXAMPLES:
1292+
1293+
A simple triangle::
1294+
1295+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
1296+
sage: G.threejs_repr(G.default_render_params())
1297+
[('surface',
1298+
{'color': '#0000ff',
1299+
'faces': [[0, 1, 2]],
1300+
'opacity': 1.0,
1301+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': 1.0},
1302+
{'x': 1.0, 'y': 1.0, 'z': 1.0},
1303+
{'x': 2.0, 'y': 0.0, 'z': 1.0}]})]
1304+
1305+
The same but with more options applied::
1306+
1307+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)], color='red', opacity=0.5,
1308+
....: render_order=2, threejs_flat_shading=True,
1309+
....: single_side=True, mesh=True)
1310+
sage: G.threejs_repr(G.default_render_params())
1311+
[('surface',
1312+
{'color': '#ff0000',
1313+
'faces': [[0, 1, 2]],
1314+
'opacity': 0.5,
1315+
'renderOrder': 2.0,
1316+
'showMeshGrid': True,
1317+
'singleSide': True,
1318+
'useFlatShading': True,
1319+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': 1.0},
1320+
{'x': 1.0, 'y': 1.0, 'z': 1.0},
1321+
{'x': 2.0, 'y': 0.0, 'z': 1.0}]})]
1322+
1323+
TESTS:
1324+
1325+
Transformations apply to the surface's vertices::
1326+
1327+
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)]).scale(2,1,-1)
1328+
sage: G.threejs_repr(G.default_render_params())
1329+
[('surface',
1330+
{'color': '#0000ff',
1331+
'faces': [[0, 1, 2]],
1332+
'opacity': 1.0,
1333+
'vertices': [{'x': 0.0, 'y': 0.0, 'z': -1.0},
1334+
{'x': 2.0, 'y': 1.0, 'z': -1.0},
1335+
{'x': 4.0, 'y': 0.0, 'z': -1.0}]})]
1336+
1337+
Per-face colors::
1338+
1339+
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
1340+
sage: from sage.plot.plot3d.texture import Texture
1341+
sage: point_list = [(2,0,0),(0,2,0),(0,0,2),(0,1,1),(1,0,1),(1,1,0)]
1342+
sage: face_list = [[0,4,5],[3,4,5],[2,3,4],[1,3,5]]
1343+
sage: col = rainbow(10, 'rgbtuple')
1344+
sage: t_list=[Texture(col[i]) for i in range(10)]
1345+
sage: S = IndexFaceSet(face_list, point_list, texture_list=t_list)
1346+
sage: S.threejs_repr(S.default_render_params())
1347+
[('surface',
1348+
{'faceColors': ['#ff0000', '#ff9900', '#cbff00', '#33ff00'],
1349+
'faces': [[0, 4, 5], [3, 4, 5], [2, 3, 4], [1, 3, 5]],
1350+
'opacity': 1.0,
1351+
'vertices': [{'x': 2.0, 'y': 0.0, 'z': 0.0},
1352+
{'x': 0.0, 'y': 2.0, 'z': 0.0},
1353+
{'x': 0.0, 'y': 0.0, 'z': 2.0},
1354+
{'x': 0.0, 'y': 1.0, 'z': 1.0},
1355+
{'x': 1.0, 'y': 0.0, 'z': 1.0},
1356+
{'x': 1.0, 'y': 1.0, 'z': 0.0}]})]
1357+
1358+
"""
1359+
surface = {}
1360+
1361+
vertices = []
1362+
cdef Transformation transform = render_params.transform
1363+
cdef point_c res
1364+
for i from 0 <= i < self.vcount:
1365+
if transform is None:
1366+
res = self.vs[i]
1367+
else:
1368+
transform.transform_point_c(&res, self.vs[i])
1369+
vertices.append(dict(x=float(res.x), y=float(res.y), z=float(res.z)))
1370+
surface['vertices'] = vertices
1371+
1372+
faces = []
1373+
cdef face_c face
1374+
for i from 0 <= i < self.fcount:
1375+
face = self._faces[i]
1376+
faces.append([int(face.vertices[j]) for j from 0 <= j < face.n])
1377+
surface['faces'] = faces
1378+
1379+
if self.global_texture:
1380+
surface['color'] = '#' + str(self.texture.hex_rgb())
1381+
else:
1382+
face_colors = []
1383+
for i from 0 <= i < self.fcount:
1384+
face = self._faces[i]
1385+
color = Color(face.color.r, face.color.g, face.color.b)
1386+
face_colors.append(str(color.html_color()))
1387+
surface['faceColors'] = face_colors
1388+
1389+
surface['opacity'] = float(self._extra_kwds.get('opacity', 1.0))
1390+
1391+
if 'render_order' in self._extra_kwds:
1392+
surface['renderOrder'] = float(self._extra_kwds['render_order'])
1393+
1394+
if self._extra_kwds.get('single_side'):
1395+
surface['singleSide'] = True
1396+
1397+
if self._extra_kwds.get('threejs_flat_shading'):
1398+
surface['useFlatShading'] = True
1399+
1400+
if self._extra_kwds.get('mesh'):
1401+
surface['showMeshGrid'] = True
1402+
1403+
return [('surface', surface)]
1404+
12871405
def obj_repr(self, render_params):
12881406
"""
12891407
Return an obj representation for ``self``.

src/sage/plot/plot3d/parametric_surface.pyx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,28 @@ cdef class ParametricSurface(IndexFaceSet):
309309
self.triangulate(render_params)
310310
return IndexFaceSet.json_repr(self, render_params)
311311

312+
def threejs_repr(self, render_params):
313+
r"""
314+
Return a represention of the surface suitable for plotting with three.js.
315+
316+
EXAMPLES::
317+
318+
sage: _ = var('x,y')
319+
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
320+
sage: P.threejs_repr(P.default_render_params())
321+
[('surface',
322+
{'color': '#6666ff',
323+
'faces': [[0, 1, 2, 3],
324+
...
325+
'opacity': 1.0,
326+
'vertices': [{'x': -2.0, 'y': -2.0, 'z': 0.0},
327+
...
328+
{'x': 2.0, 'y': 2.0, 'z': 0.0}]})]
329+
330+
"""
331+
self.triangulate(render_params)
332+
return IndexFaceSet.threejs_repr(self, render_params)
333+
312334
def is_enclosed(self):
313335
"""
314336
Return a boolean telling whether or not it is necessary to
@@ -723,7 +745,7 @@ cdef class ParametricSurface(IndexFaceSet):
723745
Draw a 3D plot of this graphics object, which just returns this
724746
object since this is already a 3D graphics object.
725747
Needed to support PLOT in doctrings, see :trac:`17498`
726-
748+
727749
EXAMPLES::
728750
729751
sage: S = parametric_plot3d( (sin, cos, lambda u: u/10), (0, 20))
@@ -754,7 +776,7 @@ class MoebiusStrip(ParametricSurface):
754776
755777
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip
756778
sage: M = MoebiusStrip(3,3)
757-
sage: M.show()
779+
sage: M.show()
758780
"""
759781

760782
def __init__(self, r, width, twists=1, **kwds):

0 commit comments

Comments
 (0)