Skip to content

scripting

Bruno Levy edited this page Jul 23, 2022 · 7 revisions

The Graphite programs module

Graphite has an embedded scripting language, based on LUA. It can also be scripted in Python using the gompy plugin.

Graphite has a Programs module. It is a text editor that can be used to write simple programs. It is a bit basic as an editor, but the big advantage is that it is completely aware of what Graphite can do, so it has automatic completion (using the <tab> key) and help bubbles: if you hover a function with the mouse it will display what the function does and what are its arguments, if you push <tab>, it will autocomplete the name of the function, if you push it twice it will write the default arguments, and if you hover an object it will list all the functions supported by this object. As shown in the small video above, it makes it very easy to quickly write programs, by pushing <tab> and hovering the code.

Note Completion and help bubble works for objets that are already constructed, hence you may want to run your code (<F5> or 'play' icon) often to be able to use autocompletion. Sometimes it will be useful to only execute a part of your program (for instance, just the new line or block you just typed). If there is a text selection, <F5> only runs the selected text (to create a text selection, use the mouse, or use <shift><right arrow>).

Scripting the Graphite scene-graph in LUA

By default, Graphite scripting language is LUA. Refer to LUA documentation for general information about the LUA language. The remainder of this tutorial will focus on Graphite specific functions.

The main object in Graphite is scene_graph. To see its list of functions, type scene_graph somewhere in the editor, and hover it with the mouse.

Now it us create an object, so type scene_graph.create_object( (or use the completion, type sc then <tab> then .cr the ), then hover it with the mouse to see the online help, then press <tab> again to generate a function call with the default arguments. You will see that the list of arguments is between both parentheses and curly braces, this is because sometimes we find it easier to have the names of the arguments in the call, hence all Graphite functions can take either a list of (unnamed) arguments, or a list of name-value pairs (the curly braces are there to construct a dictionary, because named arguments are not natively supported by LUA, unlike Python).

Now for classname, write 'OGF::MeshGrob' (do not forget the quotes), and for name write 'S'.

Press <F5>, you see that a new object appears in the Scene (top left).

Now you want to have a LUA variable that corresponds to the created object. Now type:

   S = scene_graph.objects.S

If you want to see what it does, select the line, push <F5>, and hover the left-hand S

Let us see now how to apply commands on this object. If you want to create a sphere, you can invoke Surface/Shapes/create sphere from the menu. In LUA, all the menus attached with an object are callable from interfaces attached to the object. To see the interfaces available for our object S, type S.I. then hover it and press <tab>. There is a Shapes interface that corresponds to the Surface/Shapes menu. Now press create_sph then <tab> twice, and you will obtain:

   S.I.Shapes.create_sphere({precision=4,radius=1,center='0 0 0'})

Select the line and press <F5>. Normally you will see the sphere appear.

Note you can start your script with scene_graph.clear(), then you can call it using <F5> without needing to select the new line and without fearing creating multiple objects.

Your script should look like that now:

-- Lua (keep this comment, it is an indication for editor's 'run' command)
scene_graph.clear()
scene_graph.create_object({classname='OGF::MeshGrob',name='S'})
S = scene_graph.objects.S
S.I.Shapes.create_sphere({precision=4,radius=1,center='0 0 0'})

In Graphite, everything you can do interactively can be scripted in LUA. Let us see how to change graphic attributes.

Now in the editor, write S.shader and hover it with the mouse. It says that S.shader is an OGF::MeshGrobShader and gives the names and values of all its attributes. We see for instance that S.shader.mesh_style is equal to 'false; 0 0 0 1; 1', which means mesh display disabled (false), color black (0 0 0 1, the 1 is for transparency, means opaque here), and the last one is mesh width. Let us display the mesh in blue with width 2:

S.shader.mesh_style = 'true; 0 0 1 1; 2'

The programs module has an Examples... menu, with some scripts in different languages. the example Examples.../Lua/useful_tips.lua shows more examples for scripting the scene-graph.

Manipulating the elements of a mesh in a script

creating vertices, triangles and quads

With Graphite scripting, you can do everything that you can do from the interface, and you can do also much more ! It is possible to directly modify the elements of a mesh, through the MeshEditor interface. Create a MeshEditor with:

   E = S.I.Editor

Then you can create vertices with E.create_vertex(x,y,z), triangular facets with E.create_triangle(i,j,k) (note that indices start from 0), and quadrangular facets with E.create_quad(i,j,k,l). Refer to Examples.../Lua/mesh_editor_simple.lua and to Examples.../Lua/mesh_editor_plotter.lua (in the menu of the text editor).

changing vertices coordinates

The MeshEditor gives you also a low-level access to the mesh attributes (including vertices coordinates). To access point coordinates, use XYZ = E.find_attribute('vertices.point'). By hovering it, you will see it is an object of type OGF::NL::Vector. Then you can modify point coordinates as follows:

    for v=0,E.nb_vertices-1 do
       XYZ[3*v  ] = ...
       XYZ[3*v+1] = ...
       XYZ[3*v+2] = ...
    end

(the x,y,z coordinates of vertex v are stored at XYZ[3*v],XYZ[3*v+1] and XYZ[3*v+2] respectively).

By hovering XYZ, you will see it has:

  • dimension (=3), because we have 3d points
  • size, the number of points
  • nb_elements = size * dimension, total number of coordinates in the vector

It also knows the type of the elements through XYZ.element_meta_type (if you type it and hover it, you will see it is an instance of OGF::MetaBuiltinType with name = double).

See Examples.../Lua/randomize.lua (in the menu of the text editor), create a sphere then run the script.

creating and changing mesh attributes

Similarly to vertices coordinates, arbitrary attributes and vertices selection can be created and modified in a script. Refer to Examples.../Lua/mesh_editor_attributes.lua.

Solving linear systems in LUA

We have seen in the previous section about mesh attributes that there is an NL::Vector type that can be scripted in LUA. Then, you probably guessed what comes next, of course, NL::Matrix (that represents a sparse matrix). It is possible to create a vector using NL.create_vector(dim) where dim corresponds to the number of components of the vector, and a matrix using NL.create_matrix(m,n) where m and n denote the number of rows and the number of columns respectively.

Individual coefficients of a vector v can be accessed and modified through v[.] (with indices that start from 0). A coefficient can be added to a matrix using M.add_coefficient(i,j,val). Note that if the matrix already has a coefficient (i,j), then val will be added to the previously stored value.

Refer to Examples.../Lua.linear_algebra.lua (in the menu of the text editor).

Scripts with graphic user interface

As said before, the graphic user interface of Graphite is completely written in LUA, and uses under the hood the excellent "Dear Imgui".

It is very easy to add a "module" to Graphite, that is, something that appears in the list on the left, with a dialog that can be shown or hidden.

In Graphite, each module is impemented as a dictionary, with the following attributes:

name description
name name to be used in the modules list
visible visibility of the dialog
draw_window function to draw the GUI and handle the events
x,y,w,h optional, initial coordinates of the dialog
icon optional, icon
menubar optional, true if a menubar should be drawn, then one defines draw_menu()

Let us add a module to graphite ! So we define a new module...

   my_dialog = {}
   my_dialog.visible = true
   my_dialog.name = 'My dialog'
   function my_dialog.draw_window()
      imgui.Text('Hello, world')
   end

...and we register it to Graphite:

   graphite_main_window.add_module(my_dialog)

Let us do something more interesting, add a button that does something:

   function my_dialog.draw_window()
      imgui.Text('Hello, world')
      if imgui.Button('Click me') then
        print('button pushed')
      end
   end

The commented source is in Examples.../Lua/dialog.lua (in the menu of the text editor).

The way to do things in ImGui is particular: the same function draws the GUI and handles the events. For instance, when you do if imgui.Button('Click me') then ..., it both draws the button and detects when it is pushed. As you will notice, writing a GUI this way is very convenient ! In particular, you no longer need to maintain a state, to construct widgets and event handlers, to maintain coherence between them. The GUI is dynamically constructed and handled by a simple function. It also makes it easier to show and hide elements of the GUI dynamically, in function of the context (for instance, do not display the GUI for facets and volumetric cells for a mesh that only has points).

More information about "Dear Imgui":

Note to run Dear Imgui with LUA, Graphite uses https://github.com/patrickriordan/imgui_lua_bindings. There is a little "gotcha": some functions of ImGui use "by-reference" arguments, such as bool ImGui::InputInt(const char* label, int* v) that takes a pointer to the integer currently edited. In C++, you will write something like:

   if(ImGui::InputInt("my label", &val) {
      ...
   }

However, LUA only supports "by-value" function arguments. For functions that have "by-reference" arguments, the LUA binding will take the integer as an argument, and return it. Note that LUA functions can return multiple values, so we will have something like:

    pushed,val = imgui.InputInt('my label',val)
    if pushed then
       ...
    end

You can also take a look at the sources of Graphite's GUI in the lib subdirectory to see examples of how doing this or that.

Drawing things in the graphic user interface

Dear ImGui has functions to directly draw 2D graphics in the GUI. These functions are also callable from LUA. See Examples.../Lua/custom_rendering.lua for an example.

The Statistics module of Graphite is completely implemented in LUA, using both Dear ImGui's custom rendering API and the Editor interface of the OGF::MeshGrob object to access the attributes. It is implemented in lib/histogram.lua.

Graphite Lua objects OGF::LuaGrob

For displaying 3D graphics, Graphite uses under the hood the GLUP API, that re-implements the old-fashioned (but easy-to-used) OpenGL 2.x fixed functionality pipeline using modern OpenGL 3.x. In addition, it has volumetric primitives, automatic normal generation, advanced clipping modes, full-screen effects, automatic wireframe rendering and much more !

In Graphite, it is possible to create OGF::LuaGrob objects, that only contain a LUA script that will be executed each time the object should be displayed.

To create a new OGF::LuaGrob, in the text editor, use File.../New.../LuaGrob shader. It will create an example program that displays a yellow sphere. Now push <F5> (or the play icon) and you will see the sphere.

See also Examples.../Lua/LuaGrob/ in the menu of the text editor.

  • clock.lua displays an analog clock
  • color_spheres.lua displays a 3d array of spheres with different colors

LuaGrob objects are saved with the scene, in .graphite files. They can contain a "shader" (that is, a Lua function executed each time the object should be displayed) and normal Lua code (that can be automatically executed when the scene is loaded).

Clone this wiki locally