// module: TRUE
/**
* The workflow_occupant module provides the WorkflowOccupant class for storing the
* properties of a user defined occupant with the location (position, side and row) ids of entities
* in each body part of an occupant that can be used to extract
* data from LS-DYNA results.<br><br>
*/
import {
Occupant,
OccupantBodyPart,
OccupantEntity,
OccupantPhysiology,
OccupantProduct,
OccupantSupplier,
OccupantChestRotationFactors
} from "./occupant.mjs";
import { OccupantVersion } from "./occupant_version.mjs";
import { THisHelper } from "../post/this.mjs";
import { ComponentMeasurementCurves, Measurement } from "./measurement.mjs";
export { WorkflowOccupant };
/** Class representing a occupant */
class WorkflowOccupant {
/**
*
* @param {string|Occupant} name_or_occupant Occupant or name constant
* @param {string} position Occupant position constant (WorkflowOccupant.DRIVER|WorkflowOccupant.PASSENGER)
* @param {string} side Occupant side constant
* @param {string} front_rear Front/Rear constant
* @param {OccupantBodyPart[]} body_parts Array of OccupantBodyPart instances
* @example
* let occupant = new WorkflowOccupant(
* WorkflowOccupant.HUMANETICS_HIII_50M_V1_5,
* WorkflowOccupant.DRIVER,
* WorkflowOccupant.LEFT,
* WorkflowOccupant.FRONT,
* [body_part1, body_part2]);
*/
constructor(name_or_occupant, position, side, front_rear, body_parts) {
this.SetOccupantFields = name_or_occupant;
this.position = position;
this.side = side;
this.front_rear = front_rear;
this.body_parts = body_parts;
this.upper_rib_irtracc_length = 0;
this.mid_rib_irtracc_length = 0;
this.bottom_rib_irtracc_length = 0;
this.upper_abdomen_irtracc_length = 0;
this.bottom_abdomen_irtracc_length = 0;
}
// #region Instance property getter and setters
/** Pritate getters (these properties can only be written through SetOccupantFields) */
/**
* Occupant name constant
* @type {string} */
get name() {
return this._name;
}
/**
* Occupant supplier constant
* @type {string} */
get supplier() {
return this._supplier;
}
/**
* Occupant product constant
* @type {string} */
get product() {
return this._product;
}
/**
* Occupant physiology constant
* @type {string} */
get physiology() {
return this._physiology;
}
/**
* Occupant version constant
* @type {string} */
get version() {
return this._version;
}
/**
* Set the occupant supplier, product, physiology and version fields
* this is because the supplier, product, physiology and version are all a set of variables
* that should not be changed independently.
* This is why there are no specific setters for each of them individually
*
* NOTE: This function takes an occupant name string or Occupant class
*
* @param {string|Occupant} name_or_occupant Occupant or name string
*/
set SetOccupantFields(name_or_occupant) {
let occupant;
if (typeof name_or_occupant == "string") {
occupant = OccupantVersion.GetFromName(name_or_occupant);
this._name = name_or_occupant;
} else if (name_or_occupant instanceof Occupant) {
occupant = name_or_occupant;
this._name = occupant.name; //TODO add name field to occupant
} else {
throw new Error(`Invalid name_or_occupant passed to the WorkflowOccupant constructor: ${name_or_occupant}`);
}
this._supplier = occupant.supplier;
this._product = occupant.product;
this._physiology = occupant.physiology;
this._version = occupant.version;
}
/**
* Occupant position constant
* @type {string} */
get position() {
return this._position;
}
set position(new_position) {
if (WorkflowOccupant.Positions().indexOf(new_position) == -1) {
throw new Error(`Invalid position: ${new_position}`);
}
this._position = new_position;
}
/**
* Occupant side constant
* @type {string} */
get side() {
return this._side;
}
set side(new_side) {
if (WorkflowOccupant.Sides().indexOf(new_side) == -1) {
throw new Error(`Invalid side: ${new_side}`);
}
this._side = new_side;
}
/**
* Occupant front/Rear constant
* @type {string} */
get front_rear() {
return this._front_rear;
}
set front_rear(new_front_rear) {
if (WorkflowOccupant.FrontRear().indexOf(new_front_rear) == -1) {
throw new Error(`Invalid front_rear: ${new_front_rear}`);
}
this._front_rear = new_front_rear;
}
/** Array of OccupantBodyPart instances
* @type {OccupantBodyPart[]} */
get body_parts() {
return this._body_parts;
}
set body_parts(new_body_parts) {
if (!(new_body_parts instanceof Array)) {
throw new Error("body_parts must be an array");
}
for (let new_body_part of new_body_parts) {
if (!(new_body_part instanceof OccupantBodyPart)) {
throw new Error("body_parts must be an array of OccupantBodyPart instances");
}
}
this._body_parts = new_body_parts;
/* Set the parent occupant for each body part */
for (let body_part of this.body_parts) {
body_part.occupant = this;
}
}
/**
* Upper rib irtracc length
* @type {number}
*/
get upper_rib_irtracc_length() {
return this._upper_rib_irtracc_length;
}
set upper_rib_irtracc_length(new_upper_rib_irtracc_length) {
if (typeof new_upper_rib_irtracc_length != "number") {
throw new Error("upper_rib_irtracc_length must be a number");
}
this._upper_rib_irtracc_length = new_upper_rib_irtracc_length;
}
/**
* Middle rib irtracc length
* @type {number}
*/
get mid_rib_irtracc_length() {
return this._mid_rib_irtracc_length;
}
set mid_rib_irtracc_length(new_mid_rib_irtracc_length) {
if (typeof new_mid_rib_irtracc_length != "number") {
throw new Error("mid_rib_irtracc_length must be a number");
}
this._mid_rib_irtracc_length = new_mid_rib_irtracc_length;
}
/**
* Bottom rib irtracc length
* @type {number}
*/
get bottom_rib_irtracc_length() {
return this._bottom_rib_irtracc_length;
}
set bottom_rib_irtracc_length(new_bottom_rib_irtracc_length) {
if (typeof new_bottom_rib_irtracc_length != "number") {
throw new Error("bottom_rib_irtracc_length must be a number");
}
this._bottom_rib_irtracc_length = new_bottom_rib_irtracc_length;
}
/**
* Upper abdomen irtracc length
* @type {number}
*/
get upper_abdomen_irtracc_length() {
return this._upper_abdomen_irtracc_length;
}
set upper_abdomen_irtracc_length(new_upper_abdomen_irtracc_length) {
if (typeof new_upper_abdomen_irtracc_length != "number") {
throw new Error("upper_abdomen_irtracc_length must be a number");
}
this._upper_abdomen_irtracc_length = new_upper_abdomen_irtracc_length;
}
/**
* Bottom abdomen irtracc length
* @type {number}
*/
get bottom_abdomen_irtracc_length() {
return this._bottom_abdomen_irtracc_length;
}
set bottom_abdomen_irtracc_length(new_bottom_abdomen_irtracc_length) {
if (typeof new_bottom_abdomen_irtracc_length != "number") {
throw new Error("bottom_abdomen_irtracc_length must be a number");
}
this._bottom_abdomen_irtracc_length = new_bottom_abdomen_irtracc_length;
}
// #endregion
// #region Class constant getters (read-only)
/* #region occupant name getters
*
* IMPORTANT:
* ---------
* The values need to match the names of the .csv files with the default entity ids
* in reporter_dir/library/templates/dummy_info as they are used to find the file
* and read the default entity ids from it.
*/
// #region position getters
/** Driver
* @type {string} */
static get DRIVER() {
return "Driver";
}
/** Passenger
* @type {string} */
static get PASSENGER() {
return "Passenger";
}
// #region side getters
/** Left side
* @type {string} */
static get LEFT() {
return "left";
}
/** Middle side
* @type {string} */
static get MIDDLE() {
return "middle";
}
/** Right side
* @type {string} */
static get RIGHT() {
return "right";
}
/** Front
* @type {string} */
static get FRONT() {
return "front";
}
/** Rear
* @type {string} */
static get REAR() {
return "rear";
}
// #endregion
/* Class methods */
/**
* Return an array of all the available occupant name strings
* @returns {string[]}
* @example
* let version_names = WorkflowOccupant.Versions();
*/
static Versions() {
return OccupantVersion.GetAllNames();
}
/**
* Return an array of all the available supplier strings
* @returns {string[]}
* @example
* let suppliers = WorkflowOccupant.Suppliers();
*/
static Suppliers() {
return OccupantSupplier.GetAll();
}
/**
* Return an array of all the available Product type strings
* @returns {string[]}
* @example
* let Products = WorkflowOccupant.Products();
*/
static Products() {
return OccupantProduct.GetAll();
}
/**
* Return an array of all the available physiology strings
* @returns {string[]}
* @example
* let physiologies = WorkflowOccupant.Physiologies();
*/
static Physiologies() {
return OccupantPhysiology.GetAll();
}
/**
* Return an array of all the available position strings
* @returns {string[]}
* @example
* let positions = WorkflowOccupant.Positions();
*/
static Positions() {
return [WorkflowOccupant.DRIVER, WorkflowOccupant.PASSENGER];
}
/**
* Return an array of all the available side strings
* @returns {string[]}
* @example
* let sides = WorkflowOccupant.Sides();
*/
static Sides() {
return [WorkflowOccupant.LEFT, WorkflowOccupant.MIDDLE, WorkflowOccupant.RIGHT];
}
/**
* Return an array of all the available front/rear strings
* @returns {string[]}
* @example
* let front_rear = WorkflowOccupant.FrontRear();
*/
static FrontRear() {
return [WorkflowOccupant.FRONT, WorkflowOccupant.REAR];
}
/**
* Creates a Workflow occupant from user data.
* @param {Object} user_data_occupant A single occupant object from user data
* @returns {WorkflowOccupant}
* @example
* for (let o of user_data.occupants) {
* let occupant = WorkflowOccupant.CreateFromUserData(o);
* }
*/
static CreateFromUserData(user_data_occupant) {
let o = user_data_occupant; /* Shorten variable name for convenience */
/** @type {OccupantBodyPart[]} */
let body_parts = [];
for (let b of o.body_parts) {
/** @type {OccupantEntity[]} */
let entities = [];
for (let e of b.entities) {
if (e.measurements) {
let measurements = [];
for (let m of e.measurements) {
measurements.push(new Measurement(m.name, m.component));
}
entities.push(new OccupantEntity(e.entity_type, e.id, e.name, e.tag, measurements));
}
}
body_parts.push(new OccupantBodyPart(b.component_type, entities));
}
/* Create a WorkflowOccupant instance with the required data */
if (!o.name) {
if (o.supplier && o.product && o.physiology && o.version) {
if (!OccupantSupplier.Valid(o.supplier)) {
throw new Error(`Occupant supplier ${o.supplier} is not supported`);
}
if (!OccupantProduct.Valid(o.product)) {
throw new Error(`Occupant product ${o.product} is not supported`);
}
if (!OccupantPhysiology.Valid(o.physiology)) {
throw new Error(`Occupant physiology ${o.physiology} is not supported`);
}
if (typeof o.supplier != "string") {
throw new Error(`Occupant version ${o.version} is not a string`);
}
o.name = `${o.supplier} ${o.product} ${o.physiology} ${o.version}`;
} else {
throw new Error(`Not all occupant inputs provided`);
}
}
let occupant = new WorkflowOccupant(o.name, o.position, o.side, o.front_rear, body_parts);
if (o.upper_rib_irtracc_length) {
occupant.upper_rib_irtracc_length = o.upper_rib_irtracc_length;
} else {
occupant.upper_rib_irtracc_length = 0;
}
if (o.mid_rib_irtracc_length) {
occupant.mid_rib_irtracc_length = o.mid_rib_irtracc_length;
} else {
occupant.mid_rib_irtracc_length = 0;
}
if (o.bottom_rib_irtracc_length) {
occupant.bottom_rib_irtracc_length = o.bottom_rib_irtracc_length;
} else {
occupant.bottom_rib_irtracc_length = 0;
}
if (o.upper_abdomen_irtracc_length) {
occupant.upper_abdomen_irtracc_length = o.upper_abdomen_irtracc_length;
} else {
occupant.upper_abdomen_irtracc_length = 0;
}
if (o.bottom_abdomen_irtracc_length) {
occupant.bottom_abdomen_irtracc_length = o.bottom_abdomen_irtracc_length;
} else {
occupant.bottom_abdomen_irtracc_length = 0;
}
return occupant;
}
//TODO Merge with CreateFromUserData() or create occupant in class ablove then call CreateWorkflowOccupantFromOccupant?
/**
* Returns a WorkflowOccupant with the required OccupantBodyParts
* and OccupantEntitys. The entity IDs are set to 0.
* @param {Occupant|string} name_or_occupant Occupant from JSON
* @param {string} position Occupant position
* @param {string} side Occupant side
* @param {string} front_rear Occupant front/rear
* @returns {WorkflowOccupant}
* @example
* let occupant = WorkflowOccupant.CreateWorkflowOccupantFromOccupant(
* WorkflowOccupant.HUMANETICS_HIII_50M_V1_5,
* WorkflowOccupant.DRIVER,
* WorkflowOccupant.LEFT,
* WorkflowOccupant.FRONT);
*/
static CreateWorkflowOccupantFromOccupant(name_or_occupant, position, side, front_rear) {
/* Create OccupantEntitys and OccupantBodyParts for each occupant name */
// TODO Be careful that we do not overwrite original occupant
let occupant;
if (typeof name_or_occupant == "string") {
occupant = OccupantVersion.GetFromName(name_or_occupant);
} else if (name_or_occupant instanceof Occupant) {
occupant = name_or_occupant;
} else {
throw new Error(`Invalid name_or_occupant passed to the WorkflowOccupant constructor: ${name_or_occupant}`);
}
// if (!occupant.body_parts || occupant.body_parts.length < 1) {
// return WorkflowOccupant.CreateWorkflowOccupant(occupant.name, position, side, front_rear);
// }
/** Body parts
* @type {OccupantBodyPart[]} */
let body_parts = [];
//TODO move this stuff to occupant body_part setter
for (let body_part of occupant.body_parts) {
let part = body_part.component_type.toLowerCase(); //todo check validity
let part_entities = [];
for (let entity of body_part.entities) {
part_entities.push(OccupantEntity.FromTagAndType(entity.tag, entity.entity_type, entity.id));
//Message(`${occupant.name} Add entity, ${entity.entity_type}, ${entity.tag} id: ${entity.id}`);
}
body_parts.push(new OccupantBodyPart(part, part_entities));
}
if (body_parts.length == 0) WarningMessage(`Occupant ${occupant.name} has no body parts defined.`);
return new WorkflowOccupant(occupant.name, position, side, front_rear, body_parts);
}
/* Instance methods */
/**
* String representation
* @returns {string}
* @example
* let s = occupant.toString();
*/
toString() {
return `${this.position}-${this.front_rear}-${this.side}`;
}
/**
* JSON representation
* @returns {object}
* @example
* let json = occupant.toJSON();
*/
toJSON() {
return {
name: this.name,
supplier: this.supplier,
product: this.product,
physiology: this.physiology,
position: this.position,
side: this.side,
front_rear: this.front_rear,
upper_rib_irtracc_length: this.upper_rib_irtracc_length,
mid_rib_irtracc_length: this.mid_rib_irtracc_length,
bottom_rib_irtracc_length: this.bottom_rib_irtracc_length,
upper_abdomen_irtracc_length: this.upper_abdomen_irtracc_length,
bottom_abdomen_irtracc_length: this.bottom_abdomen_irtracc_length,
body_parts: this.body_parts
};
}
/**
* Get an OccupantEntity by tag
* @param {string} tag Entity tag
* @returns {?OccupantEntity}
* @example
* let entity = occupant.GetEntityByTag(OccupantEntity.HEAD_NODE);
*/
GetEntityByTag(tag) {
for (let body_part of this.body_parts) {
let entity = body_part.GetEntityByTag(tag);
if (entity) return entity;
}
return null;
}
/**
* Get an OccupantEntity type by tag
* @param {string} tag Entity tag
* @returns {?string}
* @example
* let entity_type = occupant.GetEntityTypeFromTag(OccupantEntity.NECK_LOADCELL);
*/
GetEntityTypeFromTag(tag) {
for (let part of this.body_parts) {
for (let entity of part.entities) {
if (entity.tag == tag) return entity.entity_type;
}
}
return null;
}
/**
* Get an OccupantBodyPart by body part type
* @param {string} body_part_type Body part type
* @returns {?OccupantBodyPart}
* @example
* let body_part = occupant.GetBodyPartByType(OccupantBodyPart.HEAD);
*/
GetBodyPartByType(body_part_type) {
for (let body_part of this.body_parts) {
if (body_part.component_type === body_part_type) {
return body_part;
}
}
return null;
}
/**
* @typedef {Object} TibiaIndexCriticalLoads
* @property {number} compression Critical compression (kN)
* @property {number} bending Critical bending (Nm)
*/
/**
* Returns the NIJ critical loads for the occupant
* @returns {TibiaIndexCriticalLoads}
*/
GetTibiaIndexCriticalLoads() {
/* Default to values for female occupants */
/** @type {TibiaIndexCriticalLoads} */
let critical_loads = {
compression: 22.9,
bending: 115.0
};
//must be a hybrid 3 or THOR occupant
let test = this.physiology;
if (this.product != OccupantProduct.HIII && this.product != OccupantProduct.THOR) test = null;
switch (test) {
case OccupantPhysiology.M50:
critical_loads.compression = 35.9;
critical_loads.bending = 225.0;
break;
case OccupantPhysiology.F5:
critical_loads.compression = 22.9;
critical_loads.bending = 115.0;
break;
case "95th percentile dummy placeholder - not used anywhere yet":
case OccupantPhysiology.M95:
critical_loads.compression = 44.2;
critical_loads.bending = 307.0;
break;
default:
ErrorMessage(
`Unknown occupant ${this.name} in <WorkflowOccupant>.GetTibiaIndexCriticalLoads(). Returning values for a female occupant.`
);
}
return critical_loads;
}
/**
* @typedef {Object} NIJCriticalLoads
* @property {number} tension Critical tension (kN)
* @property {number} compression Critical compression (kN)
* @property {number} flexion Critical flexion (Nm)
* @property {number} extension Critical extension (Nm)
*/
/**
* Returns the NIJ critical loads for the occupant
* @returns {NIJCriticalLoads}
*/
GetNIJCriticalLoads() {
/* Default to values for female occupants */
/** @type {NIJCriticalLoads} */
let critical_loads = {
tension: 4.287,
compression: 3.88,
flexion: 155,
extension: 67
};
//must be a hybrid 3 occupant
let test = this.physiology;
if (this.product != OccupantProduct.HIII) test = null;
switch (test) {
case OccupantPhysiology.M50:
critical_loads.tension = 6.806;
critical_loads.compression = 6.16;
critical_loads.flexion = 310;
critical_loads.extension = 135;
break;
case OccupantPhysiology.F5:
critical_loads.tension = 4.287;
critical_loads.compression = 3.88;
critical_loads.flexion = 155;
critical_loads.extension = 67;
break;
case OccupantPhysiology.M95:
critical_loads.tension = 0;
critical_loads.compression = 0;
critical_loads.flexion = 0;
critical_loads.extension = 0;
//TODO update these placeholder values
WarningMessage("95th percentile critical Nij loads are need to be updated!");
break;
default:
ErrorMessage(
`Unknown occupant ${this.name} in <WorkflowOccupant>.GetNIJCriticalLoads(). Returning values for a female occupant.`
);
}
return critical_loads;
}
/**
* Returns the factors to convert the chest rotation values in radian to a deflection in mm
* @returns {?OccupantChestRotationFactors}
*/
GetChestRotationFactors() {
//get the occupant template which may have chest rotation factors stored
let occupant_template = OccupantVersion.GetFromName(this.name);
//if the json object has chest rotation factors use them
if (occupant_template.chest_rotation_factors) {
Message(`Using chest rotation factors from ${this.name}.json file`);
return occupant_template.chest_rotation_factors;
}
Message(
`Could not find chest rotation factors in ${this.name}.json file so using supplier and physiology to determine appropriate values.`
);
let test = `${this.supplier}${this.physiology}`;
//TODO add this to occupant definition?
//must be a hybrid 3 occupant
if (this.product != OccupantProduct.HIII) test = null;
Message(`test ${test}`);
switch (test) {
case `${OccupantSupplier.ATD}${OccupantPhysiology.M50}`:
return new OccupantChestRotationFactors(OccupantChestRotationFactors.LINEAR, [-147.0]);
case `${OccupantSupplier.LSTC}${OccupantPhysiology.M50}`:
return new OccupantChestRotationFactors(OccupantChestRotationFactors.LINEAR, [-146.0]);
case `${OccupantSupplier.LSTC}${OccupantPhysiology.F5}`:
return new OccupantChestRotationFactors(OccupantChestRotationFactors.LINEAR, [-104.0]);
case `${OccupantSupplier.HUMANETICS}${OccupantPhysiology.M50}`:
return new OccupantChestRotationFactors(
OccupantChestRotationFactors.THIRD_ORDER,
[25.13, -35.77, -136.26]
);
case `${OccupantSupplier.HUMANETICS}${OccupantPhysiology.F5}`:
return new OccupantChestRotationFactors(
OccupantChestRotationFactors.THIRD_ORDER,
[-15.61, 33.84, 81.53]
);
default:
ErrorMessage(
`Unknown occupant ${this.name} in <WorkflowOccupant>.GetChestRotationFactors(). Returning null.`
);
return null;
}
}
/**
* Reads the raw body part measurements into T/HIS and returns
* them in a ComponentMeasurementCurves instance
* @param {Model} model model to read data from
* @param {string} body_part_type Body part type to read data for
* @returns {ComponentMeasurementCurves}
*/
ReadRawBodyPartMeasurements(model, body_part_type) {
let entity_tags = OccupantEntity.EntityTags(body_part_type);
let entities = [];
for (let tag of entity_tags) {
entities.push(this.GetEntityByTag(tag));
}
let measurement_curves = new ComponentMeasurementCurves();
for (let entity of entities) {
if (entity != null) {
for (let measurement of entity.measurements) {
let curve = THisHelper.ReadData(model, entity.entity_type, entity.id, measurement.component, true);
if (curve) {
measurement_curves.AddCurve(measurement.name, curve);
}
}
}
}
return measurement_curves;
}
}