post/t-his/post_automotive_assessment.js

// module: TRUE
// @ts-ignore
import { gui } from "./post_automotive_assessment_gui.jsi";
import { JSPath } from "../../modules/shared/path.mjs";
import { OccupantBodyPart } from "../../modules/shared/occupant.mjs";
import { WorkflowOccupant } from "../../modules/shared/workflow_occupant.mjs";
import {
    ProtocolAssessment,
    DoOccupantAssessmentOccupantData,
    DoStructureAssessmentStructureData,
    DoAssessmentOptions
} from "../../modules/post/this/this_automotive_assessments.mjs";
import { Protocol, Protocols } from "../../modules/shared/protocols.mjs";
import { AssessmentType } from "../../modules/shared/assessment_types.mjs";
import { Structure } from "../../modules/shared/structure.mjs";
import { THisHelper } from "../../modules/post/this/this.mjs";

/**
 * The user data object in the workflow file
 * @typedef {Object} UserData
 * @property {string} crash_test Crash test
 * @property {string[]} regulations Regulations
 * @property {WorkflowOccupant[]} occupants Array of WorkflowOccupants
 * @property {Structure[]} structures Array of Structures
 */

/* New UI */

Window.Theme(Window.THEME_CURRENT);

/* Set the workflows directory */
JSPath.SetWorkflowsDirectory(JSPath.POST);

/* Initialise the Protocols class */

Protocols.Initialise();

/* Setup the GUI and show the main window */

if (gui) {
    try {
        setup_gui();
    } catch (e) {
        ErrorMessage(`Something went wrong: ${e}.\n${e.stack}`);
    }
}

/**
 * Sets the GUI up with things not set in the GUI Builder,
 * e.g. combobox items are added dynamically here using the list
 * of possible values from the Protocol class and the user data
 * passed to the script from the framework.
 */
function setup_gui() {
    /* Assign callbacks */

    gui.wdw_post_automotive.btn_plot.onClick = plot;

    gui.wdw_post_automotive.cbx_regulation.onChange = update_versions;
    gui.wdw_post_automotive.cbx_version.onChange = update_menu;
    gui.wdw_post_automotive.lbx_occupants.onClick = update_menu;
    gui.wdw_post_automotive.lbx_body_parts.onClick = update_menu;
    gui.wdw_post_automotive.lbx_occupant_assessment_types.onClick = update_menu;

    gui.wdw_post_automotive.lbx_structures.onClick = update_menu;
    gui.wdw_post_automotive.lbx_structure_assessment_types.onClick = update_menu;

    gui.wdw_post_automotive.btn_select_all_body_parts.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_select_all_occupants.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_select_all_structures.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_select_all_occupant_assessment_types.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_select_all_structure_assessment_types.onClick = toggle_all_widget_items;

    gui.wdw_post_automotive.btn_deselect_all_body_parts.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_deselect_all_occupants.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_deselect_all_structures.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_deselect_all_occupant_assessment_types.onClick = toggle_all_widget_items;
    gui.wdw_post_automotive.btn_deselect_all_structure_assessment_types.onClick = toggle_all_widget_items;

    /* Add ticks to select all buttons and crosses to deselect all buttons */

    gui.wdw_post_automotive.btn_select_all_body_parts.Tick();
    gui.wdw_post_automotive.btn_select_all_occupants.Tick();
    gui.wdw_post_automotive.btn_select_all_structures.Tick();
    gui.wdw_post_automotive.btn_select_all_occupant_assessment_types.Tick();
    gui.wdw_post_automotive.btn_select_all_structure_assessment_types.Tick();

    gui.wdw_post_automotive.btn_deselect_all_body_parts.Cross();
    gui.wdw_post_automotive.btn_deselect_all_occupants.Cross();
    gui.wdw_post_automotive.btn_deselect_all_structures.Cross();
    gui.wdw_post_automotive.btn_deselect_all_occupant_assessment_types.Cross();
    gui.wdw_post_automotive.btn_deselect_all_structure_assessment_types.Cross();

    /* Check the crash test type for each selected model is the same
     * don't allow different ones for now */
    let num_selected_models = Workflow.NumberOfSelectedModels();

    let crash_test = "";
    for (let i = 0; i < num_selected_models; i++) {
        /** @type {UserData} */
        // @ts-ignore
        let user_data = Workflow.ModelUserDataFromIndex(i);

        if (i == 0) {
            crash_test = user_data.crash_test;
        } else {
            if (user_data.crash_test != crash_test) {
                Window.Message("", "The crash test type for each model must be the same.");
                return;
            }
        }
    }

    /* Store the crash test type and display on GUI label */

    gui.crash_test = crash_test;
    gui.wdw_post_automotive.lbl_crash_test.text = `Crash Test: ${gui.crash_test}`;

    /* Get list of possible options for the regulation combobox and add widget items */

    /** @type Set<string> */
    let regulations = new Set(); /* Use a Set to get unique list of regulations */
    for (let i = 0; i < num_selected_models; i++) {
        /** @type {UserData} */
        // @ts-ignore
        let user_data = Workflow.ModelUserDataFromIndex(i);

        for (let regulation of user_data.regulations) {
            regulations.add(regulation);
        }
    }

    for (let regulation of regulations) new WidgetItem(gui.wdw_post_automotive.cbx_regulation, regulation);

    /* Get a list of all the different assessment types to store whether they are selected or not.
     * Initially all are selected. Ideally we'd store the selection status on the widget items, but
     * they get removed and created in the update_assessment_types() function, so this separate list
     * is required */

    let assessment_types = AssessmentType.GetAll();

    gui.selected_assessment_types = {};

    for (let assessment_type of assessment_types) {
        gui.selected_assessment_types[assessment_type] = true;
    }

    /* Add occupants, body parts and structures to listboxes */

    /** @type {Set<string>} */
    let body_part_types = new Set(); /* Use a Set as we want a list of unique body part types */

    let selected_first_occupant = false;

    for (let i = 0; i < num_selected_models; i++) {
        /** @type {UserData} */
        // @ts-ignore
        let user_data = Workflow.ModelUserDataFromIndex(i);
        let model_id = Workflow.ModelIdFromIndex(i);
        let unit_system = Workflow.ModelUnitSystemFromIndex(i);

        let occupants = user_data.occupants;

        if (occupants) {
            for (let o of occupants) {
                let occupant = WorkflowOccupant.CreateFromUserData(o);

                for (let b of o.body_parts) {
                    body_part_types.add(b.component_type); /* Add to set of body part types */
                }

                /* Add an occupant widget item to the combobox
                 *
                 * Store the occupant instance, model_id and unit_system on it in
                 * a DoOccupantAssessmentOccupantData instance. This is used in
                 * the call to DoOccupantAssessment() */

                let occupant_string = "";
                if (num_selected_models > 1) {
                    occupant_string = `M${model_id} - ${occupant.toString()}`;
                } else {
                    occupant_string = occupant.toString();
                }

                let occupant_widget_item = new WidgetItem(gui.wdw_post_automotive.lbx_occupants, occupant_string);

                if (!selected_first_occupant) {
                    occupant_widget_item.selected = true;
                    selected_first_occupant = true;
                }

                // @ts-ignore
                occupant_widget_item.occupant_data = new DoOccupantAssessmentOccupantData(
                    occupant,
                    model_id,
                    unit_system
                );

                occupant_widget_item.hover = occupant.version;
            }
        }

        let structures = user_data.structures;

        if (structures) {
            for (let s of structures) {
                let structure = Structure.CreateFromUserData(s);

                /* Add a structure widget item to the combobox
                 *
                 * Store the structure instance, model_id and unit_system on it in
                 * a DoStructureAssessmentStructureData instance. This is used in
                 * the call to DoStructureAssessment() */

                let structure_string = "";
                if (num_selected_models > 1) {
                    structure_string = `M${model_id} - ${structure.toString()}`;
                } else {
                    structure_string = structure.toString();
                }

                let structure_widget_item = new WidgetItem(gui.wdw_post_automotive.lbx_structures, structure_string);

                // @ts-ignore
                structure_widget_item.structure_data = new DoStructureAssessmentStructureData(
                    structure,
                    model_id,
                    unit_system
                );
                structure_widget_item.hover = structure.component_type;
            }
        }
    }

    /* Create a list of all the different body part types to store whether they are selected or not.
     * Initially all are selected. Ideally we'd store the selection status on the widget items, but
     * they get removed and created in the update_body_part_types() function, so this separate list
     * is required */

    gui.selected_body_part_types = {};

    for (let body_part_type of body_part_types) {
        gui.selected_body_part_types[body_part_type.toUpperCase()] = false;
    }

    /* Make sure it stays on top and doesn't get hidden when new graphs are created */

    gui.wdw_post_automotive.keepOnTop = true;

    /* Update the menu */

    update_versions();
    update_menu();

    /* Show the window */

    gui.wdw_post_automotive.Show(false);
}

/* Callback functions */

/**
 * Carries out an assessment on the selected body part according
 * to the selected regulation and occupant, plotting the results
 * as curves in T/HIS.
 */
function plot() {
    /* Get the selected occupants */

    let occupants_data = get_selected_occupants_data();
    let structures_data = get_selected_structures_data();

    /* Get the selected assessment types */

    let occupant_assessment_types = get_selected_assessment_types("occupants");
    let structure_assessment_types = get_selected_assessment_types("structures");

    if (occupant_assessment_types.length == 0 && structure_assessment_types.length == 0) {
        Message("No occupant or structure assessment types selected");
        return;
    }

    /* Get the selected regulation */

    /** @type {string} */
    let regulation = gui.wdw_post_automotive.cbx_regulation.selectedItem.text;

    /* Create a protocol instance with the required datums */

    let protocol = ProtocolAssessment.CreateDefaultProtocol(
        regulation,
        gui.crash_test,
        gui.wdw_post_automotive.cbx_version.selectedItem.text
    );

    /* Get the graph layout option */

    let graph_layout = "";

    if (gui.wdw_post_automotive.rdb_graphs.selectedItem == gui.wdw_post_automotive.rdb_graphs.wi_same_page) {
        graph_layout = DoAssessmentOptions.GRAPH_LAYOUT_SAME_PAGE;
    } else if (gui.wdw_post_automotive.rdb_graphs.selectedItem == gui.wdw_post_automotive.rdb_graphs.wi_separate_page) {
        graph_layout = DoAssessmentOptions.GRAPH_LAYOUT_SEPARATE_PAGES;
    }

    /* Default options to overwrite existing graphs */
    let first_graph_id = 1;
    let first_page_id = 1;
    let blank_all = true;
    let remove_existing_graphs = true;

    /* Options to append to existing graphs */

    if (
        gui.wdw_post_automotive.rdb_overwrite_append.selectedItem ==
        gui.wdw_post_automotive.rdb_overwrite_append.wi_append
    ) {
        /* Use the next available graph id for the results of the first assessment */

        first_graph_id = Graph.Total() + 1;

        /* Use page 1 for the results of the first assessment if the option to put them all on the
         * same page is selected.
         *
         * Use the last used page + 1 if the option to them on separate pages is selected */

        if (graph_layout == DoAssessmentOptions.GRAPH_LAYOUT_SAME_PAGE) {
            first_page_id = 1;
        } else if (graph_layout == DoAssessmentOptions.GRAPH_LAYOUT_SEPARATE_PAGES) {
            for (let page_id = THisHelper.MAX_GRAPHS; page_id >= 1; page_id--) {
                let graphs = Page.ReturnGraphs(page_id);

                if (graphs.length == 0) {
                    first_page_id = page_id;
                }
            }
        }

        /* Don't blank all existing curves and datums */

        blank_all = false;

        /* Don't remove existing graphs from pages */

        remove_existing_graphs = false;
    }

    /* Options to pass to DoOccupantAssessment and DoStructureAssessment */

    let options = new DoAssessmentOptions(
        graph_layout,
        first_graph_id,
        first_page_id,
        blank_all,
        remove_existing_graphs
    );

    /* Clear the widget items in the output listbox */

    gui.wdw_post_automotive.lbx_output.RemoveAllWidgetItems();

    /* Do the occupant assessment */

    let last_graph_id = 0;
    let last_page_id = 0;

    if (occupant_assessment_types.length > 0) {
        let results = protocol.DoOccupantAssessment(occupants_data, occupant_assessment_types, options);

        /* Last graph ID used */

        last_graph_id = results.last_graph_id;

        /* Last page ID used */

        last_page_id = results.last_page_id;

        /* Display the results in the output listbox */

        for (let output of results.outputs) {
            for (let value of output.values) {
                new WidgetItem(
                    gui.wdw_post_automotive.lbx_output,
                    `M${output.model_id} ${output.location.replace("-", " ")} ${output.component.toUpperCase()} ${
                        value.parameter
                    }: ${value.value.toPrecision(6)} ${value.units}`
                );
            }
        }
    }

    /* Do the structure assessment */
    if (structure_assessment_types.length > 0) {
        /* Graph to use for the first assessment */

        options.first_graph_id = last_graph_id + 1;

        /* If an occupant assessment was done
         * Don't blank existing curves and datums
         * Don't remove existing graphs from pages
         * Set the first page to use if graphs are on separate pages
         */

        if (occupant_assessment_types.length > 0) {
            options.blank_all = false;
            options.remove_existing_graphs = false;

            if (options.graph_layout == DoAssessmentOptions.GRAPH_LAYOUT_SEPARATE_PAGES) {
                options.first_page_id = last_page_id + 1;
            }
        }

        let results = protocol.DoStructureAssessment(structures_data, structure_assessment_types, options);

        /* Display the results in the output listbox */

        for (let output of results.outputs) {
            for (let value of output.values) {
                new WidgetItem(
                    gui.wdw_post_automotive.lbx_output,
                    `M${output.model_id} ${output.location.replace("-", " ")} ${output.component.toUpperCase()} ${
                        value.parameter
                    }: ${value.value.toPrecision(6)} ${value.units}`
                );
            }
        }
    }
}

/**
 * Selects or deselect all the widget items in a
 * listbox depending on the button that is pressed.
 */
function toggle_all_widget_items() {
    /** @type {WidgetItem[]} */
    let widget_items = null;

    if (
        this == gui.wdw_post_automotive.btn_select_all_body_parts ||
        this == gui.wdw_post_automotive.btn_deselect_all_body_parts
    ) {
        widget_items = gui.wdw_post_automotive.lbx_body_parts.WidgetItems();
    } else if (
        this == gui.wdw_post_automotive.btn_select_all_occupants ||
        this == gui.wdw_post_automotive.btn_deselect_all_occupants
    ) {
        widget_items = gui.wdw_post_automotive.lbx_occupants.WidgetItems();
    } else if (
        this == gui.wdw_post_automotive.btn_select_all_structures ||
        this == gui.wdw_post_automotive.btn_deselect_all_structures
    ) {
        widget_items = gui.wdw_post_automotive.lbx_structures.WidgetItems();
    } else if (
        this == gui.wdw_post_automotive.btn_select_all_occupant_assessment_types ||
        this == gui.wdw_post_automotive.btn_deselect_all_occupant_assessment_types
    ) {
        widget_items = gui.wdw_post_automotive.lbx_occupant_assessment_types.WidgetItems();
    } else if (
        this == gui.wdw_post_automotive.btn_select_all_structure_assessment_types ||
        this == gui.wdw_post_automotive.btn_deselect_all_structure_assessment_types
    ) {
        widget_items = gui.wdw_post_automotive.lbx_structure_assessment_types.WidgetItems();
    } else {
        ErrorMessage("Unknown button in <toggle_all_widget_items>");
        return;
    }

    if (!widget_items) return;

    for (let widget_item of widget_items) {
        if (
            this == gui.wdw_post_automotive.btn_select_all_body_parts ||
            this == gui.wdw_post_automotive.btn_select_all_occupants ||
            this == gui.wdw_post_automotive.btn_select_all_structures ||
            this == gui.wdw_post_automotive.btn_select_all_occupant_assessment_types ||
            this == gui.wdw_post_automotive.btn_select_all_structure_assessment_types
        ) {
            widget_item.selected = true;
        } else if (
            this == gui.wdw_post_automotive.btn_deselect_all_body_parts ||
            this == gui.wdw_post_automotive.btn_deselect_all_occupants ||
            this == gui.wdw_post_automotive.btn_deselect_all_structures ||
            this == gui.wdw_post_automotive.btn_deselect_all_occupant_assessment_types ||
            this == gui.wdw_post_automotive.btn_deselect_all_structure_assessment_types
        ) {
            widget_item.selected = false;
        }
    }

    if (
        this == gui.wdw_post_automotive.btn_select_all_body_parts ||
        this == gui.wdw_post_automotive.btn_deselect_all_body_parts ||
        this == gui.wdw_post_automotive.btn_select_all_occupants ||
        this == gui.wdw_post_automotive.btn_deselect_all_occupants ||
        this == gui.wdw_post_automotive.btn_select_all_structures ||
        this == gui.wdw_post_automotive.btn_deselect_all_structures
    ) {
        update_assessment_types();
    }

    update_selected_assessment_types();
}

/**
 * Returns an array of the selected body parts
 * @returns {string[]}
 */
function get_selected_body_parts() {
    /** @type {string[]} */
    let body_parts = [];

    /** @type {WidgetItem[]} */
    let widget_items = gui.wdw_post_automotive.lbx_body_parts.WidgetItems();

    if (!widget_items) return body_parts;

    for (let wi of widget_items) {
        if (wi.selected) {
            body_parts.push(wi.text);
        }
    }

    return body_parts;
}

/**
 * Returns an array of the selected occupants
 * @returns {DoOccupantAssessmentOccupantData[]}
 */
function get_selected_occupants_data() {
    /** @type {DoOccupantAssessmentOccupantData[]} */
    let occupants = [];

    /** @type {WidgetItem[]} */
    let widget_items = gui.wdw_post_automotive.lbx_occupants.WidgetItems();

    if (!widget_items) return occupants;

    for (let wi of widget_items) {
        if (wi.selected) {
            // @ts-ignore
            occupants.push(wi.occupant_data);
        }
    }

    return occupants;
}

/**
 * Returns an array of the selected structures
 * @returns {DoStructureAssessmentStructureData[]}
 */
function get_selected_structures_data() {
    /** @type {DoStructureAssessmentStructureData[]} */
    let structures = [];

    /** @type {WidgetItem[]} */
    let widget_items = gui.wdw_post_automotive.lbx_structures.WidgetItems();

    if (!widget_items) return structures;

    for (let wi of widget_items) {
        if (wi.selected) {
            // @ts-ignore
            structures.push(wi.structure_data);
        }
    }

    return structures;
}

/**
 * Returns an array of the selected assessment types for
 * occupants or structures
 * @param {string} what "occupants" or "structures"
 * @returns {string[]}
 */
function get_selected_assessment_types(what) {
    /** @type {string[]} */
    let assessment_types = [];

    /** @type {?WidgetItem[]} */
    let widget_items = null;

    if (what == "occupants") {
        widget_items = gui.wdw_post_automotive.lbx_occupant_assessment_types.WidgetItems();
    } else if (what == "structures") {
        widget_items = gui.wdw_post_automotive.lbx_structure_assessment_types.WidgetItems();
    } else {
        ErrorMessage(`Invalid <what> in <get_selected_assessment_types>: ${what}`);
    }

    if (!widget_items) return assessment_types;

    for (let wi of widget_items) {
        if (wi.selected) {
            assessment_types.push(wi.text);
        }
    }

    return assessment_types;
}

/**
 * Update the menu
 */
function update_menu() {
    /* Update the list of body part types */

    update_body_part_types();

    /* Update the list of assessment types */

    update_assessment_types();
}

/**
 * Updates the combobox versions based on the currently selected regulation
 */
function update_versions() {
    let regulation = gui.wdw_post_automotive.cbx_regulation.selectedItem.text;

    let versions = Protocol.Versions(regulation, gui.crash_test);

    gui.wdw_post_automotive.cbx_version.RemoveAllWidgetItems();

    for (let version of versions) {
        new WidgetItem(gui.wdw_post_automotive.cbx_version, version);
    }
}

/**
 * Update the list of body part types in the listbox
 * based on the selected regulation and selected occupants.
 */
function update_body_part_types() {
    update_selected_body_part_types();

    let regulation = gui.wdw_post_automotive.cbx_regulation.selectedItem.text;
    let occupants_data = get_selected_occupants_data();

    /** @type {WorkflowOccupant[]} */
    let occupants = [];
    for (let od of occupants_data) {
        occupants.push(od.occupant);
    }

    /* Get the current body parts listed */

    /** @type {string[]} */
    let listed_body_parts = [];

    /** @type {WidgetItem[]} */
    let body_part_type_widget_items = gui.wdw_post_automotive.lbx_body_parts.WidgetItems();

    if (body_part_type_widget_items) {
        for (let wi of body_part_type_widget_items) {
            listed_body_parts.push(wi.text);
        }
    }

    /* Get the body part types relevant to the selected regulation and occupants */

    let protocol = ProtocolAssessment.CreateDefaultProtocol(
        regulation,
        gui.crash_test,
        gui.wdw_post_automotive.cbx_version.selectedItem.text
    );

    let all_body_part_types = OccupantBodyPart.Types();

    /** @type {string[]} */
    let body_part_types = [];

    for (let body_part_type of all_body_part_types) {
        let assessment_types = protocol.UniqueOccupantAssessmentTypes(occupants, [body_part_type]);

        if (assessment_types.length > 0) body_part_types.push(body_part_type.toUpperCase());
    }

    /* If the body parts to show is different to the ones currently listed
     * clear it and rebuild the list */

    let rebuild = false;

    if (listed_body_parts.length != body_part_types.length) {
        rebuild = true;
    } else {
        for (let body_part_type of body_part_types) {
            if (!listed_body_parts.includes(body_part_type)) {
                rebuild = true;
                break;
            }
        }
    }

    if (!rebuild) return;

    /* Clear the list of body part types and add the required ones */

    gui.wdw_post_automotive.lbx_body_parts.RemoveAllWidgetItems();

    for (let body_part_type of body_part_types) {
        let wi = new WidgetItem(gui.wdw_post_automotive.lbx_body_parts, body_part_type);

        /* Set selected status */
        wi.selected = gui.selected_body_part_types[body_part_type];
    }

    /* If the number of widget items in a list box has decreased you can end up
     * with a grey row, so redraw the window */

    gui.wdw_post_automotive.Redraw();
}

/**
 * Update the gui.selected_body_part_types object
 * based on the selected body part types in the listbox
 */
function update_selected_body_part_types() {
    /** @type {WidgetItem[]} */
    let body_part_type_widget_items = gui.wdw_post_automotive.lbx_body_parts.WidgetItems();

    if (body_part_type_widget_items) {
        for (let wi of body_part_type_widget_items) {
            gui.selected_body_part_types[wi.text] = wi.selected;
        }
    }
}

/**
 * Update the list of assessment types in the occcupant assessment type and
 * structure assessment type listboxes.
 *
 * For the occupant assessment types it's based on the selected regulation,
 * body part type and selected occupants.
 *
 * For the structure assessment types it's based on the selected regulation
 * and selected structures
 */
function update_assessment_types() {
    update_selected_assessment_types();

    let regulation = gui.wdw_post_automotive.cbx_regulation.selectedItem.text;
    let body_part_types = get_selected_body_parts();
    let occupants_data = get_selected_occupants_data();
    let structures_data = get_selected_structures_data();

    /** @type {WorkflowOccupant[]} */
    let occupants = [];
    for (let od of occupants_data) {
        occupants.push(od.occupant);
    }

    /** @type {Structure[]} */
    let structures = [];
    for (let sd of structures_data) {
        structures.push(sd.structure);
    }

    /* Two passes,
     * First one for the occupant assessment types
     * second one for the structure assessment types
     */
    for (let pass = 0; pass < 2; pass++) {
        /* Get the current assessment types listed */

        /** @type {string[]} */
        let listed_assessment_types = [];

        /** @type {WidgetItem[]} */
        let assessment_type_widget_items = null;

        if (pass == 0) {
            assessment_type_widget_items = gui.wdw_post_automotive.lbx_occupant_assessment_types.WidgetItems();
        } else {
            assessment_type_widget_items = gui.wdw_post_automotive.lbx_structure_assessment_types.WidgetItems();
        }

        if (assessment_type_widget_items) {
            for (let wi of assessment_type_widget_items) {
                listed_assessment_types.push(wi.text);
            }
        }

        /* Get the assessment types relevant to the selected regulation,
         * body part type and occupants or structures */

        let protocol = ProtocolAssessment.CreateDefaultProtocol(
            regulation,
            gui.crash_test,
            gui.wdw_post_automotive.cbx_version.selectedItem.text
        );

        /** @type {string[]} */
        let assessment_types = [];
        if (pass == 0) {
            assessment_types = protocol.UniqueOccupantAssessmentTypes(occupants, body_part_types);
        } else {
            assessment_types = protocol.UniqueStructureAssessmentTypes(structures);
        }

        /* If the assessment types to show is different to the ones currently listed
         * clear it and rebuild the list */

        let rebuild = false;

        if (listed_assessment_types.length != assessment_types.length) {
            rebuild = true;
        } else {
            for (let assessment_type of assessment_types) {
                if (listed_assessment_types.indexOf(assessment_type) == -1) {
                    rebuild = true;
                    break;
                }
            }
        }

        if (!rebuild) continue;

        /* Clear the list of assessment types and add the required ones */

        /** @type {Widget} */
        let lbx = null;

        if (pass == 0) {
            lbx = gui.wdw_post_automotive.lbx_occupant_assessment_types;
        } else {
            lbx = gui.wdw_post_automotive.lbx_structure_assessment_types;
        }

        lbx.RemoveAllWidgetItems();

        let valid_assessment_types = AssessmentType.GetAll("this");

        for (let assessment_type of assessment_types) {
            if (!valid_assessment_types.includes(assessment_type)) continue;

            let wi = new WidgetItem(lbx, assessment_type);

            /* Set selected status */
            wi.selected = gui.selected_assessment_types[assessment_type];
        }
    }

    /* If the number of widget items in a list box has decreased you can end up
     * with a grey row, so redraw the window */

    gui.wdw_post_automotive.Redraw();
}

/**
 * Update the gui.selected_assessment_types object
 * based on the selected assessment types in the listbox
 */
function update_selected_assessment_types() {
    /* Occupant assessment types */
    let occupant_assessment_type_widget_items = gui.wdw_post_automotive.lbx_occupant_assessment_types.WidgetItems();

    if (occupant_assessment_type_widget_items) {
        for (let wi of occupant_assessment_type_widget_items) {
            gui.selected_assessment_types[wi.text] = wi.selected;
        }
    }

    /* Structure assessment types */
    let structure_assessment_type_widget_items = gui.wdw_post_automotive.lbx_structure_assessment_types.WidgetItems();

    if (structure_assessment_type_widget_items) {
        for (let wi of structure_assessment_type_widget_items) {
            gui.selected_assessment_types[wi.text] = wi.selected;
        }
    }
}