// module: TRUE
// @ts-ignore
import { gui } from "../../pre/pre_automotive_assessment_gui.jsi";
import { BaseEntity, BaseEntityWidgets } from "../shared/base.mjs";
import {
BodyPartWidgets,
Occupant,
OccupantEntity,
OccupantEntityWidgets,
OccupantWidgets
} from "../shared/occupant.mjs";
import { JSPath } from "../shared/path.mjs";
import { Structure, StructureEntityWidgets, StructureWidgets } from "../shared/structure.mjs";
import { WorkflowUnitsCombobox } from "../../../modules/units.mjs";
import { OccupantVersion } from "../shared/occupant_version.mjs";
import { Protocol, Protocols } from "../shared/protocols.mjs";
import { ProtocolVehicle, VehicleOccupant } from "../shared/vehicle.mjs";
import { WorkflowOccupant } from "../shared/workflow_occupant.mjs";
import { ReadJSON } from "../shared/file_helper.mjs";
import { JobControl } from "./reporter_job_control.mjs";
export {
RunAutomotiveAssessmentTool,
get_user_data,
gui,
add_new_occupant,
filter_version_drop_down,
update_occupant_names_combobox,
set_selected_widget_item,
GetProtocolVehicle,
JSPath,
Protocols
};
/* Workflow definition filename is passed as an argument to this script from the Workflow menu.
* It's required when writing the user data to a file or model. */
let global_theme;
let workflow_definition_filename;
/**
* this function runs the PRIMER automotive assessments tool GUI and returns a JobControl status for use in REPORTER
* @param {string} [reporter_user_data_file="PRIMER"] either "PRIMER" or "REPORTER"
* @returns {string} job_control
*/
function RunAutomotiveAssessmentTool(reporter_user_data_file = "PRIMER") {
workflow_definition_filename = Workflow.WorkflowDefinitionFilename("Automotive Assessments");
Message(`workflow_definition_filename ${workflow_definition_filename}`);
/* Models can contain many DATABASE HISTORY cards, so increase the max number of widgets */
Options.max_widgets = 10000;
/* Set global theme */
global_theme = Window.Theme(Window.THEME_CURRENT);
/* Setup the GUI and show the main window */
if (gui) {
try {
/* set default JobControl return to be "Abort" */
gui.job_control = JobControl.ABORT;
/* Ask the user which model to use */
gui.model = Model.Select("Select the model to use");
if (gui.model == null) {
Window.Information("", "You need to select a model before you can use this workflow");
Exit();
}
/* Set up the gui */
setup_gui();
/* handle REPORTER mode*/
if (reporter_user_data_file != "PRIMER") {
Message(`looking for reporter_user_data_file: ${reporter_user_data_file}`);
if (File.Exists(reporter_user_data_file)) {
Message(`found reporter_user_data_file: ${reporter_user_data_file}`);
Message(JSON.stringify(reporter_user_data_file, null, 4));
let reporter_user_data = ReadJSON(reporter_user_data_file);
/*if we are in REPORTER mode then we need to update the gui based on the primer data
and ensure it never changes so disable regs */
for (let protocol_data of reporter_user_data) {
// let protocol = Protocol.FromJSON(protocol_data);
Message(`protocol_data:`);
Message(JSON.stringify(protocol_data, null, 4));
protocol_data.vehicle = ProtocolVehicle.FromJSON(protocol_data.vehicle);
Window.Information(
"REPORTER User Data Required",
`Some of the user inputs required for ${protocol_data.regulation} ${protocol_data.crash_test} ${protocol_data.version} ` +
`in this report are missing or invalid (see list below):\n${protocol_data.description.join(
"\n"
)}`
);
/*NOTE at the moment only one test/reg/version combo is supported */
if (protocol_data.vehicle) SetUpGUIFromREPORTERUserData(protocol_data);
else {
WarningMessage(`No ProtocolVehicle could not be constructed from reporter_user_data`);
}
}
} else {
Message(`could not find reporter_user_data_file: ${reporter_user_data_file}`);
}
}
/* Update main window - both occupants and structures */
update_main_window();
/* Show the main window */
gui.wdw_pre_automotive.Show(false);
return gui.job_control;
} catch (e) {
ErrorMessage(`Something went wrong: ${e}.\n${e.stack}`);
}
}
}
/**
* this is used to set up the initial display of the vehicle occupants selection area adding onClicks
* and hiding initially disabled buttons and moving 'Add' buttons to the correct location
*/
function setup_vehicle_occupants() {
//default set the vehicle hand drive to left when opened in PRIMER (or use user data to set it if present)
gui.drive_side = VehicleOccupant.LHD;
gui.wdw_pre_automotive.radio_hand_drive.ItemAt(0).selected = true; // = VehicleOccupant.LHD;
/*add the vehicle - Note this needs to be after drive side is set!*/
update_vehicle_image();
/*assign onClick callbacks for all buttons and add front_rear adn side properties*/
for (let front_rear_side of [
[WorkflowOccupant.FRONT, WorkflowOccupant.LEFT],
[WorkflowOccupant.FRONT, WorkflowOccupant.RIGHT],
[WorkflowOccupant.REAR, WorkflowOccupant.LEFT],
[WorkflowOccupant.REAR, WorkflowOccupant.RIGHT],
[WorkflowOccupant.REAR, WorkflowOccupant.MIDDLE]
]) {
/*store front_rear and side value in seperate temporary variables*/
let front_rear = front_rear_side[0];
let side = front_rear_side[1];
/*get hold of add, edit, delete (and occupant) buttons for the seat position*/
let add_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_add`];
let edit_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_edit`];
let delete_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_delete`];
let occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_occupant`];
//** add onClick callbacks to add, edit, delete */
add_btn_widget.onClick = add_edit_occupant;
edit_btn_widget.onClick = add_edit_occupant;
delete_btn_widget.onClick = delete_occupant;
//** add onClick callback, front_rear side properties to occupant buttons */
occupant_btn_widget.onClick = add_edit_occupant;
occupant_btn_widget.front_rear = front_rear;
occupant_btn_widget.side = side;
/*add front_rear and side properties and onClick to the buttons and hide them to start with*/
for (let button of [add_btn_widget, edit_btn_widget, delete_btn_widget]) {
button.front_rear = front_rear;
button.side = side;
button.Hide();
}
/*Move occupant 'Add' button up to make it lie on top of edit and delete buttons and Show it so it is initially visible
note they are origionally 6 units below to make it easier to work with them in the gui builder */
add_btn_widget.top = edit_btn_widget.top;
add_btn_widget.bottom = edit_btn_widget.bottom;
add_btn_widget.Show();
/* update occupant button text*/
let expected_occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_expected_occupant`];
let selected_occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_selected_occupant`];
expected_occupant_btn_widget.text = "not required";
selected_occupant_btn_widget.text = "<empty>";
selected_occupant_btn_widget.category = Widget.NO_CATEGORY;
selected_occupant_btn_widget.foreground = Widget.BLACK;
selected_occupant_btn_widget.background = Widget.COLOUR_NEUTRAL;
}
}
/**
* set the gui.drive_side and the radio button to the specified hand drive (if valid)
* defaults to LHD if not valid and prints a warning message
* @param {string} drive_side
*/
function set_vehicle_drive_side(drive_side) {
if ((drive_side = VehicleOccupant.GetValidHandDrive(drive_side))) {
gui.drive_side = drive_side;
for (let radio_wi of gui.wdw_pre_automotive.radio_hand_drive.WidgetItems()) {
if (radio_wi.text == drive_side) {
radio_wi.selected = true;
break;
}
}
}
}
/**
* update the vehicle image based on the global_theme
*/
function update_vehicle_image() {
let theme_str = `Light`;
switch (Window.Theme()) {
case Window.THEME_DARK:
theme_str = `Dark`;
break;
case Window.THEME_CLASSIC:
theme_str = `Classic`;
break;
}
let vehicle_image_path = `${JSPath.GetImagesDirectory()}/vehicles/${theme_str}${gui.drive_side}_250px.png`;
if (!File.Exists(vehicle_image_path)) {
throw Error(`${vehicle_image_path} does not exist`);
}
gui.wdw_pre_automotive.lbl_vehicle.ReadImageFile(vehicle_image_path, Widget.CENTRE | Widget.MIDDLE);
draw_lines();
gui.wdw_pre_automotive.Redraw();
}
function draw_lines() {
let widget = gui.wdw_pre_automotive.lbl_vehicle;
widget.Clear();
widget.lineWidth = 3;
let line_colour = Widget.GREY;
switch (Window.Theme()) {
case Window.THEME_DARK:
line_colour = Widget.GREY;
break;
case Window.THEME_LIGHT:
line_colour = Widget.GREY;
break;
case Window.THEME_CLASSIC:
line_colour = Widget.BLACK;
break;
}
widget.xResolution = 250;
widget.yResolution = 250;
let front_diagonals = [75, 75, 105, 107];
let rear_diagonals = [75, 167, 95, 136];
let middle_line = [125, 135, 125, 200];
//front left
widget.Line(line_colour, front_diagonals[0], front_diagonals[1], front_diagonals[2], front_diagonals[3]);
//front right
widget.Line(
line_colour,
widget.xResolution - front_diagonals[0],
front_diagonals[1],
widget.xResolution - front_diagonals[2],
front_diagonals[3]
);
//rear left
widget.Line(line_colour, rear_diagonals[0], rear_diagonals[1], rear_diagonals[2], rear_diagonals[3]);
//rear right
widget.Line(
line_colour,
widget.xResolution - rear_diagonals[0],
rear_diagonals[1],
widget.xResolution - rear_diagonals[2],
rear_diagonals[3]
);
//rear middle
widget.Line(line_colour, middle_line[0], middle_line[1], middle_line[2], middle_line[3]);
}
/**
* update the vehicle occupant gui based on the passed vehicle and hand drive
* @param {ProtocolVehicle} vehicle
* @param {string} drive_side
*/
function update_vehicle_occupants(vehicle, drive_side) {
set_vehicle_drive_side(drive_side);
if (!vehicle) {
WarningMessage("Vehicle is null");
return;
}
let current_theme = Window.Theme();
if (current_theme != global_theme) {
global_theme = current_theme;
update_vehicle_image();
}
/*
do it like this as drive_side could be invalid and
set_vehicle_drive_side also sets gui.drive_side to be valid
i.e. defaults to VehicleOccupant.LHD*/
drive_side = gui.drive_side;
for (let vehicle_occupant of vehicle.Occupants()) {
/*store front_rear and side value in seperate temporary variables*/
let front_rear = vehicle_occupant.GetRow();
let side = vehicle_occupant.GetSide(drive_side);
/* get widgets associated with occupant */
let add_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_add`];
let edit_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_edit`];
let delete_btn_widget = gui.wdw_pre_automotive[`btn_${front_rear}_${side}_delete`];
let selected_occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_selected_occupant`];
let expected_occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_expected_occupant`];
let occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_occupant`];
/* get add button and make it generic colour (or perhaps disabled) */
add_btn_widget.category = Widget.CATEGORY_GENERIC;
//reset selected_occupant_btn_widget
selected_occupant_btn_widget.text = "<empty>";
selected_occupant_btn_widget.category = Widget.NO_CATEGORY;
selected_occupant_btn_widget.foreground = Widget.BLACK;
selected_occupant_btn_widget.background = Widget.COLOUR_NEUTRAL;
//reset occupant_btn_widget
occupant_btn_widget.category = Widget.CATEGORY_LABEL_POPUP; //this gives it a faint border
// occupant_btn_widget.background = Widget.COLOUR_LATENT;
/* update expected occupant button text*/
let expected_occupant_text = "not required";
//clear vehicle_occupant property
// @ts-ignore
expected_occupant_btn_widget.vehicle_occupant = null;
//clear hover text
selected_occupant_btn_widget.hover = "";
//clear tick (or any other graphics) onoccupant_btn_widget
occupant_btn_widget.Clear();
if (!vehicle_occupant.Empty()) {
expected_occupant_text = `${vehicle_occupant.product}-${vehicle_occupant.physiology}`;
// Ignore squiggles below because Widget objects can have user defined properties added
// @ts-ignore
expected_occupant_btn_widget.vehicle_occupant = vehicle_occupant;
//set the colour of text to latent (meaning the user could/should defined it by pressing Add button)
selected_occupant_btn_widget.foreground = Widget.COLOUR_LATENT;
//set the colour of occupant_btn_widget to latent (meaning the user could/should defined it by pressing Add button)
occupant_btn_widget.category = Widget.NO_CATEGORY;
occupant_btn_widget.background = Widget.COLOUR_LATENT;
/* set add button category to the Widget.CATEGORY_APPLY because we want to encourage the user to press it*/
add_btn_widget.category = Widget.CATEGORY_APPLY;
}
/* set the expected occupant text */
expected_occupant_btn_widget.text = expected_occupant_text;
let wf_occupant = null;
if ((wf_occupant = get_occupant_from_position(vehicle_occupant.position))) {
let temp_occupant = OccupantVersion.GetFromName(wf_occupant.name);
selected_occupant_btn_widget.text = `${temp_occupant.product}-${temp_occupant.physiology}`;
selected_occupant_btn_widget.hover = `${wf_occupant.name}`;
if (expected_occupant_text == "not required") {
/**
* colour it neutral if the expected_occupant_text is "not required" as we do not care if an occupant is defined*/
selected_occupant_btn_widget.category = Widget.NO_CATEGORY;
selected_occupant_btn_widget.foreground = Widget.BLACK;
selected_occupant_btn_widget.background = Widget.COLOUR_NEUTRAL;
occupant_btn_widget.category = Widget.CATEGORY_LABEL_POPUP;
} else if (selected_occupant_btn_widget.text == expected_occupant_text) {
/**
* colour it green to show that the occupant defined for this seat
* is of the same product and physiology as required by the regulation
* a tick will be added if the definition is valid (i.e. has no latent values)*/
occupant_btn_widget.category = Widget.CATEGORY_SAFE_ACTION;
selected_occupant_btn_widget.category = Widget.CATEGORY_SAFE_ACTION;
if (are_occupant_entity_ids_valid(wf_occupant)) {
/** add a tick to the occupant_btn_widget if the occupant is valid*/
occupant_btn_widget.Tick(Widget.BLACK);
} else {
/**
* some fields are latent or invalid
* put <> around text and add explanation to hover text to show some fields are latent
*/
selected_occupant_btn_widget.text = `<${selected_occupant_btn_widget.text}>`;
selected_occupant_btn_widget.hover += " has latent fields.";
}
} else {
/**
* colour it red if the text does not match as this means that the occupant defined for this seat
* is of a different product and physiology as required by the regulation*/
selected_occupant_btn_widget.category = Widget.CATEGORY_WARNING_ACTION;
occupant_btn_widget.category = Widget.CATEGORY_WARNING_ACTION;
}
add_btn_widget.Hide();
edit_btn_widget.Show();
delete_btn_widget.Show();
} else {
add_btn_widget.active = expected_occupant_text != "not required";
add_btn_widget.Show();
edit_btn_widget.Hide();
delete_btn_widget.Hide();
}
//duplicate hover text for occupant_btn_widget
occupant_btn_widget.hover = selected_occupant_btn_widget.hover;
// Message(`wf_occupant = ${wf_occupant} ${front_rear} ${side}`);
}
/* change activity of delete button */
gui.wdw_pre_automotive.btn_delete_all_occupants.active = gui.occupants.length != 0;
/*redraw the window so that changes appear immediately*/
gui.wdw_pre_automotive.Redraw();
}
/**
* returns the workflow occupant for that seat positoin or null if not found
* @param {string} position
* @returns {?WorkflowOccupant}
*/
function get_occupant_from_position(position) {
for (let wf_occupant of gui.occupants) {
if (wf_occupant.position == position) {
return wf_occupant;
}
}
return null;
}
/**
* returns the workflow occupant for that seat positoin or null if not found
* @param {string} side
* @param {string} front_rear
* @param {string} drive_side
* @returns {string}
*/
function get_seat_position(side, front_rear, drive_side) {
switch (drive_side) {
case VehicleOccupant.LHD:
switch (front_rear) {
case WorkflowOccupant.FRONT:
switch (side) {
case WorkflowOccupant.LEFT:
return WorkflowOccupant.DRIVER;
case WorkflowOccupant.RIGHT:
return WorkflowOccupant.FRONT_PASSENGER;
}
case WorkflowOccupant.REAR:
switch (side) {
case WorkflowOccupant.LEFT:
return WorkflowOccupant.REAR_DRIVER_SIDE;
case WorkflowOccupant.RIGHT:
return WorkflowOccupant.REAR_PASSENGER_SIDE;
case WorkflowOccupant.MIDDLE:
return WorkflowOccupant.REAR_MIDDLE;
}
}
case VehicleOccupant.RHD:
switch (front_rear) {
case WorkflowOccupant.FRONT:
switch (side) {
case WorkflowOccupant.RIGHT:
return WorkflowOccupant.DRIVER;
case WorkflowOccupant.LEFT:
return WorkflowOccupant.FRONT_PASSENGER;
}
case WorkflowOccupant.REAR:
switch (side) {
case WorkflowOccupant.RIGHT:
return WorkflowOccupant.REAR_DRIVER_SIDE;
case WorkflowOccupant.LEFT:
return WorkflowOccupant.REAR_PASSENGER_SIDE;
case WorkflowOccupant.MIDDLE:
return WorkflowOccupant.REAR_MIDDLE;
}
}
default:
throw Error(
`Could not get position for side: ${side}, front_rear: ${front_rear}, drive_side: ${drive_side}`
);
}
}
/**
* update the filters on the occupant window to only allow valid occupants
*/
function set_up_occupant_window(side, front_rear) {
let wdw = gui.wdw_occupant;
try {
// if gui.current_occupant is not null then we are in edit mode
gui.current_occupant = get_occupant_from_position(get_seat_position(side, front_rear, gui.drive_side));
} catch (error) {
ErrorMessage(`${error}\nOccupant window will not be shown.`);
return;
}
if (gui.current_occupant) {
Message(`Edit occupant ${front_rear} ${side}`);
edit_occupant();
return;
}
/*add an occupant, but help the user out by pre-filtering dropdowns
/* get expected occupant button for current seat */
let expected_occupant_btn_widget = gui.wdw_pre_automotive[`lbl_${front_rear}_${side}_expected_occupant`];
//alternatively could get this from protocol, but do it this way so that REPORTER works but passing a SpecifiedVehicle class json
let vehicle_occupant = expected_occupant_btn_widget.vehicle_occupant;
if (vehicle_occupant && !vehicle_occupant.Empty()) {
wdw.cbx_occupant_name.active = true;
wdw.cbx_occupant_supplier.active = true;
wdw.cbx_occupant_product.active = true;
wdw.cbx_occupant_physiology.active = true;
set_selected_widget_item(wdw.cbx_occupant_supplier, "all");
set_selected_widget_item(wdw.cbx_occupant_product, vehicle_occupant.product);
set_selected_widget_item(wdw.cbx_occupant_physiology, vehicle_occupant.physiology);
set_selected_widget_item(wdw.cbx_occupant_position, vehicle_occupant.position);
//once all the widget items have been set call update filters to update the drop-down
//list of occupant names
let occupant_names = filter_version_drop_down();
update_occupant_names_combobox(occupant_names);
Message(`Add occupant ${front_rear} ${side}`);
add_new_occupant();
}
}
/**
* 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 WorkflowOccupant class.
*/
function setup_gui() {
/* Initialise GUI global variables */
/* Entity ID offset */
gui.offset = 0;
/* Array to store the WorkflowOccupant instances */
gui.occupants = [];
/* Currently selected occupant */
gui.current_occupant = null;
/* Array to store the Structure instances (defined once) */
gui.structures = [];
/* Array to store the Structure widgets */
gui.structure_list_widgets = [];
gui.structures_page = 1;
/* Currently selected entity */
gui.current_entity_tag = "";
gui.current_entity_type = "";
/* B-Pillar entity values */
initialise_b_pillar_entities();
/* Head Excursion entity values */
initialise_head_excursion_entities();
/* Assign callbacks */
/* Regulation and crash test on change callbacks */
gui.wdw_pre_automotive.cbx_protocol_test.onChange = crash_test_changed;
gui.wdw_pre_automotive.cbx_protocol_regulation.onChange = regulation_changed;
gui.wdw_pre_automotive.cbx_protocol_version.onChange = version_changed;
/* Set up initial state of occupants region and add occupant button callbacks */
setup_vehicle_occupants();
/* Occupant callbacks */
gui.wdw_pre_automotive.lbl_vehicle.onClick = update_vehicle_image;
gui.wdw_pre_automotive.radio_hand_drive.onClick = update_vehicle_hand_drive;
gui.wdw_pre_automotive.btn_flip_occupants.onClick = flip_occupants;
gui.wdw_pre_automotive.btn_delete_all_occupants.onClick = delete_all_occupants;
gui.wdw_occupant.btn_use_id_num.onClick = update_and_toggle_ids_between_num_and_dbhistitle;
gui.wdw_occupant.btn_use_dbhistitle.onClick = update_and_toggle_ids_between_num_and_dbhistitle;
gui.wdw_occupant.txt_entity_offset.onChange = offset_changed;
gui.wdw_occupant.btn_update_occupant.onClick = update_current_occupant_and_close;
gui.wdw_occupant.btn_cancel_occupant.onClick = close_occupant_window;
gui.wdw_occupant.cbx_occupant_name.onChange = update_occupant_window;
gui.wdw_occupant.cbx_occupant_supplier.onChange = update_filters;
gui.wdw_occupant.cbx_occupant_product.onChange = update_filters;
gui.wdw_occupant.cbx_occupant_physiology.onChange = update_filters;
gui.wdw_occupant.cbx_occupant_supplier.onClick = disable_invalid_filter_options;
gui.wdw_occupant.cbx_occupant_product.onClick = disable_invalid_filter_options;
gui.wdw_occupant.cbx_occupant_physiology.onClick = disable_invalid_filter_options;
gui.wdw_occupant.btn_save_occupant.onClick = save_occupant_to_file;
gui.wdw_occupant.btn_save_occupant.Hide(); /* This is for internal use only, so hide from users */
/* Structure callbacks */
gui.wdw_pre_automotive.lbl_structures_first.onClick = structures_page_switchers_on_click;
gui.wdw_pre_automotive.lbl_structures_previous.onClick = structures_page_switchers_on_click;
gui.wdw_pre_automotive.lbl_structures_next.onClick = structures_page_switchers_on_click;
gui.wdw_pre_automotive.lbl_structures_last.onClick = structures_page_switchers_on_click;
gui.wdw_structure.btn_update_structure.onClick = update_all_structures_and_close;
gui.wdw_structure.btn_clear_structure.onClick = clear_the_current_structure;
gui.wdw_structure.btn_reset_structure.onClick = reset_the_current_structure;
gui.wdw_structure.btn_cancel_structure.onClick = close_structure_window;
gui.wdw_structure.cbx_structure.onChange = update_structure_window;
/* Picking and selecting callbacks */
gui.popup_select_entities.btn_pick_entity.onClick = pick_entity;
gui.popup_select_entities.btn_select_entity.onClick = select_entity;
/* Save data callbacks */
gui.wdw_pre_automotive.btn_save_to_file.onClick = save_to_file;
gui.wdw_pre_automotive.btn_save_to_model.onClick = save_to_model;
/* Add widget items to the units combobox */
gui.wdw_pre_automotive.cbx_unit_system = new WorkflowUnitsCombobox(gui.wdw_pre_automotive.cbx_unit_system);
/* Get list of possible options for test combobox and add widget items */
let tests = Protocols.GetAllCrashTests();
for (let test of tests) {
new WidgetItem(gui.wdw_pre_automotive.cbx_protocol_test, test);
}
/* Add line to label to separate save buttons from selection widgets */
gui.wdw_pre_automotive.lbl_line.Line(Widget.DARKGREY, 0, 50, 100, 50);
/* Create the widgets in the structures window */
create_structures_widgets();
/* Create the widgets in the occupant window */
create_occupant_widgets();
/* Add buttons for selecting database history nodes/beams/discrete and cross sections */
add_buttons_to_select_entities_popup();
/* Get any data that already exists for the model */
let success = read_model_data();
if (!success) {
/* Set up regulations combo box with regulation (widget items) supported by currently selected crash test */
update_regulations();
/* Set up versions combo box with version (widget items) supported by currently selected crash test and regulation*/
update_versions();
/* Update Occupants gui */
update_occupants_column();
}
}
/**
* return the vehicle based on the currently selected version
* or if in REPORTER mode gui.reporter_protocol_vehicle should be set and its value is returned instead
* @returns {?ProtocolVehicle}
*/
function GetProtocolVehicle() {
if (gui.reporter_protocol_vehicle) {
return gui.reporter_protocol_vehicle;
}
let regulation = get_selected_regulation();
let crash_test = get_selected_crash_test_protocol();
let version = gui.wdw_pre_automotive.cbx_protocol_version.text;
let vehicle = Protocols.GetProtocolVehicle(regulation, crash_test, version);
if (vehicle) {
return vehicle;
} else {
ErrorMessage(`Could not find protocol vehicle for ${regulation} ${crash_test} ${version}`);
}
return null;
}
/**
* clear the combobox and then add the structure types as widget items to the combobox (in alphabetical order).
* This is mainly used when the protocol changes
* @param {string[]} structure_types
*/
function update_structures_combobox(structure_types) {
let alphabetical_structure_types = structure_types.sort();
gui.wdw_structure.cbx_structure.RemoveAllWidgetItems();
for (let i = 0; i < alphabetical_structure_types.length; i++) {
new WidgetItem(gui.wdw_structure.cbx_structure, alphabetical_structure_types[i]);
}
}
/**
* Creates the widgets in the structures window and the structures stored on gui.structures
*/
function create_structures_widgets() {
/* Get list of possible structures */
let structure_types = Structure.Types();
/* Create widgets for selecting entity IDs */
gui.structure_widgets = [];
let max_bottom = 1;
for (let structure_type of structure_types) {
let top = gui.wdw_structure.cbx_structure.bottom + 1;
let bottom = top + 6;
let structure = Structure.CreateStructure(structure_type);
/**
* add all the structure to the gui.structures array
* this means we create them only once and any values changed are set.
* we should also add logic to reset/clear the structure so user doesn't have to manually set
* all IDs to 0
*/
gui.structures.push(structure);
/* The B-Pillar and Head Excursion are non-standard structures that don't have
* any StructureEntity instances defined (see CreateBPillarEntities() for an explanation).
*
* Bespoke logic is required here to create the required widgets.
*
* For other strutures create the widgets from the StructureEntity instances.
*/
if (structure.component_type == Structure.B_PILLAR) {
bottom = create_b_pillar_widgets(gui.wdw_structure, top, bottom);
} else if (structure.component_type == Structure.HEAD_EXCURSION) {
bottom = create_head_excursion_widgets(gui.wdw_structure, top, bottom);
} else {
/* No entities for this structure */
if (structure.entities.length == 0) continue;
/* Structure header */
let structure_label = new Widget(
gui.wdw_structure,
Widget.LABEL,
1,
147,
top,
bottom,
structure.component_type.toUpperCase()
);
structure_label.category = Widget.CATEGORY_TITLE;
top = bottom + 1;
bottom = top + 6;
/* Entity widgets */
/** @type {StructureEntityWidgets[]} */
let entity_widgets = [];
for (let structure_entity of structure.entities) {
let label = new Widget(gui.wdw_structure, Widget.LABEL, 1, 82, top, bottom, structure_entity.name);
label.justify = Widget.LEFT;
let textbox = new Widget(
gui.wdw_structure,
Widget.TEXTBOX,
83,
147,
top,
bottom,
structure_entity.id.toString()
);
textbox.popupWindow = gui.popup_select_entities;
textbox.popupDirection = Widget.RIGHT;
textbox.onPopup = structure_entity_on_popup;
textbox.onChange = update_structure_window;
/* Tag and entity type used in popup from textbox for selecting the entity */
// @ts-ignore
textbox.entity_tag = structure_entity.tag;
// @ts-ignore
textbox.entity_type = structure_entity.entity_type;
top = bottom + 1;
bottom = top + 6;
entity_widgets.push(
new StructureEntityWidgets(structure_entity.entity_type, label, textbox, structure_entity.tag)
);
}
let structure_widgets = new StructureWidgets(structure.component_type, structure_label, entity_widgets);
gui.structure_widgets.push(structure_widgets);
}
max_bottom = Math.max(max_bottom, bottom);
}
/* Create a label widget at the bottom of the window so it maps to the correct size to fit all the entity widgets on */
gui.wdw_structure.lbl_occupant_bottom = new Widget(
gui.wdw_structure,
Widget.LABEL,
1,
147,
max_bottom,
max_bottom + 1,
""
);
}
/**
* Creates the B-Pillar widgets, returning the bottom position of the last widget created
* @param {Window} window Window to create widgets in
* @param {number} start_top Starting top position of widgets
* @param {number} start_bottom Starting bottom position of widgets
* @returns {number}
*/
function create_b_pillar_widgets(window, start_top, start_bottom) {
let top = start_top;
let bottom = start_bottom;
/* All the widgets are added to an object on the gui object */
gui.b_pillar_widgets = {};
gui.b_pillar_widgets.lbl_structure = new Widget(
window,
Widget.LABEL,
1,
147,
top,
bottom,
Structure.B_PILLAR.toUpperCase()
);
gui.b_pillar_widgets.lbl_structure.category = Widget.CATEGORY_TITLE;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_cut_section = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Definition Method"
);
gui.b_pillar_widgets.lbl_cut_section.justify = Widget.LEFT;
gui.b_pillar_widgets.cbx_cut_section = new Widget(window, Widget.COMBOBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.cbx_cut_section.onChange = b_pillar_callback;
new WidgetItem(gui.b_pillar_widgets.cbx_cut_section, "Constant X");
new WidgetItem(gui.b_pillar_widgets.cbx_cut_section, "Constant Y");
new WidgetItem(gui.b_pillar_widgets.cbx_cut_section, "Constant Z");
new WidgetItem(gui.b_pillar_widgets.cbx_cut_section, "Three Nodes");
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_cut_section_node_1 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Node 1"
);
gui.b_pillar_widgets.lbl_cut_section_node_1.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_cut_section_node_1 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_cut_section_node_1.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_cut_section_node_1.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_cut_section_node_1.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_cut_section_node_1.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_cut_section_node_2 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Node 2"
);
gui.b_pillar_widgets.lbl_cut_section_node_2.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_cut_section_node_2 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_cut_section_node_2.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_cut_section_node_2.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_cut_section_node_2.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_cut_section_node_2.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_cut_section_node_3 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Node 3"
);
gui.b_pillar_widgets.lbl_cut_section_node_3.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_cut_section_node_3 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_cut_section_node_3.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_cut_section_node_3.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_cut_section_node_3.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_cut_section_node_3.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_pre_crash_parts = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Pre-Crash Parts");
gui.b_pillar_widgets.lbl_pre_crash_parts.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_pre_crash_parts = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_pre_crash_parts.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_pre_crash_parts.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_pre_crash_parts.popupWindow = gui.popup_select_parts;
gui.b_pillar_widgets.txt_pre_crash_parts.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_post_crash_parts = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Post-Crash Parts"
);
gui.b_pillar_widgets.lbl_post_crash_parts.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_post_crash_parts = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_post_crash_parts.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_post_crash_parts.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_post_crash_parts.popupWindow = gui.popup_select_parts;
gui.b_pillar_widgets.txt_post_crash_parts.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_shift_deform_node_1 = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Shift Node 1");
gui.b_pillar_widgets.lbl_shift_deform_node_1.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_shift_deform_node_1 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_shift_deform_node_1.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_shift_deform_node_1.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_shift_deform_node_1.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_shift_deform_node_1.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_shift_deform_node_2 = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Shift Node 2");
gui.b_pillar_widgets.lbl_shift_deform_node_2.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_shift_deform_node_2 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_shift_deform_node_2.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_shift_deform_node_2.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_shift_deform_node_2.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_shift_deform_node_2.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_shift_deform_node_3 = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Shift Node 3");
gui.b_pillar_widgets.lbl_shift_deform_node_3.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_shift_deform_node_3 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_shift_deform_node_3.onChange = b_pillar_callback;
gui.b_pillar_widgets.txt_shift_deform_node_3.onPopup = b_pillar_on_popup;
gui.b_pillar_widgets.txt_shift_deform_node_3.popupWindow = gui.popup_pick_node;
gui.b_pillar_widgets.txt_shift_deform_node_3.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_ground_z = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Ground Z");
gui.b_pillar_widgets.lbl_ground_z.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_ground_z = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_ground_z.onChange = b_pillar_callback;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_seat_centre_y = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Seat Centre Y");
gui.b_pillar_widgets.lbl_seat_centre_y.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_seat_centre_y = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_seat_centre_y.onChange = b_pillar_callback;
top = bottom + 1;
bottom = top + 6;
gui.b_pillar_widgets.lbl_h_point_z = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "H-Point Z");
gui.b_pillar_widgets.lbl_h_point_z.justify = Widget.LEFT;
gui.b_pillar_widgets.txt_h_point_z = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.b_pillar_widgets.txt_h_point_z.onChange = b_pillar_callback;
/* Callbacks for buttons on pick/select popups */
gui.popup_pick_node.btn_pick.onClick = b_pillar_callback;
gui.popup_select_parts.btn_select.onClick = b_pillar_callback;
/* Update the values in the widgets */
update_b_pillar_widgets();
/* Return the bottom coordinate of the last widget created */
return bottom;
}
/**
* Sets the initial values for the B-Pillar entities
*/
function initialise_b_pillar_entities() {
gui.b_pillar_cut_section_method = "Constant X";
gui.b_pillar_cut_section_node_1 = 0;
gui.b_pillar_cut_section_node_2 = 0;
gui.b_pillar_cut_section_node_3 = 0;
gui.b_pillar_pre_crash_parts = [];
gui.b_pillar_post_crash_parts = [];
gui.b_pillar_shift_deform_node_1 = 0;
gui.b_pillar_shift_deform_node_2 = 0;
gui.b_pillar_shift_deform_node_3 = 0;
gui.b_pillar_ground_z = 0;
gui.b_pillar_seat_centre_y = 0;
gui.b_pillar_h_point_z = 0;
gui.b_pillar_popup_widget = null;
}
/**
* Callback function for B-Pillar widgets
*/
function b_pillar_callback() {
switch (this) {
case gui.b_pillar_widgets.cbx_cut_section:
gui.b_pillar_cut_section_method = this.text;
break;
case gui.b_pillar_widgets.txt_cut_section_node_1:
case gui.b_pillar_widgets.txt_cut_section_node_2:
case gui.b_pillar_widgets.txt_cut_section_node_3:
case gui.b_pillar_widgets.txt_shift_deform_node_1:
case gui.b_pillar_widgets.txt_shift_deform_node_2:
case gui.b_pillar_widgets.txt_shift_deform_node_3:
let new_int = parseInt(this.text);
if (isNaN(new_int)) {
WarningMessage("Invalid value, must be an integer");
} else if (new_int < 0) {
WarningMessage("Invalid value, must be greater than or equal to 0");
} else {
if (this == gui.b_pillar_widgets.txt_cut_section_node_1) {
gui.b_pillar_cut_section_node_1 = new_int;
} else if (this == gui.b_pillar_widgets.txt_cut_section_node_2) {
gui.b_pillar_cut_section_node_2 = new_int;
} else if (this == gui.b_pillar_widgets.txt_cut_section_node_3) {
gui.b_pillar_cut_section_node_3 = new_int;
} else if (this == gui.b_pillar_widgets.txt_shift_deform_node_1) {
gui.b_pillar_shift_deform_node_1 = new_int;
} else if (this == gui.b_pillar_widgets.txt_shift_deform_node_2) {
gui.b_pillar_shift_deform_node_2 = new_int;
} else if (this == gui.b_pillar_widgets.txt_shift_deform_node_3) {
gui.b_pillar_shift_deform_node_3 = new_int;
}
}
break;
case gui.b_pillar_widgets.txt_pre_crash_parts:
case gui.b_pillar_widgets.txt_post_crash_parts:
let valid = true;
let parts = this.text.trim().split(/\s+/);
let new_parts = [];
for (let p of parts) {
let part = parseInt(p);
if (isNaN(part)) {
WarningMessage("Invalid value, parts must be a space-separated list of integers");
valid = false;
break;
} else if (part < 0) {
WarningMessage("Invalid value, parts must be greater than or equal to 0");
valid = false;
break;
}
new_parts.push(part);
}
if (!valid) break;
if (this == gui.b_pillar_widgets.txt_pre_crash_parts) {
gui.b_pillar_pre_crash_parts = new_parts;
} else if (this == gui.b_pillar_widgets.txt_post_crash_parts) {
gui.b_pillar_post_crash_parts = new_parts;
}
break;
case gui.b_pillar_widgets.txt_ground_z:
case gui.b_pillar_widgets.txt_seat_centre_y:
case gui.b_pillar_widgets.txt_h_point_z:
let new_float = parseFloat(this.text);
if (isNaN(new_float)) {
WarningMessage("Invalid value, must be a number");
} else {
if (this == gui.b_pillar_widgets.txt_ground_z) {
gui.b_pillar_ground_z = new_float;
} else if (this == gui.b_pillar_widgets.txt_seat_centre_y) {
gui.b_pillar_seat_centre_y = new_float;
} else if (this == gui.b_pillar_widgets.txt_h_point_z) {
gui.b_pillar_h_point_z = new_float;
}
}
break;
case gui.popup_pick_node.btn_pick:
let node_id = BaseEntity.Pick(BaseEntity.NODE, gui.model);
if (node_id == null) break;
if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_cut_section_node_1) {
gui.b_pillar_cut_section_node_1 = node_id;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_cut_section_node_2) {
gui.b_pillar_cut_section_node_2 = node_id;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_cut_section_node_3) {
gui.b_pillar_cut_section_node_3 = node_id;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_shift_deform_node_1) {
gui.b_pillar_shift_deform_node_1 = node_id;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_shift_deform_node_2) {
gui.b_pillar_shift_deform_node_2 = node_id;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_shift_deform_node_3) {
gui.b_pillar_shift_deform_node_3 = node_id;
}
break;
case gui.popup_select_parts.btn_select:
let part_ids = BaseEntity.Select(BaseEntity.PART, gui.model, true);
if (part_ids == null) break;
if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_pre_crash_parts) {
gui.b_pillar_pre_crash_parts = part_ids;
} else if (gui.b_pillar_popup_widget == gui.b_pillar_widgets.txt_post_crash_parts) {
gui.b_pillar_post_crash_parts = part_ids;
}
break;
default:
ErrorMessage("Unknown widget in b_pillar_callback()");
}
update_b_pillar_widgets();
}
/**
* Set which widget the B-Pillar popup was opened from
*/
function b_pillar_on_popup() {
gui.b_pillar_popup_widget = this;
}
/**
* Updates the B-Pillar widgets
*/
function update_b_pillar_widgets() {
/* Update the values in the widgets */
for (let wi of gui.b_pillar_widgets.cbx_cut_section.WidgetItems()) {
if (gui.b_pillar_cut_section_method == wi.text) {
wi.selected = true;
}
}
if (gui.b_pillar_cut_section_method == "Three Nodes") {
gui.b_pillar_widgets.txt_cut_section_node_2.active = true;
gui.b_pillar_widgets.txt_cut_section_node_3.active = true;
} else {
gui.b_pillar_widgets.txt_cut_section_node_2.active = false;
gui.b_pillar_widgets.txt_cut_section_node_3.active = false;
}
gui.b_pillar_widgets.txt_cut_section_node_1.text = gui.b_pillar_cut_section_node_1;
gui.b_pillar_widgets.txt_cut_section_node_2.text = gui.b_pillar_cut_section_node_2;
gui.b_pillar_widgets.txt_cut_section_node_3.text = gui.b_pillar_cut_section_node_3;
gui.b_pillar_widgets.txt_pre_crash_parts.text = gui.b_pillar_pre_crash_parts.join(" ");
gui.b_pillar_widgets.txt_post_crash_parts.text = gui.b_pillar_post_crash_parts.join(" ");
gui.b_pillar_widgets.txt_shift_deform_node_1.text = gui.b_pillar_shift_deform_node_1;
gui.b_pillar_widgets.txt_shift_deform_node_2.text = gui.b_pillar_shift_deform_node_2;
gui.b_pillar_widgets.txt_shift_deform_node_3.text = gui.b_pillar_shift_deform_node_3;
gui.b_pillar_widgets.txt_ground_z.text = gui.b_pillar_ground_z;
gui.b_pillar_widgets.txt_seat_centre_y.text = gui.b_pillar_seat_centre_y;
gui.b_pillar_widgets.txt_h_point_z.text = gui.b_pillar_h_point_z;
/* Update the widgets to indicate if the data is valid or not */
let nodes = [
{ id: gui.b_pillar_cut_section_node_1, widget: gui.b_pillar_widgets.txt_cut_section_node_1 },
{ id: gui.b_pillar_cut_section_node_2, widget: gui.b_pillar_widgets.txt_cut_section_node_2 },
{ id: gui.b_pillar_cut_section_node_3, widget: gui.b_pillar_widgets.txt_cut_section_node_3 },
{ id: gui.b_pillar_shift_deform_node_1, widget: gui.b_pillar_widgets.txt_shift_deform_node_1 },
{ id: gui.b_pillar_shift_deform_node_2, widget: gui.b_pillar_widgets.txt_shift_deform_node_2 },
{ id: gui.b_pillar_shift_deform_node_3, widget: gui.b_pillar_widgets.txt_shift_deform_node_3 }
];
for (let node of nodes) {
if (is_entity_id_valid(node.id, BaseEntity.NODE)) {
node.widget.category = Widget.CATEGORY_TEXT_BOX;
} else {
node.widget.category = Widget.CATEGORY_WARNING_ACTION;
}
}
let parts = [
{ ids: gui.b_pillar_pre_crash_parts, widget: gui.b_pillar_widgets.txt_pre_crash_parts },
{ ids: gui.b_pillar_post_crash_parts, widget: gui.b_pillar_widgets.txt_post_crash_parts }
];
for (let part of parts) {
let valid = true;
for (let id of part.ids) {
if (!is_entity_id_valid(id, BaseEntity.PART)) {
valid = false;
break;
}
}
if (valid) {
part.widget.category = Widget.CATEGORY_TEXT_BOX;
} else {
part.widget.category = Widget.CATEGORY_WARNING_ACTION;
}
}
gui.wdw_structure.Redraw();
}
/**
* Creates the Head Excursion widgets, returning the bottom position of the last widget created
* @param {Window} window Window to create widgets in
* @param {number} start_top Starting top position of widgets
* @param {number} start_bottom Starting bottom position of widgets
* @returns {number}
*/
function create_head_excursion_widgets(window, start_top, start_bottom) {
let top = start_top;
let bottom = start_bottom;
/* All the widgets are added to an object on the gui object */
gui.head_excursion_widgets = {};
gui.head_excursion_widgets.lbl_structure = new Widget(
window,
Widget.LABEL,
1,
147,
top,
bottom,
Structure.HEAD_EXCURSION.toUpperCase()
);
gui.head_excursion_widgets.lbl_structure.category = Widget.CATEGORY_TITLE;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_cut_section_thickness = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Thickness"
);
gui.head_excursion_widgets.lbl_cut_section_thickness.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_cut_section_thickness = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_cut_section_thickness.onChange = head_excursion_callback;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_cut_section_node = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Cut Section Node"
);
gui.head_excursion_widgets.lbl_cut_section_node.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_cut_section_node = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_cut_section_node.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_cut_section_node.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_cut_section_node.popupWindow = gui.popup_pick_node;
gui.head_excursion_widgets.txt_cut_section_node.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_vehicle_direction = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Vehicle Direction"
);
gui.head_excursion_widgets.lbl_vehicle_direction.justify = Widget.LEFT;
gui.head_excursion_widgets.cbx_vehicle_direction = new Widget(window, Widget.COMBOBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.cbx_vehicle_direction.onChange = head_excursion_callback;
new WidgetItem(gui.head_excursion_widgets.cbx_vehicle_direction, "Positive X");
new WidgetItem(gui.head_excursion_widgets.cbx_vehicle_direction, "Negative X");
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_head_parts = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Head Parts");
gui.head_excursion_widgets.lbl_head_parts.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_head_parts = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_head_parts.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_head_parts.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_head_parts.popupWindow = gui.popup_select_parts;
gui.head_excursion_widgets.txt_head_parts.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_barrier_parts = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Barrier Parts"
);
gui.head_excursion_widgets.lbl_barrier_parts.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_barrier_parts = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_barrier_parts.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_barrier_parts.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_barrier_parts.popupWindow = gui.popup_select_parts;
gui.head_excursion_widgets.txt_barrier_parts.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_shift_deform_node_1 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Shift Node 1"
);
gui.head_excursion_widgets.lbl_shift_deform_node_1.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_shift_deform_node_1 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_shift_deform_node_1.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_shift_deform_node_1.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_shift_deform_node_1.popupWindow = gui.popup_pick_node;
gui.head_excursion_widgets.txt_shift_deform_node_1.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_shift_deform_node_2 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Shift Node 2"
);
gui.head_excursion_widgets.lbl_shift_deform_node_2.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_shift_deform_node_2 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_shift_deform_node_2.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_shift_deform_node_2.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_shift_deform_node_2.popupWindow = gui.popup_pick_node;
gui.head_excursion_widgets.txt_shift_deform_node_2.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_shift_deform_node_3 = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Shift Node 3"
);
gui.head_excursion_widgets.lbl_shift_deform_node_3.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_shift_deform_node_3 = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_shift_deform_node_3.onChange = head_excursion_callback;
gui.head_excursion_widgets.txt_shift_deform_node_3.onPopup = head_excursion_on_popup;
gui.head_excursion_widgets.txt_shift_deform_node_3.popupWindow = gui.popup_pick_node;
gui.head_excursion_widgets.txt_shift_deform_node_3.popupDirection = Widget.RIGHT;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_seat_centre_y = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Near Seat Centre Y"
);
gui.head_excursion_widgets.lbl_seat_centre_y.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_seat_centre_y = new Widget(window, Widget.TEXTBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.txt_seat_centre_y.onChange = head_excursion_callback;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_intrusion_from_seat_centre_y = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Intrusion From Seat Centre Y"
);
gui.head_excursion_widgets.lbl_intrusion_from_seat_centre_y.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_intrusion_from_seat_centre_y = new Widget(
window,
Widget.TEXTBOX,
83,
147,
top,
bottom
);
gui.head_excursion_widgets.txt_intrusion_from_seat_centre_y.onChange = head_excursion_callback;
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_countermeasure = new Widget(
window,
Widget.LABEL,
1,
82,
top,
bottom,
"Countermeasure"
);
gui.head_excursion_widgets.lbl_countermeasure.justify = Widget.LEFT;
gui.head_excursion_widgets.cbx_countermeasure = new Widget(window, Widget.COMBOBOX, 83, 147, top, bottom);
gui.head_excursion_widgets.cbx_countermeasure.onChange = head_excursion_callback;
new WidgetItem(gui.head_excursion_widgets.cbx_countermeasure, "Yes");
new WidgetItem(gui.head_excursion_widgets.cbx_countermeasure, "No");
top = bottom + 1;
bottom = top + 6;
gui.head_excursion_widgets.lbl_keyword_file = new Widget(window, Widget.LABEL, 1, 82, top, bottom, "Keyword File");
gui.head_excursion_widgets.lbl_keyword_file.justify = Widget.LEFT;
gui.head_excursion_widgets.txt_keyword_file = new Widget(window, Widget.TEXTBOX, 83, 136, top, bottom);
gui.head_excursion_widgets.txt_keyword_file.onChange = head_excursion_callback;
gui.head_excursion_widgets.btn_keyword_file = new Widget(window, Widget.BUTTON, 137, 147, top, bottom, "");
gui.head_excursion_widgets.btn_keyword_file.DirectoryIcon(Widget.BLACK, Widget.YELLOW);
gui.head_excursion_widgets.btn_keyword_file.onClick = head_excursion_callback;
/* Callbacks for buttons on pick/select popups */
gui.popup_pick_node.btn_pick.onClick = head_excursion_callback;
gui.popup_select_parts.btn_select.onClick = head_excursion_callback;
/* Update the values in the widgets */
update_head_excursion_widgets();
/* Return the bottom coordinate of the last widget created */
return bottom;
}
/**
* Sets the initial values for the Head Excursion entities
*/
function initialise_head_excursion_entities() {
gui.head_excursion_cut_section_thickness = 10;
gui.head_excursion_cut_section_node = 0;
gui.head_excursion_vehicle_direction = "Negative X";
gui.head_excursion_head_parts = [];
gui.head_excursion_barrier_parts = [];
gui.head_excursion_shift_deform_node_1 = 0;
gui.head_excursion_shift_deform_node_2 = 0;
gui.head_excursion_shift_deform_node_3 = 0;
gui.head_excursion_seat_centre_y = 0;
gui.head_excursion_intrusion_from_seat_centre_y = 0;
gui.head_excursion_countermeasure = "Yes";
gui.head_excursion_keyword_file = "";
gui.head_excursion_popup_widget = null;
}
/**
* Callback function for B-Pillar widgets
*/
function head_excursion_callback() {
switch (this) {
case gui.head_excursion_widgets.txt_cut_section_node:
case gui.head_excursion_widgets.txt_shift_deform_node_1:
case gui.head_excursion_widgets.txt_shift_deform_node_2:
case gui.head_excursion_widgets.txt_shift_deform_node_3:
let new_int = parseInt(this.text);
if (isNaN(new_int)) {
WarningMessage("Invalid value, must be an integer");
} else if (new_int < 0) {
WarningMessage("Invalid value, must be greater than or equal to 0");
} else {
if (this == gui.head_excursion_widgets.txt_cut_section_node) {
gui.head_excursion_cut_section_node = new_int;
} else if (this == gui.head_excursion_widgets.txt_shift_deform_node_1) {
gui.head_excursion_shift_deform_node_1 = new_int;
} else if (this == gui.head_excursion_widgets.txt_shift_deform_node_2) {
gui.head_excursion_shift_deform_node_2 = new_int;
} else if (this == gui.head_excursion_widgets.txt_shift_deform_node_3) {
gui.head_excursion_shift_deform_node_3 = new_int;
}
}
break;
case gui.head_excursion_widgets.txt_head_parts:
case gui.head_excursion_widgets.txt_barrier_parts:
let valid = true;
let parts = this.text.trim().split(/\s+/);
let new_parts = [];
for (let p of parts) {
let part = parseInt(p);
if (isNaN(part)) {
WarningMessage("Invalid value, parts must be a space-separated list of integers");
valid = false;
break;
} else if (part < 0) {
WarningMessage("Invalid value, parts must be greater than or equal to 0");
valid = false;
break;
}
new_parts.push(part);
}
if (!valid) break;
if (this == gui.head_excursion_widgets.txt_head_parts) {
gui.head_excursion_head_parts = new_parts;
} else if (this == gui.head_excursion_widgets.txt_barrier_parts) {
gui.head_excursion_barrier_parts = new_parts;
}
break;
case gui.head_excursion_widgets.txt_cut_section_thickness:
case gui.head_excursion_widgets.txt_seat_centre_y:
case gui.head_excursion_widgets.txt_intrusion_from_seat_centre_y:
let new_float = parseFloat(this.text);
if (isNaN(new_float)) {
WarningMessage("Invalid value, must be a number");
} else {
if (this == gui.head_excursion_widgets.txt_cut_section_thickness) {
gui.head_excursion_cut_section_thickness = new_float;
} else if (this == gui.head_excursion_widgets.txt_seat_centre_y) {
gui.head_excursion_seat_centre_y = new_float;
} else if (this == gui.head_excursion_widgets.txt_intrusion_from_seat_centre_y) {
gui.head_excursion_intrusion_from_seat_centre_y = new_float;
}
}
break;
case gui.popup_pick_node.btn_pick:
let node_id = BaseEntity.Pick(BaseEntity.NODE, gui.model);
if (node_id == null) break;
if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_cut_section_node) {
gui.head_excursion_cut_section_node = node_id;
} else if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_shift_deform_node_1) {
gui.head_excursion_shift_deform_node_1 = node_id;
} else if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_shift_deform_node_2) {
gui.head_excursion_shift_deform_node_2 = node_id;
} else if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_shift_deform_node_3) {
gui.head_excursion_shift_deform_node_3 = node_id;
}
break;
case gui.popup_select_parts.btn_select:
let part_ids = BaseEntity.Select(BaseEntity.PART, gui.model, true);
if (part_ids == null) break;
if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_head_parts) {
gui.head_excursion_head_parts = part_ids;
} else if (gui.head_excursion_popup_widget == gui.head_excursion_widgets.txt_barrier_parts) {
gui.head_excursion_barrier_parts = part_ids;
}
break;
case gui.head_excursion_widgets.cbx_vehicle_direction:
gui.head_excursion_vehicle_direction = this.text;
break;
case gui.head_excursion_widgets.cbx_countermeasure:
gui.head_excursion_countermeasure = this.text;
break;
case gui.head_excursion_widgets.txt_keyword_file:
gui.head_excursion_keyword_file = this.text;
break;
case gui.head_excursion_widgets.btn_keyword_file:
let filename = Window.GetFile("*", true);
if (filename) gui.head_excursion_keyword_file = filename;
break;
default:
ErrorMessage("Unknown widget in head_excursion_callback()");
}
update_head_excursion_widgets();
}
/**
* Set which widget the Head Excursion popup was opened from
*/
function head_excursion_on_popup() {
gui.head_excursion_popup_widget = this;
}
/**
* Updates the Head Excursion widgets
*/
function update_head_excursion_widgets() {
/* Update the values in the widgets */
gui.head_excursion_widgets.txt_cut_section_thickness.text = gui.head_excursion_cut_section_thickness;
gui.head_excursion_widgets.txt_cut_section_node.text = gui.head_excursion_cut_section_node;
gui.head_excursion_widgets.txt_head_parts.text = gui.head_excursion_head_parts.join(" ");
gui.head_excursion_widgets.txt_barrier_parts.text = gui.head_excursion_barrier_parts.join(" ");
gui.head_excursion_widgets.txt_shift_deform_node_1.text = gui.head_excursion_shift_deform_node_1;
gui.head_excursion_widgets.txt_shift_deform_node_2.text = gui.head_excursion_shift_deform_node_2;
gui.head_excursion_widgets.txt_shift_deform_node_3.text = gui.head_excursion_shift_deform_node_3;
gui.head_excursion_widgets.txt_seat_centre_y.text = gui.head_excursion_seat_centre_y;
gui.head_excursion_widgets.txt_intrusion_from_seat_centre_y.text = gui.head_excursion_intrusion_from_seat_centre_y;
gui.head_excursion_widgets.txt_keyword_file.text = gui.head_excursion_keyword_file;
/* Update the widgets to indicate if the data is valid or not */
let nodes = [
{ id: gui.head_excursion_cut_section_node, widget: gui.head_excursion_widgets.txt_cut_section_node },
{ id: gui.head_excursion_shift_deform_node_1, widget: gui.head_excursion_widgets.txt_shift_deform_node_1 },
{ id: gui.head_excursion_shift_deform_node_2, widget: gui.head_excursion_widgets.txt_shift_deform_node_2 },
{ id: gui.head_excursion_shift_deform_node_3, widget: gui.head_excursion_widgets.txt_shift_deform_node_3 }
];
for (let node of nodes) {
if (is_entity_id_valid(node.id, BaseEntity.NODE)) {
node.widget.category = Widget.CATEGORY_TEXT_BOX;
} else {
node.widget.category = Widget.CATEGORY_WARNING_ACTION;
}
}
let parts = [
{ ids: gui.head_excursion_head_parts, widget: gui.head_excursion_widgets.txt_head_parts },
{ ids: gui.head_excursion_barrier_parts, widget: gui.head_excursion_widgets.txt_barrier_parts }
];
for (let part of parts) {
let valid = true;
for (let id of part.ids) {
if (!is_entity_id_valid(id, BaseEntity.PART)) {
valid = false;
break;
}
}
if (valid) {
part.widget.category = Widget.CATEGORY_TEXT_BOX;
} else {
part.widget.category = Widget.CATEGORY_WARNING_ACTION;
}
}
for (let wi of gui.head_excursion_widgets.cbx_vehicle_direction.WidgetItems()) {
if (gui.head_excursion_vehicle_direction == wi.text) {
wi.selected = true;
}
}
for (let wi of gui.head_excursion_widgets.cbx_countermeasure.WidgetItems()) {
if (gui.head_excursion_countermeasure == wi.text) {
wi.selected = true;
}
}
gui.wdw_structure.Redraw();
}
/**
* Creates the widgets in the occupant window
*/
function create_occupant_widgets() {
/* Get list of possible options for comboxes in the occupant window and add widget items */
let names = WorkflowOccupant.Versions();
// prepend "all" to the array of each filter so that it is the default
let ALL = ["all"];
let suppliers = ALL.concat(WorkflowOccupant.Suppliers());
let products = ALL.concat(WorkflowOccupant.Products());
let physiologies = ALL.concat(WorkflowOccupant.Physiologies());
let positions = WorkflowOccupant.Positions();
update_occupant_names_combobox(names);
for (let i = 0; i < suppliers.length; i++) new WidgetItem(gui.wdw_occupant.cbx_occupant_supplier, suppliers[i]);
for (let i = 0; i < products.length; i++) new WidgetItem(gui.wdw_occupant.cbx_occupant_product, products[i]);
for (let i = 0; i < physiologies.length; i++)
new WidgetItem(gui.wdw_occupant.cbx_occupant_physiology, physiologies[i]);
for (let i = 0; i < positions.length; i++)
new WidgetItem(gui.wdw_occupant.cbx_occupant_position, positions[i].toString());
/* Create widgets for selecting entity IDs */
gui.occupant_widgets = [];
let max_bottom = 1;
for (let name of names) {
let top = gui.wdw_occupant.btn_use_id_num.bottom + 1;
let bottom = top + 6;
/* create a dummy occupant - defaults to Driver
TODO - I think this can actually be changed to just use an occupant retrieved with
let occupant = OccupantVersion.GetFromName(name);
*/
let occupant = WorkflowOccupant.CreateWorkflowOccupantFromOccupant(name, WorkflowOccupant.DRIVER);
/** @type {BodyPartWidgets[]} */
let body_part_widgets = [];
for (let occupant_body_part of occupant.body_parts) {
/* No entities for this body part */
if (occupant_body_part.entities.length == 0) continue;
/* Body part header */
let body_part_label = new Widget(
gui.wdw_occupant,
Widget.LABEL,
1,
147,
top,
bottom,
occupant_body_part.component_type.toUpperCase()
);
body_part_label.category = Widget.CATEGORY_TITLE;
top = bottom + 1;
bottom = top + 6;
/* Entity widgets */
/** @type {OccupantEntityWidgets[]} */
let entity_widgets = [];
for (let occupant_entity of occupant_body_part.entities) {
let label = new Widget(gui.wdw_occupant, Widget.LABEL, 1, 82, top, bottom, occupant_entity.name);
label.justify = Widget.LEFT;
let textbox = new Widget(
gui.wdw_occupant,
Widget.TEXTBOX,
83,
147,
top,
bottom,
occupant_entity.id.toString()
);
textbox.popupWindow = gui.popup_select_entities;
textbox.popupDirection = Widget.RIGHT;
textbox.onPopup = occupant_entity_on_popup;
textbox.onChange = update_occupant_window;
/* Tag and entity type used in popup from textbox for selecting the entity */
// @ts-ignore
textbox.entity_tag = occupant_entity.tag;
// @ts-ignore
textbox.entity_type = occupant_entity.entity_type;
top = bottom + 1;
bottom = top + 6;
entity_widgets.push(
new OccupantEntityWidgets(occupant_entity.entity_type, label, textbox, occupant_entity.tag)
);
}
body_part_widgets.push(new BodyPartWidgets(body_part_label, entity_widgets));
}
let occupant_widgets = new OccupantWidgets(name, body_part_widgets);
gui.occupant_widgets.push(occupant_widgets);
max_bottom = Math.max(max_bottom, bottom);
}
/* Create a label widget at the bottom of the window so it maps to the correct size to fit all the entity widgets on */
gui.wdw_occupant.lbl_occupant_bottom = new Widget(
gui.wdw_occupant,
Widget.LABEL,
1,
147,
max_bottom,
max_bottom + 1,
""
);
}
/**
* Reads user data from the selected model and stores it on the gui object
* and populates the widgets with the data
@returns {boolean} successfully read data or not
*/
function read_model_data() {
let success = false;
/* Number of models loaded in PRIMER that have data for this workflow */
let num_models = Workflow.NumberOfModels();
if (num_models == 0) {
//this is not a warning as it is expected that there will be no user data before the user has defined it
Message("No Workflow data found for any of the models");
return false;
}
/* If the model selected by the user has data read it in */
for (let i = 0; i < num_models; i++) {
let model_id = Workflow.ModelIdFromIndex(i);
if (model_id != gui.model.number) continue;
/* Get the user data */
/** @type {UserData} */
// @ts-ignore
let user_data = Workflow.ModelUserDataFromIndex(i, "Automotive Assessments");
/* Put in try-catch in case any of the saved data is invalid */
try {
/*Set the drive side from user data */
set_vehicle_drive_side(user_data.drive_side);
/* Get the crash test type */
success = set_selected_widget_item(gui.wdw_pre_automotive.cbx_protocol_test, user_data.crash_test);
if (!success) {
WarningMessage(`Invalid Workflow data (crash test:"${user_data.crash_test}") for model \
${i} so it will be ignored and the gui will default to undefined`);
return false;
}
/* update the regulation combobox so that the selection can be set (otherwise it may not exist in the list)*/
update_regulations();
/* Get the regulations */
success = set_selected_widget_item(
gui.wdw_pre_automotive.cbx_protocol_regulation,
user_data.regulations[0]
);
if (!success) {
WarningMessage(`Invalid Workflow data (regulation:"${user_data.regulations[0]}") for model \
${i} so it will be ignored and the gui will default to undefined`);
return false;
}
/* update the version comboboxe so that the selection can be set (otherwise it may not exist in the list)*/
update_versions();
/* Set the version */
success = set_selected_widget_item(gui.wdw_pre_automotive.cbx_protocol_version, user_data.version);
if (!success) {
WarningMessage(
`Invalid Workflow data (version:"${user_data.version}") for model` +
` ${i} so it will be ignored and the gui will default to undefined`
);
return false;
}
/* Get the occupants */
let occupants = user_data.occupants;
if (occupants) {
for (let occupant of occupants) {
let o = WorkflowOccupant.CreateWorkflowOccupantFromOccupant(occupant.name, occupant.position);
gui.occupants.push(o);
/* Set the entity ids */
for (let body_part of occupant.body_parts) {
for (let entity of body_part.entities) {
let oe = o.GetEntityByTag(entity.tag);
if (oe && entity.id) {
oe.id = entity.id;
}
}
}
}
}
/* Get the structures */
let structures = user_data.structures;
if (structures) {
for (let user_data_structure of user_data.structures) {
let structure = get_structure_from_type(user_data_structure.component_type);
if (structure) {
/* Set the entity ids */
for (let entity of user_data_structure.entities) {
let se = structure.GetEntityByTag(entity.tag);
if (se && entity.id) {
se.id = entity.id;
}
}
} else {
WarningMessage(
`user_data contains ${user_data_structure.component_type} structure which is invalid and not supported.`
);
}
}
}
/* Get the B-Pillar data */
/** @type {BPillarStructure} */
let b_pillar = user_data.b_pillar;
if (b_pillar) {
gui.b_pillar_cut_section_method = b_pillar.cut_section_method;
gui.b_pillar_cut_section_node_1 = b_pillar.cut_section_nodes[0];
gui.b_pillar_cut_section_node_2 = b_pillar.cut_section_nodes[1];
gui.b_pillar_cut_section_node_3 = b_pillar.cut_section_nodes[2];
gui.b_pillar_pre_crash_parts = b_pillar.pre_crash_parts;
gui.b_pillar_post_crash_parts = b_pillar.post_crash_parts;
gui.b_pillar_shift_deform_node_1 = b_pillar.shift_deform_nodes[0];
gui.b_pillar_shift_deform_node_2 = b_pillar.shift_deform_nodes[1];
gui.b_pillar_shift_deform_node_3 = b_pillar.shift_deform_nodes[2];
gui.b_pillar_ground_z = b_pillar.ground_z;
gui.b_pillar_seat_centre_y = b_pillar.seat_centre_y;
gui.b_pillar_h_point_z = b_pillar.h_point_z;
}
/* Get the Head Excursion data */
/** @type {HeadExcursionStructure} */
let head_excursion = user_data.head_excursion;
if (head_excursion) {
gui.head_excursion_cut_section_thickness = head_excursion.cut_section_thickness;
gui.head_excursion_cut_section_node = head_excursion.cut_section_node;
gui.head_excursion_vehicle_direction = head_excursion.vehicle_direction;
gui.head_excursion_head_parts = head_excursion.head_parts;
gui.head_excursion_barrier_parts = head_excursion.barrier_parts;
gui.head_excursion_shift_deform_node_1 = head_excursion.shift_deform_nodes[0];
gui.head_excursion_shift_deform_node_2 = head_excursion.shift_deform_nodes[1];
gui.head_excursion_shift_deform_node_3 = head_excursion.shift_deform_nodes[2];
gui.head_excursion_seat_centre_y = head_excursion.seat_centre_y;
gui.head_excursion_intrusion_from_seat_centre_y = head_excursion.intrusion_from_seat_centre_y;
gui.head_excursion_countermeasure = head_excursion.countermeasure;
gui.head_excursion_keyword_file = head_excursion.keyword_file;
}
/* Get the model unit system and set the combobox selected item */
let unit_system = Workflow.ModelUnitSystemFromIndex(i, "Automotive Assessments");
gui.wdw_pre_automotive.cbx_unit_system.SetSelectedUnitSystem(unit_system);
} catch (e) {
ErrorMessage(`Error reading saved data: ${e}`);
return false;
}
/* No need to check any other models */
return true;
}
}
/**
* get the currently selected crash test protocol
* @returns {string}
*/
function get_selected_crash_test_protocol() {
return gui.wdw_pre_automotive.cbx_protocol_test.text;
}
/**
* get the currently selected regulation
* @returns {string}
*/
function get_selected_regulation() {
return gui.wdw_pre_automotive.cbx_protocol_regulation.text;
}
/**
* get the currently selected version
* @returns {string}
*/
function get_selected_version() {
return gui.wdw_pre_automotive.cbx_protocol_version.text;
}
/**
* Get the user data object
* @returns {?UserData} User data object
*/
function get_user_data() {
let regulation = get_selected_regulation();
/* Calculate the rib irtracc lengths */
for (let occupant of gui.occupants) {
/* Chest */
occupant.upper_rib_irtracc_length = calculate_irtracc_length(
occupant,
OccupantEntity.CHEST_UPPER_RIB_SPRING_TRANS
);
occupant.mid_rib_irtracc_length = calculate_irtracc_length(
occupant,
OccupantEntity.CHEST_MIDDLE_RIB_SPRING_TRANS
);
occupant.bottom_rib_irtracc_length = calculate_irtracc_length(
occupant,
OccupantEntity.CHEST_BOTTOM_RIB_SPRING_TRANS
);
/* Abdomen */
occupant.upper_abdomen_irtracc_length = calculate_irtracc_length(
occupant,
OccupantEntity.ABDOMEN_UPPER_SPRING_TRANS
);
occupant.bottom_abdomen_irtracc_length = calculate_irtracc_length(
occupant,
OccupantEntity.ABDOMEN_LOWER_SPRING_TRANS
);
}
/** @type {BPillarStructure} */
let b_pillar = {
cut_section_method: gui.b_pillar_cut_section_method,
cut_section_nodes: [
gui.b_pillar_cut_section_node_1,
gui.b_pillar_cut_section_node_2,
gui.b_pillar_cut_section_node_3
],
pre_crash_parts: gui.b_pillar_pre_crash_parts,
post_crash_parts: gui.b_pillar_post_crash_parts,
shift_deform_nodes: [
gui.b_pillar_shift_deform_node_1,
gui.b_pillar_shift_deform_node_2,
gui.b_pillar_shift_deform_node_3
],
ground_z: gui.b_pillar_ground_z,
seat_centre_y: gui.b_pillar_seat_centre_y,
h_point_z: gui.b_pillar_h_point_z
};
/** @type {HeadExcursionStructure} */
let head_excursion = {
cut_section_thickness: gui.head_excursion_cut_section_thickness,
cut_section_node: gui.head_excursion_cut_section_node,
vehicle_direction: gui.head_excursion_vehicle_direction,
head_parts: gui.head_excursion_head_parts,
barrier_parts: gui.head_excursion_barrier_parts,
shift_deform_nodes: [
gui.head_excursion_shift_deform_node_1,
gui.head_excursion_shift_deform_node_2,
gui.head_excursion_shift_deform_node_3
],
seat_centre_y: gui.head_excursion_seat_centre_y,
intrusion_from_seat_centre_y: gui.head_excursion_intrusion_from_seat_centre_y,
countermeasure: gui.head_excursion_countermeasure,
keyword_file: gui.head_excursion_keyword_file
};
return {
regulations: [regulation],
crash_test: get_selected_crash_test_protocol(),
version: get_selected_version(),
drive_side: gui.drive_side,
occupants: gui.occupants,
structures: get_defined_structures(),
b_pillar: b_pillar,
head_excursion: head_excursion
};
}
/**
* returns only the structures which are defined and supported by the current protocol
* they are then written out as user_data
* @returns {Structure[]}
*/
function get_defined_structures() {
/* Structures required for this crash test/regulation/version combination
* are stored in a list on the vehicle structures property */
let structures = GetProtocolVehicle().structures;
let valid_structures = [];
for (let s of gui.structures) {
if (are_structure_entity_ids_valid(s)) {
// @ts-ignore
if (structures.includes(s.component_type)) valid_structures.push(s);
}
}
return valid_structures;
}
/**
* Calculate the irtracc length for a given occupant and rib spring
* If it doesn't exist in the occupant, returns 0.0
* @param {WorkflowOccupant} occupant Occupant
* @param {string} entity_tag Rib spring tag, e.g. OccupantEntity.CHEST_UPPER_RIB_SPRING_TRANS
* @returns {number}
* @example
* let length = calculate_irtracc_length(occupant, OccupantEntity.CHEST_UPPER_RIB_SPRING_TRANS);
*/
function calculate_irtracc_length(occupant, entity_tag) {
let irtracc_trans = occupant.GetEntityByTag(entity_tag);
/* Enitity not defined/not in occupant, so just set length to zero */
if (!irtracc_trans) return 0.0;
/* If the ID is a database history string, convert it to a number
* so it can be used in the GetFromID function */
let id = -1;
if (typeof irtracc_trans.id == "string") {
let history = History.First(gui.model, History.DISCRETE);
while (history) {
if (history.heading == irtracc_trans.id) {
id = history.id;
break;
}
history = history.Next();
}
} else {
id = irtracc_trans.id;
}
if (id == -1) {
ErrorMessage(`Could not find numerical ID for ${irtracc_trans.name}. Setting IR-TRACC length to 0.0.`);
return 0.0;
}
/* Calculate length */
let el = Discrete.GetFromID(gui.model, id);
/* If it's not in the model, return 0.0 */
if (!el) {
return 0.0;
}
let n1 = Node.GetFromID(gui.model, el.n1);
let n2 = Node.GetFromID(gui.model, el.n2);
let x1 = n1.x;
let x2 = n2.x;
let y1 = n1.y;
let y2 = n2.y;
let z1 = n1.z;
let z2 = n2.z;
let dx = x2 - x1;
let dy = y2 - y1;
let dz = z2 - z1;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* Get the extra data object
* @returns {WriteToFileArgument_extra} extra data object
*/
function get_extra_data() {
return {
model_unit_system: gui.wdw_pre_automotive.cbx_unit_system.GetSelectedUnitSystem()
};
}
/**
* Add buttons to the 'popup_select_entities' popup for selecting database
* history nodes/beams/discretes and database cross sections directly, rather
* than having to pick/select them in the graphics window
*/
function add_buttons_to_select_entities_popup() {
/* Create buttons for each node/beam/discrete database history item in the
* model, storing them on the gui object so they can be (un)mapped as
* appropriate depending on the entity type that the popup is being mapped for. */
gui.database_buttons = [];
let dy = 6;
let y1 = gui.popup_select_entities.lbl_database_history.bottom;
let y2 = y1 + dy;
let x1 = 1;
let x2 = 81;
let history = History.First(gui.model);
while (history) {
switch (history.type) {
case History.NODE:
case History.BEAM:
case History.DISCRETE:
let history_label = "";
if (history.heading == "") {
history_label = history.id.toString();
} else {
history_label = history.heading;
}
let button = new Widget(gui.popup_select_entities, Widget.BUTTON, x1, x2, y1, y2, history_label);
button.justify = Widget.LEFT;
button.onClick = select_database_item;
/* Store the entity type and label on the widget object to make it easy
* to (un)map the correct buttons depending on what entity type is being selected
* and when processing the button click */
if (history.type == History.NODE) {
// @ts-ignore
button.entity_type = BaseEntity.NODE;
} else if (history.type == History.BEAM) {
// @ts-ignore
button.entity_type = BaseEntity.BEAM_BASIC;
} else if (history.type == History.DISCRETE) {
// @ts-ignore
button.entity_type = BaseEntity.SPRING_TRANSLATIONAL;
}
// @ts-ignore
button.entity_label = history_label;
/* Store the button widget so we can loop over them in the update function */
gui.database_buttons.push(button);
break;
}
history = history.Next();
}
/* Database cross sections */
let xsec = CrossSection.First(gui.model);
while (xsec) {
let xsec_label = "";
if (xsec.heading == "") {
xsec_label = xsec.label.toString();
} else {
xsec_label = xsec.heading;
}
let button = new Widget(gui.popup_select_entities, Widget.BUTTON, x1, x2, y1, y2, xsec_label);
button.justify = Widget.LEFT;
button.onClick = select_database_item;
/* Store the history type and label on the widget object to make it easy
* to (un)map the correct buttons depending on what entity type is being selected
* and when processing the button click */
// @ts-ignore
button.entity_type = BaseEntity.XSECTION;
// @ts-ignore
button.entity_label = xsec_label;
/* Store the button widget so we can loop over them in the update function */
gui.database_buttons.push(button);
xsec = xsec.Next();
}
/* Sort into alphabetical then numerical order */
gui.database_buttons.sort(function (a, b) {
// Sort array of buttons into alphabetical then numerical order
var ai = parseInt(a.text);
var bi = parseInt(b.text);
/* Both text - case insensitive */
if (isNaN(ai) && isNaN(bi)) {
if (a.text.toLowerCase() > b.text.toLowerCase()) {
return 1;
} else if (a.text.toLowerCase() < b.text.toLowerCase()) {
return -1;
} else {
return 0;
}
/* Both numbers */
} else if (!isNaN(ai) && !isNaN(bi)) {
return ai - bi;
/* One text, one number -> text should come first */
} else {
if (isNaN(ai)) {
return -1;
} else {
return 1;
}
}
});
}
/* Callback functions */
/**
* Open the occupant window to add a new occupant
*/
function add_new_occupant() {
gui.current_occupant = null;
update_occupant_window();
update_entity_ids();
gui.wdw_occupant.Show(false);
}
/**
* Open the occupant window to edit the current occupant
*/
function edit_occupant() {
if (!gui.current_occupant) return;
initialise_occupant_window();
gui.wdw_occupant.Show(false);
}
/**
* update the ids in the gui to use the numbers
*/
function update_entity_ids() {
let use_db_history = false;
if (!gui.wdw_occupant.btn_use_dbhistitle.active) use_db_history = true;
let occupant_name = gui.wdw_occupant.cbx_occupant_name.selectedItem.text;
let occupant_widgets = get_occupant_widgets(occupant_name);
let occupant = OccupantVersion.GetFromName(occupant_name);
//var workflow_occupant = gui.current_occupant;
/* Find the the entity widgets with the same tag and set the text to the ID + the offset */
for (let body_part_widgets of occupant_widgets.body_part_widgets) {
for (let entity_widgets of body_part_widgets.entity_widgets) {
let entity = occupant.GetEntityByTag(entity_widgets.tag);
if (use_db_history) {
if (entity.history_title != "") {
entity_widgets.textbox.text = entity.history_title;
} else if (entity.id) {
entity_widgets.textbox.text = entity.id + gui.offset;
}
} else {
if (entity.id) {
entity_widgets.textbox.text = entity.id + gui.offset;
}
}
}
}
// update_occupant_window();
}
/**
* callback to trigger changes crash test changes
*/
function crash_test_changed() {
update_regulations();
update_versions();
update_main_window(); //this also calls update_occupants_column()
}
/**
* callback to trigger changes when regulation changes
*/
function regulation_changed() {
update_versions();
update_main_window(); //this also calls update_occupants_column()
}
/**
* callback to trigger changes when version changes
*/
function version_changed() {
update_main_window(); //this also calls update_occupants_column()
}
/**
* update regulation combobox based on current crash test selection - try to keep regulation the same if it exists in new list
*/
function update_regulations() {
Message(`update_regulations`);
let crash_test = get_selected_crash_test_protocol();
let current_regulation = get_selected_regulation();
let protocols = Protocols.GetOnly("ALL", crash_test, "ALL");
let cbx = gui.wdw_pre_automotive.cbx_protocol_regulation;
/* clears all versions in the combobox and adds the versions for the new regulation */
cbx.RemoveAllWidgetItems();
let regulations = [];
for (let protocol of protocols) {
if (regulations.indexOf(protocol.regulation) == -1) regulations.push(protocol.regulation);
}
/* sort regulation strings alphabetically and select the same one as is currently selected if possible */
for (let regulation of regulations.sort()) {
let wi = new WidgetItem(cbx, regulation.toString());
if (regulation == current_regulation) wi.selected = true;
}
}
/**
* update version combobox based on current crash test selection - try to keep version the same if it exists in new list
*/
function update_versions() {
Message(`update_versions`);
let crash_test = get_selected_crash_test_protocol();
let regulation = get_selected_regulation();
let current_version = get_selected_version();
let cbx = gui.wdw_pre_automotive.cbx_protocol_version;
/* clears all versions in the combobox and adds the versions for the new regulation */
cbx.RemoveAllWidgetItems();
let versions = Protocol.Versions(regulation, crash_test);
for (let version of versions) {
let wi = new WidgetItem(cbx, version);
if (version == current_version) wi.selected = true;
}
}
/**
* callback to toggle between using id numbers and database history titles (if they exisit in JSON or can be extracted from model using IDs)
*/
function update_and_toggle_ids_between_num_and_dbhistitle() {
//make sure to toggle buttons before updating so that the correct state is used
gui.wdw_occupant.btn_use_dbhistitle.active = !gui.wdw_occupant.btn_use_dbhistitle.active;
gui.wdw_occupant.btn_use_id_num.active = !gui.wdw_occupant.btn_use_id_num.active;
update_entity_ids();
}
/**
* callback to toggle between using id numbers and database history titles (if they exisit in JSON or can be extracted from model using IDs)
*/
function update_vehicle_hand_drive() {
//make sure to toggle buttons before updating so that the correct state is used
Message(`Hand drive = ${this.text}`);
gui.drive_side = this.text;
/**
* we need to update the vehicle image because the drive side could have changed
*/
update_vehicle_image();
update_occupants_column();
}
/**
* function to update vehicle gui when regulation/crash_test/version change
*/
function update_occupants_column() {
Message("update_occupants_column");
update_vehicle_occupants(GetProtocolVehicle(), gui.drive_side);
}
/**
* When the offset is changed in the textbox, check it's an integer
* If it's not, then reset it to the original value
*/
function offset_changed() {
//if empty string then set offset to 0
if (gui.wdw_occupant.txt_entity_offset.text == "") gui.offset = 0;
let offset = parseInt(gui.wdw_occupant.txt_entity_offset.text);
if (!isNaN(offset)) {
gui.offset = offset;
}
gui.wdw_occupant.txt_entity_offset.text = gui.offset;
update_entity_ids();
}
/**
* Open the structure window to edit the current structure (that user clicked)
*/
function edit_structure() {
initialise_structure_window(this.structure_type);
gui.wdw_structure.Show(false);
}
/**
* Saves the selected data to a workflow JSON file
*/
function save_occupant_to_file() {
/* Get data to write to file */
let occupant = GetOccupantFromGUI();
if (occupant) {
update_occupant_entity_ids_from_gui(occupant);
// if (occupant.version) var current_version = occupant.version;
let match = occupant.name.match(/(.* v)(.*)/i);
if (match != null) {
var root_name = match[1]; //first capture group
var current_version = match[2]; //first capture group
} else {
Message(`failed to extract version from ${occupant.name}`);
return;
}
let version = Window.GetString(
"Occupant Version",
`Input the occupant version for:\n${occupant.name}`,
current_version
);
if (!version) return;
if (Unix()) {
var occupants_directory = `${GetInstallDirectory()}/workflows/scripts/automotive_assessments/occupants`;
var sub_folders = ["", `/${occupant.supplier}`, `/${occupant.product}`, `/${occupant.physiology}`];
var slash = "/";
} else {
var occupants_directory = `${GetInstallDirectory()}\\workflows\\scripts\\automotive_assessments\\occupants`;
var sub_folders = ["", `\\${occupant.supplier}`, `\\${occupant.product}`, `\\${occupant.physiology}`];
var slash = "\\";
}
//check each subfolder exists and make it if it does not.
for (let sub_folder of sub_folders) {
occupants_directory += sub_folder;
if (!File.Exists(occupants_directory)) {
//make it if it doesn't exist
if (File.Mkdir(occupants_directory)) {
Message(`Successfully made ${occupants_directory} directory.`);
} else {
Message(`Failed to make ${occupants_directory} directory.`);
return;
}
}
}
occupants_directory += slash;
let output_filename = `${occupants_directory}${root_name}${version}.json`;
if (!output_filename) return;
if (File.Exists(output_filename)) {
var answer = Window.Warning(
"Warning",
`${output_filename} already exists. Are you sure you want to overwrite it?`,
Window.YES | Window.CANCEL
);
if (answer == Window.CANCEL) return;
Message(`Overwritting ${output_filename} with new data`);
}
//construct an Occupant so that we can call toJSON to write it out in a consistent format
let occupant_json = new Occupant(
occupant.supplier,
occupant.product,
occupant.physiology,
version,
occupant.body_parts,
occupant.GetChestRotationFactors()
); //make a copy of occupant
/* Write occupant json file */
let f = new File(output_filename, File.WRITE);
let json = occupant_json.toJSON();
for (let p of json.body_parts) {
for (let e of p.entities) {
//try and add a history title if it can be found and is not blank
if (!e.history_title) {
let history_title = BaseEntity.GetHistoryTitleForId(e.entity_type, e.id);
if (history_title != "") {
e.history_title = history_title;
}
}
}
}
f.Write(JSON.stringify(json, null, 4)); //4 spaces
f.Close();
Message("Written occupant to " + output_filename);
}
}
/**
* Saves the selected data to a workflow JSON file
*/
function save_to_file() {
/* Get data to write to file */
let user_data = get_user_data();
//do not save the workflow if some required user data is missing
if (!user_data) return;
let extra = get_extra_data();
/* Ask the user where to write it */
var output_filename = Window.GetFile(".json", true);
if (output_filename == null) return;
/* API call to write workflow file */
Workflow.WriteToFile(user_data, output_filename, workflow_definition_filename, extra);
Message("Written workflow file to " + output_filename);
}
/**
* Saves the selected data to the keyword file
*/
function save_to_model() {
/* Get data to write to file */
let user_data = get_user_data();
//do not save the workflow if some required user data is missing
if (!user_data) return;
let extra = get_extra_data();
/* Ask the user which model to write it to */
var model = Model.Select("Select the model to write to");
if (model == null) return;
/* API call to write workflow to a model */
Workflow.WriteToModel(user_data, model, workflow_definition_filename, extra);
Message(
"Workflow data added to post *END data. You need to write out the model from the main Model->Write menu to save the additions."
);
}
/**
* onclick callback for add and edit buttons
*/
function add_edit_occupant() {
set_up_occupant_window(this.side, this.front_rear);
}
/**
* Delete the associated occupant
*/
function delete_occupant() {
let position = get_seat_position(this.side, this.front_rear, gui.drive_side);
for (let i = 0; i < gui.occupants.length; i++) {
if (gui.occupants[i].position == position) {
gui.occupants.splice(i, 1);
gui.current_occupant = null;
Message(`Deleted ${this.front_rear} ${this.side} occupant (${position})`);
break;
}
}
update_occupants_column();
}
/**
* Delete all the occupants
*/
function delete_all_occupants() {
for (let occupant of gui.occupants) {
Message(`Deleted ${occupant.position}`);
}
gui.occupants = [];
gui.current_occupant = null;
Message(`Deleted all occupants`);
update_occupants_column();
}
/**
* Flip all the occupant seats right/left all the occupants and correctly set driver
*/
function flip_occupants() {
for (let occupant of gui.occupants) {
switch (occupant.position) {
case WorkflowOccupant.DRIVER:
occupant.position = WorkflowOccupant.FRONT_PASSENGER;
break;
case WorkflowOccupant.FRONT_PASSENGER:
occupant.position = WorkflowOccupant.DRIVER;
break;
case WorkflowOccupant.REAR_DRIVER_SIDE:
occupant.position = WorkflowOccupant.REAR_PASSENGER_SIDE;
break;
case WorkflowOccupant.REAR_PASSENGER_SIDE:
occupant.position = WorkflowOccupant.REAR_DRIVER_SIDE;
break;
}
}
Message(`swapped occupant seats (right/left)`);
update_occupants_column();
}
/**
* Close the occupant window
*/
function close_occupant_window() {
gui.wdw_occupant.Hide();
}
/**
* Update the current occupant with the data selected by the user
* and then close the occupant window
*/
function update_current_occupant_and_close() {
let updated = update_current_occupant();
if (updated) {
gui.wdw_occupant.Hide();
update_occupants_column();
}
}
/**
* Update the current occupant with the data selected by the user
* Returns true if the occupant was updated, false otherwise
* @return {boolean}
*/
function update_current_occupant() {
let wdw = gui.wdw_occupant;
/* A null current occupant means a new one is being added.
* Create a new occupant with the selected values. */
if (gui.current_occupant == null) {
/* If an occupant in this position/side/front_rear already exists ask the user if
* they want to overwrite it and if they say yes set the current occupant to it.
*
* If it doesn't already exist create a new one and add it to the gui.occupants list */
/** @type {?WorkflowOccupant} */
let existing_occupant = null;
for (let occupant of gui.occupants) {
if (occupant.position == wdw.cbx_occupant_position.selectedItem.text) {
existing_occupant = occupant;
break;
}
}
if (existing_occupant) {
let answer = Window.Message(
"Occupant already defined",
`'${existing_occupant.toString()}' occupant already exists. Update with new values?`,
Window.YES | Window.NO
);
if (answer == Window.NO) {
return false;
} else {
gui.current_occupant = existing_occupant;
}
} else {
gui.current_occupant = GetOccupantFromGUI();
gui.occupants.push(gui.current_occupant);
}
} else {
/* Update the current occupant with the selected values */
gui.current_occupant.SetOccupantFields = wdw.cbx_occupant_name.selectedItem.text;
gui.current_occupant.position = wdw.cbx_occupant_position.selectedItem.text;
}
/* Set the entity IDs */
update_occupant_entity_ids_from_gui(gui.current_occupant);
return true;
}
/**
* update the ids for the occupant
* @param {WorkflowOccupant|Occupant} occupant
*/
function update_occupant_entity_ids_from_gui(occupant) {
for (let body_part of occupant.body_parts) {
for (let entity of body_part.entities) {
let id = get_occupant_entity_id_from_widget_by_tag(occupant.name, entity.tag);
entity.id = id;
}
}
}
/**
* get occupant from the current gui inputs
* @returns {WorkflowOccupant}
*/
function GetOccupantFromGUI() {
let wdw = gui.wdw_occupant;
return WorkflowOccupant.CreateWorkflowOccupantFromOccupant(
wdw.cbx_occupant_name.selectedItem.text,
wdw.cbx_occupant_position.selectedItem.text
);
}
/**
* Close the structure window
*/
function close_structure_window() {
gui.wdw_structure.Hide();
}
/**
* Update the current structure with the data selected by the user
* and then close the structure window
*/
function update_all_structures_and_close() {
update_all_structure();
gui.wdw_structure.Hide();
update_structures_column();
}
/**
* Update the all the structureS with the data selected by the user (i.e. widget text fields)
*/
function update_all_structure() {
for (let structure of gui.structures) {
/* Set the entity IDs */
// Message(`Updating ${structure.component_type} in gui.structures`);
for (let entity of structure.entities) {
let id = get_structure_entity_id_from_widget_by_tag(structure.component_type, entity.tag);
entity.id = id;
}
}
}
/** reset the input fields for the current structure (to match those stored in corresponding structure in gui.structures) */
function reset_the_current_structure() {
let structure_type = gui.wdw_structure.cbx_structure.selectedItem.text;
reset_structure_widgets_entity_fields(structure_type);
update_structure_window();
}
/** clear/remove the input fields for the current structure and reset the values of the corresponding structure in gui.structures (i.e set to zero/undefined) */
function clear_the_current_structure() {
let wdw = gui.wdw_structure;
let structure_type = wdw.cbx_structure.selectedItem.text;
//we need to create overwrite the corresponding structure in gui.structures to set the values (back to 0)
for (let i = 0; i < gui.structures.length; i++) {
if (gui.structures[i].component_type == structure_type) {
gui.structures[i] = Structure.CreateStructure(structure_type);
continue;
}
}
/**
* set structure_widget text fields - this will be defaults as we have overwritten the structure in gui.structures above.
*/
reset_the_current_structure();
}
/**
* Store what the occupant entity pick is for so it can process it correctly
*/
function occupant_entity_on_popup() {
/* Store what the pick is for so it can process it correctly */
gui.current_entity_popup = "occupant";
/* Current entity tag and type */
gui.current_entity_tag = this.entity_tag;
gui.current_entity_type = this.entity_type;
entity_on_popup(this.entity_type);
}
/**
* Store what the structure entity pick is for so it can process it correctly
*/
function structure_entity_on_popup() {
/* Store what the pick is for so it can process it correctly */
gui.current_entity_popup = "structure";
/* Current entity tag and type */
gui.current_entity_tag = this.entity_tag;
gui.current_entity_type = this.entity_type;
entity_on_popup(this.entity_type);
}
/**
* Set the currently selected entity when the entity popup is mapped from the textbox
* and map the appropriate database history items for the entity type
* @param {string} entity_type The entity type, e.g. BaseEntity.NODE
*/
function entity_on_popup(entity_type) {
/* Change the text on the label to say what the type is */
if (entity_type == BaseEntity.NODE) {
gui.popup_select_entities.lbl_database_history.text = "DATABASE HISTORY NODE";
} else if (entity_type == BaseEntity.BEAM_BASIC) {
gui.popup_select_entities.lbl_database_history.text = "DATABASE HISTORY BEAM";
} else if (entity_type == BaseEntity.SPRING_ROTATIONAL || entity_type == BaseEntity.SPRING_TRANSLATIONAL) {
gui.popup_select_entities.lbl_database_history.text = "DATABASE HISTORY DISCRETE";
} else if (entity_type == BaseEntity.XSECTION) {
gui.popup_select_entities.lbl_database_history.text = "DATABASE XSECTION";
}
/* Get location to start mapping the database history buttons */
let dy = 6;
let y1 = gui.popup_select_entities.lbl_database_history.bottom;
let y2 = y1 + dy;
/* (Un)map and position the appropriate database history buttons for this entity type */
for (let button of gui.database_buttons) {
if (
entity_type == button.entity_type ||
(entity_type == BaseEntity.SPRING_ROTATIONAL && button.entity_type == BaseEntity.SPRING_TRANSLATIONAL)
) {
button.top = y1;
button.bottom = y2;
button.Show();
y1 = y2;
y2 += dy;
} else {
button.Hide();
}
}
}
/**
* Pick an entity
*/
function pick_entity() {
let id = BaseEntity.Pick(gui.current_entity_type, gui.model);
if (id == null) return;
set_widget_entity_id_by_tag(gui.current_entity_popup, id);
}
/**
* Select an entity
*/
function select_entity() {
let id = BaseEntity.Select(gui.current_entity_type, gui.model);
if (id == null) return;
set_widget_entity_id_by_tag(gui.current_entity_popup, id);
}
/**
* Select a database item
*/
function select_database_item() {
let id = this.entity_label;
set_widget_entity_id_by_tag(gui.current_entity_popup, id);
}
/**
* Set the id on the structure or occupant entity widget by entity tag
* @param {string} type Either "occupant" or "structure"
* @param {string|number|number[]} id Enitity id
*/
function set_widget_entity_id_by_tag(type, id) {
if (type != "occupant" && type != "structure") {
ErrorMessage(`Invalid type '${type}' in set_widget_entity_id_by_tag`);
return;
}
/* Take the first id if the entity ids have been passed to this function as an array */
if (Array.isArray(id)) {
id = id[0];
}
/* Call the appropriate function to set the id on the entity widget */
if (type == "occupant") {
set_occupant_widget_entity_id_by_tag(
gui.wdw_occupant.cbx_occupant_name.selectedItem.text,
gui.current_entity_tag,
id
);
} else if (type == "structure") {
set_structure_widget_entity_id_by_tag(
gui.wdw_structure.cbx_structure.selectedItem.text,
gui.current_entity_tag,
id
);
}
}
/**
* Update the main window
* This updates the structures widget list based on the currently selected protocol
* The widget category/colours are set to latent if the structure is not (fully) defined (i.e. valid).
* In REPORTER mode only the required structures are latent if undefined. The other structures appear as not required (as per occupant styling)
* It also calls an update to the vehicle GUI
*/
function update_main_window() {
update_structures_column();
update_occupants_column();
}
/**
* Update the structures column widgets on the main window
* This updates the structures widget list based on the currently selected protocol
* The widget category/colours are set to latent if the structure is not (fully) defined (i.e. valid).
* In REPORTER mode only the required structures are latent if undefined. The other structures appear as not required (as per occupant styling)
*/
function update_structures_column() {
Message("update_structures_column");
let wdw = gui.wdw_pre_automotive;
/**
* Recreate widget items in the structures column
* Only the structures which are supported by the current protocol are shown
* If they are not defined then colour them as latent
* If they are not required then colour as grey (this is only for REPORTER mode)
*
* Store the structure instance on the widgets to use when the edit/delete
* buttons are clicked. */
/**
* Remove all structures widgets before rebuilding them (also set current structure to null)
*/
for (let current_structure_widget of gui.structure_list_widgets) {
current_structure_widget.Delete();
}
gui.structure_list_widgets = [];
let protocol_structures = GetProtocolVehicle().structures;
update_structures_combobox(protocol_structures);
/**
* if the list height exceeds the max_coordinate_of_bottom then we add the widgets from the top again (but they are hidden)
* and only become active when the user presses a down/up arrow.
*/
let overflow_list = false;
let padding = 1;
let top_coord = wdw.structures_background.top + padding;
let widget_height = 6;
let max_coordinate_of_bottom = wdw.lbl_structures_pg.top - padding;
gui.number_of_structures_widgets_per_pg = Math.floor(
(max_coordinate_of_bottom - top_coord) / (widget_height + padding)
);
for (let structure of gui.structures) {
/**
* only add the widgets for this protocol's structures
*/
// @ts-ignore
if (!protocol_structures.includes(structure.component_type)) continue;
let wi = new Widget(
wdw,
Widget.LABEL,
wdw.structures_background.left + padding,
wdw.structures_background.right - padding,
top_coord,
top_coord + widget_height,
`<${structure.toString()}>`
);
// if (overflow_list)
wi.Hide();
top_coord += widget_height + padding;
if (top_coord + widget_height > max_coordinate_of_bottom) {
overflow_list = true;
/** reset top coord */
top_coord = wdw.structures_background.top + padding;
}
/* Store the structure on the widget item (to use when the edit or delete button are pressed) */
// @ts-ignore
wi.structure_type = structure.component_type;
/**
* set default colour - assumed to be latent
*/
wi.category = Widget.NO_CATEGORY;
wi.foreground = Widget.COLOUR_LATENT;
wi.background = Widget.COLOUR_NEUTRAL;
wi.onClick = edit_structure; /* Opens the edit pane for the structure when clicked */
if (are_structure_entity_ids_valid(structure)) {
wi.category = Widget.CATEGORY_SAFE_ACTION;
wi.text = structure.toString();
}
gui.structure_list_widgets.push(wi);
}
if (overflow_list) {
/**
* set overflow page text
*/
// gui.structures_page = 1;
wdw.lbl_structures_first.Show();
wdw.lbl_structures_previous.Show();
wdw.lbl_structures_next.Show();
wdw.lbl_structures_last.Show();
wdw.lbl_structures_pg.Show();
} else {
wdw.lbl_structures_first.Hide();
wdw.lbl_structures_previous.Hide();
wdw.lbl_structures_next.Hide();
wdw.lbl_structures_last.Hide();
wdw.lbl_structures_pg.Hide();
}
set_structures_page(gui.structures_page);
}
/**
* used to set the page (subset of the list) for the structures widgets
* @param {number} page
*/
function set_structures_page(page) {
let wdw = gui.wdw_pre_automotive;
let out_of_page = Math.ceil(gui.structure_list_widgets.length / gui.number_of_structures_widgets_per_pg);
//go to last page
if (page == -1 || page > out_of_page) page = out_of_page;
else if (page < 1) page = 1;
wdw.lbl_structures_pg.text = `${page}/${out_of_page}`;
let index = 1;
for (let wi of gui.structure_list_widgets) {
if (Math.ceil(index / gui.number_of_structures_widgets_per_pg) == page) {
wi.Show();
} else wi.Hide();
index++;
}
//set the structures page to the new page value one if needed to be updated
gui.structures_page = page;
wdw.Redraw();
}
/**
* onclick callback for switching structures page
*/
function structures_page_switchers_on_click() {
switch (this.text) {
case "<<":
gui.structures_page = 1;
break;
case "<":
gui.structures_page--;
break;
case ">":
gui.structures_page++;
break;
case ">>":
gui.structures_page = -1;
break;
default:
gui.structures_page = 1;
}
set_structures_page(gui.structures_page);
}
/**
* Initialise the occupant window with the data from the current occupant
*/
function initialise_occupant_window() {
let wdw = gui.wdw_occupant;
if (gui.current_occupant) {
/**we need to rebuild the name combo-box as it may only contain a filtered subset of name
* from previous selections so first empty the combobox then add just the current name.
* it doesn't matter that other name widget items are missing as the combo-box will be inactive.
*/
wdw.cbx_occupant_name.RemoveAllWidgetItems();
new WidgetItem(gui.wdw_occupant.cbx_occupant_name, gui.current_occupant.name);
set_selected_widget_item(wdw.cbx_occupant_name, gui.current_occupant.name);
set_selected_widget_item(wdw.cbx_occupant_position, gui.current_occupant.position);
/* Set the text on the entity id widgets */
let occupant_widgets = get_occupant_widgets(gui.wdw_occupant.cbx_occupant_name.selectedItem.text);
for (let body_part_widgets of occupant_widgets.body_part_widgets) {
for (let entity_widgets of body_part_widgets.entity_widgets) {
let entity = gui.current_occupant.GetEntityByTag(entity_widgets.tag);
if (entity) entity_widgets.textbox.text = entity.id;
}
}
}
update_occupant_window();
}
/**
* filter the name drop-down based on the selected supplier, model and physiology filters.
* All versions will be returned if none are selected already. i.e. their value is 'ALL'
* @returns {string[]} occupant name names
*/
function filter_version_drop_down() {
let wdw = gui.wdw_occupant;
let selected_supplier = wdw.cbx_occupant_supplier.text;
let selected_product = wdw.cbx_occupant_product.text;
let selected_physiology = wdw.cbx_occupant_physiology.text;
Message(`Selected supplier: ${selected_supplier}`);
let versions = OccupantVersion.GetOnly(selected_supplier, selected_product, selected_physiology);
if (versions.length == 0) {
let unsupported_str = "";
if (selected_supplier.toLowerCase() != "all") unsupported_str += selected_supplier + " ";
if (selected_product.toLowerCase() != "all") unsupported_str += selected_product + " ";
if (selected_physiology.toLowerCase() != "all") unsupported_str += selected_physiology + " ";
Window.Warning("Unsupported Occupant", `No ${unsupported_str} occupants supported so removing all filters.`);
//set filters to 'ALL'
wdw.cbx_occupant_supplier.ItemAt(0).selected = true;
wdw.cbx_occupant_product.ItemAt(0).selected = true;
wdw.cbx_occupant_physiology.ItemAt(0).selected = true;
//return all supported versions
return WorkflowOccupant.Versions();
}
return versions;
}
/**
* on change callback for supplier, model and physiology callbacks
* to filter the name drop-down to make it easier to pick a name from a long list
*/
function update_filters() {
/*update occupant_names based on current selection*/
let occupant_names = filter_version_drop_down();
if (occupant_names.length == 0) {
Message('No currently supported occupants match the selected filter. Filter defaulting to "all"');
set_selected_widget_item(this, "all");
occupant_names = filter_version_drop_down();
}
update_occupant_names_combobox(occupant_names);
update_occupant_window();
}
/**
* occupant_names is an array of strings of occupant names
*
* @param {string[]} occupant_names
*/
function update_occupant_names_combobox(occupant_names) {
gui.wdw_occupant.cbx_occupant_name.RemoveAllWidgetItems();
for (let i = 0; i < occupant_names.length; i++)
new WidgetItem(gui.wdw_occupant.cbx_occupant_name, occupant_names[i]);
}
/**
* on click callback for filters to grey out invalid options
*/
function disable_invalid_filter_options() {
//deactivate invalid filters (i.e those that will return no versions)
let wdw = gui.wdw_occupant;
let selected_supplier = wdw.cbx_occupant_supplier.text;
let selected_product = wdw.cbx_occupant_product.text;
let selected_physiology = wdw.cbx_occupant_physiology.text;
let filter;
/** the current drop down should not affect the filtered versions so assume it is 'all'*/
if (this == wdw.cbx_occupant_supplier) {
selected_supplier = "all";
filter = "suppliers";
} else if (this == wdw.cbx_occupant_product) {
selected_product = "all";
filter = "products";
} else if (this == wdw.cbx_occupant_physiology) {
selected_physiology = "all";
filter = "physiologies";
}
let versions = OccupantVersion.GetOnly(selected_supplier, selected_product, selected_physiology);
let filters = OccupantVersion.GetValidFilters(versions);
let filter_options = filters[filter];
for (let wi of this.WidgetItems()) {
if (wi.selected) continue;
//if not selected assume it is unselectable unless it is an option in filter_options
wi.selectable = false;
for (let option of filter_options) {
if (wi.text == option) {
wi.selectable = true;
break;
}
}
}
}
/**
* Update the occupant window
*/
function update_occupant_window() {
let wdw = gui.wdw_occupant;
Message(`update_occupant_window`);
/* get selected name name */
let name = wdw.cbx_occupant_name.selectedItem.text;
/* Populate the combobox widgets with data from the currently selected occupant */
if (gui.current_occupant) {
wdw.btn_update_occupant.text = "Update";
/* Grey out name combobox - can't change the occupant name once it's been set
* as the gui.current_occupant instance may not have the same body parts
* as the one it's being changed to, so would require deleting the current occupant
* and then recreating another one, which is a bit more complicated to manage. */
wdw.cbx_occupant_name.active = false;
Message(`Update! ${gui.current_occupant.supplier}`);
// update the values of the name meta data fields then make them inactive
let occupant = OccupantVersion.GetFromName(name);
set_selected_widget_item(wdw.cbx_occupant_supplier, occupant.supplier);
set_selected_widget_item(wdw.cbx_occupant_product, occupant.product);
set_selected_widget_item(wdw.cbx_occupant_physiology, occupant.physiology);
wdw.cbx_occupant_supplier.active = false;
wdw.cbx_occupant_product.active = false;
wdw.cbx_occupant_physiology.active = false;
} else {
wdw.btn_update_occupant.text = "Add";
wdw.cbx_occupant_name.active = true;
wdw.cbx_occupant_supplier.active = true;
wdw.cbx_occupant_product.active = true;
wdw.cbx_occupant_physiology.active = true;
}
/* Reposition and show the relevant entity widgets */
let top = wdw.btn_use_id_num.bottom + 1;
let bottom = top + 6;
for (let occupant_widgets of gui.occupant_widgets) {
if (occupant_widgets.name == wdw.cbx_occupant_name.selectedItem.text) {
occupant_widgets.Show();
for (let body_part_widgets of occupant_widgets.body_part_widgets) {
body_part_widgets.label.top = top;
body_part_widgets.label.bottom = bottom;
top = bottom + 1;
bottom = top + 6;
for (let entity_widgets of body_part_widgets.entity_widgets) {
entity_widgets.label.top = top;
entity_widgets.label.bottom = bottom;
entity_widgets.textbox.top = top;
entity_widgets.textbox.bottom = bottom;
/* Colour the textbox red if the entity ID is invalid */
if (is_entity_widget_id_valid(entity_widgets)) {
entity_widgets.textbox.category = Widget.CATEGORY_TEXT_BOX;
} else {
entity_widgets.textbox.category = Widget.CATEGORY_WARNING_ACTION;
}
top = bottom + 1;
bottom = top + 6;
}
}
} else {
/* Not relevant to this occupant name, so hide the widgets */
occupant_widgets.Hide();
}
}
wdw.Redraw();
}
/**
* Initialise the structure window with the data from the current structure
* @param {string} structure_type
*/
function initialise_structure_window(structure_type) {
let wdw = gui.wdw_structure;
set_selected_widget_item(wdw.cbx_structure, structure_type);
/**
* reset all the widget fields to match entities of Structures stored in gui.structures
*/
for (let wi of gui.wdw_structure.cbx_structure.WidgetItems()) {
reset_structure_widgets_entity_fields(wi.text);
}
update_structure_window();
}
/**
* this (re)sets the structure widget entity fields to be those stored on matching structure_type in gui.structures array
* @param {string} structure_type
*/
function reset_structure_widgets_entity_fields(structure_type) {
if (structure_type == Structure.B_PILLAR) {
update_b_pillar_widgets();
} else if (structure_type == Structure.HEAD_EXCURSION) {
update_head_excursion_widgets();
} else {
/* Set the text on the entity id widgets */
let structure_widgets = get_structure_widgets(structure_type);
let structure = get_structure_from_type(structure_type);
// Message(`Reset Structure = ${structure_type}`);
if (structure_widgets) {
for (let entity_widgets of structure_widgets.entity_widgets) {
let entity = structure.GetEntityByTag(entity_widgets.tag);
if (entity) entity_widgets.textbox.text = entity.id.toString();
else ErrorMessage(`entity not defined.`);
}
}
}
}
/**
* get structure from gui.structures
* @param {string} structure_type
* @returns {?Structure}
*/
function get_structure_from_type(structure_type) {
for (let structure of gui.structures) {
if (structure.component_type == structure_type) return structure;
}
ErrorMessage(`Could not find ${structure_type} in gui.structures.`);
}
/**
* Update the structure window - show widgets for current structure type and hide others
*/
function update_structure_window() {
let wdw = gui.wdw_structure;
/* Show the relevant entity widgets */
for (let structure_widgets of gui.structure_widgets) {
if (structure_widgets.structure_type == wdw.cbx_structure.selectedItem.text) {
structure_widgets.Show();
for (let entity_widgets of structure_widgets.entity_widgets) {
/* Colour the textbox red if the entity ID is invalid */
if (is_entity_widget_id_valid(entity_widgets)) {
entity_widgets.textbox.category = Widget.CATEGORY_TEXT_BOX;
} else {
entity_widgets.textbox.category = Widget.CATEGORY_WARNING_ACTION;
}
}
} else {
/* Not relevant to this structure type, so hide the widgets */
structure_widgets.Hide();
}
}
/* B-Pillar widgets */
for (let w in gui.b_pillar_widgets) {
if (wdw.cbx_structure.selectedItem.text == Structure.B_PILLAR) {
gui.b_pillar_widgets[w].Show();
} else {
gui.b_pillar_widgets[w].Hide();
}
}
/* Head Excursion widgets */
for (let w in gui.head_excursion_widgets) {
if (wdw.cbx_structure.selectedItem.text == Structure.HEAD_EXCURSION) {
gui.head_excursion_widgets[w].Show();
} else {
gui.head_excursion_widgets[w].Hide();
}
}
wdw.Redraw();
}
/**
* Returns whether the value in the textbox is a valid entity ID,
* i.e. does it exists in any of the models currently loaded
* @param {BaseEntityWidgets} entity_widgets
* @returns {boolean}
*/
function is_entity_widget_id_valid(entity_widgets) {
return is_entity_id_valid(entity_widgets.textbox.text, entity_widgets.entity_type);
}
/**
* Checks if an entity ID is valid (exists in the model)
* @param {string} entity_id Entity ID to check
* @param {string} entity_type Entity type
* @returns {boolean}
*/
function is_entity_id_valid(entity_id, entity_type) {
let id_number = parseInt(entity_id);
if (isNaN(id_number)) {
let history = History.First(gui.model);
let xsec = CrossSection.First(gui.model);
/* It's text, so check the Database history items or Databse cross sections */
switch (entity_type) {
case BaseEntity.NODE:
while (history) {
if (history.type == History.NODE && history.heading == entity_id) return true;
history = history.Next();
}
break;
case BaseEntity.BEAM_BASIC:
while (history) {
if (history.type == History.BEAM && history.heading == entity_id) return true;
history = history.Next();
}
break;
case BaseEntity.SPRING_TRANSLATIONAL:
case BaseEntity.SPRING_ROTATIONAL:
while (history) {
if (history.type == History.DISCRETE && history.heading == entity_id) return true;
history = history.Next();
}
break;
case BaseEntity.XSECTION:
while (xsec) {
if (xsec.heading == entity_id) return true;
xsec = xsec.Next();
}
break;
}
} else {
/* It's a number, so check if the item exists */
switch (entity_type) {
case BaseEntity.NODE:
if (Node.GetFromID(gui.model, id_number)) return true;
break;
case BaseEntity.BEAM_BASIC:
if (Beam.GetFromID(gui.model, id_number)) return true;
break;
case BaseEntity.SPRING_TRANSLATIONAL:
case BaseEntity.SPRING_ROTATIONAL:
if (Discrete.GetFromID(gui.model, id_number)) return true;
break;
case BaseEntity.XSECTION:
if (CrossSection.GetFromID(gui.model, id_number)) return true;
break;
case BaseEntity.JOINT:
if (Joint.GetFromID(gui.model, id_number)) return true;
break;
case BaseEntity.PART:
if (Part.GetFromID(gui.model, id_number)) return true;
break;
}
}
/* Get here and it's not been found */
return false;
}
/**
* Checks if all the entity IDs in the occupant are valid
* @param {WorkflowOccupant} occupant WorkflowOccupant instance to check
* @returns {boolean}
*/
function are_occupant_entity_ids_valid(occupant) {
for (let body_part of occupant.body_parts) {
for (let entity of body_part.entities) {
if (!is_entity_id_valid(entity.id.toString(), entity.entity_type)) return false;
}
}
/* Get here and all the entity IDs are valid */
return true;
}
/**
* Checks if all the entity IDs in the structure are valid
* @param {Structure} structure Structure instance to check
* @returns {boolean}
*/
function are_structure_entity_ids_valid(structure) {
for (let entity of structure.entities) {
if (!is_entity_id_valid(entity.id.toString(), entity.entity_type)) return false;
}
/* Get here and all the entity IDs are valid */
return true;
}
/**
* Returns the entity widgets for an occupant name
* @param {string} name Occupant name
* @returns {OccupantWidgets}
*/
function get_occupant_widgets(name) {
for (let occupant_widgets of gui.occupant_widgets) {
if (occupant_widgets.name == name) {
return occupant_widgets;
}
}
return null;
}
/**
* Returns the id on the entity widget by occupant name and entity tag
* @param {string} name Occupant name
* @param {string} tag Entity tag
* @returns {string|number}
*/
function get_occupant_entity_id_from_widget_by_tag(name, tag) {
let occupant_widgets = get_occupant_widgets(name);
if (!occupant_widgets) return "";
for (let body_part_widgets of occupant_widgets.body_part_widgets) {
for (let entity_widgets of body_part_widgets.entity_widgets) {
if (entity_widgets.tag == tag) {
// @ts-ignore
if (!isNaN(entity_widgets.textbox.text)) {
return parseInt(entity_widgets.textbox.text);
} else {
return entity_widgets.textbox.text;
}
}
}
}
return "";
}
/**
* Set the id on the occupant entity widget by entity tag
* @param {string} name Occupant name
* @param {string} tag Entity tag
* @param {number|string} id Entity id
*/
function set_occupant_widget_entity_id_by_tag(name, tag, id) {
let occupant_widgets = get_occupant_widgets(name);
if (!occupant_widgets) return;
for (let body_part_widgets of occupant_widgets.body_part_widgets) {
for (let entity_widgets of body_part_widgets.entity_widgets) {
if (entity_widgets.tag == tag) {
entity_widgets.textbox.text = id.toString();
}
}
}
update_occupant_window();
}
/**
* Returns the entity widgets for an structure name
* @param {string} structure_type Structure type
* @returns {StructureWidgets}
*/
function get_structure_widgets(structure_type) {
for (let structure_widgets of gui.structure_widgets) {
if (structure_widgets.structure_type == structure_type) {
return structure_widgets;
}
}
return null;
}
/**
* Returns the id on the entity widget by structure type and entity tag
* @param {string} structure_type Structure type
* @param {string} tag Entity tag
* @returns {string|number}
*/
function get_structure_entity_id_from_widget_by_tag(structure_type, tag) {
let structure_widgets = get_structure_widgets(structure_type);
if (!structure_widgets) return "";
for (let entity_widgets of structure_widgets.entity_widgets) {
if (entity_widgets.tag == tag) {
// @ts-ignore
if (!isNaN(entity_widgets.textbox.text)) {
return parseInt(entity_widgets.textbox.text);
} else {
return entity_widgets.textbox.text;
}
}
}
return "";
}
/**
* Set the id on the structure entity widget by entity tag
* @param {string} structure_type Structure type
* @param {string} tag Entity tag
* @param {number|string} id Entity id
*/
function set_structure_widget_entity_id_by_tag(structure_type, tag, id) {
let structure_widgets = get_structure_widgets(structure_type);
if (!structure_widgets) return;
for (let entity_widgets of structure_widgets.entity_widgets) {
if (entity_widgets.tag == tag) {
entity_widgets.textbox.text = id.toString();
}
}
update_structure_window();
}
/**
* Set the combobox/listbox selected item from a value
* @param {Widget} cbx Combobox
* @param {string} value Value of the selected item
* @returns {boolean} true if the widget was successfully set
*/
function set_selected_widget_item(cbx, value) {
cbx.selectedItem = null;
for (let widget_item of cbx.WidgetItems()) {
if (widget_item.text == value) {
cbx.selectedItem = widget_item;
return true;
}
}
if (cbx.selectedItem == null) {
WarningMessage(`Could not select ${value} as it did not match one of the combobox widget items`);
return false;
}
}
/**
* Set the listbox selected items from a list of values
* @param {Widget} lbx listbox
* @param {string[]} values Values of the selected items
*/
function set_selected_widget_items(lbx, values) {
lbx.selectedItem = null;
for (let widget_item of lbx.WidgetItems()) {
for (let value of values) {
if (widget_item.text == value) {
widget_item.selected = true;
lbx.selectedItem = widget_item;
}
}
}
}
function FixCombobox(cbx, value) {
cbx.RemoveAllWidgetItems();
new WidgetItem(cbx, value);
cbx.active = false;
}
/**
* VehicleOccupant-like object
* @typedef {Object} VehicleOccupantJSON
* @property {string} position Occupant position
* @property {string} product Occupant product type
* @property {string} physiology Occupant physiology
*/
/**
* an array of VehicleOccupant-like objects
* @typedef {Object} ProtocolVehicleJSON
* @property {VehicleOccupantJSON[]} occupants
* @property {string[]} structures
*/
/**
* The user data object in the workflow file
* @typedef {Object} ReporterUserData
* @property {string} regulation Regulation
* @property {string} crash_test Crash test
* @property {string} version Protocol version
* @property {string[]} description Protocol version
* @property {ProtocolVehicleJSON} vehicle Protocol version
*/
/**
*
* @param {ReporterUserData} reporter_user_data
*/
function SetUpGUIFromREPORTERUserData(reporter_user_data) {
/*Set the drive side from user data */
set_vehicle_drive_side(VehicleOccupant.LHD); //reporter_user_data.drive_side);
/*set the gui reporter_protocol_vehicle property so that it is used instead of a ProtocolVehicle from the Protocols class which
the reporter_user_data.vehicle has only the positions defined for the positions requested by the REPORTER template */
gui.reporter_protocol_vehicle = reporter_user_data.vehicle;
// add_reporter_structures(reporter_user_data.vehicle.structures);
/*set the crash test to match the REPORTER template crash test */
FixCombobox(gui.wdw_pre_automotive.cbx_protocol_test, reporter_user_data.crash_test);
/*set the regulation to match the REPORTER template crash test */
FixCombobox(gui.wdw_pre_automotive.cbx_protocol_regulation, reporter_user_data.regulation);
/*set the version to match the REPORTER template crash test */
FixCombobox(gui.wdw_pre_automotive.cbx_protocol_version, reporter_user_data.version);
update_occupants_column();
/*override the save callbacks to set gui state and hide gui */
gui.wdw_pre_automotive.btn_save_to_file.onClick = save_to_file_and_close;
gui.wdw_pre_automotive.btn_save_to_model.onClick = save_to_model_and_close;
}
function save_to_file_and_close() {
save_to_file();
gui.job_control = JobControl.RUN;
gui.wdw_pre_automotive.Hide();
}
function save_to_model_and_close() {
let answer = Window.Question(
"Save Model",
`To write Post *END Automotive Assessment Workflow Data the model needs to be saved.\n` +
`Are you sure you want to save the model? The model Master file will be overwritten so any changes to it will be irreversible`,
Window.YES | Window.NO
);
if (answer == Window.NO) return;
save_to_model();
Message(`Writing Master file to ${gui.model.filename}.`);
gui.model.Write(gui.model.filename, { method: Include.MASTER_ONLY });
gui.job_control = JobControl.RUN;
gui.wdw_pre_automotive.Hide();
}
/**
* 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
*/