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.
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.
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
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");
}
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
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.
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:
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).
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.