In the object scripts we looked at in previous chapters, we used the script variable to interact with the rest of the program. In tool scripts, you use a different variable called window. This is a reference to the window (artofillusion.LayoutWindow) from which the script was executed. The following examples show some of the things you can do with it.
Here is a very simple tool script, which selects every sphere in the scene:
scene = window.getScene(); for (i = 0; i < scene.getNumObjects(); i++) if (scene.getObject(i).getObject() instanceof Sphere) window.addToSelection(i);
To execute it, select Tools->Edit Script. Type or paste in the script, then click "Execute".
This script is so simple, it hardly needs explanation. We call window.getScene() to get a reference to the scene, just as we called script.getScene() from object scripts. We loop over each object in the scene. scene.getObject(i) returns the ObjectInfo, and getObject() returns the corresponding Object3D. If it is a sphere, we call window.addToSelection() to select it.
Here is a slightly more complicated script. It looks for a texture named "glass", then sets every object which is currently selected to use this texture:
scene = window.getScene(); glass = scene.getTexture("glass"); if (glass == null) { new MessageDialog(window, "There is no texture called 'glass'."); return; } for (i = 0; i < scene.getNumObjects(); i++) if (window.isObjectSelected(i)) scene.getObject(i).setTexture(glass, glass.getDefaultMapping(scene.getObject(i).getObject()));
If there is no texture called "glass", it displays a warning message and returns immediately. This is the first script we have seen that can actually present its own user interface. Although this is a trivial example, interfaces are not limited to simple messages like this. Here is a more complex example: a script which generates spiral seashells. It first asks the user to enter various parameters, then creates a seashell based on them. ValueField, ComponentsDialog, and MessageDialog (used in the script above) are all classes in the artofillusion.ui package.
lengthField = new ValueField(30, ValueField.POSITIVE+ValueField.INTEGER); heightField = new ValueField(0.1, ValueField.POSITIVE); widthField = new ValueField(0.05, ValueField.POSITIVE); thicknessField = new ValueField(0.1, ValueField.POSITIVE); dlg = new ComponentsDialog(window, "Select Parameters for Seashell", (Widget []) [lengthField, heightField, widthField, thicknessField], (String []) ["Length", "Height Scale", "Width Scale", "Thickness Scale"]); if (!dlg.clickedOk()) return; numVert = lengthField.getValue(); heightScale = heightField.getValue(); widthScale = widthField.getValue(); thicknessScale = thicknessField.getValue(); vert = new Vec3 [numVert]; rad = new double [numVert]; smooth = new float [numVert]; height = new double [numVert]; for (i = 0; i < numVert; i++) { angle = i*0.25*Math.PI; rad[i] = thicknessScale*angle; height[i] = heightScale*angle; if (i >= 8) height[i] += height[i-8]; vert[i] = new Vec3(widthScale*angle*Math.cos(angle), height[i], widthScale*angle*Math.sin(angle)); smooth[i] = 1.0f; } shell = new Tube(vert, smooth, rad, Tube.INTERPOLATING, Tube.OPEN_ENDS); window.addObject(shell, new CoordinateSystem(), "Seashell", null);
This script is obviously intended to be used many times, not just typed in, executed once, and then discarded. You can use the "Load" and "Save" buttons in the script editing window to load and save scripts to disk.
Try saving this script in the ArtOfIllusion/Scripts/Tools directory. Name the file Seashell.groovy. If you now go to the Tools menu, you will discover that a new item has appeared in the Scripts submenu called "Seashell". Any script saved in this directory will automatically be added to the Tools menu. This allows you to effectively add new commands to Art of Illusion.
Here is an even more complicated script, which performs "batch rendering" of many images at once. It prompts the user to select a folder of scene files and various rendering options. It then loops over each scene in the folder, loads each one, renders an image from the viewpoint of every camera in the scene, and saves them to disk as JPEG images.
// Prompt the user to select a directory. fc = new BFileChooser(BFileChooser.SELECT_FOLDER, "Select a Folder of Scene Files"); if (!fc.showDialog(window)) return; // Prompt the user for other options. rendererChoice = new BComboBox(); renderers = PluginRegistry.getPlugins(Renderer.class); for (i = 0; i < renderers.size(); i++) rendererChoice.add(renderers.get(i).getName()); widthField = new ValueField(400, ValueField.POSITIVE+ValueField.INTEGER); heightField = new ValueField(300, ValueField.POSITIVE+ValueField.INTEGER); qualitySlider = new ValueSlider(0, 100, 100, 80); dlg = new ComponentsDialog(window, "Select Parameters for Rendered Images", (Widget []) [rendererChoice, widthField, heightField, qualitySlider], (String []) ["Renderer", "Width", "Height", "Quality"]); if (!dlg.clickedOk()) return; rend = renderers.get(rendererChoice.getSelectedIndex()); width = (int) widthField.getValue(); height = (int) heightField.getValue(); quality = (int) qualitySlider.getValue(); // Display the main configuration panel for the renderer. dlg = new PanelDialog(window, "Select Rendering Options", rend.getConfigPanel()); if (!dlg.clickedOk()) return; rend.recordConfiguration(); // Loop over files in the directory. dir = fc.getSelectedFile(); files = dir.list(); for (i = 0; i < files.length; i++) { if (!files[i].endsWith(".aoi")) continue; // Load the scene and find the cameras in it. sc = new Scene(new File(dir, files[i]), true); for (j = 0; j < sc.getNumObjects(); j++) { obj = sc.getObject(j); if (!(obj.getObject() instanceof SceneCamera)) continue; // Render an image and save it to disk. print("Rendering "+files[i]+" from camera "+obj.name); image = obj.getObject().renderScene(sc, width, height, rend, obj.coords); filename = files[i].substring(0, files[i].length()-4)+" "+obj.name+".jpg"; outfile = new File(dir, filename); ImageSaver.saveImage(image, outfile, ImageSaver.FORMAT_JPEG, quality); } }
Although this is a much longer script than the ones we have seen before, it actually contains very few new elements. Note the call to PluginRegistry.getPlugins() to get the list of all installed renderers. Renderers (artofillusion.Renderer) are one of the many types of plugin used by Art of Illusion. Once the user has selected a renderer, we call getConfigPanel() on it. This returns a panel in which the user can enter any configuration options which are specific to that renderer. We then call recordConfiguration() to actually record the options they selected.
Loading the scene from disk is easy: we simply pass a File object to the Scene object constructor. Rendering is accomplished by calling renderScene() on a SceneCamera. This renders the image synchronously. (That is, the method does not return until rendering is complete.) Alternatively, you can render images asynchronously by calling the renderer's renderScene() method. Finally, we use the ImageSaver class to save the image to disk in JPEG format.