If you've recorded a macro in Muk3D to do something, you'll notice that the user selected layers are usaually referred to by name. In the example below, which is simply the contouring of a surface, the selected surface is shown in Line 13- Dump_0-dump_shell.msurface
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': [u'Dump_0-dump_shell.msurface']})
|
If we wanted to apply this script to a different surface, then we'd have to change the name of the surface in Line 13.
There would be 2 ways we could modify this script to allow a user selected surface to be used. The first approach would be to allow a user to select a saved file, load it, then contour it. The other would be to allow a user to select a surface from the 3D window. We'll explore both of these approaches below.
Selecting a file to load
For this approach we need to build a simple user interface using functions in the Muk3D Python API module api.ui.forms.
1 2 3 4 5 |
from muk3d.ui.forms import ask_filename
result = ask_filename("Surface", filters=['Surfaces (*.msurface)',], select_existing_file=True)
print result
|
Hit ok and the following is written to the output window.
Dump_0-dump_shell.msurface
The next step is to load the layer to the 3D window so it can be contoured. We can do that using the API function muk3d.file.load_file_to_layer.
1 2 3 4 5 6 |
from muk3d.ui.forms import ask_filename
from muk3d.file import load_file_to_layer
file_name = ask_filename("Surface", filters=['Surfaces (*.msurface)',], select_existing_file=True)
load_file_to_layer(file_name)
|
We can then merge this with our contouring script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from muk3d.ui.forms import ask_filename
from muk3d.file import load_file_to_layer
file_name = ask_filename("Surface", filters=['Surfaces (*.msurface)',], select_existing_file=True)
load_file_to_layer(file_name)
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': [file_name,]})
|
There are 2 issues that we still need to fix with this script. Try running it and instead of hitting ok on the dialog, hit cancel. An error message will be written to the output window. When Cancel is hit, the return value for the ask_filename command is None. If a subsequent command tries to do something with this variable and is expecting to find a value instead of None, then the error will occur. We need to check this return value and then exit if the user cancelled the command. We do this using the API command muk3d.util.is_valid.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from muk3d.ui.forms import ask_filename
from muk3d.file import load_file_to_layer
from muk3d.util import is_valid
file_name = ask_filename("Surface", filters=['Surfaces (*.msurface)',], select_existing_file=True)
is_valid(file_name)
load_file_to_layer(file_name)
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': [file_name,]})
|
Now the script will exit silently if the user presses Cancel on the dialog. The other issue to address is selecting a file from another directory (not from the working directory).
Press ok and the following message appears.
Layer " ../dumps/Dump_0-dump_shell.msurface " doesnt exist
This is because when a layer is added into the scene manager all the path information is stripped from it. So we cant use the filename to reference the layer. The easiest way to get around this is to customise the name that is used in the scene manager when the layer is loaded.
The API function load_file_to_layer can take a keyword argument called layer_name which will be the name used for the layer in the Scene manager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from muk3d.ui.forms import ask_filename
from muk3d.file import load_file_to_layer
from muk3d.util import is_valid
file_name = ask_filename("Surface", filters=['Surfaces (*.msurface)',], select_existing_file=True)
is_valid(file_name)
load_file_to_layer(file_name, layer_name='surface')
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': ['surface']})
|
In Line 8, we assign the layer_name keyword argument to be 'surface'. When the file is loaded, the name in the scene manager is 'surface'. In Line 22, we hard-wire the name of the surface in the contouring function as 'surface'.
Selecting a surface from the 3D window
Within the Muk3D API there is a module called muk3d.interact that has some functions for selecting objects in the 3D window. The example below shows how a surface can be selected and how we get the name of the layer.
1 2 3 4 5 6 |
from muk3d.interact import select_surface
surface_pick = select_surface()
surface_layer = surface_pick.get_layer_name()
print 'surface layer name', surface_layer
|
In Line 1 the select_surface function is imported. Calling this function in Line 3 returns a value that contains a data structure called a SurfacePickResult (see the API docs for more details about this). To get the layer name from the surface_pick variable, we call the method get_layer_name(), which will return the name of the layer, as shown in the scene manager.
We can then combine the above script with the contouring script to allow the user to select the surface to contour.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from muk3d.interact import select_grid, select_surface
surface_pick = select_surface()
surface_layer = surface_pick.get_layer_name()
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': [surface_layer,]})
|
In Line 6 we get the name of the layer and assign it to the surface_layer variable. In Line 18, we replace the hard wired surface layer with the surface_layer variable. When we run the script the selected layer is contoured.
There's one more thing to do. If you run the script and instead of selecting the surface, press escape. The following error is written to the output window.
Error in script: script.py Traceback (most recent call last): AttributeError: 'NoneType' object has no attribute 'get_layer_name'
When we hit escape, the return value for the select_surface function is None. If we try and call get_layer_name on this None value, the error above is reported. To allow the script to exit gracefully, we can use the API function muk3d.util.is_valid to check the return value and then exit the script if the return value is None.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from muk3d.interact import select_grid, select_surface
from muk3d.util import is_valid
surface_pick = select_surface()
is_valid(surface_pick)
surface_layer = surface_pick.get_layer_name()
# running command Contour surface
# Parameters from dialog
cmd = get_command('Contour surface')
result = cmd({ 'colour1': [0.0, 0.0, 0.0],
'colour2': [128.0, 128.0, 128.0],
'create_index': True,
'index_spacing': 5,
'interval': 1.0,
'maxz': 174.0,
'minz': 120.0,
'new_layer': u'Dump_0-dump_shell!contours-1.0',
'separate_layers': False,
'surface': [surface_layer,]})
|
We import the is_valid function in Line 2, and then in Line 5 call it with the surface_pick variable as the argument. If surface_pick is None then the script will end, otherwise it'll continue if the value is valid.