blob: d5a651f804539c9f95d86aa169fa7ccfa2635dd1 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Writing POV-Ray Support for NetBeans VII&#8212;Support For Running POV-Ray</title>
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/>
<meta name="AUDIENCE" content="NBUSER"/>
<meta name="TYPE" content="ARTICLE"/>
<meta name="EXPIRES" content="N"/>
<meta name="developer" content="geertjan.wielenga@oracle.com"/>
<meta name="indexed" content="y"/>
<meta name="description"
content="NetBeans POV-Ray Support Tutorial Part VII&#8212;How to launch an external process and monitor its output in the output window"/>
<!-- Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1>Writing POV-Ray Support for NetBeans VII&#8212;Support For Running POV-Ray</h1>
<p>This is a continuation of the tutorial for building a POV-Ray rendering application on
the NetBeans Platform. If you have not read the <a href="nbm-povray-1.html">first</a>,
<a href="nbm-povray-2.html">second</a>, <a href="nbm-povray-3.html">third</a>,
<a href="nbm-povray-4.html">fourth</a>, <a href="nbm-povray-5.html">fifth</a>, and
<a href="nbm-povray-6.html">sixth</a>
parts of this tutorial, you may want to start there.</p>
<h2 class="tutorial"><a name="executing"></a>Obtaining POV-Ray</h2>
<p>At this point, we're almost ready to run code, so it would be a good idea
to download a copy of POV-Ray. </p>
<ul>
<li>Official builds can be obtained from
<a href="http://povray.org">povray.org</a>.
<!-- but there is a caveat&#8212;the
Windows version of POV-Ray is a GUI executable, which will want to open
an editor rather than render your file to disk. So Windows users should
download an &quot;unofficial&quot; build such as
<a href="http://www.imagico.de/files/povcyg_350c.zip">this one</a>, or create
a command-line build using cygwin and gcc.-->
</li>
<li>Macintosh users may find
<a href="http://darwinports.opendarwin.org/">DarwinPorts</a> the easiest
way&#8212;simply install DarwinPorts and then run
<code>sudo port install povray</code>.</li>
<li>Linux and other Unix users should be
fine with the downloads available from <a href="http://povray.org">povray.org</a>,
using <a href="http://povray.org/download/linux.php">these instructions</a>.
Everything should work out of the box for these users, without any
tweaks or post-install configurations. Make sure, however, that the
POV-Ray launcher has the correct permissions, otherwise it will not
execute. Run it from the command line to check that it executes.</li>
</ul>
<p class="notes"><b>Note:</b> For details on setting up POV-Ray, especially
for detailed instructions for Windows, <a href="nbm-povray-10.html#appendix">see the Appendix</a>.</p>
<h2 class="tutorial"><a name="setup"></a>Executing POV-Ray and Displaying the Output</h2>
<p>At this point, we are ready to write the code that will actually invoke the
external POV-Ray executable, pass it the correct arguments and display its
output.</p>
<p>At the end of this part of the tutorial, you will have an application
that looks like this (click to enlarge the image):</p>
<p><a href="../images/tutorials/povray/71/ch7/pic1.png"><img alt="" src="../images/tutorials/povray/71/ch7/pic2.png"/></a></p>
<p>POV-Ray has two kinds of output: It will write out status and
success failure on the command line, in a similar way to what a compiler
does, and it will write out an image file on disk, which we will want to
display.</p>
<p>The first part is just being able to invoke the external executable.
NetBeans has some API classes that can help with that.</p>
<div class="indent">
<ol>
<li><p>Add the following constructor and fields to the <code>Povray</code>
class:</p>
<pre class="examplecode"> private final RendererServiceImpl renderService;
private final FileObject toRender;
private final Properties settings;
Povray (RendererServiceImpl renderService, FileObject toRender, Properties settings) {
this.renderService = renderService;
this.toRender = toRender;
this.settings = settings == null ? renderService.getRendererSettings(
renderService.getPreferredRendererSettingsName()) : settings;
}</pre>
</li>
<li><p>Next we will implement a method that will find the file to render.
We were passed a <code>FileObject</code>, but now we need an actual
<code>java.io.File</code> to get the path from, to pass on the command
line to POV-Ray. There are two caveats: 1. The file passed to the
constructor may be null&#8212;in that case we should find the main file of
the project and use that; and 2. It is conceivable that the file will
not exist on disk&#8212;NetBeans filesystems are virtual, after all, and
the file could exist in a remote FTP filesystem or such. Since NetBeans
4.0, this is rather unlikely, but we should still test for this condition
(<code>FileUtil.toFile()</code> returns null). So we will add a method
to <code>Povray</code> as follows:</p>
<pre class="examplecode">private File getFileToRender() throws IOException {
FileObject render = toRender;
if (render == null) {
PovrayProject proj = renderService.getProject();
MainFileProvider provider = (MainFileProvider)
proj.getLookup().lookup (MainFileProvider.class);
if (provider == null) {
throw new IllegalStateException ("Main file provider missing");
}
render = provider.getMainFile();
if (render == null) {
ProjectInformation info = (ProjectInformation)
proj.getLookup().lookup(ProjectInformation.class);
//XXX let the user choose
throw new IOException (NbBundle.getMessage(Povray.class,
"MSG_NoMainFile", info.getDisplayName()));
}
}
assert render != null;
File result = FileUtil.toFile (render);
if (result == null) {
throw new IOException (NbBundle.getMessage(Povray.class,
"MSG_VirtualFile", render.getName()));
}
assert result.exists();
assert result.isFile();
return result;
}</pre>
</li>
<li><p>Next we need to assemble the command-line arguments that need to
be passed to POV-Ray. These take the form of
<code>+[some character][somevalue]</code>, for example,
<code>+A0.9</code> sets the anti-aliasing parameter to 0.9 pixels.
So we need to iterate the <code>Properties</code> object passed to the
constructor and assemble from it a set of command line arguments:</p>
<pre class="examplecode">private String getCmdLineArgs(File includesDir) {
StringBuilder cmdline = new StringBuilder();
for (Iterator i=settings.keySet().iterator(); i.hasNext();) {
String key = (String) i.next();
String val = settings.getProperty(key);
cmdline.append ('+');
cmdline.append (key);
cmdline.append (val);
cmdline.append (' ');
}
cmdline.append ("+L");
cmdline.append (includesDir.getPath());
return cmdline.toString();
}</pre>
</li>
<li><p>Next we need to implement a couple of utility methods that the
rendering method will use:</p>
<pre class="examplecode">private File getImagesDir() {
PovrayProject proj = renderService.getProject();
FileObject fob = proj.getImagesFolder(true);
File result = FileUtil.toFile(fob);
assert result != null && result.exists();
return result;
}
private String stripExtension(File f) {
String sceneName = f.getName();
int endIndex;
if ((endIndex = sceneName.lastIndexOf('.')) != -1) {
sceneName = sceneName.substring(0, endIndex);
}
return sceneName;
}</pre>
<p>Neither is terribly exciting&#8212;one gets the images directory from the
project as a <code>java.io.File</code>, and the other trims the file
extension off a file name (so we can create an image file with the same
name as the scene file).</p>
</li>
<li><p>The next method we will add is another utility method. When we
render, we will want to show messages on the status bar that describe
what is happening&#8212;or what went wrong in the event of failure. The
UI Utilities API contains a class called <code>StatusDisplayer</code>
that lets any code in NetBeans that wants to write to the status bar
(the actual implementation of <code>StatusDisplayer</code> is in the
windowing system implementations, <code>core/windows</code> in NetBeans
CVS).</p>
<p>Implement the following method, and then add a dependency on the
UI Utilities API module from the Povray Projects module:</p>
<pre class="examplecode">private void showMsg (String msg) {
StatusDisplayer.getDefault().setStatusText(msg);
}</pre>
</li>
<li><p>At this point, we've added a bunch of status messages our code can
display, so it is time to add actual text for those messages to the
resource bundle. Note that in a number of cases we call:</p>
<pre class="examplecode">NbBundle.getMessage(SomeClass.class, &quot;MSG_Something&quot;, <i><u>someStringArgument</u></i>);</pre>
<p>to fetch a localized string. <code>NbBundle</code> supports embedding
arguments inside of a localized string&#8212;you can either use the above
method, or a variant that takes an array of arguments to embed. So you
can define strings in a resource bundle using the syntax:</p>
<pre class="examplecode">Could not delete {0} because {1}</pre>
<p>and <code>{0}</code> and <code>{1}</code> will be replaced by arguments
passed to <code>getMessage()</code>. This is extremely useful, as often
the order in which such strings occur in the result text will be different
in different human languages.</p>
<p>So let's go ahead and add the warning messages we need to
<code>Bundle.properties</code> in the same package as <code>PovrayProject</code>:
<pre class="examplecode">MSG_NoMainFile=Main scene file not set for {0}
MSG_VirtualFile=Not a file on disk: {0}
MSG_Rendering=Rendering {0}
MSG_NoPovrayExe=No POV-Ray executable, cannot render
MSG_NoPovrayInc=No POV-Ray includes dir, cannot render
MSG_Success=Rendered {0} successfully
MSG_Failure=Failed to render {0}
MSG_CantDelete=Could not delete {0}, it is locked or in use</pre>
</p></li>
<li><p>Now we are almost ready to get down to the nitty-gritty of actually
invoking POV-Ray from NetBeans. We will do this in the standard Java
way, using <code>Runtime.exec()</code> to start an external process.
We also will want to display the text output from the process as it
reports its progress, in the output window. This means we will need a
way to write to the output window. So we will add one more dependency
to Povray Projects&#8212;add a dependency on the IO API module (use the class
name <code>InputOutput</code> in the Add Dependency dialog).</p>
</li>
<li><p>Handling output from a process is tricky&#8212;we will actually have
three threads running to handle our process:</p>
<ul>
<li>The thread that invoked the process and is waiting for it to
terminate</li>
<li>A thread that is collecting output from the standard output of
the POV-Ray process and writing it to the output window</li>
<li>Another thread that is doing the same thing for the error output
of the POV-Ray process</li>
</ul>
<p>So we will need some kind of <code>Runnable</code> which will wait for
data from each output stream and route it to the output window in
NetBeans as it becomes available. Writing to the output window is quite
easy&#8212;you get an <code>InputOutput</code> object from
<code>IOProvider.getDefault()</code> and then write to one of its streams&#8212;for
example:</p>
<pre class="examplecode"> InputOutput io = IOProvider.getDefault().getIO ("Hello", true);
io.select();
io.getOut().println ("Hello world");
io.getErr().println ("This is the standard error output&#8212;it should be red");</pre>
<p>is all it takes to make the output window pop up and display some output.</p>
<p>So before we implement the code that will create the process, lets create
the runnable that will wait for output from the process and route it to
the output window&#8212;it will be a static nested class inside the
<code>Povray</code> class:</p>
<pre class="examplecode"> static class OutHandler implements Runnable {
private Reader out;
private OutputWriter writer;
public OutHandler (Reader out, OutputWriter writer) {
this.out = out;
this.writer = writer;
}
@Override
public void run() {
while (true) {
try {
while (!out.ready()) {
try {
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
close();
return;
}
}
if (!readOneBuffer() || Thread.currentThread().isInterrupted()) {
close();
return;
}
} catch (IOException ioe) {
//Stream already closed, this is fine
return;
}
}
}
private boolean readOneBuffer() throws IOException {
char[] cbuf = new char[255];
int read;
while ((read = out.read(cbuf)) != -1) {
writer.write(cbuf, 0, read);
}
return read != -1;
}
private void close() {
try {
out.close();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
} finally {
writer.close();
}
}
}</pre>
</li>
<li><p>Now we are ready to implement the <code>render()</code> method in the
<tt>Povray</tt> class, in the Povray Project module, that
will invoke <code>POV-Ray</code>. This method should be never be invoked from
the event thread, because it would block the UI until POV-Ray is finished.
So the first thing we do is sanity check what thread we're running on.
Then we get the file we need to render, sanity checking that. Then we
call <code>getPovray()</code> which may open a file chooser to let the
user pick it, and similarly get the default include directory which we
will need to pass on the command line. Then we get the directory where
we will put the output, assemble our output file name (we use PNG format
since NetBeans' Image module supports that).</p>
<p>Then we compute the command
line that should be passed to POV-Ray. Then we call
<code>Runtime.exec()</code> with that argument, wire up the output window
to the output streams from the resulting process, and wait for the
process to exit. Once it exits, we determine if it succeeded or failed,
show an appropriate message, and if it succeeded, return a
<code>FileObject</code> representing the file that was created.</p>
<pre class="examplecode">public FileObject render () throws IOException {
if (EventQueue.isDispatchThread()) {
throw new IllegalStateException ("Tried to run povray from the " +
"event thread");
}
//Find the scene file pass to POV-Ray as a java.io.File
File scene;
try {
scene = getFileToRender();
} catch (IOException ioe) {
showMsg (ioe.getMessage());
return null;
}
//Get the POV-Ray executable
File povray = getPovray();
if (povray == null) {
//The user cancelled the file chooser w/o selecting
showMsg(NbBundle.getMessage(Povray.class, "MSG_NoPovrayExe"));
return null;
}
//Get the include dir, if it isn't under povray's home dir
File includesDir = getStandardIncludeDir(povray);
if (includesDir == null) {
//The user cancelled the file chooser w/o selecting
showMsg (NbBundle.getMessage(Povray.class, "MSG_NoPovrayInc"));
return null;
}
//Find the image output directory for the project
File imagesDir = getImagesDir();
//Assemble and format the line switches for the POV-Ray process based
//on the contents of the Properties object
String args = getCmdLineArgs(includesDir);
String outFileName = stripExtension (scene) + ".png";
//Compute the name of the output image file
File outFile = new File(imagesDir.getPath() + File.separator +
outFileName);
//Delete the image if it exists, so that any current tab viewing the file is
//closed and the file will definitely be re-read when it is re-opened
if (outFile.exists() && !outFile.delete()) {
showMsg (NbBundle.getMessage(Povray.class,
"LBL_CantDelete", outFile.getName()));
return null;
}
//Append the input file and output file arguments to the command line
String cmdline = povray.getPath() + ' ' + args + " +I" +
scene.getPath() + " +O" + outFile.getPath();
System.err.println(cmdline);
showMsg (NbBundle.getMessage(Povray.class, "MSG_Rendering",
scene.getName()));
final Process process = Runtime.getRuntime().exec (cmdline);
//Get the standard out of the process
InputStream out = new BufferedInputStream (process.getInputStream(), 8192);
//Get the standard in of the process
InputStream err = new BufferedInputStream (process.getErrorStream(), 8192);
//Create readers for each
final Reader outReader = new BufferedReader (new InputStreamReader (out));
final Reader errReader = new BufferedReader (new InputStreamReader (err));
//Get an InputOutput to write to the output window
InputOutput io = IOProvider.getDefault().getIO(scene.getName(), false);
//Force it to open the output window/activate our tab
io.select();
//Print the command line we're calling for debug purposes
io.getOut().println(cmdline);
//Create runnables to poll each output stream
OutHandler processSystemOut = new OutHandler (outReader, io.getOut());
OutHandler processSystemErr = new OutHandler (errReader, io.getErr());
//Get two different threads listening on the output & err
//using the system-wide thread pool
RequestProcessor.getDefault().post(processSystemOut);
RequestProcessor.getDefault().post(processSystemErr);
try {
//Hang this thread until the process exits
process.waitFor();
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
//Close the output window's streams (title will become non-bold)
processSystemOut.close();
processSystemErr.close();
if (outFile.exists() && process.exitValue() == 0) {
//Try to find the new image file
FileObject outFileObject = FileUtil.toFileObject(outFile);
showMsg (NbBundle.getMessage(Povray.class, "MSG_Success",
outFile.getPath()));
return outFileObject;
} else {
showMsg (NbBundle.getMessage(Povray.class, "MSG_Failure",
scene.getPath()));
return null;
}
}</pre>
</li>
<li><p>The last thing is to fix our implementation of
<code>RendererService</code> to call <code>Povray.render()</code>.
Open <code>RendererServiceImpl</code> in the code editor, and modify
the render method:</p>
<pre class="examplecode">@Override
public FileObject render(FileObject scene, Properties renderSettings) {
Povray pov = new Povray (this, scene, renderSettings);
try {
return pov.render();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
return null;
}
}</pre>
</li>
<li><p>The last step is to open the image when the rendering process is
complete. This is quite simple to implement&#8212;we just need to look
for an <code>OpenCookie</code> on the <code>Node</code> for the image
that was rendered. If you are running a standard configuration of the
NetBeans IDE, you already have the Image module installed&#8212;it will
provide support for opening an image, displaying it in the editor area.
So implement <code>RendererAction.RenderWithSettingsAction.run()</code> like
this:</p>
<pre class="examplecode">public void run() {
DataObject ob = node.getDataObject();
FileObject toRender = ob.getPrimaryFile();
<b>Properties mySettings = renderer.getRendererSettings(name);</b>
FileObject image = renderer.render(toRender, <b>mySettings</b>);
if (image != null) {
try {
DataObject dob = DataObject.find (image);
Node n = dob.getNodeDelegate();
OpenCookie ck = (OpenCookie) n.getLookup().lookup(OpenCookie.class);
if (ck != null) {
ck.open();
}
} catch (DataObjectNotFoundException e) {
//Should never happen
Exceptions.printStackTrace(e);
}
}
}</pre>
</ol>
</div>
<p>With that, you should be able to clean, build and run the application and
be able to run POV-Ray and generate an image in the <code>images/</code>
subdirectory of your project:</p>
<p><a href="../images/tutorials/povray/71/ch7/pic1.png"><img alt="" src="../images/tutorials/povray/71/ch7/pic2.png"/></a></p>
<h2 class="tutorial"><a name="next"></a>Next Steps</h2>
<p>The <a href="nbm-povray-8.html">next section</a> will cover implementing
<code>ViewService</code> and adding actions for that.</p>
</body>
</html>