Exposure time calibration


#1

Does Photonic3D have an exposure time calibration feature, where multiple exposure times can be printed at the same time? Other SLA control software allow for this, such as nanoDLP, or when controlling the printer directly with creation workshop. Is this a foreseen feature?

Otherwise, how do people calibrate their exposure time for a new resin using Photonic3D?

Thanks in advance.


#2

I assume the way that it works is that different parts of the plate light up at different exposure rates? Sounds cool I could do that.


#3

The best calibration routine I’ve used so far is the one for the Octave Light. It prints a solid grid platform about 5mm tall, and then exposes the segments between the solid grid parts at various exposures. You remove the part and measure the different bridges to create a function of thickness vs. exposure time via linear regression.


#4

The calibration routine you describe is also what Creation Workshop does. However, it only works when Creation Workshop is controlling directly the printer board (like RAMPS) since it can not save a scene file with multiple exposure times to be printed with Photonics3D.

Currently I do not have a solution to calibrate my exposure time using Photonics3D. This is what I am looking for.


#5

So here is what I’m thinking. In any Javascript calculator I’m going to give you the ability to execute timers. Those timers are guarenteed to be started at the same time that the exposure is started. In those timers, you can execute any code that you’d like. Most likely, everyone will do something like:

buildPlatformGraphics.setColor(java.awt.Color.BLACK);
buildPlatformGraphics.fillRect(13, 10, 22, 41);

Of course, I’ll create a push button reference example on the customizers page so that you don’t have to code anything. It will be something like: “duplication grid with variable exposure times”. The default implementation will allow you to expose any given printable at a variety of exposure times simultaneously.

This way, anyone could still build the exact same calibration routine you two are describing and add it as an example on the following page:
https://wiki.photonic3d.com/doku.php?id=calculator_examples

I wouldn’t even mind putting together the calibration example, if I understood a bit more about how it worked.


#6

There are two ways I have seen for calibration:

  1. The calibration method of NanoDLP: Multiple pilars with different exposure times. Basically printing a grid of pillars, rectangular with a “shear” factor to make them more trapezoidal. Each pillar with an increasing exposure time, for example to cover exposure times from 5 to 15 seconds in 0.5 second increments for each layer. The size of the pillars, the shear factor as well as the star time, end time and increments should be selectable, so that one can do a coarse calibration if the resin is completely unknown, or a fine calibration with smaller increments for a specific size of features.

  2. The Creation Workshop calibration method: The print starts with a grid structure something like several joined capital H, something like HHHH but without the gaps in between each letter. after a few millimetres of such structure, the gaps on top and bottom of the H are closed (or bridged) with one single layer with different exposure times for each gap. This way, the user can determine what is the minimum exposure time required to solidly generate a layer of the given layer size of the print.

I hope this helps! Please let me know if it is clear enough to understand the concept!


#7

Thanks for the explanation, it does help. The first situation sounds almost identical to the pegs code I give on the examples page here:
https://wiki.photonic3d.com/doku.php?id=calculator_examples

Obviously I haven’t yet built in the variable exposure rate, but evaluate and determine if it’s the same sort of column structure you described. The variables are fully adjustable through the first datastructure:

var pegSettingsMM = {
rows: 5,
columns: 5,
fontDepth: .5,
fontPointSize: 42,
startingOverhangDegrees: 45,
degreeIncrement: 1,
pegDiameter: 3,
pegStandHeight: 1,
pegStandWidth: 5,
distanceBetweenStands: 1}

A couple of questions:
In the first situation it doesn’t sound like that calibration has any bridging capabilities?
In the second situation, I assume that by “a few millimeters” you mean a few “vertical” millimeters?

I could whip up the second calibration awfully quickly and add it to the examples page as well, so you’d have both calibration routines to use.


#8

Here is what the function will look like now. Before I used ‘x’ and ‘y’ to change the angle for every column. Since there are two factors changing now, columns show differences in exposure time, and rows show difference in overhang. I’ll push the new version allows exposureTimers this weekend. If I have time, I’ll post the other calibration routine this weekend as well.

var pegSettingsMM = {
  rows: 5, 
  columns: 5, 
  fontDepth: .5,
  fontPointSize: 42,
  startingOverhangDegrees: 45,
  degreeIncrement: 5,
  pegDiameter: 3,
  pegStandHeight: 1, 
  pegStandWidth: 5,
  distanceBetweenStands: 1,
  exposureTimeDecrementMillis: 1000};

var pegStandCount = pegSettingsMM.pegStandHeight / $LayerThickness;
var fontCount = pegSettingsMM.fontDepth / $LayerThickness;
var pegSettingsPixels = {
  pegDiameterX: pegSettingsMM.pegDiameter * pixelsPerMMX,
  pegDiameterY: pegSettingsMM.pegDiameter * pixelsPerMMY,
  pegStandWidthX: pegSettingsMM.pegStandWidth * pixelsPerMMX,
  pegStandWidthY: pegSettingsMM.pegStandWidth * pixelsPerMMY,
  distanceBetweenStandsX: pegSettingsMM.distanceBetweenStands * pixelsPerMMX,
  distanceBetweenStandsY: pegSettingsMM.distanceBetweenStands * pixelsPerMMY,
  pegStandDifferenceOffsetX: ((pegSettingsMM.pegStandWidth * pixelsPerMMX) - (pegSettingsMM.pegDiameter * pixelsPerMMX)) / 2,
  pegStandDifferenceOffsetY: ((pegSettingsMM.pegStandWidth * pixelsPerMMY) - (pegSettingsMM.pegDiameter * pixelsPerMMY)) / 2
}
if ($CURSLICE < pegStandCount) {
   for (var x = 0; x < pegSettingsMM.columns; x++) {
      for (var y = 0; y < pegSettingsMM.rows; y++) {
         var overhangAngle = pegSettingsMM.startingOverhangDegrees + y * pegSettingsMM.degreeIncrement;
         var startingX = x * pegSettingsPixels.pegStandWidthX + x * pegSettingsPixels.distanceBetweenStandsX;
         var startingY = y * pegSettingsPixels.pegStandWidthY + y * pegSettingsPixels.distanceBetweenStandsY;
         buildPlatformGraphics.setColor(java.awt.Color.WHITE);
         buildPlatformGraphics.fillRect(
            startingX,
            startingY,
            pegSettingsPixels.pegStandWidthX,
            pegSettingsPixels.pegStandWidthY);
         if ($CURSLICE < fontCount) {
            buildPlatformGraphics.setColor(java.awt.Color.BLACK);
            buildPlatformGraphics.setFont(new java.awt.Font("Dialog", 0, pegSettingsMM.fontPointSize));
            buildPlatformGraphics.drawString(overhangAngle + "", startingX, startingY + pegSettingsPixels.pegStandWidthY);
         }
         exposureTimers.add({
             "millisDelay":$FirstLayerTime - (pegSettingsMM * x), 
             "parameter":{"x":startingX, "y":startingY, "width":pegSettingsPixels.pegStandWidthX, "height":pegSettingsPixels.pegStandWidthY}, 
             "function":function(blackRect) {
                buildPlatformGraphics.setColor(java.awt.Color.BLACK);
                buildPlatformGraphics.fillRect(
                   blackRect.x,
                   blackRect.y,
                   blackRect.width,
                   blackRect.height);
             }
         });
      }
   }
} else {
   for (var x = 0; x < pegSettingsMM.columns; x++) {
      for (var y = 0; y < pegSettingsMM.rows; y++) {
         var overhangAngle = pegSettingsMM.startingOverhangDegrees + y * pegSettingsMM.degreeIncrement;
         var singleOverhangIncrement = java.lang.Math.tan(java.lang.Math.toRadians(overhangAngle)) * $LayerThickness * pixelsPerMMX;
         var circleOffsetX = pegSettingsPixels.pegStandDifferenceOffsetX * ((x + 1) * 2 - 1) + (singleOverhangIncrement * ($CURSLICE - pegStandCount)) + (x * pegSettingsPixels.pegDiameterX) + (x * pegSettingsPixels.distanceBetweenStandsX);
         var circleOffsetY = pegSettingsPixels.pegStandDifferenceOffsetY * ((y + 1) * 2 - 1) + (y * pegSettingsPixels.pegDiameterY) + (y * pegSettingsPixels.distanceBetweenStandsY);
         buildPlatformGraphics.fillOval(
            circleOffsetX,
            circleOffsetY,
            pegSettingsPixels.pegDiameterX,
            pegSettingsPixels.pegDiameterY);
         exposureTimers.add({
             "millisDelay":$FirstLayerTime - (pegSettingsMM * x), 
             "parameter":{"x":circleOffsetX, "y":circleOffsetY, "width":pegSettingsPixels.pegDiameterX, "height":pegSettingsPixels.pegDiameterY}, 
             "function":function(blackRect) {
                buildPlatformGraphics.setColor(java.awt.Color.BLACK);
                buildPlatformGraphics.fillOval(
                   blackRect.x,
                   blackRect.y,
                   blackRect.width,
                   blackRect.height);
             }
         });
      }
   }
}

#9

Hi! Sorry for my disappearance, but I was away for a while :slight_smile:

It looks like the peg calculator is suitable if variable exposures can be done for each peg.

Yes, you are right, the peg example does not have bridging capabilities, it is more to find a “coarse” exposure time for a resin (to have a working printer), and then use the other calibration method with bridges to refine.

One question: where will you implement the new timer capability? on the latest dev version?

Thanks so much!

Jonathan


#10

Yep that’s the plan. Sorry it’s so late into the dev branch. I’ve literally been busy every week; from Easter, to a couple of large projects at work to the Red Hat Summit this week. The code I’ve posted above is going to be the exact code.

I had a naive implementation immediately, but it was complete garbage. It turns out that I was using the same script engine for both the “processing image” as I was the “display image”. This caused some crazy synchronization issues. Now I’ve isolated the script engine into the rendering cache so that each image uses it’s own bindings on the script engine. That way I don’t get cross pollination between exposureTimers that belong to two different BufferedImages.

I’ll be testing this week.


#11

Finally got this out there, so you can play with it. It’s working fantastic on my Windows laptop.

Before I go to prod, I’m planning on putting in 3 different built in examples via button click, but I’ve got to do some debugging on the internal slicer before I get to this.

The 3 examples will be:

  1. ‘H’ calibration you spoke about.
  2. Peg matrix that I already posted earlier in this thread.
  3. Duplication grid with multiple exposure levels (tests the same print with different exposure levels)

#12

Number 3 is complete. It allows you to duplicate any print and run it at different exposure levels. The button is called Variable Exposure Time. Notice that it has all of the same variables that the regular duplication grid had, only that it allows each duplication to be exposed at a different rate. The exposure time decrementer is a bit cheesy, but the idea is that it take off that many milliseconds from each different image and the remainder is left for the last exposure. You can play with that a bit, maybe it would be cool to use a percentage. All of the “magic” is in this line:
delayMillis:$LayerTime - ((y * gridDataInMM.numberOfColumns) + x) * gridDataInMM.exposureTimeDecrementMillis,


#13

Number 1 and Number 2 are also complete now. Here are where all of the buttons are located:
https://wiki.photonic3d.com/doku.php?id=variable_exposure_timing

Here are the H-Bridge Calibration settings. They are all pretty self explanatory, but left me know if something isn’t clear.

var hBridgeInMM = { wallWidth:1, gapLength:4, firstGapWidth:3, numberOfGapsInRow:6, gapWidthIncrement:3, distanceBetweenRows:1, numberOfRows:5, exposureTimeDecrementMillis:1000 };

This is what the H-Bridge Exposure Calibration looks like. Each row decreases the exposure time by the specified value…

Let me know if there is anything else I can do.


#14

This looks great, I’m eager to try this featutre, but I don´t have those options in my photonic.
I´m using a YHD-101 with RPI, the photonic was preinstalled, is version 1.0.14

cheers!


#15

You are at our stable version on area515 then :slight_smile: Wes added this new feature in the development branch which you can install by executing the following code over ssh:

sudo wget https://github.com/WesGilster/Creation-Workshop-Host/raw/master/host/bin/start.sh
sudo chmod 777 start.sh
sudo ./start.sh WesGilster  

As far as I’m aware we are also doing a new push to the stable branch soon, so alternatively you can wait for that :wink:!


#16

thank you for the instant answer! to execute the code over ssh I have to be runing linux?


#17

Well the device you set up the SSH connection from can be any OS, for windows you can use putty and for linux you can just use the terminal!

The instructions I just mentioned were assuming you run Photonic3D from your raspberry pi. So you can just login with putty into your raspberry pi and run these commands.

But if you are running Photonic3D from windows than you can just download a new realese from here : https://github.com/WesGilster/Photonic3D/releases and than you can open photonic from there which will run the development release :slight_smile:

Did this answer your question?


#18

ok, so I downloaded Putty, figure it out how to conect to the raspberry pi, and paste those comands, now it´s installing the dev version!

Edit: Ok it works, but now when I try to conect to the printer via the browser it asks me for a user and password…

Thank you very much for the explanation, I´m totally new to this kind of technology, so I´m always a bit insecure of what I´m doing…


#19

no reason to be insecure! The most learnfull experiences are when you screw up and believe me we do it all the time as well :stuck_out_tongue: at least me! And we will be here to help you fix it :wink:


#20

Ahh hmm I know @WesGilster is working on some user additions but I haven’t pulled them yet… Let me take a look for you :slight_smile: be right back!