In this example, we'll develop a simple script to raise a dam and deposit a user defined volume of tailings. By combining a couple of functions into a single 'command' using a macro can help save time and make it easier to run different scenarios.
In this example, we'll create a macro to raise a dam using a dam template and then pour a target volume of tailings into it, with a fixed pond volume.
Assumptions
This tutorial assumes that a dam template for the dam will have already been created.
Record the basic steps in a macro
To record a macro, run Scripts/Record macro.
The steps we want to record are:
- View/Clear graphics
- Dam templates/Dams/Raise dam
- Ooze/Single stream deposition/Fixed pond volume/Flow path down slope
The first step is to run the command Clear graphics. This will get rid of any old geometry that is loaded in the 3D window.
Raise a dam template to any elevation - by the time we're done with this script then the raise elevation will be a user entered value.
When raising the dam, make sure that the discharge points are generated upstream. We'll use these points in the deposition model.
Once the dam raise is done, run the deposition model. In this case its Ooze/Single stream/Fixed pond volume/Flow path down slope.
Again, the values entered in here will be replaced with user specified values later on.
Once the deposition run has completed, run Scripts/End macro recording to finish recording the macro.
Editing the macro
If you open the macro in a text editor you should see the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': 110.0,
'generate_discharge_points': True,
'grid': u'../../../grid-10.mgrid',
'save': True,
'upstream_toe_extension': 0})
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'initialDischargeElevation': 110.0,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': 109.0,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': 105.0,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': 50000.0,
'verticalOffset': 0.0})
|
Lets break the code down. The first 4 lines are generated when we run Clear graphics. The command is found using get_command
and stored in the cmd variable in line #3. This is called with a Python dictionary of options in line 4. This command only takes a single option which is dont_ask. If this is False (the default value) then the user will be prompted to save any unsaved layers when the command is called. If dont_ask is True then unsaved layers will be unloaded without consulting the user.
1 2 3 4 |
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
|
The next code is for raising the dam.
6 7 8 9 10 11 12 13 14 15 16 17 |
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': 110.0,
'generate_discharge_points': True,
'grid': u'../../../grid-10.mgrid',
'save': True,
'upstream_toe_extension': 0})
|
The Raise dam command is found in line 8 and assigned to the cmd variable. Its called in line #9 with variables corresponding to the values entered in the dialog box.
Finally, the deposition function is called.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': 200000.0,
'initialDischargeElevation': 110.0,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': 109.0,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': 105.0,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': 50000.0,
'verticalOffset': 0.0})
|
This is all Python code, and the macro can be executed in Muk3D by dragging & dropping the script file into the 3D window, or double clicking the script file in the Project Explorer. If we run the script again, it will just run the dam raise and deposition model using the values that are hardwired into the script file. To make this interesting we need to replace many of the hardwired values with variables.
Running with user supplied values
To make this script more useful we're going to replace some of the hardwired values with variables. At the top of the script file, add the following variables. For the base grid, since this file is relative to the directory you recorded the macro in, you should copy and paste the value for base grid in the Raise dam parameters (Line 15 of the script above).
Replace the hardwired numbers in the script with these variables. (Line numbers refer to the script below).
- Line #19:
'elevation': dam_elevation
- Line #21:
'grid': base_grid
- Line #31:
'fluidVolume': fluid_volume
- Line #32:
'initialDischargeElevation': dam_elevation-1
. The initial discharge elevation is set 1m below the dam crest. - Line #35:
'maxPondElevation': dam_elevation - 1
. The maximum pond elevation is set to be 1m below the dam crest. - Line #39:
'pondElevation': pond_elevation_guess
- Line #47:
'tonnage': tailings_volume
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
dam_elevation = 110
base_grid = '../../../grid-10.mgrid'
fluid_volume = 200000
tailings_volume = 50000
pond_elevation_guess = dam_elevation - 5
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': fluid_volume,
'initialDischargeElevation': dam_elevation-1,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': dam_elevation - 1,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': tailings_volume,
'verticalOffset': 0.0})
|
If we run this code again we should still see the same result.
Creating a simple user interface
The next step is to add a simple user interface so the variables can be set by the user when the script is run, rather than having to manually edit them in the script. This can be done using the Muk3D API module called muk3d.ui.forms.
Within the module there are some functions that can be used to create simple user interfaces. For this script we just need to get some decimal (floating point) numbers and a filename (for the grid). These can be added to a form using get_float and get_filename. The dialog is created and shown using the ask function.
To use these functions they need to be explicitly imported from the muk3d.ui.forms module using the syntax:
from full.module.name import function1, function2
We'll create a new script file to refine the user interface before adding it to our deposition script. Create a new script by right clicking the working directory and selecting Blank script.
Call the script show_ui (you can add .py to the end if you want, otherwise Muk3D will add it for you).
Right click the new script and press Edit to open it in your text editor. Now we can start writing the code. In line #1 we import the functions ask, get_float, and get_filename from the muk3d.ui.forms module. The ask function takes at least one named argument called fields. This is a list of values returned from the get_* functions. In this case there are 3 x get_float calls and one get_filename.
The keyword argument called last_values_key is used to identify the form so that the Last values for all fields right-click item in the dialog can be used. The return value of the ask function is assigned after the user either clicks Ok or Cancel on the dialog.
1 2 3 4 5 6 7 8 9 |
from muk3d.ui.forms import ask, get_float, get_filename
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
|
Run the show_ui.py script by double clicking. A dialog box will pop up.
After you click ok, nothing happens. Lets add a print statement to see what the result output variable contains.
1 2 3 4 5 6 7 8 9 10 |
from muk3d.ui.forms import ask, get_float, get_filename
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
print result
|
Run the script, enter some values and press ok. Run the script again and press cancel. The following should be shown in the Output Window:
{'Base grid': u'../../../grid-10.mgrid', 'Fluid volume': 200000.0, 'Tailings volume': 50000.0, 'Dam elevation': 110.0} None
The first line is the result when Ok was pressed. Its a Python dictionary (key-value data structure). When cancel was pressed, the return value was None, which is the same as a null value in other languages.
Considering just the case when the user presses Ok, if we want to access values in the returned dictionary, we can access them using the field names entered in the get_float/get_filename function, using the [ ]
notation. Create some new variables after the call to ask, and assign the return values from the dialog box fields to them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from muk3d.ui.forms import ask, get_float, get_filename
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
dam_elevation = result['Dam elevation']
fluid_volume = result['Fluid volume']
tailings_volume = result['Tailings volume']
base_grid = result['Base grid']
print 'Dam elevation:', dam_elevation
|
Run this code and you will see the dam elevation printed to the output window. Run the script again and press Cancel instead of Ok. You will see an error written to the output window.
Error in script: show_ui.py
Traceback (most recent call last):
TypeError: 'NoneType' object has no attribute '__getitem__'
This error happens because the return value when Cancel is pressed is None. If we try and use the [ ] operator on a None value, the error above will be raised. To make sure the user doesn't see messages like this, we need to check for an appropriate return type and exit the script if the user presses Cancel.
We can do this by using the Muk3D API function muk3d.util.is_valid. We import the function in line #2 and then call it in line 11. If the result variable contains None, then the script will end. If the result is valid (it contains a dictionary in this case) then the script will continue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from muk3d.ui.forms import ask, get_float, get_filename
from muk3d.util import is_valid
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
is_valid(result)
dam_elevation = result['Dam elevation']
fluid_volume = result['Fluid volume']
tailings_volume = result['Tailings volume']
base_grid = result['Base grid']
print 'Dam elevation:', dam_elevation
|
Run the script again and press Cancel. The script should stop without any error messages. Now that we've got this working, we can go and add this user interface to our deposition script.
Incorporating the UI in the deposition script
Copy and paste the show_ui.py script to the top of the deposition script. Because we've also assigned the UI result values to variables (Lines 13 - 16) we can comment out the hardwired variables (lines 18 - 21). Make sure the pond_elevation_guess variable is not commented out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
from muk3d.ui.forms import ask, get_float, get_filename
from muk3d.util import is_valid
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
is_valid(result)
dam_elevation = result['Dam elevation']
fluid_volume = result['Fluid volume']
tailings_volume = result['Tailings volume']
base_grid = result['Base grid']
# dam_elevation = 110
# base_grid = '../../../grid-10.mgrid'
# fluid_volume = 200000
# tailings_volume = 50000
pond_elevation_guess = dam_elevation - 5
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': fluid_volume,
'initialDischargeElevation': dam_elevation-1,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': dam_elevation - 1,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': tailings_volume,
'verticalOffset': 0.0})
|
Run the script again, enter the initial values, and you should see the dam created and the tailings deposited.
Getting output data from the dam raise and deposition run
Dam raise volume
When the dam raise command is called, the value returned into the result variable will be a Python dictionary with some basic results. You can see the contents of the return value by adding a print statement after the dam raise function is called.
{'footprint_area': 262062.7792993188, 'cell_volumes': {}, 'crest_elevation': 110.0, 'fill_volume': 1367639.6864229345}
If we want the fill volume for the dam then we just use the [ ] operator on the result variable.
fill_volume = result['fill_volume']
Deposition results
A tutorial on parsing return values from a deposition run can be found here. The code from the tutorial has been pasted at the end of the deposition script file (lines 69 - 76), and the print statements removed, assigning the results to variables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
from muk3d.ui.forms import ask, get_float, get_filename
from muk3d.util import is_valid
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
is_valid(result)
dam_elevation = result['Dam elevation']
fluid_volume = result['Fluid volume']
tailings_volume = result['Tailings volume']
base_grid = result['Base grid']
# dam_elevation = 110
# base_grid = '../../../grid-10.mgrid'
# fluid_volume = 200000
# tailings_volume = 50000
pond_elevation_guess = dam_elevation - 5
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
dam_volume = result['fill_volume']
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': fluid_volume,
'initialDischargeElevation': dam_elevation-1,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': dam_elevation - 1,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': tailings_volume,
'verticalOffset': 0.0})
vars = {}
script_text = open('results.py','rt').read()
exec script_text in None, vars
result_dict = vars['results']
pipelines = result_dict['pipelines']
pipeline = pipelines[0]
beach_elevation = pipeline['discharge elevation']
pond_elevation = pond_summary['pond elevation']
|
Presenting results to the user
The final task here is to present a neat summary of the key results to the user. To do this we'll use a popup window that appears at the end of the deposition run and populate it with summary text. The Muk3D API has a function in muk3d.ui.show_notification that will allow us to pass text or HTML markup to the function and then display the text.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
from muk3d.ui.forms import ask, get_float, get_filename
from muk3d.ui import show_notification
from muk3d.util import is_valid
result = ask(fields=[get_float("Dam elevation", default_value=0),
get_float("Fluid volume", default_value=0),
get_float("Tailings volume", default_value=0),
get_filename("Base grid", filters=['Grids (*.mgrid)',], select_existing_file=True)
],
last_values_key='Custom_storage_curve')
is_valid(result)
dam_elevation = result['Dam elevation']
fluid_volume = result['Fluid volume']
tailings_volume = result['Tailings volume']
base_grid = result['Base grid']
# dam_elevation = 110
# base_grid = '../../../grid-10.mgrid'
# fluid_volume = 200000
# tailings_volume = 50000
pond_elevation_guess = dam_elevation - 5
# running command Clear graphics
# Parameters from dialog
cmd = get_command('Clear graphics')
result = cmd({ 'dont_ask': False})
# running command Raise dam
# Parameters from dialog
cmd = get_command('Raise dam')
result = cmd({ 'cells': u'',
'dam': u'Single profile dam:Dam_1',
'discharge_height': 1.0,
'discharge_spacing': 150.0,
'elevation': dam_elevation,
'generate_discharge_points': True,
'grid': base_grid,
'save': True,
'upstream_toe_extension': 0})
dam_volume = result['fill_volume']
# running command Single stream fixed pond volume, auto flow path
# Parameters from dialog
cmd = get_command('Single stream fixed pond volume, auto flow path')
result = cmd({ 'base': u'Dam_1/grid+dam.mgrid',
'deposition model': 'Single stream fixed pond volume, auto flow path',
'dischargePoints': u'Dam_1/dam_discharge_points.mcurve',
'fluidVolume': fluid_volume,
'initialDischargeElevation': dam_elevation-1,
'maxChange': 2.0,
'maxIterations': 40,
'maxPondElevation': dam_elevation - 1,
'maxPondElevationChange': 1.0,
'maxPondIterations': 20,
'pond': 'pond',
'pondElevation': pond_elevation_guess,
'pondTolerance': 1.0,
'pond_location': ( 2969.521593195585,
-1931.214423544741,
99.40282791899548),
'seCurveIncrement': 0.1,
'tailings': u'Sand',
'tolerance': 1.0,
'tonnage': tailings_volume,
'verticalOffset': 0.0})
vars = {}
script_text = open('results.py','rt').read()
exec script_text in None, vars
result_dict = vars['results']
pipelines = result_dict['pipelines']
pipeline = pipelines[0]
beach_elevation = pipeline['discharge elevation']
pond_summary = result_dict['pond_summary']
pond_elevation = pond_summary['pond elevation']
text = """<h1>Run summary</h1>Dam elevation: {:.1f} m<br/>Dam fill volume: {:,.0f} m3 <br/>
Beach elevation: {:.1f} m<br/> Pond elevation: {:.1f} m<br/>
""".format(dam_elevation, dam_volume, beach_elevation, pond_elevation)
show_notification(text)
|
In lines 82 - 84, a HTML string is created to pass to the show_notification function. This is done using the python string format method and passing the relevant variables to it. Variables are inserted into the string where there are { }, and the values inside the { } control number formatting.
For full details on formatting of Python strings, please see pyformat.info for help.
Finally, the popup window is displayed in line 86 by passing the HTML text string to it.
Summary
Its quite straightforward to record a macro and expose just the relevant variables for the user to complete. In this example, the dam template, tailings stream, and discharge points are hardwired in still, so to allow the user to choose these would require extra steps still.
The final script file is available below as an attachment to this article.