Light Rays - Frequently Asked Questions
1 Read function #12 projectDetails() of the Light Rays contract with an input tokenId (0–99) This will read out 7 variables:
projectName, artist, description, website, license, getRenderer, getTokenScript
The scripts to generate the artwork are stored in the last two variables:
▪ getRenderer = Blender 3.0.1
▪ getTokenScript =U = 'Color';T = False;S = 'Damped Track';R = 'Empty';N = 'Array.002';M = 'Array.001';L = 'ARRAY';K = 'Material';J = 'OBJECT';I = 'EDIT';F = 'Cube';E = 'DESELECT';D = 'Material.001';C = 'Principled BSDF';B = True;f = 'Empty';e = 'Camera';b = round;import bpy as A, random as a;A.context.scene.render.engine = 'CYCLES';A.context.scene.cycles.feature_set = 'SUPPORTED';A.context.scene.cycles.samples = 80;A.context.scene.cycles.use_adaptive_sampling = B;A.context.scene.render.resolution_x = 2400;A.context.scene.render.resolution_y = 2400;A.context.scene.frame_start = 1;A.context.scene.frame_end = 1;A.ops.object.select_all(action = E);A.data.objects['Light'].select_set(B);A.ops.object.delete();A.ops.object.select_all(action = E);O = A.data.objects.new(R, None);A.context.scene.collection.objects.link(O);A.ops.object.select_all(action = E);A.context.view_layer.objects.active = A.data.objects[e];A.ops.object.constraint_add(type = 'DAMPED_TRACK');A.context.object.constraints[S].target = A.data.objects[R];A.context.object.constraints[S].track_axis = 'TRACK_NEGATIVE_Z';A.ops.object.select_all(action = E);A.context.view_layer.objects.active = A.data.objects[F];A.data.objects[F].select_set(B);A.ops.object.modifier_add(type = L);A.context.object.modifiers['Array'].count = 5;A.ops.object.modifier_add(type = L);A.context.object.modifiers[M].relative_offset_displace[0] = 0;A.context.object.modifiers[M].relative_offset_displace[1] = 1;A.context.object.modifiers[M].count = 5;A.ops.object.modifier_add(type = L);A.context.object.modifiers[N].relative_offset_displace[0] = 0;A.context.object.modifiers[N].relative_offset_displace[2] = 1;A.context.object.modifiers[N].count = 5;A.data.objects[F].location[0] = -4;A.data.objects[F].location[1] = -4;A.data.objects[F].location[2] = -4;A.ops.object.mode_set(mode = I);A.ops.mesh.subdivide(number_cuts = 2);A.ops.mesh.select_all(action = E);A.ops.mesh.select_mode(use_extend = T, use_expand = T, type = 'FACE');A.ops.object.mode_set(mode = J);A.context.active_object.data.polygons[11].select = B;A.context.active_object.data.polygons[19].select = B;A.context.active_object.data.polygons[27].select = B;A.context.active_object.data.polygons[35].select = B;A.context.active_object.data.polygons[43].select = B;A.context.active_object.data.polygons[51].select = B;A.ops.object.mode_set(mode = I);A.ops.object.material_slot_assign();A.ops.object.mode_set(mode = J);A.data.materials[K].node_tree.nodes[C].inputs[9].default_value = 1;A.data.materials[K].node_tree.nodes[C].inputs[17].default_value = 1;A.data.materials[K].node_tree.nodes[C].inputs[0].default_value = 0, 0, 0, 1;A.data.materials[K].node_tree.nodes[C].inputs[21].default_value = 0.5;A.ops.object.mode_set(mode = I);A.ops.mesh.select_all(action = 'INVERT');A.ops.object.mode_set(mode = J);A.ops.object.material_slot_add();A.context.active_object.data.materials[1] = A.data.materials.new(name = D);A.data.materials[D].use_nodes = B;A.ops.object.mode_set(mode = I);A.ops.object.material_slot_assign();A.ops.object.mode_set(mode = J);A.data.materials[D].node_tree.nodes[C].inputs[9].default_value = 0;A.data.materials[D].node_tree.nodes[C].inputs[17].default_value = 1;A.data.materials[D].node_tree.nodes[C].inputs[0].default_value = 1, 1, 1, 1;A.data.materials[D].node_tree.nodes[C].inputs[21].default_value = 0.1;G = A.data.worlds[A.context.scene.world.name].node_tree.nodes.new(type = 'ShaderNodeTexSky');H = A.data.worlds[A.context.scene.world.name].node_tree.nodes['Background'];G.location.x = H.location.x - 300;G.location.y = H.location.y;P = G.outputs[U];Q = H.inputs[U];A.data.worlds[A.context.scene.world.name].node_tree.links.new(P, Q);a.seed(0);DD = A.data.worlds['World'].node_tree.nodes['Sky Texture'];A.data.objects[f].location[0] = b(a.uniform(-3, 3), 5);A.data.objects[f].location[1] = b(a.uniform(-3, 3), 5);A.data.objects[f].location[2] = b(a.uniform(-3, 3), 5);A.data.objects[e].location[0] = b(a.uniform(-10, 10), 5);A.data.objects[e].location[1] = b(a.uniform(-10, 10), 5);A.data.objects[e].location[2] = b(a.uniform(-10, 10), 5);A.data.objects[e].data.dof.use_dof = True;A.data.objects[e].data.dof.focus_distance = b(a.uniform(1, 12), 5);A.data.objects[e].data.dof.aperture_fstop = b(a.uniform(.05, 8), 5);A.data.objects[e].data.lens = b(a.uniform(40, 90), 0);DD.sun_elevation = b(a.uniform(0, 5), 5);DD.sun_size = b(a.uniform(.001, 0.07), 5);DD.sun_intensity = b(a.uniform(.01, 3), 5);DD.sun_rotation = b(a.uniform(0, 6.28319), 5);DD.altitude = b(a.uniform(0, 55), 5);DD.air_density = b(a.uniform(0, 4), 5);DD.dust_density = b(a.uniform(0, 10), 5);DD.ozone_density = b(a.uniform(0, 3), 5);
For those without a coding background:
1 Install and open Blender 3.0.1.
2 Either click esc key to exit the startup menu or click New File --> General.
3 Activate the Scripting Workspace by clicking Scripting on the topmost toolbar.
4 Click New in the text editor and paste in the getTokenScript.
This is stored in function #12 of the Light Rays contract.
5 Run the script by pressing the play button ▶️ in the text editor.
6 Click Viewport Shading to see the changes.
7 Hit F12 or on the main toolbar, navigate to Render --> Render Image.
For those with a coding background:
1 Install Blender 3.0.1 and open a command shell. 2 Retreive the getTokenScript (function #12) from the Light Rays contract. 3 Save the getTokenScript as a python file (i.e. getTokenScript.py) 4 Issue the command in your shell:
/path/to/Blender -P /path/to/{getTokenScript}.py --folder {path/to/where/you/would/like/to/save} -f 0 -b
Additional resources:
For your convienence, I have prepared the .blend and .py files for you. They are available on my website:
api.cooperjamieson.com/lightrays/{tokenId}.blend
api.cooperjamieson.com/lightrays/{tokenId}.py
This link may auto-download the file. This may be blocked or issue a warning by your web browser.
This project is intended to be viewed on square digital displays. BlueCanvas makes a nice matte screen for viewing these works. If you are interested in viewing these works in a printed form, continue reading:
First, determine the required image dimension for your desired print size. Recall that 300 pixels per inch (ppi) will yield a high quality print. An 8" x 8" print requires a 2400 x 2400 pixel image. Multiply your desired print size by 300 to determine your required image resolution.
For those without a coding background:
1 Open blender and the .blend file associated with your token.
2 Navigate to Output Properties in the Data Properties panel.
3 In the Dimensions dropdown, click the Resolution % and type in 200, 300, 400, etc... This will double (4800 x 4800), triple (7200 x 7200), or quadruple (9600 x 9600) your image dimensions. Careful, doubling the Resolution % will quadruple render time.
For those with a coding background:
1 Append --resolution {desired-resolution} to your command line where desired-resolution is an integer like 4800 or 7200:
/path/to/Blender -P /path/to/{getTokenScript}.py --folder {path/to/where/you/would/like/to/save} --resolution {desired-resolution} -f 0 -b
Blender is a freely distributed an open source program. The Blender Foundation stands by their statement: "You will be able to download every version of Blender at any time. It's yours to keep forever." All releases are available for download here: https://download.blender.org/release/
To insure that the artwork from Light Rays remains accessible, it is quite important for the software to remain available and accessible to the public. Therefore, Blender seemed like the ideal candidate 3D program for blockchain artworks.
Blender files are quite large in size, often exceeding 10 mb. Even the default startup scene file is 764 kb! If you wanted to host this file on the blockchain, assuming a gas price of 50 gwei, this would cost > 24 ethereum...More than $60,000 USD at current valuations!
Therefore, I had to come up with a method to shrink the file size. I found that generating the scene from python commands to be only ~ 3 kb. At this point, I added a few extra kb to create diversity among the output images. In total, the script to generate the outputs of Light Rays is ~ 4 kb and was, essentially minified from 764 kb. I had the opportunity to truly uglify the script and save another kb or two, but I opted away from this to assist others who want to use parts of this code for starting their own projects.
If your question is not answered here, please reach out via email: cooperjamieson@gmail.com