Skip to main content
PLAXIS Scripting

Soil Layering for a Single Borehole with GIBR

Download: #

OneDrive Link

Preamble: #

This is a simple script for creating single borehole soil layering with GIBR information, with limited use cases.

I have used this script for bored tunnels analysis, which typically considers a single borehole only.

It also serves as a proof-of-concept for automating soil inputs in PLAXIS.

Function: #

The Python script is written to create soil layers and properties for a single borehole, given GIBR properties.

  1. Read a manually created GIBR table, considering variables SPT-N and upper limits.
  2. Create a single borehole based on variable depth.
  3. Create Drained MC soil properties based on variable SPT-N.

Example GIBR Input: #

An example GIBR input, based on a local (Singapore) project.

Example GIBR Input

3 symbols are used beside numbers.

  1. N: represents SPT-N, which needs not be an integer.
  2. +: allows addition of variable SPT-N with constants.
  3. ^: sets an upper limit to the calculated number.

Example: The formula 3N+50^250 will input 3N+50 to a maximum of 250.

Example Borehole Input: #

An example borehole input, based on a local (Singapore) project. Northing and Easting are not required for this program to work.

Example Borehole Input

Using the Script: #

A folder named "Soil Data" must be created on the Desktop.

A file containing the GIBR input should be saved as GIBR.csv in the folder. The rest of the files are to be borehole inputs.

Example Filing System for Inputs

Run the script on a new model. A selection screen will be created for user to choose a borehole.

Example Selection of Borehole File

Generated Model: #

The soil layering with the appropriate GIBR soil properties are generated based on the user's inputs.

Example Generated PLAXIS Input Model

Script: #

import plxscripting.easy
import easygui
import csv
import os

class MakeSoil():

    def __init__(self, g_i):
        self.g_i = g_i

    def run(self):
        #1. Code for Inputs
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        folder_path = os.path.join(desktop_path, "Soil Data")
        
        onedrive_desktop_path = os.path.join(os.path.expanduser("~"), "OneDrive\\Desktop")
        onedrive_folder_path = os.path.join(onedrive_desktop_path, "Soil Data")
        try:
            selected_file = select_file_in_folder(folder_path)
        except:
            selected_file = select_file_in_folder(onedrive_folder_path)
            folder_path = onedrive_folder_path
        borehole_1 = g_i.borehole(0)
        g_i.setproperties("UnitTime", "s")
        g_i.SoilContour.initializerectangular(-75,0,75,10)
        
        #1.1. Record GIBR
        csv_GIBR_path = os.path.join(folder_path, "GIBR.csv")
        
        # Initialize an empty list to store the dictionaries
        GIBRData = []

        # Open the CSV file and read it as a dictionary
        with open(csv_GIBR_path, 'r', encoding='utf-8-sig') as csv_file:  # Use utf-8-sig to strip off the BOM
            # Create a CSV reader object
            csv_reader = csv.DictReader(csv_file)
    
            # Initialize a variable to keep track of the row number
            row_number = 1
    
            # Iterate through each row in the CSV file
            for row in csv_reader:
                # Check if it's the second row
                if row_number == 1:
                    units = row  # Save the second row as a dictionary with the name "units"
                else:
                    # Append the row (dictionary) to the data list
                    GIBRData.append(row)
        
                # Increment the row number
                row_number += 1

        #End of GIBR

        #1.2. Read Soil Data and Generate the Soil Material
        csv_soil_path = selected_file
        
        g_i.soillayer(0)
        g_i.Soillayers[0].Zones[0].Top.set(10)
        # Open the CSV file and read it as a dictionary
        with open(csv_soil_path, 'r', encoding='utf-8-sig') as csv_file:  # Use utf-8-sig to strip off the BOM
            # Create a CSV reader object
            csv_reader = csv.reader(csv_file)
    
            # Initialize a variable to keep track of the row number
            row_number = 1
    
            # Iterate through each row in the CSV file
            for row in csv_reader:
                if (row_number == 4):
                    g_i.Soillayers[0].Zones[0].Top.set(float(row[1]))
                if row_number >6:
                    if(len(row[0].strip())!=0):
                        bottomOfLayer = float (row[0])
                        layer = row[1].strip()
                        spt = 0
                        try:
                            spt = float(row[2])
                        except ValueError:
                            spt = 0
                        
                        #add the layers.
                        if(row_number != 7): #if not the first layer
                            g_i.soillayer(0) #add a layer
                        g_i.Soillayers[row_number-7].Zones[0].Bottom.set(bottomOfLayer) #set the bottom of the layer
                        
                        for material in GIBRData:
                            if(material["Material"] == layer):
                                #add SPT name if soil is SPT dependent.
                                if(checkIfNDependent(material)):
                                    layer = layer + " N = " + str(truncate_float_to_one_decimal_point(spt))
                                #check if soil exists.
                                soilExists = False
                                for existingMaterial in g_i.Materials:
                                    if(existingMaterial.Identification == layer):
                                        g_i.Soillayers[row_number-7].Soil.Material = existingMaterial
                                        soilExists = True
                                        break
                                if(not soilExists):
                                    material1 = g_i.soilmat()
                                    material1.setproperties(
                                        "Identification",layer,
                                        "SoilModel",2,
                                        "gammaUnsat", parseGIBRData(material["Weight"], spt),
                                        "gammaSat", parseGIBRData(material["Weight"], spt),
                                        "Eref",1000*parseGIBRData(material["E"], spt),
                                        "nu", parseGIBRData(material["v"], spt),
                                        "cref", parseGIBRData(material["c"], spt),
                                        "phi", parseGIBRData(material["phi"], spt),
                                        "PermHorizontalPrimary", parseGIBRData(material["k"], spt),
                                        "PermVertical", parseGIBRData(material["k"], spt),
                                        "InterfaceStrengthDetermination", "Manual",
                                        "Rinter", 0.67,
                                        "K0Determination", "Manual",
                                        "K0Primary", parseGIBRData(material["K0"], spt),
                                        #"K0Primary", parseGIBRData(material["Ktunnel"], spt),
                                        "Colour", round(parseGIBRData(material["Colour"], round(spt))),
                                    )
                                    g_i.Soillayers[row_number-7].Soil.Material = material1

                        
                    print(row)
        
                # Increment the row number
                row_number += 1

        selectionClose = easygui.buttonbox(msg="Soil profile was created based on "+ csv_soil_path+".", title='Close the window', choices = ["Close","About"])
        if selectionClose == "About":
            easygui.msgbox(msg='v1.0, released 11/11/2023\n\nChangelog:\nInitial Release.\n\n- Chen Teck, 2023',title='About',ok_button='OK')

def parseGIBRData(string, spt):
    value = 0
    if 'N' in string:
        split_values = string.split('N')
        try:
            parsed_float = float(split_values[0].strip())
            value = spt * parsed_float
        except:
            print("A term contains N but do not contain a prefix, please check.")
            
        if '^' in string:
            split_values = string.split('^')
            try:
                parsed_float = float(split_values[-1].strip())
                if (value > parsed_float):
                    return parsed_float
                else:
                    return value
            except:
                print("A term contains ^ but do not contain a suffix, please check.")
                return value
            
        if '+' in string:
            split_values = string.split('+')
            try:
                parsed_float = float(split_values[-1].strip())
                return value + parsed_float
            except:
                print("A term contains + but do not contain a suffix, please check.")
                return value
        else:
            return value
    else:
        try:
            value = float(string)
            return value
        except:
            print("A term is unable to be parsed, please check.")
    return value

def checkIfNDependent(dictionary):
    char_to_check = 'N'
    found = False

    # Loop through each string in the list
    for string in dictionary.values():
        # Check if the character is in the string
        if char_to_check in string:
            found = True
            break
        
    return found

def truncate_float_to_one_decimal_point(value):
    truncated_value = round(value, 1)
    return int(truncated_value) if truncated_value.is_integer() else truncated_value

def select_file_in_folder(folder_path):
    # Get a list of all files in the folder
    files = os.listdir(folder_path)
    filtered_list = [item for item in files if item != "GIBR.csv"]
    # Display a choice box with the list of files
    choice = easygui.choicebox("Select a borehole file:", "File Selection", filtered_list)

    # If the user selects a file, return the full path to the selected file
    if choice:
        selected_file_path = os.path.join(folder_path, choice)
        return selected_file_path
    else:
        return None
if __name__ == "__main__": 
    s_i, g_i = plxscripting.easy.new_server()
    soil = MakeSoil(g_i)
    soil.run()

Customization #

By default, the input K value refers to the K0 value in column F. For tunnel structural analysis, a Ktunnel could be used instead, which refers to a distortional factor.

Original Excerpt, considering K=K0

"K0Primary", parseGIBRData(material["K0"], spt),
#"K0Primary", parseGIBRData(material["Ktunnel"], spt),

If the intention is to use the Ktunnel, we can switch the '#' and the script will now run the other line.

Modified Excerpt:

#"K0Primary", parseGIBRData(material["K0"], spt),
"K0Primary", parseGIBRData(material["Ktunnel"], spt),