Writing support for a new type of printable file requires three steps:
It really is that simple.
Now let's talk about #1 for a bit since steps 2 and 3 are more of a finalizing formality. There are quite a few things to think about when it comes to building the ```PrintFileProcessor```; however, we've tried to give you a pretty good head start with these implementations:
One thing you will quickly see is that ```org.area515.resinprinter.job.AbstractPrintFileProcessor<G, E>``` is the common base class for all implementations for print file processors. It certainly doesn't have to be the base class for your new class, but if it isn't, you will need to deal with the following concepts:
You probably don't want to do that stuff…
```JobStatus processFile(PrintJob printJob)``` - This is the workhorse method for printing, and we'll cover how this method should be structured in the rest of this doc:
public JobStatus processFile(PrintJob printJob) throws Exception { try { // Setup your data aid with: DataAid dataAid = initializeJobCacheWithDataAid(printJob); // Note: If you have already called initializeJobCacheWithDataAid() in your prepareEnvironment() then call this method instead: DataAid dataAid = getDataAid(printJob); // get ALL configuration information for your print through the dataAid that was given to you: // 1. dataAid.configuration // 2. dataAid.printer // 3. dataAid.printJob // 4. dataAid.slicingProfile // 5. dataAid.inkConfiguration // 6. dataAid.scriptEngine // 7. dataAid.customizer // 8. dataAid.xPixelsPerMM // 9. dataAid.yPixelsPerMM // 10. And much much more is available... // Get the total number of slices from your file by using a combination of: // 1. dataAid.customizer.getZScale() // 2. dataAid.sliceHeight printJob.setTotalSlices([totalslices]); // Determine the starting slice and final slice of your print with: // 1. dataAid.slicingProfile.getDirection() // 2. dataAid.customizer.getNextSlice() int startPoint = dataAid.slicingProfile.getDirection() == BuildDirection.Bottom_Up?[BottomSliceIndexOfImage]:[TopSliceIndexOfImage]; int endPoint = dataAid.slicingProfile.getDirection() == BuildDirection.Bottom_Up?[TopSliceIndexOfImage]:[BottomSliceIndexOfImage]; // Set the first "renderingPointer" for your image cache. This can be an object of any type // but it needs to correspond to the first image that you are rendering. You could use an integer // that represents the slice count or perhaps the filename of a slice that you have extracted on // on disk. Ultimately it's just a key to a hashmap that contains the image in cache. dataAid.cache.setCurrentRenderingPointer(startPoint); // Create your renderer. More about this later.... Future<RenderedData> currentImage = Main.GLOBAL_EXECUTOR.submit([createYourRendererHere]); // Finally you are able to send your first gcode initialization to your printer like this: performHeader(dataAid); // Now you are ready to start printing. Basically you are going to count from // top->bottom or bottom->top, making sure that your print is still active after each // iteration. Your for loop could look something like this: for (int z = startPoint; dataAid.slicingProfile.getDirection().isSliceAvailable(z, endPoint) && dataAid.printer.isPrintActive(); z += dataAid.slicingProfile.getDirection().getVector()) { // Next we execute gcode that occurs "before" a slice is displayed JobStatus status = performPreSlice(dataAid, null); // Next we check to determine if the user cancelled the print and exit if necessary. if (status != null) { return status; } // Our image has been rendering since we started it on the previous iteration or if // this is the first iteration, when we initialized it outside of the loop. // When all rendering and volume computation is finished, // this method will return the newly rendered image. Unless your processing is quite // complicated, you won't have to wait for this method to return. BufferedImage image = currentImage.get().getPrintableImage(); // Now that the next image has rendered, let's set the renderingPointer to it. // This basically tells the rest of the world that we are ready to display the next image. dataAid.cache.setCurrentRenderingPointer(nextRenderingPointer); // Get the next rendering pointer to start rendering the image nextRenderingPointer = z + dataAid.slicingProfile.getDirection().getVector(); // Only start the next renderer if there is actually another image to print if (dataAid.slicingProfile.getDirection().isSliceAvailable(z, endPoint + dataAid.slicingProfile.getDirection().getVector())) { currentImage = Main.GLOBAL_EXECUTOR.submit([createYourRendererHere]); } // Prints the image that we just rendered. Keep in mind that this is NOT the image // that we just submitted above. status = printImageAndPerformPostProcessing(dataAid, image); // Next we check to determine if the user cancelled the print and exit if necessary. if (status != null) { return status; } } // Perform the final "footer" gcode and return the final status to the gui return performFooter(dataAid); } finally { // Execute the method below and perform any cleanup unique to your file type for this printjob clearDataAid(printJob); } }
In Step 4(in the program above) I mentioned two places where you need to: ```[createYourRendererHere]``` To do this you simply implement the ```java.util.concurrent.Callable``` interface and return an instance of ```org.area515.resinprinter.job.render.RenderedData```. This can be a bit tricky because there are a few responsibilities you need to handle in order to optimize caching properly and compute the current slice volume. If you want these things done for you just simply create a new class that implements ```org.area515.resinprinter.job.render.CurrentImageRenderer``` and implement the ```renderImage(BufferedImage)``` method. This makes it pretty easy and all you really need to do is take care of the specific rendering for your file type.
Below are several examples of how you might want to do this: