< All Topics

Creating a benched dam using a script

Muk3D has a range of functions for creating simple earth structures, but sometimes these might not do exactly what you want them to do.  One example of this might be creating a benched dam from a toe, raised upstream.  This short tutorial will go through the process of writing a simple script to automate the building of such a structure using tools in the Muk3D Python API.

The image below shows what we are aiming to create.

If you just want the finished script, its available for download at the bottom of this article.

Overview of the needed script

The first step is to decide what we want to do.  The steps are outlined below:

  1. Ask the user for the basic dam geometry (slopes, lift heights, bench widths, crest width).
  2. Ask the user to select the line to use as the dam toe. 
  3. Offset the dam toe to create the first lifts downstream crest.
  4. If more than one lift is being created, offset the downstream crest to create the toe of the next lift, offset this up to create the new downstream crest of the next lift.
  5. Offset the current downstream crest by the crest width to create the upstream crest.
  6. Offset the upstream crest back down to the initial toe line elevation.
  7. Create a layer with the new crest/toe lines.
  8. Triangulate the new toe/crest lines to create the dam shell.
  9. (optional) Create discharge points on the upstream face of the dam by offsetting the upstream crest down a short distance at the upstream slope.

To create this script, we’ll run through each of the steps and build it progressively.

Getting user information

To get information about the dam geometry there are 3 options.  One is to hardcode values into the various functions, another is to create variables and use those in the functions (with user manually changing values in the script as needed), and another approach is to create a dialog box that shows when the script is run to prompt the user to enter information.

The user dialog box offers the most flexibility, especially if you want to give your script to someone else to use (who might not like the idea of editing a script file).  To create the dialog box we’ll use some of the functions in the Muk3D Python API.

The functions that we’re interested in are in the muk3d.ui.forms module. (You can get the Muk3D Python API docs to display by going to Help/Show Python API docs)

from muk3d.ui.forms import ask, get_float, get_integer


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam')

The first line imports the relevant functions from the muk3d.ui.forms module. 

  • ask : function that takes some user defined fields and creates a dialog that is shown to the user.  This returns either a Python dictionary with the user entered values if Ok is pressed, or a None value if Cancel is pressed.
  • get_float : function that creates a floating point/decimal field on the form.  The label (e.g. Downstream slope) ends up becoming the dictionary key used to access the user entered value in the return value from the ask command.
  • get_integer: function that creates an integer field on the form.  The label (e.g. Number of raises) ends up becoming the dictionary key used to access the user entered value in the return value from the ask command.

To build the form, ask is called with the fields argument set to be an array of get_xxxx function calls to define the fields.

The parameter last_values_key allows for the last used values on the form to be stored and retrieved by the user by right-click/Last values for all fields.

Running this script shows the following dialog box.

If we want to change the window title, we can use the title keyword argument for the ask function. (Line 12 below)

from muk3d.ui.forms import ask, get_float, get_integer


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

This shows the following dialog box.

Now, to see what the user_params value looks like, we can just print the user_params (Line 14) after the function call.  

from muk3d.ui.forms import ask, get_float, get_integer


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')
            
print user_params

Running the script and hitting OK gives the following output (formatted for clarity):

{
    'Number of raises': 1,
     'Crest width': 10.0,
     'Downstream slope': 1.5,
     'Bench width': 10.0,
     'Upstream slope': 2.0,  
     'Raise height': 10.0
}

Hitting Cancel gives the following:

None

We’ll come back to dealing with the Cancel button in a later step.

Selecting the dam toe line

To get the user to select a line from the 3D window, we can use the select_line function in the Muk3D Python API module muk3d.interact.  This command will prompt the user to select a line from the 3D window, and return a LinePickResult object.

The LinePickResult object contains some information about the line that was picked, including the layer it was picked from, the index of the line in the layer (if there is more than one line in the layer), and most importantly for us, an array of 3D points that represent the line.

These 3D points can be used to feed into various offset functions that will create the dam surface for us.

from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.interact import select_line


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

print toe_points

In Line 2, we import the select_line function and its called on Line 15.  To get the array of points that make up the selected line, we call the  get_points() method on the line_pick variable.  This returns an array of 3D points, which we can seen when print statement is called in Line 18.

Load up a line to represent the dam and run the script.  The output (formatted for clarity) is shown below:

(
    (1229.2299204989415, -3635.9416453893173, 95.0), 
    (2173.1131101143565, -4256.693652974231, 95.0), 
    (3278.5618907450225, -4367.238531037297, 95.0), 
    (3822.782828901659, -3874.0383058328457, 95.0), 
    (3652.71378572771, -2964.16892485222, 95.0), 
    (3355.0929601733, -1977.7684744433172, 95.0), 
    (2224.1338230665406, -1926.7477614911327, 95.0)
)

Offsetting the downstream to to create first lift crest

Next, we’ll take the dam toe and offset it by a slope and height to create the downstream crest of the first lift.  To offset a line, we can use a function in the muk3d.geometry.polyline module called  offset_points_by_slope_height.  This allows us to pass an array of 3D points, a raise height, slope, and directions (up/down, left/right) and it will return a new array of points containing the offset line.

from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.interact import select_line
from muk3d.geometry.polyline import offset_points_by_slope_height

# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

In Line 3, the function offset_points_by_slope_height is imported.  In Line 19, its called using the points from the selected toe, the downstream slope, raise height, and in this case, its offset UP and to the LEFT.

Since the variable user_params is a Python dictionary, the values for Downstream slope and Raise height from the user entered data on the dialog can be accessed using square brackets with the value name in it.  Note that Python is case sensitive, so downstream slope is different to Downstream slope.

Running the above script does nothing (or at least there is no visible output).  What we will do is create a new PolyLine geometry object and add it to the scene so we can see the results of the offset command.

Adding line data to the scene

To add line data to the 3D scene, we need to create a PolyLine object using the Muk3D Python API.  Two things need to imported.  The first is the PolyLine class from the muk3d.geometry.polyline module. This will allow us to create a PolyLine object and add lines to it.  The second is the add_to_scene function from the muk3d.view module.  This function will allow us to add the PolyLine object that’s created to the 3D window so we can see it.

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene

# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

In Lines 4 and 5 above, we import the PolyLine class and the add_to_scene function. 

The PolyLine object is created in Line 25 by using a static function of the PolyLine class called From_Points.  This function takes a point list and creates a new PolyLine object from it (the return value is stored in the variable breaklines).

You might wonder why we add the existing toe line (the line that we selected) to the new PolyLine object.  The reason is that later on we’ll create a surface from all the breaklines, and we need to have this toe line in the layer with all the offset lines to create the proper dam shell surface.

In Line 28, the crest points created from the offset function are appended to the layer as a separate line.

Finally, the PolyLine object with toe line and the first offset line are added to the 3D scene in Line 31.  The layer name is set to be ‘dam breaklines‘.

If we run this script we should see a new layer added to the 3D window after we select our toe line.  This layer will have a copy of the toe line, and the offset crest line.

Create additional lifts if needed

The next step is to create any additional raises (if they are needed).  Using the user entered parameter Number of raises, we can loop through the required number of additional raises (beyond the first on that has been done by the script so far) and create a crest & toe lines for the additional lifts.

The looping is done using Python’s for loop.

for i in range(5):
     print 'This is', i

In the example above, loops runs through the values generated by the range function, which is simply an array of 5 numbers, from 0 to 4. Each time the loop is executed, the variable i is assigned to the next value in the array. Running this script would produce the output:

This is  0
This is  1
This is  2
This is  3
This is  4

Within the for loop, the code that is to be run needs to be indented to delineate this as the execution block for the loop.  The Python standard is to indent 4 spaces (Note: Tabs and spaces are not equivalent.  If you use a mixture of them, you’ll get errors).  If Line 2 in the simple example above wasn’t indented, then it would not be executed in the for loop.

The other thing that we’ll need to do is to import the function to offset a line horizontally from the  muk3d.geometry.polyline module called offset_points_by_width.  This command allows us to specify an array of points, offset distance, and horizontal side and will return the offset line.

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene
from muk3d.geometry.polyline import offset_points_by_width


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

# number of additional raises is (Number of raises - 1)
num_additional_raises = user_params['Number of raises'] - 1

# loop to create each additional raise.
# if num_additional_raises == 0, then the loop won't execute.

for i in range(num_additional_raises):

    # offset crest horizontally to create the bench
    bench_points = offset_points_by_width(crest_points, user_params['Bench width'], 'LEFT')
    
    # add new bench line to our breaklines
    breaklines.new_line_from_points(bench_points)
    
    # offset the bench (toe of next lift) to create the downstream crest for the next lift
    crest_points = offset_points_by_slope_height(bench_points, user_params['Downstream slope'],  
                                                   user_params['Raise height'], 'UP', 'LEFT')
    
    # add new crest_points to the breaklines
    breaklines.new_line_from_points(crest_points)

In Line 6 above, we’ve imported offset_points_by_width.

The number of times we need to loop through the bench building commands is represented by the variable num_additional_raises. If only one raise is being done (num_additional_raises equals 0), then we don’t want to go through the loop in Line 41, and so a value of 0 will give an empty array of numbers to loop through, in which case the loop is not executed.

Within the for loop, the variable i that is assigned for each loop iteration isn’t used at all in this instance.

The bench offset is done in Line 44 by offsetting the current crest line by the bench width.  This offset line is then projected up by the slope and lift height in Line 50.

After both offsets, the new offset line are added to the breaklines PolyData object.

Running the script and set Number of raises to be greater than 1.  The result should be a series of offset lines showing the benches in the dam.

Creating the dam crest

Now that all of  the benches have been created, its time to offset the final downstream crest to create the upstream crest of the dam, and then an upstream toe.

The upstream crest is created using the offset_points_by_width function, offsetting the points currently stored in the crest_points variable by the user entered value of Crest width.

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene
from muk3d.geometry.polyline import offset_points_by_width


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

# number of additional raises is (Number of raises - 1)
num_additional_raises = user_params['Number of raises'] - 1

# loop to create each additional raise.
# if num_additional_raises == 0, then the loop won't execute.

for i in range(num_additional_raises):

    # offset crest horizontally to create the bench
    bench_points = offset_points_by_width(crest_points, user_params['Bench width'], 'LEFT')
    
    # add new bench line to our breaklines
    breaklines.new_line_from_points(bench_points)
    
    # offset the bench (toe of next lift) to create the downstream crest for the next lift
    crest_points = offset_points_by_slope_height(bench_points, user_params['Downstream slope'],  
                                                   user_params['Raise height'], 'UP', 'LEFT')
    
    # add new crest_points to the breaklines
    breaklines.new_line_from_points(crest_points)
  
# do the crest
upstream_crest = offset_points_by_width(crest_points, user_params['Crest width'], 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_crest)    

# do the upstream face
upstream_toe = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'], 
                                               user_params['Raise height'] * user_params['Number of raises'], 
                                               'DOWN', 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_toe)

The upstream crest is created in Line 57 by offsetting the crest_points line by the user specified Crest width.  

In Line 62, the upstream crest is then offset down by the total height of raises (Number of raises * Raise height – it could be whatever distance you want/need it to be) and the upstream slope to create the upstream toe.

Both these offset lines are added to the breaklines PolyData object. 

Running the script again should show a complete set of breaklines for the dam.

Creating a dam shell from the breaklines

The next step is to create a 3D surface from the breaklines.  There is currently no function in the Muk3D Python API to do this, so we’ll use an existing Muk3D command. If you run the command Surface/Create/Triangulate adjacent lines, it will take each line in a PolyLine layer and create a surface, stitching the first line to the second, then the second to the third, third to fourth and so on. For this command, the order of lines in the PolyLine layer is important.

The resulting shell from this command is shown below.

Having to do this manually after we’ve run the script to create the breaklines defeats the purpose of automation, so what we can do is record ourselves running this command in a macro, then copy and paste the code into our script file.  To record a macro, run Scripts/Record macro. Enter a name for the macro, and hit ok. Now, when we run commands they’ll be recorded and written to a Python script file.

Run the command Surface/Create/Triangulate adjacent lines, select the breaklines layer, and then finish recording the macro. This is done by running Scripts/End macro recording, or pressing the red button on the macro floating toolbar.

The recorded script file will appear in your working directory, with the name you gave to it on the Record macro dialog box. Open the script file by right clicking on it and selecting Edit. It should look similar to the code below.

# muk3d macro
# muk3d version: v2018.2.1
# architecture: 64-bit
# Recorded on: 2018-11-30 22:37:07.539000
# Recorded by: test_user



# running command Triangulate adjacent lines
# Parameters from dialog
cmd = get_command('Triangulate adjacent lines')
result = cmd({   'layer': [u'dam breaklines'], 'layer_name': u'surface'})

There are 2 lines that are important in this code.

Line 11 is where the menu command that was run, Triangulate adjacent lines, is stored in the variable cmd.

Line 12 is where the command is executed and passed a Python dictionary of parameters.  In this case, the command is expecting 2 parameters:

  • layer: This is a Python array with single entry which is the name of the layer containing the breaklines PolyData.  In this case it was called ‘breaklines‘.
  • layer_name: This is the name that the new surface layer will be given.

To use this in our script, we can just copy and paste the 2 lines into our script.

As a note, there is a u character preceding the names.  In Python 2.7 (which is what the current version of Muk3D is based on), a unicode string is denoted by a u preceding the quotation marks.  It’s not necessary to maintain this character, and so has been stripped from the code in the following script.

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene
from muk3d.geometry.polyline import offset_points_by_width


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

# number of additional raises is (Number of raises - 1)
num_additional_raises = user_params['Number of raises'] - 1

# loop to create each additional raise.
# if num_additional_raises == 0, then the loop won't execute.

for i in range(num_additional_raises):

    # offset crest horizontally to create the bench
    bench_points = offset_points_by_width(crest_points, user_params['Bench width'], 'LEFT')
    
    # add new bench line to our breaklines
    breaklines.new_line_from_points(bench_points)
    
    # offset the bench (toe of next lift) to create the downstream crest for the next lift
    crest_points = offset_points_by_slope_height(bench_points, user_params['Downstream slope'],  
                                                   user_params['Raise height'], 'UP', 'LEFT')
    
    # add new crest_points to the breaklines
    breaklines.new_line_from_points(crest_points)
  
# do the crest
upstream_crest = offset_points_by_width(crest_points, user_params['Crest width'], 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_crest)    

# do the upstream face
upstream_toe = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'], 
                                               user_params['Raise height'] * user_params['Number of raises'], 
                                               'DOWN', 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_toe)

cmd = get_command('Triangulate adjacent lines')
result = cmd({   'layer': ['dam breaklines'], 'layer_name': 'surface'})

The code that triangulates the lines has been pasted into Lines 68 and 69.

Run the script to see the result. If the layers already exist for the breaklines and surface, then you might be prompted to overwrite them.  

(Optional) Creating discharge points on the upstream face of the dam

If we wanted to create the dam shell and then later deposit tailings from it, we might want to create some discharge points on the upstream face of the dam crest.  We can do this through a couple of simple steps. The first is to offset the upstream crest down the upstream face of the dam so its just below the dam crest to create our basic discahrge line.

The second step is to subdivide it (if appropriate) to create the discharge points we feed into the deposition model.  Both these tasks can be done using the Muk3D Python API.

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene
from muk3d.geometry.polyline import offset_points_by_width


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

line_pick = select_line('Select the dam toe')
toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

# number of additional raises is (Number of raises - 1)
num_additional_raises = user_params['Number of raises'] - 1

# loop to create each additional raise.
# if num_additional_raises == 0, then the loop won't execute.

for i in range(num_additional_raises):

    # offset crest horizontally to create the bench
    bench_points = offset_points_by_width(crest_points, user_params['Bench width'], 'LEFT')
    
    # add new bench line to our breaklines
    breaklines.new_line_from_points(bench_points)
    
    # offset the bench (toe of next lift) to create the downstream crest for the next lift
    crest_points = offset_points_by_slope_height(bench_points, user_params['Downstream slope'],  
                                                   user_params['Raise height'], 'UP', 'LEFT')
    
    # add new crest_points to the breaklines
    breaklines.new_line_from_points(crest_points)
  
# do the crest
upstream_crest = offset_points_by_width(crest_points, user_params['Crest width'], 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_crest)    

# do the upstream face
upstream_toe = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'], 
                                               user_params['Raise height'] * user_params['Number of raises'], 
                                               'DOWN', 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_toe)

cmd = get_command('Triangulate adjacent lines')
result = cmd({   'layer': ['dam breaklines'], 'layer_name': 'surface'})

# generate discharge points on the upstream side
discharge_points = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'],
                                                1.0, 'DOWN', 'LEFT')

# create a new PolyLine to represent the discharge points                                                
discharge_points = PolyLine.From_Points(discharge_points)

# subdivide the points at a 50m spacing.  This returns an array of points that are 
# subdivided at the user specified spacing.
subdivided_points = discharge_points.subdivide_line(0, 50.0, keep_existing_points=False)

# create a new PolyLine object with the subdivided points
subdivided_discharge_points = PolyLine.From_Points(subdivided_points)

# add the subdivided points to the 3D window.
add_to_scene('discharge points', subdivided_discharge_points)   

The code for generating discharge points is in Lines 71 to 86.

In Line 72, the discharge points line is created by offsetting the upstream_crest by the Upstream slope, a height of 1m below the dam crest.  (This could be turned into a user specified parameter if desired).

A new PolyLine object is created in Line 76 from the offset discharge_points.

The next step is to subdivide the discharge line (in this case, at an arbitrary 50m spacing, but again, this could be made a user parameter).  To subdivide the discharge_points line, we use the method subdivide_line of the PolyLine object (Line 80). This takes the line index (there is only a single line in discharge_points, so the index is 0), the spacing, and an optional parameter to keep any points on the line that don’t fall on a 50m spacing. This returns a new list of 3D points with the subdivided line.

In Line 83, a new PolyLine is created for the subdivided discharge points and is added to the scene in Line 86.

Error checking

The last thing that we’ll do with this script is add some basic error checking.  If you run the script and hit either Cancel on the dialog box, or escape when selecting the dam toe, you’ll probably get an ugly error message.

Error in script: create_benched_upstream_dam.py 
Traceback (most recent call last):
TypeError: 'NoneType' object has no attribute '__getitem__'

Rather than leave it to the user to deal with, we can add some checks after the dialog is shown and the line is selected to exit gracefully in the event the user tries to cancel the command.  We can do this using the is_valid function from the muk3d.util module. This function will abort the script if the return data from a dialog or a user pick is invalid (i.e. user cancels dialog/pick).

from muk3d.interact import select_line
from muk3d.ui.forms import ask, get_float, get_integer
from muk3d.geometry.polyline import offset_points_by_slope_height
from muk3d.geometry.polyline import PolyLine
from muk3d.view import add_to_scene
from muk3d.geometry.polyline import offset_points_by_width
from muk3d.util import is_valid


# get the geometry information from the user
user_params = ask(fields=[get_float('Downstream slope', default_value=1.5),
                    get_float('Bench width', default_value=10),
                    get_float('Crest width', default_value=10),
                    get_float('Upstream slope', default_value=2),
                    get_float('Raise height', default_value=10),
                    get_integer('Number of raises', default_value=1)],
            last_values_key='Raise benched dam',
            title='Create benched dam')

# check that the user_params is valid.  i.e. user didn't press Cancel            
is_valid(user_params)

line_pick = select_line('Select the dam toe')

# check that the line_pick is valid, i.e. user didn't press Escape
is_valid(line_pick)

toe_points = line_pick.get_points()

# offset crest
crest_points = offset_points_by_slope_height(toe_points, user_params['Downstream slope'], 
                                                user_params['Raise height'], 'UP', 'LEFT')

# create PolyLine object from the toe_points
breaklines = PolyLine.From_Points(toe_points)

# add the crest_points to the breaklines PolyLine object
breaklines.new_line_from_points(crest_points)
       
# add the breaklines to the Muk3D scene in a layer called 'dam breaklines'
add_to_scene('dam breaklines', breaklines)   

# number of additional raises is (Number of raises - 1)
num_additional_raises = user_params['Number of raises'] - 1

# loop to create each additional raise.
# if num_additional_raises == 0, then the loop won't execute.

for i in range(num_additional_raises):

    # offset crest horizontally to create the bench
    bench_points = offset_points_by_width(crest_points, user_params['Bench width'], 'LEFT')
    
    # add new bench line to our breaklines
    breaklines.new_line_from_points(bench_points)
    
    # offset the bench (toe of next lift) to create the downstream crest for the next lift
    crest_points = offset_points_by_slope_height(bench_points, user_params['Downstream slope'],  
                                                   user_params['Raise height'], 'UP', 'LEFT')
    
    # add new crest_points to the breaklines
    breaklines.new_line_from_points(crest_points)
  
# do the crest
upstream_crest = offset_points_by_width(crest_points, user_params['Crest width'], 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_crest)    

# do the upstream face
upstream_toe = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'], 
                                               user_params['Raise height'] * user_params['Number of raises'], 
                                               'DOWN', 'LEFT')
# add to breaklines
breaklines.new_line_from_points(upstream_toe)

cmd = get_command('Triangulate adjacent lines')
result = cmd({   'layer': ['dam breaklines'], 'layer_name': 'surface'})

# generate discharge points on the upstream side
discharge_points = offset_points_by_slope_height(upstream_crest, user_params['Upstream slope'],
                                                1.0, 'DOWN', 'LEFT')

# create a new PolyLine to represent the discharge points                                                
discharge_points = PolyLine.From_Points(discharge_points)

# subdivide the points at a 50m spacing.  This returns an array of points that are 
# subdivided at the user specified spacing.
subdivided_points = discharge_points.subdivide_line(0, 50.0, keep_existing_points=False)

# create a new PolyLine object with the subdivided points
subdivided_discharge_points = PolyLine.From_Points(subdivided_points)

# add the subdivided points to the 3D window.
add_to_scene('discharge points', subdivided_discharge_points)   
                                                                    

In line 7, the is_valid function is imported.  In Line 21, is_valid is used to check that the user_params contains data (if the user pressed Cancel, then it will contain a None value).

In Line 26, is_valid checks that the user selected a line and will exit if the user hit cancel instead of picking a line.

Further work

This script creates a basic shell and discharge lines.  Other functions that could be incorporated include:

  • merge the dam shell into a grid, and calculate the raise volume;
  • pour tailings (either from fixed elevation or target volume) from the discharge points for whatever pond condition is required;
Tags:
Table of Contents