Animating 3D plots

341 views
Skip to first unread message

Aielyn

unread,
Sep 25, 2011, 9:02:30 AM9/25/11
to sage-devel
I feel I should start by noting that I'm new to Sage itself, and I'm
no developer.

Anyway, one thing that I immediately looked for upon compiling Sage on
my system was a way to animate plots. For 2D plots, it was simple, of
course. But I was concerned when I saw that there was no animation
functionality for 3D plots.

Having a little bit of experience with coding in python, and using a
bit of simple initiative, I figured out that 3D animation using
Tachyon in Sage is actually quite trivial (at least in Notebook mode),
in that it follows the exact same code approach used for 2D animation
- indeed, no need to even specify Tachyon, as it seems to default to
that when using the png-generation function that is used by animate.

Anyway, I suppose I'm wondering why such a simple functionality has
not been added to Sage, yet?

Here's how it works:

def png3d(G, dir=None):
d = sage.misc.misc.tmp_dir()
for i, frame in enumerate(G):
filename = '%s/%s'%(d,sage.misc.misc.pad_zeros(i,8))
frame.save(filename + '.png')
return d

def gif3d(animset, delay=20, savefile=None, iterations=0,
show_path=False):
if not savefile:
savefile = sage.misc.misc.graphics_filename(ext='gif')
if not savefile.endswith('.gif'):
savefile += '.gif'
savefile = os.path.abspath(savefile)
d = png3d(animset)
cmd = 'cd "%s"; sage-native-execute convert -delay %s -loop %s
*.png "%s"'%(d, int(delay), int(iterations), savefile)
from subprocess import check_call, CalledProcessError
try:
check_call(cmd, shell=True)
if show_path:
print "Animation saved to file %s." % savefile
except (CalledProcessError, OSError):
print ""
print "Error: ImageMagick does not appear to be installed.
Saving an"
print "animation to a GIF file or displaying an animation
requires"
print "ImageMagick, so please install it and try again."
print ""
print "See www.imagemagick.org, for example."

def animate3dshow(animset, delay=20, iterations=0):
gif3d(animset,delay = delay, iterations = iterations)

Most of this code is just directly from Sage's animate.py, with
appropriate changes. All the comments, obviously useless parts (in
context), etc, have been stripped away, so I could test it quickly. It
produces 3D animations using tachyon, and outputs them in the
notebook, just as desired.

Jonathan

unread,
Sep 26, 2011, 9:40:02 AM9/26/11
to sage-devel
For animations where the function is changing this is a good idea as
we haven't implemented efficient communication with Jmol yet. For
things like spinning, zooming, etc...that functionality is in the 3-D
Jmol applet used for default 3-D display in the notebook.

Jmol can do animation with user control of frame rate and the ability
to step back and forth, it just requires building the input data
sets. Nobody has done that yet.

Jonathan
>             print "Seewww.imagemagick.org, for example."

Aielyn

unread,
Sep 28, 2011, 12:56:31 PM9/28/11
to sage-devel
Looking into the issue of Jmol animation, it took me a while to figure
it out... but it's actually not that complicated.

Right now, a typical "SCRIPT" file might start like this:
------
data "model list"
10
empty
Xx -4.00002829615 -5.00002829615 -6.0
Xx 0.0 -5.00002829615 -6.0
Xx 4.00002829615 -5.00002829615 -6.0
Xx 5.00002829615 -4.00002829615 -6.0
Xx 5.00002829615 0.0 -6.0
Xx 5.00002829615 4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 0.0
Xx -5.00002829615 -4.00002829615 6.0
Xx 5.5 5.5 5.5
end "model list"; show data
------
There are ten points described here - the first nine are the locations
that will hold the labels marking the axes, and the tenth is added to
make sure that the applet scales correctly. Following this, it would
have various other commands organised to prepare the scene. Then, it
would have a bit of script like this:
-----
pmesh obj_863466 "obj_863466.pmesh"
color pmesh [0,0,255]
pmesh obj_979891 "obj_979891.pmesh"
color pmesh [255,0,0]
draw line_556707 diameter 1 curve {-4.00002829615 -4.00002829615
-6.0} {-4.00002829615 4.00002829615 -6.0}
color $line_556707 translucent 0.5 [0,0,0]
-----
These (in this case) represent two "plots" drawn on the same image,
the first coloured blue, the second red, and then the first of the set
of lines framing the plot (the axes, basically) - there would be
twelve such "draw line_#" commands, for the twelve edges of a cube.

To add in animations, you would need to do two things. First, the data
section would need a repetition of the coordinates provided. For
instance, for two frames in the animation, it would look like this:
------
data "model list"
10
empty
Xx -4.00002829615 -5.00002829615 -6.0
Xx 0.0 -5.00002829615 -6.0
Xx 4.00002829615 -5.00002829615 -6.0
Xx 5.00002829615 -4.00002829615 -6.0
Xx 5.00002829615 0.0 -6.0
Xx 5.00002829615 4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 0.0
Xx -5.00002829615 -4.00002829615 6.0
Xx 5.5 5.5 5.5
10
empty
Xx -4.00002829615 -5.00002829615 -6.0
Xx 0.0 -5.00002829615 -6.0
Xx 4.00002829615 -5.00002829615 -6.0
Xx 5.00002829615 -4.00002829615 -6.0
Xx 5.00002829615 0.0 -6.0
Xx 5.00002829615 4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 -6.0
Xx -5.00002829615 -4.00002829615 0.0
Xx -5.00002829615 -4.00002829615 6.0
Xx 5.5 5.5 5.5
end "model list"; show data
------
Notice that the "data" and "end" lines remain, but the lines in
between get repeated as a block? The result of this is that it creates
a frame for each batch of coordinates (they're being stored as atoms,
you see), in this case named 1.1 and 1.2 (it names them this way
automatically). More generally, if you have N coordinates, it will
name them 1.1, 1.2, ... 1.N, with 1.10 following 1.9, and so on.

From here, you need to populate each frame with the axes themselves
(no need to re-mark the axes with the labels) and the plots relevant
to each frame. To do this, the section I noted would look something
like this:
-----
frame 1.1
pmesh obj_863466 "obj_863466.pmesh"
color pmesh [0,0,255]
pmesh obj_979891 "obj_979891.pmesh"
color pmesh [255,0,0]
draw line_556707 diameter 1 curve {-4.00002829615 -4.00002829615
-6.0} {-4.00002829615 4.00002829615 -6.0}
color $line_556707 translucent 0.5 [0,0,0]
... <More of the "draw line_" commands> ...
frame 1.2
pmesh obj_123456 "obj_123456.pmesh"
color pmesh [0,0,255]
pmesh obj_234567 "obj_234567.pmesh"
color pmesh [255,0,0]
draw line_345678 diameter 1 curve {-4.00002829615 -4.00002829615
-6.0} {-4.00002829615 4.00002829615 -6.0}
color $line_345678 translucent 0.5 [0,0,0]
... <More of the "draw line_" commands> ...
frame 1.3
... <You get the point, I think> ...
-----
Once this is done, the remainder of the script can remain as generated
by sage already, which should then be followed by
----
frame 1.1;animation mode loop 0 0;animation fps 10;animation on
----
where the "0 0" after loop can be replaced by decimals indicating the
number of seconds to pause on the first and last frames, respectively,
and the "10" can be replaced by an integer representing the number of
frames per second.

A couple of side notes. First, the scripts as they stand are somewhat
bloated, doing more than is necessary (due to added functionality in
the version of Jmol being used by Sage right now) - specifically, all
of the lines that say "color $line_345678 translucent 0.5 [0,0,0]" can
be replaced by one single line at the end of the "draw line_" series
of commands, in the form "color $line_* translucent 0.5 [0,0,0]",
which will apply that to *all* of the lines (on all frames).

Second, it should actually be possible to create an interactive
version of Jmol that doesn't have to be re-loaded with each change. It
should be fairly trivial, for example, to delete a plot (pmesh) and
replace it with another one, without having to restart the entire load
process. The first step to this would be to have an animation start/
stop control of some sort provided. But I think just getting animation
working to start with would be a good idea, and let the rest of it be
addressed once that step is done.

-Aielyn

Aielyn

unread,
Sep 29, 2011, 12:04:31 PM9/29/11
to sage-devel
OK, I actually have managed to hack up a modification to the 3d
plotting base.pyx file, so that I can make it generate an animation in
jmol... however, it's somewhat clunky, and produces a lot of redundant
script content in the resulting file. However, using it, I have
managed to get an animation running in the notebook.

In order to get it to work, since I'm no expert in coding complex
programs like Sage by any means, I basically just added a bit of extra
code to the "show" and "export_jmol" functions for the
Graphics3d(SageObject) class. The resulting command to make it produce
an animation in jmol ends up being:

P[0].show(moreframes=P[1:])

Here, P is the list of "frames" (so, P[0] may be given from "P[0] =
sphere((1,0,1)) + plot3d(x^2+y^2,(x,-1,1),(y,-1,1))", for instance).
The "moreframes" option lets you put in extra frames - if there are no
extra frames (moreframes=[] is the default), it just performs the same
operations it always has. Unfortunately, my understanding of the code
has reached its limit, at this point, so I cannot identify the
locations of the other changes I need to make. Notably, the code
really requires a way to input the x, y, and z limits of the plot box,
so that the frames can be matched up even when limits of the plots
don't line up, and it also needs fine-tuning to get rid of the excess
fluff in the resulting jmol script file.

Here's a sample set of notebook instructions to be tested with the
code:
x,y=var('x y')
P=[plot3d(cos(2*pi*sqrt((x^2+y^2)/2)+t),(x,-1,1),
(y,-1,1),color='blue',adaptive=True,aspect_ratio=(1,1,1))
+cube((0,0,1.5),color='red').rotate((0,0,1),t/4) for t in
srange(0,2*pi,pi/12)]
P[0].show(moreframes=P[1:])

Here's the modified pieces of code themselves, with the function
descriptions removed to save space - export_jmol:
def export_jmol(self, filename='jmol_shape.jmol',
force_reload=False,
zoom=100, spin=False, background=(1,1,1),
stereo=False,
mesh=False, dots=False,
perspective_depth = True,
orientation = (-764,-346,-545,76.39),
moreframes = [1, 1, 1, 1, 1, []], **ignored_kwds):
# orientation chosen to look same as tachyon
render_params = self.default_render_params()
render_params.mesh = mesh
render_params.dots = dots
render_params.output_file = filename
render_params.force_reload = render_params.randomize_counter =
force_reload
render_params.output_archive = zipfile.ZipFile(filename, 'w',
zipfile.ZIP_DEFLATED, True)
# Render the data
all = flatten_list([self.jmol_repr(render_params), ""])

numextraframes=len(moreframes[5])
if numextraframes:
all_otherframes = []
frame = moreframes[0]
axes = moreframes[1]
frame_aspect_ratio = moreframes[2]
aspect_ratio = moreframes[3]
zoom_other = moreframes[4]
moreframes=moreframes[5]
for framenum in xrange(numextraframes):
MF=moreframes[framenum]._prepare_for_jmol(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom_other)

all_otherframes.append(flatten_list([MF.jmol_repr(render_params),
""]))
f = StringIO()

if render_params.atom_list:
# Load the atom model
f.write('data "model list"\n')
for framenum in xrange(numextraframes+1):
f.write('%s\nempty\n' % (len(render_params.atom_list)
+ 1))
for atom in render_params.atom_list:
f.write('Xx %s %s %s\n' % atom)
f.write('Xx 5.5 5.5 5.5\n') # so the zoom fits the box
f.write('end "model list"; show data\n')
f.write('select *\n')
f.write('wireframe off; spacefill off\n')
f.write('set labelOffset 0 0\n')


# Set the scene background color
f.write('background [%s,%s,%s]\n'%tuple([int(a*255) for a in
background]))
if spin:
f.write('spin ON\n')
else:
f.write('spin OFF\n')
if stereo:
if stereo is True: stereo = "redblue"
f.write('stereo %s\n' % stereo)
if orientation:
f.write('moveto 0 %s %s %s %s\n'%tuple(orientation))

f.write('centerAt absolute {0 0 0}\n')
f.write('zoom %s\n'%zoom)
f.write('frank OFF\n') # jmol logo

if perspective_depth:
f.write('set perspectivedepth ON\n')
else:
f.write('set perspectivedepth OFF\n')

# Put the rest of the object in
f.write("\n".join(all))
if numextraframes:
for framenum in xrange(numextraframes):
f.write('\nframe 1.%s\n'%(framenum+2))
f.write("\n".join(all_otherframes[framenum]))
# Make sure the lighting is correct
f.write("isosurface fullylit; pmesh o* fullylit; set
antialiasdisplay on;\n")
if numextraframes:
f.write("frame 1.1;animation mode loop 0 0;animation fps
10;animation on\n")

render_params.output_archive.writestr('SCRIPT', f.getvalue())
render_params.output_archive.close()

And show:
def show(self, moreframes=[], **kwds):
opts = self._process_viewing_options(kwds)

viewer = opts['viewer']
verbosity = opts['verbosity']
figsize = opts['figsize']
aspect_ratio = opts['aspect_ratio']
frame_aspect_ratio = opts['frame_aspect_ratio']
zoom = opts['zoom']
frame = opts['frame']
axes = opts['axes']

import sage.misc.misc
if 'filename' in kwds:
filename = kwds['filename']
del kwds['filename']
else:
filename = sage.misc.misc.tmp_filename()

from sage.plot.plot import EMBEDDED_MODE, DOCTEST_MODE
ext = None

# Tachyon resolution options
if DOCTEST_MODE:
opts = '-res 10 10'
filename = sage.misc.misc.SAGE_TMP + "/tmp"
elif EMBEDDED_MODE:
opts = '-res %s %s'%(figsize[0]*100, figsize[1]*100)
filename = sage.misc.misc.graphics_filename()[:-4]
else:
opts = '-res %s %s'%(figsize[0]*100, figsize[1]*100)

if DOCTEST_MODE or viewer=='tachyon' or (viewer=='java3d' and
EMBEDDED_MODE):
T = self._prepare_for_tachyon(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom)
tachyon_rt(T.tachyon(), filename+".png", verbosity, True,
opts)
ext = "png"
import sage.misc.viewer
viewer_app = sage.misc.viewer.browser()

if DOCTEST_MODE or viewer=='java3d':
f = open(filename+".obj", "w")
f.write("mtllib %s.mtl\n" % filename)
f.write(self.obj())
f.close()
f = open(filename+".mtl", "w")
f.write(self.mtl_str())
f.close()
ext = "obj"
viewer_app = os.path.join(sage.misc.misc.SAGE_LOCAL, "bin/
sage3d")

if DOCTEST_MODE or viewer=='jmol':
# Temporary hack: encode the desired applet size in the
end of the filename:
# (This will be removed once we have dynamic resizing of
applets in the browser.)
base, ext = os.path.splitext(filename)
fg = figsize[0]
#if fg >= 2:
# fg = 2
filename = '%s-size%s%s'%(base, fg*100, ext)
ext = "jmol"
archive_name = "%s.%s.zip" % (filename, ext)
if EMBEDDED_MODE:
# jmol doesn't seem to correctly parse the ?params
part of a URL
archive_name = "%s-%s.%s.zip" % (filename, randint(0,
1 << 30), ext)

T = self._prepare_for_jmol(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom)
T.export_jmol(archive_name, force_reload=EMBEDDED_MODE,
zoom=zoom*100, moreframes=[frame, axes, frame_aspect_ratio,
aspect_ratio, zoom, moreframes], **kwds)
viewer_app = os.path.join(sage.misc.misc.SAGE_LOCAL, "bin/
jmol")

# We need a script to load the file
f = open(filename + '.jmol', 'w')
f.write('set defaultdirectory "%s"\n' % archive_name)
f.write('script SCRIPT\n')
f.close()

if viewer == 'canvas3d':
T = self._prepare_for_tachyon(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom)
data =
flatten_list(T.json_repr(T.default_render_params()))
f = open(filename + '.canvas3d', 'w')
f.write('[%s]' % ','.join(data))
f.close()
ext = 'canvas3d'

if ext is None:
raise ValueError, "Unknown 3d plot type: %s" % viewer

if not DOCTEST_MODE and not EMBEDDED_MODE:
if verbosity:
pipes = "2>&1"
else:
pipes = "2>/dev/null 1>/dev/null &"
os.system('%s "%s.%s" %s' % (viewer_app, filename, ext,
pipes))

Hopefully, someone more competent at coding, etc, can improve on what
I've done, and make a function suitable for inclusion in the real
package. Right now, it would undoubtedly be too glitchy and too fiddly
to be official.

Aielyn

unread,
Sep 29, 2011, 10:36:36 PM9/29/11
to sage-devel
OK, I've further tweaked the code, so it no longer produces excess
label markers. I did this by having it only produce labels for the
first frame - it then duplicates them for all the other frames.
Unfortunately, I still haven't figured out how to make the frames
match in terms of coordinates - I figure that the easiest thing to do
would be to simply combine all frames in order to generate the
coordinate system, then use that coordinate system for each frame
separately.

Anyway, here's the modifications I made to get rid of duplicated label
atoms:

def _prepare_for_jmol(self, frame, axes, frame_aspect_ratio,
aspect_ratio, zoom, labels=True):
from sage.plot.plot import EMBEDDED_MODE
if EMBEDDED_MODE:
s = 6
else:
s = 3
box_min, box_max =
self._rescale_for_frame_aspect_ratio_and_zoom(s, frame_aspect_ratio,
zoom)
a_min, a_max = self._box_for_aspect_ratio(aspect_ratio,
box_min, box_max)
return self._transform_to_bounding_box(box_min, box_max,
a_min, a_max, frame=frame,
axes=axes, thickness=1,
labels = labels) #
jmol labels are implemented

Here, I added the "labels=True" input, and replaced "labels = True" in
the final line with "labels = labels". This allows me to disable
labels within the code. Then, all I had to do was modify this line in
my modified export_jmol:
MF=moreframes[framenum]._prepare_for_jmol(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom_other)
To say this:
MF=moreframes[framenum]._prepare_for_jmol(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom_other,labels=False)

Jonathan

unread,
Sep 30, 2011, 1:09:10 AM9/30/11
to sage-devel

> Second, it should actually be possible to create an interactive
> version of Jmol that doesn't have to be re-loaded with each change. It
> should be fairly trivial, for example, to delete a plot (pmesh) and
> replace it with another one, without having to restart the entire load
> process.

It would definitely be good to figure out an interactive version. I
keep intending to work on this but have had no time. It would make
interacts work much better. The problem is that by default the server
deletes everything in a cell output div when the cell is
recalculated. I was thinking about putting the jmol in a separate
"applet" div that would only be deleted if the recalculation did not
generate Jmol data. This requires reworking both the python of plot3D
and the notebook javascript. If you've got time to work on this now,
I'm willing to provide insight into Jmol, just not much coding time.
I personally think the interactive version may be higher priority.

Jonathan

Aielyn

unread,
Oct 3, 2011, 12:08:36 AM10/3/11
to sage-devel
Well, I've continued to work on it, if only because I thought it would
be good to get experience with coding something like this. And I think
I've improved things somewhat. I still haven't worked on fixing the
problem with *how* it displays when it comes to animations (that is, I
haven't sorted out animations where frames aren't all well-aligned
already), but I've tweaked things in terms of the interface itself.

So, here are the relevant changes, including all of the previous
changes (where necessary to have it run by just issuing the
appropriate cython compile command and then "sage -b"):

---- /devel/sagenb-main/sagenb/notebook/cell.py - between "elif
F.endswith('.jmol'):" and "elif F.endswith('.jmol.zip'):" lines ----
elif F.endswith('.jmol'):
# If F ends in -size500.jmol then we make the viewer
applet with size 500.
i = F.rfind('-size')
if i != -1:
if F.endswith('a.jmol'):
size = F[i+5:-6]
else:
size = F[i+5:-5]
else:
size = 500

if self.worksheet().docbrowser():
jmol_name = os.path.join(self.directory(), F)
jmol_file = open(jmol_name, 'r')
jmol_script = jmol_file.read()
jmol_file.close()

jmol_script =
jmol_script.replace('defaultdirectory "', 'defaultdirectory "' +
self.url_to_self() + '/')

jmol_file = open(jmol_name, 'w')
jmol_file.write(jmol_script)
jmol_file.close()

if F.endswith('a.jmol'):
script = '<div><script>jmol_applet_anim(%s, "%s?
%d");</script></div>' % (size, url, time.time())
else:
script = '<div><script>jmol_applet(%s, "%s?%d");</
script></div>' % (size, url, time.time())
images.append(script)
elif F.endswith('.jmol.zip'):

(This allows the code to recognise the difference between animated
jmol applets and normal ones, so that the animated ones have animation
control on the interface.

---- /devel/sagenb-main/sagenb/data/sage/js/jmol_lib.js - replacing
function jmol_applet(size, url) ----
function jmol_applet(size, url) {
var s;
jmolSetDocument(cell_writer);
jmolSetAppletCssClass('jmol_applet');
cell_writer.write('<table border=0 bgcolor="white"><tr><td>');
jmolApplet(size, "script " + url, jmol_count);
s = '</td><td><input type="button" onclick="jmol_image(' +
jmol_count +
');return false;" value="Get Image"><br>' +
'<form name="MyForm'+jmol_count+'" action="#"
onsubmit="jmolResize(100*document.MyForm'+jmol_count+'.jmolResizeText'
+ jmol_count + '.value,100*document.MyForm'+jmol_count
+'.jmolResizeText' + jmol_count + '.value,' + jmol_count + ');return
false;"><input type="text" size=2 value=5 name="jmolResizeText' +
jmol_count + '"><input type="submit" value="Resize"></form>' +
'</td></tr></table>';
cell_writer.write(s);
jmol_count += 1;
return s;
}

function jmol_applet_anim(size, url) {
var s;
jmolSetDocument(cell_writer);
jmolSetAppletCssClass('jmol_applet');
cell_writer.write('<table border=0 bgcolor="white"><tr><td>');
jmolApplet(size, "script " + url, jmol_count);
s = '</td><td><input type="button" onclick="jmol_image(' +
jmol_count +
');return false;" value="Get Image"><br>' +
'Animation: <input type="button"
onclick="jmolFrameControl(true,'+jmol_count+')" value="On"><input
type="button" onclick="jmolFrameControl(false,'+jmol_count+')"
value="Off"><br>' +
'<form name="MyForm'+jmol_count+'" action="#"
onsubmit="jmolResize(100*document.MyForm'+jmol_count+'.jmolResizeText'
+ jmol_count + '.value,100*document.MyForm'+jmol_count
+'.jmolResizeText' + jmol_count + '.value,' + jmol_count + ');return
false;"><input type="text" size=2 value=5 name="jmolResizeText' +
jmol_count + '"><input type="submit" value="Resize"></form>' +
'</td></tr></table>';
cell_writer.write(s);
jmol_count += 1;
return s;
}

function jmolFrameControl(onval,jmolcount) {
if (onval) {
jmolScript("frame play",jmolcount);
} else {
jmolScript("frame pause",jmolcount);
}
}

(The first function is the original jmol_applet with some
modifications. The second function is the animation-appropriate
version, and the third function provides the command functionality for
the animations)

---- /devel/sage-main/sage/plot/plot3d/base.pyx - export_jmol
function, main comment removed ----
def export_jmol(self, filename='jmol_shape.jmol',
force_reload=False,
zoom=100, spin=False, background=(1,1,1),
stereo=False,
mesh=False, dots=False,
perspective_depth = True,
orientation = (-764,-346,-545,76.39),
moreframes = [1, 1, 1, 1, 1, []], **ignored_kwds):
render_params = self.default_render_params()
render_params.mesh = mesh
render_params.dots = dots
render_params.output_file = filename
render_params.force_reload = render_params.randomize_counter =
force_reload
render_params.output_archive = zipfile.ZipFile(filename, 'w',
zipfile.ZIP_DEFLATED, True)
# Render the data
all = flatten_list([self.jmol_repr(render_params), ""])

numextraframes=len(moreframes[5])
if numextraframes:
all_otherframes = []
frame = moreframes[0]
axes = moreframes[1]
frame_aspect_ratio = moreframes[2]
aspect_ratio = moreframes[3]
zoom_other = moreframes[4]
moreframes=moreframes[5]
for framenum in xrange(numextraframes):
MF=moreframes[framenum]._prepare_for_jmol(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom_other,labels=False)

---- /devel/sage-main/sage/plot/plot3d/base.pyx - _prepare_for_jmol
function ----
def _prepare_for_jmol(self, frame, axes, frame_aspect_ratio,
aspect_ratio, zoom, labels=True):
from sage.plot.plot import EMBEDDED_MODE
if EMBEDDED_MODE:
s = 6
else:
s = 3
box_min, box_max =
self._rescale_for_frame_aspect_ratio_and_zoom(s, frame_aspect_ratio,
zoom)
a_min, a_max = self._box_for_aspect_ratio(aspect_ratio,
box_min, box_max)
return self._transform_to_bounding_box(box_min, box_max,
a_min, a_max, frame=frame,
axes=axes, thickness=1,
labels = labels) #
jmol labels are implemented

(added "labels=True" in arguments and replaced "labels=True" in return
command with "labels=labels")

---- /devel/sage-main/sage/plot/plot3d/base.pyx - show function, main
comment removed ----
if moreframes:
f = open(filename + 'a.jmol', 'w')
else:
f = open(filename + '.jmol', 'w')
f.write('set defaultdirectory "%s"\n' % archive_name)
f.write('script SCRIPT\n')
f.close()

if viewer == 'canvas3d':
T = self._prepare_for_tachyon(frame, axes,
frame_aspect_ratio, aspect_ratio, zoom)
data =
flatten_list(T.json_repr(T.default_render_params()))
f = open(filename + '.canvas3d', 'w')
f.write('[%s]' % ','.join(data))
f.close()
ext = 'canvas3d'

if ext is None:
raise ValueError, "Unknown 3d plot type: %s" % viewer

if not DOCTEST_MODE and not EMBEDDED_MODE:
if verbosity:
pipes = "2>&1"
else:
pipes = "2>/dev/null 1>/dev/null &"
os.system('%s "%s.%s" %s' % (viewer_app, filename, ext,
pipes))

(note that for animated jmol animations, this puts an "a" on the end
of the filename, prior to the extension. This will cause the function
to fail if cell.py and jmol_lib.js are not amended as noted above)


I *think* that's all of the code that I changed - if the code doesn't
seem to work, let me know, so I can try to find any bits of change
that I may have made. So, what does it do? Right now, it adjusts the
display to have a "Get Image" button rather than a text link, it adds
a resize option (put a "3" into the text box and hit the resize
button, and it'll make the applet 300x300, for instance), and if it's
an animation, it provides animation "on" and "off" buttons. As a test
run, use the following code in the notebook:

x,y=var('x y')
ps1=[plot3d(cos(2*pi*sqrt((x^2+y^2)/2)+t),(x,-1,1),
(y,-1,1),color='blue',adaptive=True,aspect_ratio=(1,1,1))
+cube((0,0,1.5),color='red').rotate((0,0,1),t/4) for t in
srange(0,2*pi,pi/12)]
ps1[0].show(moreframes=ps1[1:])

This should create an animated jmol of a rotating red cube above a
blue plot that appears like a circular wave rippling out from the
centre. To see the animation buttons disappear, and to confirm that
the code still works without animation, just remove
"moreframes=ps1[1:]" from the show command, and it will show the first
frame only, with the resize and get image options, but no animation
option.

I'm still working on it, though. I'm certainly getting a better feel
of how sage's code works, so I may be able to look into "interact"
jmol plots that don't require reloading of the applet. But the next
step is to fix it so that it uses an appropriate scaling to keep the
entire plot stable, rather than having each frame generated with its
own bounds.

Eviatar

unread,
Oct 4, 2011, 12:22:16 AM10/4/11
to sage-...@googlegroups.com
Haven't tried it, but this functionality would be very useful. Have you considered making a ticket on Trac (http://trac.sagemath.org/sage_trac/)? Other people could then change the code.

Dan Drake

unread,
Oct 4, 2011, 1:17:55 AM10/4/11
to sage-...@googlegroups.com
On Sun, 02 Oct 2011 at 09:08PM -0700, Aielyn wrote:
> ---- /devel/sagenb-main/sagenb/notebook/cell.py - between "elif
> F.endswith('.jmol'):" and "elif F.endswith('.jmol.zip'):" lines ----
> elif F.endswith('.jmol'):

You should learn how to use "patch" (or Mercurial). It makes this kind
of thing much easier! The Developer's Guide has lots of information on this:

http://www.sagemath.org/doc/developer/

Dan

--
--- Dan Drake
----- http://mathsci.kaist.ac.kr/~drake
-------

signature.asc
Reply all
Reply to author
Forward
0 new messages