Writing POV-Ray Support for NetBeans II—Project Type Design
This tutorial needs a review. You can edit it in GitHub following these contribution guidelines. |
In this part of the tutorial, we will walk through how to create a basic project type. It will be a project type that supports building 3D graphics scenes using POV-Ray's scene language, and eventually, rendering them as images and displaying the result in NetBeans.
It is assumed you have completed the steps in the previous tutorial for creating basic POV-Ray support.
This tutorial will go through the up-front design and thinking through needed to successfully implement a project type that will serve its users well. The subsequent tutorial walks through the implementation of the basic project.
Design and Coupling
A NetBeans project is a directory on disk; typically there is some signature, such as having a subdirectory with a specific name, which identifies that directory as being a project.
The major requirement of identifying a folder as being a project is that the test fail quickly—the code that determines that a folder is not a project must complete very quickly, because it is going to be called once for every folder visible in the file chooser that lets users open projects. We will stick with what works, and use a specific subdirectory name to identify our POV-Ray projects.
Most Project types in NetBeans use Apache Ant as their build infrastructure, and there are APIs for building Ant-based projects. This example does not use Ant, but implements a simple basic project type. Even if you know you will use Ant, you will encounter all of the concepts presented here in implementing an Ant-based project.
Notice that we implemented support for .pov
and .inc
files in a separate module. Design-wise this makes sense—data recognition and project types are orthagonal—and it provides a good demonstration of how to do loose coupling between modules.
So at this point we need to think about what we’re designing, what functionality belongs to which module, and what will serve our users' needs best. Before we do any coding, some design has to happen. Here’s what we know about POV-Ray and its usage patterns:
-
POV-Ray has a scene language, and it "compiles" textual
.pov
files down into image files.
-
A
.pov
file can reference other files, which typically get the extension.inc
.
-
POV-Ray has a huge number of options such as antialiasing, jitter, output image size, quality, number of reflections, animation clock.
-
Users will render small test versions of a scene at low quality to check their work as they go; large, high quality renders can take a long time.
-
Typically the user knows the size and aspect ratio they’ll want for the final output, but that’s also not what most renders will use.
-
To save rendering time while working, a user may split individual objects from a scene into separate files and render them individually as they work.
Design Choices—Don’t Try to Save the World
The biggest killer of projects is trying to "save the world" - trying to provide every combination of every possible option to the user, such that the user is confronted with a bewildering array of choices when most of the time they want to do something very simple. Trying to support every imaginable permutation of anything leads to projects which are permanently six months from completion. So we need to limit the scope while providing value to the user. But we also want to keep power users happy and give them the ability to do what they need to do. So we have some choices to make, and some hard thinking to do about what is the right UI to balance the power of POV-Ray with the need to keep things simple and easy to use for beginners.
The first question, from item 1, is, which module owns the rendering infrastructure? Well, when you render an image, you need a place to put it. A project owns a directory and its subdirectories. So we’ll make an executive decision right now that we don’t render a random .pov
file on disk—it must belong to a project which can provide a place to put the result. So the code that actually calls out to POV-Ray will be part of the project support module, not the file support module.
A follow-on to this is, should it be possible to render absolutely any .pov
or .inc
file? Probably yes, due to fact 6—but we’ll only do that if it belongs to a project.
Do we want to provide structure (subfolders, etc.) in POV-Ray projects? It would probably be nice for the future, but to keep the scope of things manageable, we’re not going to do that now. In practice, POV-Ray projects can be just as messy as projects written in C, with source files scattered hither and yon. We are not going to try to solve all the world’s problems here; but neither are we going to impose our own idea of what a well structured POV-Ray project looks like on the user.
How many of the myriad command-line options that POV-Ray supports should we expose the user to? The real usage pattern here is that, for test renders, any of a few sets of standard settings will do for almost all cases. So we will provide a set of standard resolutions like 320x200, 640x480, 1024x768. Those settings will be unmodifiable. But, associated with each project will be a set of production settings, which are saved with the project, are shareable and go with the project wherever it goes. We will provide a basic GUI customizer for the production settings. It will not cover every possible setting POV-Ray has—however, the customizer will be writing into the project’s project.properties
file - and a power user can edit in their own settings to do anything they want, so the full power of POV-Ray stays available to power users—any desired line switch can be encoded into the project’s properties file and end up passed on the command line to POV-Ray.
Because of item 6, we know there can be multiple files in a project, but only one is the master scene that is the final output of the project. So we will give our projects the concept of a main file which is what gets rendered when they invoke Build, and we’ll need a UI to select which file it is.
[[Physical Structure of a POV-Ray Project]]
We also need to make some decisions about what will be stored on disk and where. We know that projects typically have Build and Clean actions, and these will be useful for POV-Ray projects as well. Where should scene files go? Well, we want to keep some flexibility for future versions, so we don’t want them in the root directory of our projects—we’ll create a scenes/
folder for them.
We also know we will end up with image files from rendering. The Clean operation will be much less complex to implement if it just means deleting a directory. And the UI will be less cluttered if we put images in their own directory, so rendered images will go in an images/
subdirectory of the project. The project UI won’t even show the images/
directory—you’ll just right-click a scene file and select View, to render and show a single file, or build the project to show the "main file". As with compiling, we will do a date check to see if the source file is newer than the image file, and if so, render it again.
We also know we need to use a subdirectory to identify our project type, and to store configuration data that should be saved, such as which file is the main file and what are the production renderer settings. So we’ll decide that every POV-Ray project will have a pvproject
subdirectory, and in that directory will be a properties
file. So on disk, a typical POV-Ray project will look like:
-
Wonderland/
-
images/
-
Wonderland.png
-
pvproject/
-
project.properties
-
scenes/
-
Alice.inc
-
Wonderland.pov
-
LookingGlass.inc
-
MadQueen.inc
-
MockTurtle.inc
Coupling Between POV-Ray Files and POV-Ray Project
Typically a user is going to be dealing with files, and we already decided that a user should be able to render any file in a project; and we know we are going to have to give the user the ability to pick which file is the main file for a project. That means our Node`s for `.pov
files will need to have Action`s that will be invoking rendering and main-file-setting code that belongs to the project module, not to their module. So we know we that the project module will need to expose some API for doing these things, and the `Node
of a POV-Ray file will need to be able to find the project that owns it and call this code. But if a .pov
is orphaned on disk, or is inside, say, a J2EE project for some reason, it must fail gracefully.
This gives a great opportunity to demonstrate how loose coupling between modules is done. There are two simple mechanisms that will allow us to easily do this: The first is FileOwnerQuery
—this is a class from the Project
module, which allows one to find out what project owns any file. The second is that Project
, the interface we implement to create our project type, has a method getLookup()
. So we will provide some interfaces in the POV-Ray project type which we’ll make available as API. When a node finds out what project its file belongs to, it can simply request the implementation of one of those interfaces. If it gets non-null, it can call the rendering or main-file-setting functionality; if it gets null, or the project is null, those actions will just be disabled.
Summary
Design is an inescapable first-step in developing modules. It pays to think hard about what functionality belongs where, and what the user experience should be before starting to code.