post_intrusion_contour_plot.js

// module: TRUE
// @ts-ignore
import { gui } from "./post_intrusion_contour_plot_gui.jsi";

let state;
let setLastState = true; // Setting this to false stops the GUI updating the state number to the maximum of all models when in the last_state function needed when one model might be out of bounds so sets to it's last state
let setOnlyLastStateThisModel = 0; // Setting this to the model number makes sure that only this model gets set to the last state and not all of them needed when one mddel might be out of bounds so sets to it's last state
let maxStateOfModels = 0;
let numSelectedModels = Workflow.NumberOfSelectedModels(); // Gets number of models selected by the general workflow GUI
DialogueInput("/DEFORM REF_STATE OFF"); // If the program was closed whist reference state was turned on, switches itself off when script is rerun
DialogueInput("/VIEW_OPTIONS TARGET_AND_EYE UP_VECTOR Z_GLOBAL");
DialogueInput("/VIEW_OPTIONS TARGET_AND_EYE UPDATE_VIEW");

set_up();

gui.Window1.keepOnTop = true; // Makes the GUI stay on top, otherwise when you move the graphics window it gets hidden behind it and you can't see it
// Brings up the post intrusion contour plot GUI ready for callback functions
if (gui) gui.Window1.Show(false);

function set_up() {
    /**
     * Loops through the models to set them up to their last state and resultant displacement
     */
    for (let i = 0; i < numSelectedModels; i++) {
        // Gets model data and sets model and window correctly
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            let userData = Workflow.ModelUserDataFromIndex(i);
            let info = GetModelInfo(modelID);
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);

            let n = GetNumberOf(WINDOW);
            var windowID = -1;
            let nm = [1, 1];
            for (let m = 1; m <= n; m++) {
                // Model numbers in Window i
                let wm = GetWindowModels(m);
                for (let j = 0; j < wm.nm; j++) {
                    if (wm.list[j] == modelID) {
                        windowID = m;
                        nm[0] = wm.nm;
                        nm[1] = j + 1;
                        break;
                    }
                }
            }

            if (nm[0] > 1) {
                let answer = Window.Error(
                    "Error",
                    "This tool does not work correctly with multiple models in one window. \nExit the script?",
                    Window.YES | Window.NO
                );
                if (answer == Window.YES) {
                    Exit();
                }
            }

            // Gets the values of the 3 selected nodes
            let n1 = userData["node1"];
            let n2 = userData["node2"];
            let n3 = userData["node3"];

            // Sets to the last state and adds the three selected nodes to ref_node
            DialogueInput("/STATE LAST");
            DialogueInput("/DEFORM REF_NODE THREE_NODES", n1, n2, n3);

            // Shows coordinate geometry and redraws
            DialogueInput("/DEFORM REF_NODE LOCAL");
            DialogueInput("/DEFORM REF_NODE SHOW_NODES_ON");
            DialogueInput("/REDRAW");

            // Sets D3PLOT to use SI contour plot
            DialogueInput("/SI_SHADED_IMAGE GO");
            DialogueInput("/COMPONENT DR_DISP_RESULTANT");

            // Finds what the last state is and updates the GUI with this information. Also finds the maximum state of all the selected models and stores in maxStateOfModels
            state = info.num_states;
            if (state > maxStateOfModels) {
                maxStateOfModels = state;
                gui.Window1.stateNumber.text = `${state}`;
            }
        }
    }

    keep_view_updated();

    // When the reference model combo box drop down is clicked, collects all the valid models and pust them into the combo box ready for selection.
    gui.Window1.comboModel.RemoveAllWidgetItems(); // removes all items added before otherwise the list appends each time the drop down is pressed
    let firstModelNumber = 55; // required for setting default model to the first valid model
    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (SetCurrentModel(modelID)) {
            // TRUE if model is present (false if model is deleted etc)
            let widgetText = "" + modelID;
            let newWidgetItem = new WidgetItem(gui.Window1.comboModel, widgetText);
            if (modelID < firstModelNumber) {
                firstModelNumber = modelID;
                newWidgetItem.selected = true; // sets the default to the first valid model
            }
        }
    }

    // Set up callback functions
    gui.Window1.firstState.onClick = first_state;
    gui.Window1.prevState.onClick = prev_state;
    gui.Window1.stateNumber.onChange = update_plot;
    gui.Window1.nextState.onClick = next_state;
    gui.Window1.finalState.onClick = last_state;
    gui.Window1.checkboxRefState.onClick = ref_state;
    gui.Window1.txtRefStateNumber.onChange = ref_state;
    gui.Window1.comboModel.onChange = ref_state;
    gui.Window1.checkboxView.onClick = ref_state;
    gui.Window1.helpBtn.onClick = workflow_manual;
}

/**
 * Entered when the user manually changes the state value. Checks if the state is valuid, if not prints warning message and changes it to the last state via the last state function
 */
function update_plot() {
    let newState = gui.Window1.stateNumber.text; // newState number that has been entered by the user.
    let isNum = /^\d+$/.test(newState);
    if (isNum == true) {
        for (let i = 0; i < numSelectedModels; i++) {
            let modelID = Workflow.ModelIdFromIndex(i);
            if (modelID != null) {
                DialogueInput("/CM", modelID.toString());
                SetCurrentModel(modelID);

                // Checks if state number is out of bounds. If it is valid then sets to the new state, if not sets to the last state of the model.
                let maxState;
                let numWindows = GetNumberOf(WINDOW);
                for (let a = 1; a <= numWindows; a++) {
                    let models = GetWindowModels(a);
                    for (let j = 0; j < models.nm; j++) {
                        if (modelID == models.list[j]) {
                            maxState = GetWindowMaxFrame(a);
                        }
                    }
                }

                if (newState >= 0 && newState <= maxState) {
                    DialogueInput("/STATE " + newState);
                } else {
                    WarningMessage("State number out of bounds for model " + modelID + " Resetting to its last state");
                    setLastState = false; // Setting this to false stops the GUI updating the state number to the maximum of all models when in the last_state function needed when one model might be out of bounds so sets to it's last state
                    setOnlyLastStateThisModel = modelID; // Setting this to the model number makes sure that only this model gets set to the last state and not all of them needed when one model might be out of bounds so sets to it's last state
                    last_state();
                }
            }
        }
    } else {
        last_state();
        WarningMessage("There are characters that are not valid in your text. Resetting all models to their last state");
    }

    setLastState = true;
    if (gui.Window1.checkboxView.pushed == true) {
        keep_view_updated();
    }
}

/**
 * Function that changes it to the first state when first button is pressed. Remember that the first state is state 0 and not state 1
 */
function first_state() {
    state = 0;
    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);
            DialogueInput("/STATE 0");
            SetWindowActive(modelID, OFF);
        }
    }
    gui.Window1.stateNumber.text = `${state}`;

    if (gui.Window1.checkboxView.pushed == true) {
        keep_view_updated();
    }
}

/**
 * Function that changes it to the previous state when prev button is pressed. Checks to see if it is at the first state, in which case it can't go back any further and prints warning message
 */
function prev_state() {
    let maxCurrentStateOfModels = 0;
    // loops through all the models and gets the highest state
    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);

            // Checks to see if the current state is bigger than the maximum current state of all the models
            // If it is bigger than the maximum current state of all the models is set to the current state value
            // This is to make sure that states move as per normal D3PLOT tools. For example if two models are moved to their last state then...
            // if model 1 has 5 states and model 2 has 3 states, if you back 1 state then model 1 should go state 4, but model 2 should STAY at the state 3
            let currentState;
            let numWindows = GetNumberOf(WINDOW);
            for (let a = 1; a <= numWindows; a++) {
                let models = GetWindowModels(a);
                for (let j = 0; j < models.nm; j++) {
                    if (modelID == models.list[j]) {
                        currentState = GetWindowFrame(a);
                    }
                }
            }

            if (currentState > maxCurrentStateOfModels) {
                maxCurrentStateOfModels = currentState;
            }
        }
    }

    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);

            // If current state is highest state then move it back by one state, if not leave state where it is as described above in mass comment section
            let currentState;
            let maxState = 0;
            let numWindows = GetNumberOf(WINDOW);
            for (let a = 1; a <= numWindows; a++) {
                let models = GetWindowModels(a);
                for (let j = 0; j < models.nm; j++) {
                    if (modelID == models.list[j]) {
                        currentState = GetWindowFrame(a);
                        maxState = GetWindowMaxFrame(a);
                    }
                }
            }

            if (currentState == maxCurrentStateOfModels) {
                state = parseInt(gui.Window1.stateNumber.text);
                if (state - 1 >= 0) {
                    state -= 1;
                    DialogueInput("/STATE", state.toString());
                } else {
                    WarningMessage("This is already the first state.");
                    return;
                }
            }

            // If the user uses the general D3PLOT GUI state change and messes up the states then this else if is required
            let guiCurrent = parseInt(gui.Window1.stateNumber.text);
            if (guiCurrent <= maxState) {
                if (guiCurrent > currentState) {
                    state = parseInt(gui.Window1.stateNumber.text);
                    state -= 1;
                    DialogueInput("/STATE", state.toString());
                }
            }
            if (guiCurrent > maxState) {
                DialogueInput("/STATE LAST");
            }
        }
    }
    gui.Window1.stateNumber.text = `${state}`;

    if (gui.Window1.checkboxView.pushed == true) {
        keep_view_updated();
    }
}

/**
 * Function that changes it to the next state when next button is pressed. Checks to see if it is at the last state, in which it can't go any further forward and prints warning message
 */
function next_state() {
    let setStateToMaxValue = true; // If both models are at the maximum state then we need to make sure the GUI sets the stateNumber to the max value. Without this there is a bug where it sometimes sets to zero
    let maxStateInNextLoop = 0;
    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);

            state = parseInt(gui.Window1.stateNumber.text);
            let maxState;
            let numWindows = GetNumberOf(WINDOW);

            for (let a = 1; a <= numWindows; a++) {
                let models = GetWindowModels(a);
                for (let j = 0; j < models.nm; j++) {
                    if (modelID == models.list[j]) {
                        maxState = GetWindowMaxFrame(a);
                    }
                }
            }

            state += 1;
            if (state <= maxState) {
                DialogueInput("/STATE", state.toString());

                let currentState;
                let numWindows = GetNumberOf(WINDOW);

                for (let a = 1; a <= numWindows; a++) {
                    let models = GetWindowModels(a);
                    for (let j = 0; j < models.nm; j++) {
                        if (modelID == models.list[j]) {
                            currentState = GetWindowFrame(a);
                        }
                    }
                }

                maxStateInNextLoop = currentState;
                setStateToMaxValue = false; // if this gets hit then it means that one of the models is not at the last state, therefore we don't want to set the stateNumber to the max value
            } else {
                DialogueInput("/STATE LAST"); // This needs to be here incase user uses the general D3PLOT GUI state change and messes up the states
                WarningMessage("Model " + modelID + " is already at its last state.");
            }
        }
    }
    if (setStateToMaxValue == true) {
        gui.Window1.stateNumber.text = maxStateOfModels;
    } else {
        gui.Window1.stateNumber.text = maxStateInNextLoop;
    }

    if (gui.Window1.checkboxView.pushed == true) {
        keep_view_updated();
    }
}

/**
 * Function that changes it to the last state when final button is pressed
 */
function last_state() {
    // This if statement is for when we might want to set a state to last to only one model, this is normally in out of bounds purposes
    if (setOnlyLastStateThisModel != 0) {
        DialogueInput("/STATE LAST");
        setOnlyLastStateThisModel = 0;
    }

    // This is normally what happens when the final button is pressed, and all models are set to their last state
    else {
        for (let i = 0; i < numSelectedModels; i++) {
            let modelID = Workflow.ModelIdFromIndex(i);
            if (modelID != null) {
                DialogueInput("/CM", modelID.toString());
                SetCurrentModel(modelID);
                DialogueInput("/STATE LAST");
            }
        }
        if (setLastState == true) {
            gui.Window1.stateNumber.text = maxStateOfModels;
        }
    }

    // Updates GUI if invalid state numbers are given. Such as bigger than the max state of all models, smaller than zero or not a number.
    if (parseInt(gui.Window1.stateNumber.text) > maxStateOfModels) {
        gui.Window1.stateNumber.text = maxStateOfModels;
    }
    if (parseInt(gui.Window1.stateNumber.text) < 0) {
        gui.Window1.stateNumber.text = maxStateOfModels;
    }
    if (isNaN(parseInt(gui.Window1.stateNumber.text))) {
        gui.Window1.stateNumber.text = maxStateOfModels;
    }

    if (gui.Window1.checkboxView.pushed == true) {
        keep_view_updated();
    }
}

/**
 * If the reference state button is pressed. This is where the resultant displacement is calculated based off a selected state and model rather than state 0 of each model
 */
function ref_state() {
    // If it is pushed, then sets the model and state number selection buttons and labels to active
    if (gui.Window1.checkboxRefState.pushed == true) {
        gui.Window1.lblModel.active = true;
        gui.Window1.comboModel.active = true;
        gui.Window1.lblRefStateNumber.active = true;
        gui.Window1.txtRefStateNumber.active = true;

        // Validition checking for models, make sure model selected exists
        if (parseInt(gui.Window1.comboModel.text) > 0) {
            let selectedModel = parseInt(gui.Window1.comboModel.text);

            let selectedModelMaxStates;
            let numWindows = GetNumberOf(WINDOW);

            for (let a = 1; a <= numWindows; a++) {
                let models = GetWindowModels(a);
                for (let j = 0; j < models.nm; j++) {
                    if (selectedModel == models.list[j]) {
                        selectedModelMaxStates = GetWindowMaxFrame(a);
                    }
                }
            }

            // Validation checking for state in the model, make sure the state in the selected model exists.
            if (parseInt(gui.Window1.txtRefStateNumber.text) <= selectedModelMaxStates && parseInt(gui.Window1.txtRefStateNumber.text) > -1) {
                let selectedState = parseInt(gui.Window1.txtRefStateNumber.text);

                // Loops through the model and windows and turns on reference state based on the given reference model and state if they are valid and redraws screen
                for (let i = 0; i < numSelectedModels; i++) {
                    let modelID = Workflow.ModelIdFromIndex(i);
                    if (modelID != null) {
                        DialogueInput("/CM", modelID.toString());
                        SetCurrentModel(modelID);

                        DialogueInput("/DEFORM REF_STATE REF_MODEL NUMBER", selectedModel.toString());
                        DialogueInput("/DEFORM REF_STATE SET_REF_STATE NUMBER", selectedState.toString());
                        DialogueInput("/DEFORM REF_STATE ON");
                    }
                }
                DialogueInput("/REDRAW");

                if (gui.Window1.checkboxView.pushed == true) {
                    keep_view_updated();
                }
            } else {
                WarningMessage("The state you entered for the selected model in reference state is not valid, please enter a valid state");
            }
        } else {
            WarningMessage("The model you entered for reference state is not valid, please enter a valid model");
        }
    }

    // If the reference state option is unticked then turns off the reference state option and returns to normal resultant displacement based on state 0 of it's model
    else {
        for (let i = 0; i < numSelectedModels; i++) {
            let modelID = Workflow.ModelIdFromIndex(i);
            if (modelID != null) {
                DialogueInput("/CM", modelID.toString());
                SetCurrentModel(modelID);
                DialogueInput("/DEFORM REF_STATE OFF");
            }
        }
        DialogueInput("/REDRAW");

        // Makes the selection model and reference state number buttons and labels inactive
        gui.Window1.lblModel.active = false;
        gui.Window1.comboModel.active = false;
        gui.Window1.lblRefStateNumber.active = false;
        gui.Window1.txtRefStateNumber.active = false;
    }
}

/**
 * If the Keep View Updated checkbox is ticked, the function resets the camera to the default view anytime the plot state or reference state is changed
 */
function keep_view_updated() {
    for (let i = 0; i < numSelectedModels; i++) {
        let modelID = Workflow.ModelIdFromIndex(i);
        if (modelID != null) {
            let userData = Workflow.ModelUserDataFromIndex(i);
            DialogueInput("/CM", modelID.toString());
            SetCurrentModel(modelID);

            // Gets the values of the 3 selected nodes
            let n1 = userData["node1"];
            let n2 = userData["node2"];
            let n3 = userData["node3"];

            // Blanks all the parts and then unblanks the selected ones. p1 is the first part used in setting up the camera
            DialogueInput("/BLANK ALL");
            let intrusionPartsString = userData["parts"];
            let p1 = parseInt(intrusionPartsString);
            DialogueInput("/UNBLANK PARTS", intrusionPartsString);

            // Setting up the camera, copied from the reporter post233 script
            let n01 = GetData(BV, NODE, -1 * n1);
            let n02 = GetData(BV, NODE, -1 * n2);

            let avgEye = n01[0] + "," + n01[1] + "," + n01[2];
            let avgTarget = n02[0] + "," + n02[1] + "," + n02[2];
            // Set the EYE and TARGET and set state
            DialogueInput("/VIEW_OPTIONS TARGET_AND_EYE EYE_POS", avgEye);
            DialogueInput("VIEW_OPTIONS TARGET_AND_EYE TARGET_POS", avgTarget);
            DialogueInput("/AC");
        }
    }

    // We couldn't do this in the loop as it works for all windows not just the selected one
    DialogueInput("/VIEW_OPTIONS TARGET_AND_EYE UP_VECTOR Z_GLOBAL");
    DialogueInput("/VIEW_OPTIONS TARGET_AND_EYE UPDATE_VIEW");

    let numberOfModels = GetNumberOf(MODEL);
    for (let i = 1; i < numberOfModels + 1; i++) {
        if (ModelExists(i)) {
            DialogueInput("/CM", i.toString());
            SetCurrentModel(i);
            DialogueInput("/AC");
        }
    }
    DialogueInput("/VIEW_OPTIONS PERSPECTIVE OFF");
}

/**
 * Opens up the clickhelp manual when the help button is pressed
 */
function workflow_manual() {
    OpenManual("d3plot", "intrusion-contour-plot.html");
}