Writing POV-Ray Support for NetBeans VII—Support For Running POV-Ray

This is a continuation of the tutorial for building a POV-Ray rendering application on the NetBeans Platform. If you have not read the first, second, third, fourth, fifth, and sixth parts of this tutorial, you may want to start there.

Obtaining POV-Ray

At this point, we're almost ready to run code, so it would be a good idea to download a copy of POV-Ray.

  • Official builds can be obtained from povray.org.
  • Macintosh users may find DarwinPorts the easiest way—simply install DarwinPorts and then run sudo port install povray.
  • Linux and other Unix users should be fine with the downloads available from povray.org, using these instructions. 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.

Note: For details on setting up POV-Ray, especially for detailed instructions for Windows, see the Appendix.

Executing POV-Ray and Displaying the Output

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.

At the end of this part of the tutorial, you will have an application that looks like this (click to enlarge the image):

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.

The first part is just being able to invoke the external executable. NetBeans has some API classes that can help with that.

  1. Add the following constructor and fields to the Povray class:

        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;
        }
  2. Next we will implement a method that will find the file to render. We were passed a FileObject, but now we need an actual java.io.File 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—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—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 (FileUtil.toFile() returns null). So we will add a method to Povray as follows:

    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;
    }
  3. Next we need to assemble the command-line arguments that need to be passed to POV-Ray. These take the form of +[some character][somevalue], for example, +A0.9 sets the anti-aliasing parameter to 0.9 pixels. So we need to iterate the Properties object passed to the constructor and assemble from it a set of command line arguments:

    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();
    }
  4. Next we need to implement a couple of utility methods that the rendering method will use:

    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;
    }

    Neither is terribly exciting—one gets the images directory from the project as a java.io.File, 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).

  5. 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—or what went wrong in the event of failure. The UI Utilities API contains a class called StatusDisplayer that lets any code in NetBeans that wants to write to the status bar (the actual implementation of StatusDisplayer is in the windowing system implementations, core/windows in NetBeans CVS).

    Implement the following method, and then add a dependency on the UI Utilities API module from the Povray Projects module:

    private void showMsg (String msg) {
        StatusDisplayer.getDefault().setStatusText(msg);
    }
  6. 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:

    NbBundle.getMessage(SomeClass.class, "MSG_Something", someStringArgument);

    to fetch a localized string. NbBundle supports embedding arguments inside of a localized string—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:

    Could not delete {0} because {1}

    and {0} and {1} will be replaced by arguments passed to getMessage(). This is extremely useful, as often the order in which such strings occur in the result text will be different in different human languages.

    So let's go ahead and add the warning messages we need to Bundle.properties in the same package as PovrayProject:

    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
  7. 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 Runtime.exec() 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—add a dependency on the IO API module (use the class name InputOutput in the Add Dependency dialog).

  8. Handling output from a process is tricky—we will actually have three threads running to handle our process:

    • The thread that invoked the process and is waiting for it to terminate
    • A thread that is collecting output from the standard output of the POV-Ray process and writing it to the output window
    • Another thread that is doing the same thing for the error output of the POV-Ray process

    So we will need some kind of Runnable 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—you get an InputOutput object from IOProvider.getDefault() and then write to one of its streams—for example:

        InputOutput io = IOProvider.getDefault().getIO ("Hello", true);
        io.select();
        io.getOut().println ("Hello world");
        io.getErr().println ("This is the standard error output—it should be red");

    is all it takes to make the output window pop up and display some output.

    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—it will be a static nested class inside the Povray class:

        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();
                }
            }
    
        }
  9. Now we are ready to implement the render() method in the Povray class, in the Povray Project module, that will invoke POV-Ray. 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 getPovray() 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).

    Then we compute the command line that should be passed to POV-Ray. Then we call Runtime.exec() 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 FileObject representing the file that was created.

    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;
        }
    
    }
  10. The last thing is to fix our implementation of RendererService to call Povray.render(). Open RendererServiceImpl in the code editor, and modify the render method:

    @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;
        }
    }
  11. The last step is to open the image when the rendering process is complete. This is quite simple to implement—we just need to look for an OpenCookie on the Node for the image that was rendered. If you are running a standard configuration of the NetBeans IDE, you already have the Image module installed—it will provide support for opening an image, displaying it in the editor area. So implement RendererAction.RenderWithSettingsAction.run() like this:

    public void run() {
        DataObject ob = node.getDataObject();
        FileObject toRender = ob.getPrimaryFile();
        Properties mySettings = renderer.getRendererSettings(name);
        FileObject image = renderer.render(toRender, mySettings);
        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);
            }
        }
    }

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 images/ subdirectory of your project:

Next Steps

The next section will cover implementing ViewService and adding actions for that.

Project Features

Project Links

About this Project

Platform was started in November 2009, is owned by Antonin Nebuzelsky, and has 149 members.
By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20160708.bf2ac18). © 2014, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
 
 
Close
loading
Please Confirm
Close