User Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
supporting_new_files [2021/03/24 16:50]
188.34.156.36 old revision restored (2021/02/12 20:48)
supporting_new_files [2021/03/27 10:42] (current)
65.21.62.17 old revision restored (2017/02/25 05:57)
Line 3: Line 3:
 Writing support for a new type of printable file requires three steps: Writing support for a new type of printable file requires three steps:
   - Writing a new java class that extends this simple interface: ```org.area515.resinprinter.job.PrintFileProcessor```   - Writing a new java class that extends this simple interface: ```org.area515.resinprinter.job.PrintFileProcessor```
-  - Adding the following line to your config.properties file: ```printFileProcessor.[NameOfYourNewClass]=true```+  - Add the following line to your config.properties file: ```printFileProcessor.[NameOfYourNewClass]=true```
   - Update the ```resourcesnew\cwh\js\index.js``` with the gui icon of your choice in the javascript function: ```getPrintFileProcessorIconClass()```   - Update the ```resourcesnew\cwh\js\index.js``` with the gui icon of your choice in the javascript function: ```getPrintFileProcessorIconClass()```
  
Line 12: Line 12:
 {{:pasted:20170108-151934.png}} {{:pasted:20170108-151934.png}}
  
-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, but if it isn't, you will need to deal with the following things:+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: 
 +  - Throttle up/down CPU execution time during gcode execution hotspots 
 +  - Engaging and communicating with resin detectors at the appropriate times
   - Negotiating hardware with other available printers through things like the SerialManager and DisplayManager   - Negotiating hardware with other available printers through things like the SerialManager and DisplayManager
   - Applying the bulb mask from the printer setup   - Applying the bulb mask from the printer setup
Line 27: Line 29:
 ==== Step 1: Is your new file a 3d modelable file? ==== ==== Step 1: Is your new file a 3d modelable file? ====
  
-  - ```boolean isThreeDimensionalGeometryAvailable()``` - If you'd like the next two methods to be called on your new file type then return true. That simple. +  - ```boolean isThreeDimensionalGeometryAvailable()``` - First you have to ask yourself if your file can be visualized from a '3d' standpoint. If so, you are going to want the GUI to call the next two methods, so return true from this method. That simple. 
-  - ```<G> getGeometry(PrintJob printJob)``` - If your new file type is a true 3d file and you'd like your file displayed as a 3d model when the print job is running you need to return a list of json marshallable objects that will be sent to the gui.  + 
-     * The json format spec is very simple: ```[{v:[x:double,y:double,z:double],n:[x:double,y:double,z:double]}]``` +  - ```<G> getGeometry(PrintJob printJob)``` - If your new file type is a true 3d file and you'd like your file displayed as a 3d model when the print job is running you need to return a list of json marshallable objects that will be sent to the gui. 
-     * ```v``` is an array of exactly three vectors corresponding to a single triangle +    * The json format spec is very simple: ```[{v:[x:double,y:double,z:double],n:[x:double,y:double,z:double]}]``` 
-     * ```n``` is the normal of that triangle.+    * ```v``` is an array of exactly three vectors corresponding to a single triangle 
 +    * ```n``` is the normal of that triangle. 
   - ```<E> getErrors(PrintJob printJob)``` - If you'd like your print job to be paused when you experience an error while printing/slicing your 3d file then return those errors from this method. You also have the ability to highlight portions of your 3d file that have been determined to be "in error". For example if you are printing an STL file and errors in the geometry are found, those triangles involved in the invalid geometry are highlighted.   - ```<E> getErrors(PrintJob printJob)``` - If you'd like your print job to be paused when you experience an error while printing/slicing your 3d file then return those errors from this method. You also have the ability to highlight portions of your 3d file that have been determined to be "in error". For example if you are printing an STL file and errors in the geometry are found, those triangles involved in the invalid geometry are highlighted.
-     * The json format spec is very simple: ```[{i:integer}]``` 
-     * ```i``` is the index of a triangle that is involved in some sort of geometrical problem. 
  
 +     * The json format spec is very simple: ```[{i:integer}]```
 +     * ```i``` is the index of a triangle in the array that was sent down as a part of the ```getGeometry(PrintJob printJob)``` call above. All indices will be highlighted in the gui signifying that they are the result of a geometry error.
  
 ==== Step 2: Does your print require some initial setup or teardown procedures? ==== ==== Step 2: Does your print require some initial setup or teardown procedures? ====
Line 45: Line 49:
  
   - ```String[] getFileExtensions()``` - Return an array of file extensions that you are going to support.   - ```String[] getFileExtensions()``` - Return an array of file extensions that you are going to support.
-  - ```String getFriendlyName()``` - Return the nice name of your printable file for the gui to display.+  - ```String getFriendlyName()``` - Return the nice name of your printable file type for the gui to display.
   - ```boolean acceptsFile(File processingFile)``` - This method is going to be called when a file is uploaded that matches a file extension that is returned from the above method: ```getFileExtensions()```. Inside of this method, you have to internally inspect this file to determine if you would like to take responsibility for printing it. This is necessary because there may be many print file processors that will register for the "zip" file extension. Yet only one print file processor can take responsibility for printing it.   - ```boolean acceptsFile(File processingFile)``` - This method is going to be called when a file is uploaded that matches a file extension that is returned from the above method: ```getFileExtensions()```. Inside of this method, you have to internally inspect this file to determine if you would like to take responsibility for printing it. This is necessary because there may be many print file processors that will register for the "zip" file extension. Yet only one print file processor can take responsibility for printing it.
  
Line 52: Line 56:
 ```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: ```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 { +<code> 
- try { +public JobStatus processFile(PrintJob printJob) throws Exception { 
- STLDataAid dataAid = (STLDataAid)initializeJobCacheWithDataAid(printJob); + try { 
- boolean overrideNormals = dataAid.configuration.getMachineConfig().getOverrideModelNormalsWithRightHandRule() == null?false:dataAid.configuration.getMachineConfig().getOverrideModelNormalsWithRightHandRule(); +                // Setup your data aid with: 
- ZSlicer slicer = new ZSlicer(dataAid.customizer.getZScale(),  +                DataAid dataAid = initializeJobCacheWithDataAid(printJob); 
- dataAid.xPixelsPerMM / dataAid.customizer.getZScale(),  + // Note: If you have already called initializeJobCacheWithDataAid() in your prepareEnvironment() then call this method instead: 
- dataAid.yPixelsPerMM / dataAid.customizer.getZScale() + DataAid dataAid getDataAid(printJob); 
- dataAid.sliceHeight +  
- dataAid.sliceHeight / 2,  + // get ALL configuration information for your print through the dataAid that was given to you: 
- true,  + //    1. dataAid.configuration 
- overrideNormals, + //    2dataAid.printer 
- new CloseOffMend()); + //    3. dataAid.printJob 
- dataAid.slicer slicer+ //    4dataAid.slicingProfile 
- dataAid.slicer.loadFile(new FileInputStream(printJob.getJobFile()), null, null); + //    5. dataAid.inkConfiguration 
- printJob.setTotalSlices(slicer.getZMaxIndex() - slicer.getZMinIndex());+ //    6. dataAid.scriptEngine 
 + //    7. dataAid.customizer 
 + //    8dataAid.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 rendererMore 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 printingBasically 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()) {
   
- //Get the slicer queued up for the first image; + // Next we execute gcode that occurs "before" a slice is displayed 
- int startPoint dataAid.slicingProfile.getDirection() == BuildDirection.Bottom_Up?(slicer.getZMinIndex() + 1 + dataAid.customizer.getNextSlice()): (slicer.getZMaxIndex() + 1)+ JobStatus status performPreSlice(dataAid, null)
- int endPoint = dataAid.slicingProfile.getDirection() == BuildDirection.Bottom_Up?(slicer.getZMaxIndex() + 1 - dataAid.customizer.getNextSlice()): (slicer.getZMinIndex() + 1); + // Next we check to determine if the user cancelled the print and exit if necessary. 
- dataAid.slicer.setZIndex(startPoint); + if (status != null
- Boolean nextRenderingPointer = (Boolean)dataAid.cache.getCurrentRenderingPointer(); + return status
- Future<RenderedData> currentImage = Main.GLOBAL_EXECUTOR.submit(new STLImageRenderer(dataAid, this, nextRenderingPointer, false));+
 +  
 + // 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 imageUnless 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);
   
- //Everything needs to be setup in the dataByPrintJob before we start the header + // Get the next rendering pointer to start rendering the image 
- performHeader(dataAid);+ nextRenderingPointer = z + dataAid.slicingProfile.getDirection().getVector();
   
- for (int z = startPoint; dataAid.slicingProfile.getDirection().isSliceAvailable(z, endPoint) && dataAid.printer.isPrintActive();+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())) { 
- //Performs all of the duties that are common to most print files + currentImage = Main.GLOBAL_EXECUTOR.submit([createYourRendererHere]);
- JobStatus status = performPreSlice(dataAid, slicer.getStlErrors()); +
- if (status != null) { +
- return status; +
- +
-  +
- logger.info("SliceOverheadStart:{}", ()->Log4jTimer.startTimer(STL_OVERHEAD)); +
-  +
- //Wait until the image has been properly rendered. Most likely, it's already done though... +
- BufferedImage image = currentImage.get().getPrintableImage(); +
-  +
- logger.info("SliceOverhead:{}", ()->Log4jTimer.completeTimer(STL_OVERHEAD)); +
-  +
- //Now that the image has been rendered, we can make the switch to use the pointer that we were using while we were rendering +
- dataAid.cache.setCurrentRenderingPointer(nextRenderingPointer); +
-  +
- //Start the exposure timer +
- // logger.info("ExposureStart:{}", ()->Log4jTimer.startTimer(EXPOSURE_TIMER)); +
-  +
- //Cure the current image +
- //dataAid.printer.showImage(image); +
-  +
- //Get the next pointer in line to start rendering the image into +
- nextRenderingPointer = !nextRenderingPointer; +
-  +
- //Render the next image while we are waiting for the current image to cure +
- if (z < slicer.getZMaxIndex() + 1) { +
- slicer.setZIndex(z); +
- currentImage = Main.GLOBAL_EXECUTOR.submit(new STLImageRenderer(dataAid, this, nextRenderingPointer, false)); +
-+
-  +
- //Performs all of the duties that are common to most print files +
- status = printImageAndPerformPostProcessing(dataAid, image); +
- if (status != null) { +
- return status; +
- }+
  }  }
   
- return performFooter(dataAid); + // Prints the image that we just rendered. Keep in mind that this is NOT the image 
- } finally { + // that we just submitted above. 
- clearDataAid(printJob);+ 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);
  }  }
 +}
 +</code>
 +
 +==== Step 5: Write your rendering code ====
 +
 +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:
 +
 +{{:pasted:20170109-023900.png}}