Saving (parts of) code on each 'render'

Hello,

I’m currently using Processing as a tool to design a pattern for a product. It’s a relatively small and simple sketch: I’m loading in a PShape and am applying transformations on iterations of this PShape, depending on some image inputs, noise values, et cetera.
Since time is short and a lot of revisions are required I’ve decided to make use of Processing’s Tweak Mode, looking for interesting visual results, which I then save as a .pdf with a keypress. This way I can adjust things on the fly, make loads of variations, discuss these, and with renewed feedback make a new variation of the Processing folder and jump back in.
The inevitable has now happened where the client wants to jump back to a previous version/variation of the design. Luckily I have a .pde file that is close to what they would want to make more variations on, so I can probably replicate the result and work from there. It has however got me thinking that it would be very useful for me to be able to have a ‘saved state’ or ‘copy’ of the code (or at least of some of the variables used in the code), which gets saved every time I make a ‘render’ with the code, so that in case that I need to revert back to an exact previous version I can access and use it. It’s not about the calculated outcome of the variables, but more about saving the formulas used to get to the visual result that I have achieved.
My current working method is visually very effective in the sense that I can make a lot of variations quickly, but code-wise it’s sloppy. It’s reliant on a lot of variables with a lot of direct alterations, and dragging values up and down doesn’t make for easy retracing of my steps.
I feel like it must be possible to do this relatively easily, but I’ve never seen a sketch in which this has been done. I have been looking around for an answer to this in the forums, but i haven’t seen a topic raise this specific issue. I have some ideas of what might work, maybe using saveStrings, but I can foresee things becoming very complicated using that approach. I was wondering if somebody could point me in the right direction for a solution.

Any help would be much appreciated. Thanks a lot in advance for any of your help.

Namrad

If I understand it correctly, you have a working sketch. In that sketch you change variables using e.g. sliders and buttons; when you’re happy with the resukt you want to save those settings.

If that is a correct interpretation of your requirement, you can save your settings to a file in e.g. json or xml format. You can later load that file again to continue working.

2 Likes

That sounds about right. Slight exception being that I haven’t bothered with a system of actual sliders and buttons, but am just changing variables in the files itself using Tweak Mode (this should have been more of a red flag at the beginning that this wasn’t a viable working method, but alas).
I’m curious how it would be possible to export certain data to a .json or .xml format, or what counts as ‘settings’ in your description. A crude solution, like a method/function which would save the entire sketch’s code to a .txt file on the same keypress as a .pdf export, should do the trick. Would you care to elaborate?

Disclaimer: I’m quite new to Processing, not so new to programming.

I don’t have much to work on without seeing your code :wink:

Below is an example how you can save variables to a json file. The code declares two rectangles, two circles and a colour. The code uses classes to combine the related information for a rectangle or circle; the circles are placed in an ArrayList.

For the rectangles, the code demonstrates how to add two single objects (rectangles) to a json array. For the circles, the code demonstrates how to loop through an ArrayList to add the circles to a json array. For the colour, it demonstrates how to add a simple variable.

class Rectangle
{
  int x;
  int y;
  int w;
  int h;

  color fillColour;
  color strokeColour;

  Rectangle(int x, int y, int w, int h, color fillColour, color strokeColour)
  {
    this.x= x;
    this.y = y;
    this.w = w;
    this.h = h;

    this.fillColour = fillColour;
    this.strokeColour = strokeColour;
  }
}

class Circle
{
  int x;
  int y;
  int d;

  color fillColour;
  color strokeColour;

  Circle(int x, int y, int d, color fillColour, color strokeColour)
  {
    this.x= x;
    this.y = y;
    this.d = d;

    this.fillColour = fillColour;
    this.strokeColour = strokeColour;
  }
}

Rectangle r1;
Rectangle r2;

ArrayList<Circle> circles = new ArrayList<Circle>();

color green = 0xFF00FF00;

void setup()
{
  size(400, 400);

  r1 = new Rectangle(100, 50, 50, 50, 0xFFFF0000, 0xFF0000FF);
  r2 = new Rectangle(250, 150, 50, 50, 0xFF000000, 0xFF0000FF);

  Circle c = new Circle(30, 30, 10, 0xFF808080, 0xFF000000);
  circles.add(c);
  c = new Circle(30, 300, 10, 0xFF000000, 0xFF808080);
  circles.add(c);


  saveJsonArray_to_File();
}


void draw()
{
  if (r1 != null)
  {
    fill(r1.fillColour);
    stroke(r1.strokeColour);

    rect(r1.x, r1.y, r1.w, r1.h);
  }

  if (r2 != null)
  {
    fill(r2.fillColour);
    stroke(r2.strokeColour);

    rect(r2.x, r2.y, r2.w, r2.h);
  }

  for (int cnt = 0; cnt < circles.size(); cnt++)
  {
    fill(circles.get(cnt).fillColour);
    stroke(circles.get(cnt).strokeColour);
    ellipse(circles.get(cnt).x, circles.get(cnt).y, circles.get(cnt).d, circles.get(cnt).d);
  }
}

void saveJsonArray_to_File()
{
  // new json array
  JSONArray ja = new JSONArray();
  JSONObject jo = new JSONObject();

  // json object for first rectangle
  jo.setInt("xPos", r1.x);
  jo.setInt("yPos", r1.y);
  jo.setInt("width", r1.w);
  jo.setInt("height", r1.h);
  jo.setInt("fillColour", r1.fillColour);
  jo.setInt("strokeColour", r1.strokeColour);
  // add to json array
  ja.setJSONObject(0, jo);

  // json object for first rectangle
  jo = new JSONObject();
  jo.setInt("xPos", r2.x);
  jo.setInt("yPos", r2.y);
  jo.setInt("width", r2.w);
  jo.setInt("height", r2.h);
  jo.setInt("fillColour", r2.fillColour);
  jo.setInt("strokeColour", r2.strokeColour);
  // add to json array
  ja.setJSONObject(1, jo);

  // json object for ractangle array
  JSONObject jRectangles = new JSONObject();
  jRectangles.setJSONArray("rectangles", ja);

  // json object for background colour
  JSONObject jColours = new JSONObject();
  jColours.setInt("background", green);

  // new json array for circles
  ja = new JSONArray();
  // loop through the circles
  for (int cnt = 0; cnt < circles.size(); cnt++)
  {
    // get the circle configuration
    Circle c = circles.get(cnt);

    // json object for circle
    jo = new JSONObject();
    jo.setInt("xPos", c.x);
    jo.setInt("yPos", c.y);
    jo.setInt("dia", c.d);
    jo.setInt("fillColour", c.fillColour);
    jo.setInt("strokeColour", c.strokeColour);
    // add to json array
    ja.setJSONObject(cnt, jo);
  }

  // json object for circles array
  JSONObject jCircles = new JSONObject();
  jCircles.setJSONArray("circles", ja);

  // final json array consisting of rectangles, circles and background json objects
  JSONArray jFinal = new JSONArray();
  jFinal.append(jRectangles);
  jFinal.append(jCircles);
  jFinal.append(jColours);

  // save the final array to file
  saveJSONArray(jFinal, "data/jsonArray.json");
}

The output file looks like below

[
  {"rectangles": [
    {
      "strokeColour": -16776961,
      "yPos": 50,
      "fillColour": -65536,
      "width": 50,
      "xPos": 100,
      "height": 50
    },
    {
      "strokeColour": -16776961,
      "yPos": 150,
      "fillColour": -16777216,
      "width": 50,
      "xPos": 250,
      "height": 50
    }
  ]},
  {"circles": [
    {
      "strokeColour": -16777216,
      "yPos": 30,
      "fillColour": -8355712,
      "xPos": 30,
      "dia": 10
    },
    {
      "strokeColour": -8355712,
      "yPos": 300,
      "fillColour": -16777216,
      "xPos": 30,
      "dia": 10
    }
  ]},
  {"background": -16711936}
]

I will spend some time tomorrow on reading it back from file.

I hope this helps you. If you can provide information about what you want to save (variabless and types) I can try to tailor it a bit.

References:

Note:
I have no idea about “Tweak Mode”; as said, new to Processing.

I figured out (while trying to read the file back) that the generated file was not quite what presents a valid json file. So had to rework the erlier part that saves to json a little. After that, I’ve added the reader to read a selected filr and set the variables to the values in the json file.

I’ve added options to save and load using the keyboard. ‘l’ will prompt you for a previously save file to be loaded, ‘s’ will save to a file with a filename yyyyMMdd-hhmmss.json. The sketch no longer draws some rectangles and circles when starting, it will now draw after you’ve loaded a file.

I’ve added some harderning for the reading and saving so it does not blow up in your face :wink:

class Rectangle
{
  int x;
  int y;
  int w;
  int h;

  color fillColour;
  color strokeColour;

  Rectangle(int x, int y, int w, int h, color fillColour, color strokeColour)
  {
    this.x= x;
    this.y = y;
    this.w = w;
    this.h = h;

    this.fillColour = fillColour;
    this.strokeColour = strokeColour;
  }
}

class Circle
{
  int x;
  int y;
  int d;

  color fillColour;
  color strokeColour;

  Circle(int x, int y, int d, color fillColour, color strokeColour)
  {
    this.x= x;
    this.y = y;
    this.d = d;

    this.fillColour = fillColour;
    this.strokeColour = strokeColour;
  }
}

Rectangle r1;
Rectangle r2;

ArrayList<Circle> circles = new ArrayList<Circle>();

color green = 0xFF008000;


String fileDirectory = "data/";
String filenameFormat = "%s%04d%02d%02d-%02d%02d%02d.json";


void setup()
{
  size(400, 400);
}


void draw()
{
  // draw first rectangle
  if (r1 != null)
  {
    fill(r1.fillColour);
    stroke(r1.strokeColour);
    rect(r1.x, r1.y, r1.w, r1.h);
  }

  // draw second rectangle
  if (r2 != null)
  {
    fill(r2.fillColour);
    stroke(r2.strokeColour);

    rect(r2.x, r2.y, r2.w, r2.h);
  }

  // loop through circles
  for (int cnt = 0; cnt < circles.size(); cnt++)
  {
    fill(circles.get(cnt).fillColour);
    stroke(circles.get(cnt).strokeColour);
    ellipse(circles.get(cnt).x, circles.get(cnt).y, circles.get(cnt).d, circles.get(cnt).d);
  }
}

void keyPressed()
{
  if (key == 'L' || key == 'l')
  {
    // prompt user for file to load
    selectInput("Select json file", "readJson");
  } //
  else if (key == 'S' || key == 's')
  {
    // create filename
    String filename = String.format(filenameFormat,
      fileDirectory, year(), month(), day(), hour(), minute(), second());
    try
    {
      saveToJsonFile(filename);
    }
    catch(Exception ex)
    {
      println(ex.getMessage());
    }
  }
}

/*
Callback for reading file
 In:
 file selected by user
 */
void readJson(File selectedFile)
{
  // if the user did not cancel
  if (selectedFile != null)
  {
    try
    {
      // read json file and update variables
      readFromJsonFile(selectedFile.getAbsolutePath());
    }
    catch(Exception ex)
    {
      println(ex.getMessage());
    }
  }
}

/////////////////////////////
// json related
/////////////////////////////
/*
Save variables to json file
 In:
 filepath
 */
void saveToJsonFile(String filePath)
{
  // working variable for json arrays
  JSONArray ja = new JSONArray();
  // working variable for json objects
  JSONObject jo = new JSONObject();
  // final json object to be saved to file
  JSONObject joFinal = new JSONObject();

  // json object for first rectangle
  jo.setInt("xPos", r1.x);
  jo.setInt("yPos", r1.y);
  jo.setInt("width", r1.w);
  jo.setInt("height", r1.h);
  jo.setInt("fillColour", r1.fillColour);
  jo.setInt("strokeColour", r1.strokeColour);
  // add to json array
  ja.setJSONObject(0, jo);

  // json object for first rectangle
  jo = new JSONObject();
  jo.setInt("xPos", r2.x);
  jo.setInt("yPos", r2.y);
  jo.setInt("width", r2.w);
  jo.setInt("height", r2.h);
  jo.setInt("fillColour", r2.fillColour);
  jo.setInt("strokeColour", r2.strokeColour);
  // add to json array
  ja.setJSONObject(1, jo);

  // add rectangles to final json
  joFinal.setJSONArray("rectangles", ja);

  // new json array for circles
  ja = new JSONArray();
  // loop through the circles
  for (int cnt = 0; cnt < circles.size(); cnt++)
  {
    // get the circle configuration
    Circle c = circles.get(cnt);

    // json object for circle
    jo = new JSONObject();
    jo.setInt("xPos", c.x);
    jo.setInt("yPos", c.y);
    jo.setInt("dia", c.d);
    jo.setInt("fillColour", c.fillColour);
    jo.setInt("strokeColour", c.strokeColour);
    // add to json array
    ja.setJSONObject(cnt, jo);
  }

  // add circles to final json
  joFinal.setJSONArray("circles", ja);

  // add background colour to final json
  joFinal.setInt("background", green);

  // save the final object to file
  saveJSONObject(joFinal, filePath);
}

/*
Read variables from json file and update
 In:
 filepath
 */
void readFromJsonFile(String filePath)
{
  JSONObject json = loadJSONObject(filePath);
  // get the rectangles, circles and the background
  getRectangles(json);
  getCircles(json);
  getBackground(json);
}

/*
Extract rectangles from json object and update global variables
 In
 json object
 */
void getRectangles(JSONObject j)
{
  JSONArray ja = j.getJSONArray("rectangles");
  JSONObject jo;

  if (ja.size() > 0)
  {
    jo = ja.getJSONObject(0);
    r1 = new Rectangle(jo.getInt("xPos"), jo.getInt("yPos"), jo.getInt("width"), jo.getInt("height"), jo.getInt("fillColour"), jo.getInt("strokeColour"));
  }

  if (ja.size() > 1)
  {
    jo = ja.getJSONObject(1);
    r2 = new Rectangle(jo.getInt("xPos"), jo.getInt("yPos"), jo.getInt("width"), jo.getInt("height"), jo.getInt("fillColour"), jo.getInt("strokeColour"));
  }
}

/*
Extract circles from json object and update global variables
 In
 json object
 */
void getCircles(JSONObject j)
{
  JSONArray ja = j.getJSONArray("circles");
  JSONObject jo;

  // destroy current circles
  circles.clear();

  // loop through circles in json object
  for (int cnt = 0; cnt < ja.size(); cnt++)
  {
    jo = ja.getJSONObject(cnt);
    Circle c = new Circle(jo.getInt("xPos"), jo.getInt("yPos"), jo.getInt("dia"), jo.getInt("fillColour"), jo.getInt("strokeColour"));
    circles.add(c);
  }
}

/*
Extract background color from json object and update global variable
 In
 json object
 */
void getBackground(JSONObject j)
{
  green = j.getInt("background");
}

This is just a demo how it can be approached. Hope it helps. If you have questions, ask.

Thanks for your continued effort to answer my questions, sterretje.

For starters: Tweak Mode is a running mode in Processing which allows you to alter values in your code while the sketch runs. You do it by hover+dragging numerical values using the cursor (only works on hard-coded values). For colour values it gives you a colour picker. You run a sketch in Tweak mode by saving the sketch and using hotkey Shift+Cmd+T, or via dropdown Sketch > Tweak. After altering the values and closing the sketch you can determine whether you want to keep the changes you’ve made. Here’s a baby-sketch as an example, running this in Tweak Mode gives you control over the rectangles’ colour, location and size.

void setup() {
  size(500, 500);
}

void draw() {
  background(0);
  noStroke();
  fill(255,0,255);
  float x = 200;
  rect(x, 200, 30.0, 30.00);
}

In my project-sketch I’m offsetting the location and size of shape-instances based on noise or mapped trigonometric values. A lot of variables are dependent on each other or feed off each other. Because I’m using Tweak Mode I became lackluster with properly declaring/naming values my code. Lines started looking like this:

float tA = constrain(map(sin((locT/11.6) + int(whichDisc/18.2) +4 .7), -0.4, 1.0, -1.0, 1.4), 0.6, 95.1);

Every hard-coded value here is something I would alter by dragging, which is great for testing variations and finding unexpected results quickly. I felt like declaring and setting all of these would cause there to be an abundance of variables, which I envision would become overly organized and cluttered/chaotic. I must say that it’s kind of nice that everything is just there, within the formula.

Thing is, I have about 10 variables like this, which combined make the final design, so it’s an abundance either way. It also isn’t very future-proof: When I’m happy with the way a design looks, I render it out to .pdf, and happily go on altering the variables, but by doing that I lose the way I have reached a specific result. It’s hard to replicate a look becaue it is dependant on multiple individual variables. This is why I was curious whether it’s possible to not just save the value of a variable (for which your answers give a good example), but to save just entire lines of code, or the complete sketch, as text. In pseude-code what I’m looking for would look something like this:

void setup(){
  size(500, 500);
}

void draw(){
  if (sPDF) {
    beginRecord(PDF, "PDFs/render_" + frameCount + ".pdf");
    println("saving frame");
  }

//Code for design here

  render();
}

void render() {
  if (sPDF) {
    endRecord();
    sPDF = !sPDF;
    saveLines();
    println("saved frame", frameCount);
  }
}


void saveLines(){
  String line = codeLines.get(25); //Reference a specific line to be saved
  saveText(text, "saved_text/line.txt");
}

Writing that out makes me realize that my choice to base my workflow on Tweak Mode was not the smartest. I’m wondering whether the best way of going about this now is to create variables for every value I need anyways, and to make a setup with sliders to alter everything in real time, saving everything in JSON afterwards using your method. To me it doesn’t seem like what I’m asking should be too complex though, I just have no idea how to go about this.

Again, thanks a lot for your help thusfar, if you would need any additional information about the project let me know.

If Tweak mode is modifying the code directly, then the safest way to preserve it would be to save the entire sketch along with each PDF. One way would be to save each PDF in its own directory and then save and copy your PDE code in there as well. It’s the brute-force method, but has the advantage that if you ever later make structural changes to your code, you’ll still have the exact version that generated that specific PDF.

For the more conventional way, consider putting all your settings as variables in a single object and look up Java object serialization. Serialization turns an object into a byte stream that you can then save to a file. Even then, you’d need to save the variables file along with each PDF. The PDF format probably has ways that you could include data in the file itself, but I doubt Processing has any mechanism for doing so.

Note that serialization saves the object in binary, so it’s not human-readable. You’d have to write code to reload and use the parameters. JSON is safer for readability.

I initially could not find the Tweak Mode; found it now.

Option 1
After changing variables and closing the run window, you are prompted to save the file. Assuming that you want to save immediately after generating a PDF, you can simply close the run Window and safe the sketch; make a backup before you even start running the sketch.

Option 2
Below does not write to (or read from) JSON but it demonstrates the use of variables (and classes). You can modify the variables in the draw() method with the Tweak Mode. Because draw recreates the objects (rectangle and formula) the members of the variables (settings) are always accessible.

Pressing ‘S’ or ‘s’ will display the current settings.

Usually numbered variables (f1 … f9 in the Formula class) are indicating bad code (one should use arrays) but I don’t know most of your code. Giving them proper names that describe their purpose is the way to go. If you only have one formula to apply or if you always have the same number of parameters, use of an array is OK). You will probably also have plenty of rectangles, circles etc. and they should go in arrays (or ArrayLists).

class Rectangle
{
  float x = 200;
  float y = 200;
  float w = 30.0;
  float h = 30.0;

  int r = 255;
  int g = 0;
  int b = 255;

  Rectangle(float x, float y, float w, float h, int r, int g, int b)
  {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.r = r;
    this.g = g;
    this.b = b;
  }
}


class Formula
{
  float f1;
  float f2;
  float f3;
  float f4;
  float f5;
  float f6;
  float f7;
  float f8;
  float f9;
  
  float loc;
  float which;

  Formula(float f1, float f2, float f3, float f4, float f5, float f6, float f7, float f8, float f9)
  {
    this.f1 = f1;
    this.f2 = f2;
    this.f3 = f3;
    this.f4 = f4;
    this.f5 = f5;
    this.f6 = f6;
    this.f7 = f7;
    this.f8 = f8;
    this.f9 = f9;
  }

  float calculate(float locT, float whichDisc)
  {
    which = whichDisc;
    loc = locT;
    return constrain(map(sin((locT/f1) + int(whichDisc/f2) + f3), f4, f5, f6, f7), f8, f9);
  }

  // float tA = constrain(map(sin((locT/11.6) + int(whichDisc/18.2) +4.7), -0.4, 1.0, -1.0, 1.4), 0.6, 95.1);
  //                                    1                     2      3     4     5    6     7     8    9
}


Rectangle r1;
Formula f1;


void setup()
{
  size(500, 500);
}

void draw()
{
  r1 = new Rectangle(200, 200, 30.0, 30.0, 255, 0, 255);
  background(0);
  noStroke();
  fill(r1.r, r1.g, r1.b);
  rect(r1.x, r1.y, r1.w, r1.h);

  f1 = new Formula(11.6, 18.2, 4.5, -0.4, 1.0, -1.0, 1.4, 06, 95.1);
  float tA = f1.calculate(100, 200);
}

void keyPressed()
{
  if (key == 'S' || key == 's')
  {
    println("saving rectangle");
    println("x = " + str(r1.x));
    println("y = " + str(r1.y));
    println("w = " + str(r1.w));
    println("h = " + str(r1.h));
    println("r = " + str(r1.r));
    println("g = " + str(r1.g));
    println("b = " + str(r1.b));
    
    println("saving formula");
    println("f1 = " + str(f1.f1)); 
    println("f2 = " + str(f1.f2)); 
    println("f3 = " + str(f1.f3)); 
    println("f4 = " + str(f1.f4)); 
    println("f5 = " + str(f1.f6)); 
    println("f6 = " + str(f1.f6)); 
    println("f7 = " + str(f1.f7)); 
    println("f8 = " + str(f1.f8)); 
    println("f9 = " + str(f1.f9));
    println("loc = " + str(f1.loc));
    println("which = " + str(f1.which));
  }
}

Option 3
Binary serialisation as mentioned by @scudly. My experience with it i(in C#) is not great (and that is an absolute understatement, a whole list of explitives come to mind).
If you modify your code, it’s possible the earlier saved object can no longer be used. I suspect that that happens when your reshuffle, add extra code and variables etc.; it might also happen when a different version of the Java JRE is used.

So personally I would stay away from it and will never consider it as an option. As a customer of a company that used it in their C# products, each software update broke the deserialisation; we had to send the file to them and they would regenerate it in the correct format.

When you save the Sketch prior to running,
it can save a copy of itself with loadStrings/ saveStrings with a date/timeStamp including seconds

1 Like