post/d3plot/d3plot_automotive_assessment.js

// module: TRUE
// @ts-ignore
import { gui } from "./d3plot_automotive_assessment_gui.jsi";
import { JSPath } from "../../modules/shared/path.mjs";
import { WorkflowOccupant } from "../../modules/shared/workflow_occupant.mjs";
import {
    ProtocolAssessment,
    DoStructureAssessmentStructureData
} from "../../modules/post/d3plot/d3plot_automotive_assessment.mjs";
import { Protocol, Protocols } from "../../modules/shared/protocols.mjs";
import { AssessmentType } from "../../modules/shared/assessment_types.mjs";
import { Structure } from "../../modules/shared/structure.mjs";

/**
 * Object to store data for B-Pillar
 * @typedef {Object} BPillarStructure
 * @property {string} cut_section_method Cut section method
 * @property {number[]} cut_section_nodes Cut section nodes
 * @property {number[]} pre_crash_parts Pre-crash parts
 * @property {number[]} post_crash_parts Post-crash parts
 * @property {number[]} shift_deform_nodes Shift deform nodes
 * @property {number} ground_z Ground Z coordinate
 * @property {number} seat_centre_y Seat centre Y coordinate
 * @property {number} h_point_z H-Point Z coordinate
 */

/**
 * Object to store data for Head Excursion
 * @typedef {Object} HeadExcursionStructure
 * @property {number} cut_section_thickness Cut section thickness
 * @property {number} cut_section_node Cut section node
 * @property {string} vehicle_direction Vehicle direction (either 'positive X' or 'negative X')
 * @property {number[]} head_parts Head parts
 * @property {number[]} barrier_parts Barrier parts
 * @property {number[]} shift_deform_nodes Shift deform nodes
 * @property {number} seat_centre_y Seat centre Y coordinate
 * @property {number} intrusion_from_seat_centre_y Intrusion From Seat centre Y coordinate
 * @property {string} countermeasure Countermeasure (either 'Yes' or 'No')
 * @property {string} keyword_file Keyword filename
 */

/**
 * The user data object in the workflow file
 * @typedef {Object} UserData
 * @property {string} crash_test Crash test
 * @property {string[]} regulations Regulations
 * @property {string} version Version
 * @property {string} drive_side either 'LHD' or 'RHD' vehicle
 * @property {WorkflowOccupant[]} occupants Array of WorkflowOccupants
 * @property {Structure[]} structures Array of Structures
 * @property {BPillarStructure} b_pillar B-Pillar structure
 * @property {HeadExcursionStructure} head_excursion Head Excursion structure
 */

/* 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}`);
    }
}

function setup_gui() {
    /* Callbacks */

    gui.wdw_d3plot_automotive.btn_run.onClick = run;

    gui.wdw_d3plot_automotive.cbx_regulation.onChange = update_versions;
    gui.wdw_d3plot_automotive.cbx_version.onChange = update_menu;

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

    gui.wdw_d3plot_automotive.btn_select_all_structures.onClick = toggle_all_widget_items;
    gui.wdw_d3plot_automotive.btn_select_all_structure_assessment_types.onClick = toggle_all_widget_items;

    gui.wdw_d3plot_automotive.btn_deselect_all_structures.onClick = toggle_all_widget_items;
    gui.wdw_d3plot_automotive.btn_deselect_all_structure_assessment_types.onClick = toggle_all_widget_items;

    gui.wdw_d3plot_automotive.cbx_images.onChange = update_image;

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

    gui.wdw_d3plot_automotive.btn_select_all_structures.Tick();
    gui.wdw_d3plot_automotive.btn_select_all_structure_assessment_types.Tick();

    gui.wdw_d3plot_automotive.btn_deselect_all_structures.Cross();
    gui.wdw_d3plot_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_d3plot_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_d3plot_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 Structures to listboxes */

    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 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_d3plot_automotive.lbx_structures, structure_string);

                let extra_data = {};

                if (structure.component_type == Structure.B_PILLAR) {
                    extra_data = user_data.b_pillar;
                } else if (structure.component_type == Structure.HEAD_EXCURSION) {
                    extra_data = user_data.head_excursion;
                }

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

    /* Update the menu */

    update_versions();
    update_menu();

    /* Show the window */

    gui.wdw_d3plot_automotive.Show(false);
}

/**
 * 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_d3plot_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
 * @returns {string[]}
 */
function get_selected_assessment_types() {
    /** @type {string[]} */
    let assessment_types = [];

    /** @type {?WidgetItem[]} */
    let widget_items = gui.wdw_d3plot_automotive.lbx_structure_assessment_types.WidgetItems();

    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 assessment types */

    update_assessment_types();
}

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

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

    gui.wdw_d3plot_automotive.cbx_version.RemoveAllWidgetItems();

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

/**
 * Carries out assessments on the selected structures according
 * to the selected regulation
 */
function run() {
    /* Get the selected structures */

    let structures_data = get_selected_structures_data();

    /* Get the selected assessment_types */

    let structure_assessment_types = get_selected_assessment_types();

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

    /* Get the selected regulation */

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

    /* Create a protocol instance */

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

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

    gui.wdw_d3plot_automotive.lbx_output.RemoveAllWidgetItems();

    /* Clear the image on the widget */

    gui.wdw_d3plot_automotive.lbl_image.ReadImageFile(null);

    /* Do assessment on each structure */

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

    /* Display the results in the output listbox */

    for (let prop in results.output) {
        new WidgetItem(gui.wdw_d3plot_automotive.lbx_output, `${prop}: ${results.output[prop]}`);
    }

    /* Populate the combobox to select images */

    gui.wdw_d3plot_automotive.cbx_images.RemoveAllWidgetItems();

    for (let image_filename of results.image_filenames) {
        /* Get the model from the filename, which will be of the form
         *
         * <dir>/b_pillar_image_M<model_id>.png
         * OR
         * <dir>/head_excursion_image_M<model_id>.png */

        let regex = /.*\/b_pillar_image_(M\d+)/;

        let match = image_filename.match(regex);

        if (match) {
            let wi = new WidgetItem(gui.wdw_d3plot_automotive.cbx_images, match[1]);

            /* Store the filename so we can update the image when it's selected */
            // @ts-ignore
            wi.image_filename = image_filename;
        }
    }

    /* Get the first image and display it on a widget */

    if (results.image_filenames.length == 0) {
        gui.wdw_d3plot_automotive.lbl_image.ReadImageFile(null);
    } else {
        display_image(results.image_filenames[0]);
    }

    /* Redraw to show image */

    gui.wdw_d3plot_automotive.Redraw();
}

/**
 * Displays an image in the image widget
 * @param {string} image_filename Filename of image to display
 */
function display_image(image_filename) {
    if (!File.Exists(image_filename)) {
        ErrorMessage(`Unable to find the image ${image_filename}.`);
        gui.wdw_d3plot_automotive.lbl_image.ReadImageFile(null);
    } else {
        gui.wdw_d3plot_automotive.lbl_image.ReadImageFile(image_filename, Widget.SCALE);
    }

    gui.wdw_d3plot_automotive.Redraw();
}

/**
 * Callback function when the user selects an image from the combobox
 */
function update_image() {
    let image_filename = gui.wdw_d3plot_automotive.cbx_images.selectedItem.image_filename;

    display_image(image_filename);
}

/**
 * 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_d3plot_automotive.btn_select_all_structures ||
        this == gui.wdw_d3plot_automotive.btn_deselect_all_structures
    ) {
        widget_items = gui.wdw_d3plot_automotive.lbx_structures.WidgetItems();
    } else if (
        this == gui.wdw_d3plot_automotive.btn_select_all_structure_assessment_types ||
        this == gui.wdw_d3plot_automotive.btn_deselect_all_structure_assessment_types
    ) {
        widget_items = gui.wdw_d3plot_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_d3plot_automotive.btn_select_all_structures ||
            this == gui.wdw_d3plot_automotive.btn_select_all_structure_assessment_types
        ) {
            widget_item.selected = true;
        } else if (
            this == gui.wdw_d3plot_automotive.btn_deselect_all_structures ||
            this == gui.wdw_d3plot_automotive.btn_deselect_all_structure_assessment_types
        ) {
            widget_item.selected = false;
        }
    }

    if (
        this == gui.wdw_d3plot_automotive.btn_select_all_structures ||
        this == gui.wdw_d3plot_automotive.btn_deselect_all_structures
    ) {
        update_assessment_types();
    }

    update_selected_assessment_types();
}

/*
/**
 * Update the list of assessment types in the structure assessment type
 * listbox based on the selected regulation and selected structures
 */
function update_assessment_types() {
    update_selected_assessment_types();

    let regulation = gui.wdw_d3plot_automotive.cbx_regulation.selectedItem.text;
    let structures_data = get_selected_structures_data();

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

    /* Get the current assessment types listed */

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

    /** @type {WidgetItem[]} */
    let assessment_type_widget_items = gui.wdw_d3plot_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_d3plot_automotive.cbx_version.selectedItem.text
    );

    let 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.includes(assessment_type)) {
                rebuild = true;
                break;
            }
        }
    }

    if (!rebuild) return;

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

    /** @type {Widget} */
    let lbx = gui.wdw_d3plot_automotive.lbx_structure_assessment_types;

    lbx.RemoveAllWidgetItems();

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

    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_d3plot_automotive.Redraw();
}

/**
 * Update the gui.selected_assessment_types object
 * based on the selected assessment types in the listbox
 */
function update_selected_assessment_types() {
    /* Structure assessment types */
    let structure_assessment_type_widget_items = gui.wdw_d3plot_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;
        }
    }
}