/* REPORTER Script to generate an image for the B-Pillar assessment
*
* This is done in REPORTER rather than D3PLOT as REPORTER has an Image class
* that can drawn lines and text, whereas D3PLOT can draw lines, but not text.
*
* The script reads the cut-section coordinates from the files generated by
* D3PLOT (filenames passed to the template as variables) and draws the image
* saving it to a file that D3PLOT can then pick up and display on a widget
*
* The template assumes all coordinates are in mm, i.e. they have been converted
* from the model units in the D3PLOT script that passes the values to this
* template.
*
* Note: I'm using 'var' instead of 'let' to declare variables so that if the
* template is generated more than once in a single session it still works.
* Reporter only has one global instance and if you define things with 'let'
* it complains the second time the script is run as it thinks you're trying
* to redeclare and existing variable.
*/
draw_b_pillar_image();
/* TODO - split up the function into separate functions, e.g. calculate_rating, min and max coords */
/**
* Draw the B-pillar image
* Also calculates the B-pillar deformation rating
*/
function draw_b_pillar_image() {
var t = Template.GetCurrent();
var ground_z = parseFloat(t.GetVariableValue("GROUND_Z"));
var seat_centre_y = parseFloat(t.GetVariableValue("SEAT_CENTRE_Y"));
var hpoint_z = parseFloat(t.GetVariableValue("H_POINT_Z"));
if (isNaN(seat_centre_y) || seat_centre_y == 0) {
LogError("Invalid SEAT_CENTRE_Y = " + t.GetVariableValue("SEAT_CENTRE_Y"));
LogPrint("Unable to draw image and calculate B-pillar deformation rating.");
Exit();
}
/* Structural intrusion ratings for B-pillar to seat centerline distance (mm)
* Note Reporter variable in cm but drawing needs it in mm so x10 */
var B_PILLAR_DEFORMATION_GOOD = parseFloat(t.GetVariableValue("B_PILLAR_DEFORMATION_GOOD")) * 10; // >=18.0 cm
var B_PILLAR_DEFORMATION_ACCEPTABLE = parseFloat(t.GetVariableValue("B_PILLAR_DEFORMATION_ACCEPTABLE")) * 10; // >=14.0 cm
var B_PILLAR_DEFORMATION_MARGINAL = parseFloat(t.GetVariableValue("B_PILLAR_DEFORMATION_MARGINAL")) * 10; // >=10.0 cm
if (isNaN(B_PILLAR_DEFORMATION_GOOD)) {
LogError("Invalid B_PILLAR_DEFORMATION_GOOD = " + t.GetVariableValue("B_PILLAR_DEFORMATION_GOOD"));
LogPrint("Unable to draw image and calculate B-pillar deformation rating.");
Exit();
}
if (isNaN(B_PILLAR_DEFORMATION_ACCEPTABLE)) {
LogError("Invalid B_PILLAR_DEFORMATION_ACCEPTABLE = " + t.GetVariableValue("B_PILLAR_DEFORMATION_ACCEPTABLE"));
LogPrint("Unable to draw image and calculate B-pillar deformation rating.");
Exit();
}
if (isNaN(B_PILLAR_DEFORMATION_MARGINAL)) {
LogError("Invalid B_PILLAR_DEFORMATION_MARGINAL = " + t.GetVariableValue("B_PILLAR_DEFORMATION_MARGINAL"));
LogPrint("Unable to draw image and calculate B-pillar deformation rating.");
Exit();
}
/* Get max and min coordinates
* need these so we know how big to make the image to fit the lines
* and where to reflect as Z is the other way around */
var max_y = -Number.MAX_VALUE;
var max_z = -Number.MAX_VALUE;
var min_y = Number.MAX_VALUE;
var min_z = Number.MAX_VALUE;
var y_mid = 0;
for (var pass = 0; pass < 2; pass++) {
if (pass == 0) {
var filename = `${t.GetVariableValue("TEMPLATE_DIR")}/first_state_coords.txt`;
var f = new File(filename, File.READ);
}
if (pass == 1) {
var filename = `${t.GetVariableValue("TEMPLATE_DIR")}/last_state_coords.txt`;
var f = new File(filename, File.READ);
}
var found = false;
var line;
// @ts-ignore - ReadLongLine should have it's return type set to number | string
while ((line = f.ReadLongLine()) != File.EOF) {
var data = line.split(",");
var ncut = parseInt(data[0]);
if (ncut == 2) {
found = true;
y1 = parseFloat(data[2]);
y2 = parseFloat(data[5]);
z1 = parseFloat(data[3]);
z2 = parseFloat(data[6]);
max_y = Math.max(y1, y2, max_y);
max_z = Math.max(z1, z2, max_z);
min_y = Math.min(y1, y2, min_y);
min_z = Math.min(z1, z2, min_z);
}
}
if (!found) {
max_y = 1;
min_y = 0;
max_z = 1;
min_z = 0;
}
}
/* Seat centre line */
max_y = Math.max(seat_centre_y, max_y);
min_y = Math.min(seat_centre_y, min_y);
/* Ground Z */
min_z = Math.min(ground_z, min_z);
LogPrint("max_y: " + max_y.toFixed(2) + " min_y: " + min_y.toFixed(2));
LogPrint("max_z: " + max_z.toFixed(2) + " min_z: " + min_z.toFixed(2));
/* Now create image */
var buffer = 400;
var width = max_y - min_y + buffer;
var height = max_z - min_z + buffer;
var reflect = height / 2;
var y_offset = min_y - buffer / 2; // Y offset so the image starts on the left hand side
var z_offset = min_z - buffer / 2; // Z offset so the image starts at the bottom
LogPrint("width: " + width.toFixed(2) + " height: " + height.toFixed(2));
var img = new Image(Math.round(width), Math.round(height));
img.lineWidth = 3;
/* Draw colour bands */
var band_bottom_z, band_top_z;
[band_bottom_z, band_top_z] = draw_colour_bands();
/* Draw cross sections of parts
* Two passes - one for first state, one for the last
* Format of file is <number of cuts>, <x1>, <y1>, <z1>, .... <xn>, <yn>, <zn> */
var min_space_remaining = Number.MAX_VALUE;
var y_at_max_intrusion = 0.0;
var z_at_max_intrusion = 0.0;
img.lineStyle = Reporter.LINE_SOLID;
for (var pass = 0; pass < 2; pass++) {
if (pass == 0) {
var filename = `${t.GetVariableValue("TEMPLATE_DIR")}/first_state_coords.txt`;
var f = new File(filename, File.READ);
img.lineColour = "#0000FF";
img.lineWidth = 2;
}
if (pass == 1) {
var filename = `${t.GetVariableValue("TEMPLATE_DIR")}/last_state_coords.txt`;
var f = new File(filename, File.READ);
img.lineColour = "#000000";
img.lineWidth = 6;
}
var line;
var y1, y2, z1, z2;
// @ts-ignore - ReadLongLine should have it's return type set to number | string
while ((line = f.ReadLongLine()) != File.EOF) {
var data = line.split(",");
var ncut = parseInt(data[0]);
if (ncut == 2) {
var y1_model = parseFloat(data[2]);
var y2_model = parseFloat(data[5]);
var z1_model = parseFloat(data[3]);
var z2_model = parseFloat(data[6]);
y1 = model_y_to_image_y(y1_model);
y2 = model_y_to_image_y(y2_model);
z1 = model_z_to_image_z(z1_model);
z2 = model_z_to_image_z(z2_model);
img.Line(y1, z1, y2, z2);
LogPrint(
"Line from " + y1.toFixed(2) + ", " + z1.toFixed(2) + " to " + y2.toFixed(2) + ", " + z2.toFixed(2)
);
if (pass == 1) {
/* Check for intrusion only up to 540mm above H-Point and -100m below H-Point
* (and ignore coordinates on the opposite B-pillar) */
if (z2 >= band_top_z && z1 <= band_bottom_z && seat_centre_y * y2_model > 0) {
if (seat_centre_y > 0) {
var space_remaining = y2_model - seat_centre_y;
} else {
var space_remaining = seat_centre_y - y2_model;
}
if (space_remaining < min_space_remaining) {
min_space_remaining = space_remaining;
y_at_max_intrusion = y2;
z_at_max_intrusion = z2;
}
}
}
}
}
f.Close();
}
/* Draw extra gubbins */
draw_hpoint();
draw_axis();
draw_legend();
draw_max_intrusion_point();
/* Save image */
img.Save(`${t.GetVariableValue("TEMPLATE_DIR")}/b_pillar_image_M${t.GetVariableValue("MODEL_ID")}.png`, Image.PNG);
/* Write intrusion values to Reporter variables - distance from centre line (min_space_remaining) and
* height above ground */
var height_above_ground = image_z_to_model_z(z_at_max_intrusion) - ground_z;
if (min_space_remaining != -Number.MAX_VALUE) {
new Variable(t, "MAX_INTRUSION", "Maximum intrusion", min_space_remaining.toString(), "Number");
new Variable(t, "HEIGHT_ABOVE_GROUND", "Height above ground", height_above_ground.toString(), "Number");
if (min_space_remaining >= B_PILLAR_DEFORMATION_GOOD) {
new Variable(t, "B_PILLAR_RATING", "B-pillar deformation rating", "GOOD", "General");
} else if (min_space_remaining >= B_PILLAR_DEFORMATION_ACCEPTABLE) {
new Variable(t, "B_PILLAR_RATING", "B-pillar deformation rating", "ACCEPTABLE", "General");
} else if (min_space_remaining >= B_PILLAR_DEFORMATION_MARGINAL) {
new Variable(t, "B_PILLAR_RATING", "B-pillar deformation rating", "MARGINAL", "General");
} else {
new Variable(t, "B_PILLAR_RATING", "B-pillar deformation rating", "POOR", "General");
}
} else {
new Variable(t, "MAX_INTRUSION", "Maximum intrusion", "", "General");
new Variable(t, "HEIGHT_ABOVE_GROUND", "Height above ground", "", "General");
new Variable(t, "B_PILLAR_RATING", "B-pillar deformation rating", "POOR", "General");
}
/**
* Draws the colour bands on the image
* @returns {number[]} The top and bottom Z coords of the colour bands
*/
function draw_colour_bands() {
// Draws the colour bands and returns the top of the image top Z coord
var y1, z1, y2, z2;
var GREEN_COL = "#52B442"; // Green
var YELLOW_COL = "#FCE702"; // Yellow
var ORANGE_COL = "#F79800"; // Orange
var RED_COL = "#F40000"; // Red
img.lineWidth = 3;
img.lineStyle = Reporter.LINE_NONE;
z1 = model_z_to_image_z(hpoint_z - 100); // bottom is 100m below H-Point
z2 = model_z_to_image_z(hpoint_z + 540); // Top is 540mm above H-Point
var band_bottom_z = z1;
var band_top_z = z2;
/* GREEN */
y1 = model_y_to_image_y(min_y);
y2 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_GOOD);
img.fillColour = GREEN_COL;
img.Polygon(y1, z1, y1, z2, y2, z2, y2, z1);
/* YELLOW */
y1 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_GOOD);
y2 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_ACCEPTABLE);
img.fillColour = YELLOW_COL;
img.Polygon(y1, z1, y1, z2, y2, z2, y2, z1);
/* ORANGE */
y1 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_ACCEPTABLE);
y2 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_MARGINAL);
img.fillColour = ORANGE_COL;
img.Polygon(y1, z1, y1, z2, y2, z2, y2, z1);
/* RED */
y1 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_MARGINAL);
y2 = model_y_to_image_y(y_mid); //draw red zone to middle of car
img.fillColour = RED_COL;
img.Polygon(y1, z1, y1, z2, y2, z2, y2, z1);
/* Return the top of the band in image coords */
return [band_bottom_z, band_top_z];
}
/**
* Draws the H-Point line on the image
*/
function draw_hpoint() {
var y1, z1, y2, z2;
y1 = model_y_to_image_y(seat_centre_y - B_PILLAR_DEFORMATION_MARGINAL);
y2 = model_y_to_image_y(y_mid); //draw H-point line zone to middle of car
z1 = model_z_to_image_z(hpoint_z);
z2 = z1;
img.lineWidth = 3;
img.lineStyle = Reporter.LINE_DOT;
img.lineColour = "#000000";
img.Line(y1, z1, y2, z2);
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(36);
img.fontJustify = Reporter.JUSTIFY_LEFT;
img.Text(y1, z2 - 4, "H-Point");
}
/**
* Draws the axis on the image
*/
function draw_axis() {
var y1, z1, y2, z2;
img.lineWidth = 3;
img.lineStyle = Reporter.LINE_SOLID;
img.lineColour = "#000000";
// Vertical - Height from ground
y1 = model_y_to_image_y(min_y);
y2 = y1;
z1 = model_z_to_image_z(min_z);
z2 = model_z_to_image_z(max_z);
img.Line(y1, z1, y2, z2);
/* Label */
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(36);
img.fontJustify = Reporter.JUSTIFY_CENTRE;
img.fontAngle = 270;
z1 = Math.floor(0.5 * (z1 + z2));
img.Text(70, z1, "Height from ground (cm)");
/* Add marks every 100mm */
var delta = 10; // 10cm
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(24);
img.fontJustify = Reporter.JUSTIFY_RIGHT;
img.fontAngle = 1;
var height_above_ground;
var i = 0;
z1 = model_z_to_image_z(min_z);
while (z1 > z2) {
height_above_ground = delta * i; // In cm
img.Text(y1 - 6, z1 + 12, height_above_ground.toString());
img.Line(y1, z1, y1 + 10, z1);
i++;
z1 = model_z_to_image_z(min_z + delta * 10 * i); // mm
}
/* Horizontal - Lateral distance from seat centre line */
y1 = model_y_to_image_y(min_y);
y2 = model_y_to_image_y(max_y);
z1 = model_z_to_image_z(min_z);
z2 = z1;
img.Line(y1, z1, y2, z2);
/* Label */
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(36);
img.fontJustify = Reporter.JUSTIFY_CENTRE;
img.fontAngle = 0;
var centerline_ref = t.GetVariableValue("CENTERLINE");
draw_x_ticks(centerline_ref, y1, y2, z1);
}
/**
* Draws the x-axis ticks on the image
* @param {string} centerline_ref Centreline reference - "VEHICLE" or "SEAT"
* @param {number} y1 Image y coordinate of the left side of the X-Axis
* @param {number} y2 Image y coordinate of the right side of the X-Axis
* @param {number} z1 Image z coordinate of the bottom of the Y-Axis
*/
function draw_x_ticks(centerline_ref, y1, y2, z1) {
var vehicle_centre_img_y = Math.floor(0.5 * (y1 + y2));
if (centerline_ref.toUpperCase() == "SEAT") {
/* Driver seat centreline */
var centerline_ref_y = seat_centre_y;
y1 = model_y_to_image_y(seat_centre_y);
img.Text(vehicle_centre_img_y, height - 70, "Lateral distance from driver centreline (cm)");
} else {
/* vehicle centreline */
var centerline_ref_y = image_y_to_model_y(vehicle_centre_img_y);
y1 = vehicle_centre_img_y; //use vehicle centerline
img.Text(vehicle_centre_img_y, height - 70, "Lateral distance from vehicle centreline (cm)");
}
/* Add marks every 100mm - starting at the seat centre line */
var delta = 10; // 10cm
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(24);
img.fontJustify = Reporter.JUSTIFY_CENTRE;
var lateral_distance;
//reference point (zero at either seat centerline or vehicle centerline)
var i = 0;
while (y1 < y2) {
lateral_distance = delta * i; // In cm
img.Text(y1, z1 + 30, lateral_distance.toString());
img.Line(y1, z1, y1, z1 - 10);
i++;
y1 = model_y_to_image_y(centerline_ref_y + delta * 10 * i); // mm
}
i = 1;
y1 = model_y_to_image_y(centerline_ref_y - delta * 10); // mm
while (y1 > model_y_to_image_y(min_y)) {
lateral_distance = -delta * i; // In cm
img.Text(y1, z1 + 30, lateral_distance.toString());
img.Line(y1, z1, y1, z1 - 10);
i++;
y1 = model_y_to_image_y(centerline_ref_y - delta * 10 * i); // mm
}
}
/**
* Draws the legend on the image
*/
function draw_legend() {
var y1, z1, y2, z2;
y1 = model_y_to_image_y(min_y);
y2 = y1 + 100;
z1 = z2 = 40;
img.lineWidth = 2;
img.lineStyle = Reporter.LINE_SOLID;
img.lineColour = "#0000FF";
img.Line(y1, z1, y2, z2);
y1 = y2 + 10;
z1 = z2 + 18;
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(36);
img.fontJustify = Reporter.JUSTIFY_LEFT;
img.Text(y1, z1, "Precrash");
y1 = model_y_to_image_y(min_y);
y2 = y1 + 100;
z1 = z2 = 100;
img.lineWidth = 6;
img.lineStyle = Reporter.LINE_SOLID;
img.lineColour = "#000000";
img.Line(y1, z1, y2, z2);
y1 = y2 + 10;
z1 = z2 + 18;
img.font = "Segoe UI";
img.fontSize = pixels_to_font_size(36);
img.fontJustify = Reporter.JUSTIFY_LEFT;
img.Text(y1, z1, "Postcrash");
}
/**
* Draws the max intrusion point on the image
*/
function draw_max_intrusion_point() {
var y1, z1, y2, z2;
/* Vertical line */
img.lineWidth = 1;
img.lineStyle = Reporter.LINE_DASH;
img.lineColour = "#000000";
y1 = y_at_max_intrusion;
y2 = y_at_max_intrusion;
z1 = model_z_to_image_z(min_z);
z2 = z_at_max_intrusion;
img.Line(y1, z1, y2, z2);
/* Horizontal line */
y1 = model_y_to_image_y(min_y);
y2 = y_at_max_intrusion;
z1 = z_at_max_intrusion;
z2 = z_at_max_intrusion;
img.Line(y1, z1, y2, z2);
}
/**
* Converts the model Y coordinate to an image Y coordinate
* @param {number} model_y Model Y coordinate
* @returns {number}
*/
function model_y_to_image_y(model_y) {
return Math.floor(model_y - y_offset);
}
/**
* Converts the image Y coordinate to a model Y coordinate
* @param {number} image_y Image Y coordinate
* @returns {number}
*/
function image_y_to_model_y(image_y) {
return Math.floor(image_y + y_offset);
}
/**
* Converts the model Z coordinate to an image Z coordinate
* @param {number} model_z Model Z coordinate
* @returns {number}
*/
function model_z_to_image_z(model_z) {
var z = model_z - z_offset;
z = Math.floor(2 * reflect - z);
return z;
}
/**
* Converts the image Z coordinate to a model Z coordinate
* @param {number} image_z Image Z coordinate
* @returns {number}
*/
function image_z_to_model_z(image_z) {
var z = 2 * reflect - image_z;
z = z + z_offset;
return z;
}
}
/**
* Determines the choice of font point size that will give consistent absolute font height
* independent of the screen resolution
* @returns {number}
*/
function determine_appropriate_font_size() {
var t = Template.GetCurrent();
//var img_dir = t.GetVariableValue("IMAGES_DIR") + "/text_test/"; //used in testing
var width = 1;
var height = 200;
var test_img = new Image(width, height);
test_img.font = "Segoe UI";
test_img.fontColour = "black";
test_img.fontJustify = Reporter.JUSTIFY_CENTRE;
var average_pixels_per_pt = 0;
var samples = 0;
for (var i = 6; i <= 100; i += 6) {
test_img.fontSize = i;
test_img.Text(width, height, "I");
var nblack = width * height - test_img.PixelCount("white");
//LogPrint("Font size " + i + "pt generates an 'I' of height " + nblack + " which is " + nblack/i + "pixels per pt");
//test_img.Save(img_dir + "I_" + i+ "pt_npixels_"+nblack + ".png", Image.PNG);
//clean with white rectangle
test_img.lineColour = "white";
test_img.fillColour = "white";
test_img.lineWidth = 1;
test_img.lineStyle = Reporter.LINE_SOLID;
test_img.Rectangle(0, 0, width, height);
average_pixels_per_pt += nblack / i;
samples++;
}
average_pixels_per_pt = average_pixels_per_pt / samples;
return average_pixels_per_pt;
}
/**
* Converts a desired pixel height of upper case I to a font point size
* @param {number} desired_pixel_height_upper_case_I Desired height of upper case I in pixels
* @returns {number}
*/
function pixels_to_font_size(desired_pixel_height_upper_case_I) {
var average_pixels_per_pt = determine_appropriate_font_size(); //this is based on screen res
var required_font_point = Math.round(desired_pixel_height_upper_case_I / average_pixels_per_pt);
LogPrint(
"For your monitor font size " +
required_font_point +
"pt should generate text of height of " +
desired_pixel_height_upper_case_I +
"px"
);
return required_font_point;
}