Skip to content
Snippets Groups Projects
Select Git revision
  • c21a4d96ed8da00943fabd05f3c5602dbbd3d67b
  • dev default
2 results

main.js

Blame
  • main.js 30.38 KiB
    import "normalize.css";
    import "./css/main.css";
    import tailorbirdLogo from "./img/logo-new.jpg";
    
    // Zdog
    import * as Zdog from "zdog";
    
    // Font Awesome
    import "@fortawesome/fontawesome-free/css/fontawesome.css";
    import "@fortawesome/fontawesome-free/css/solid.css";
    
    var aboutText = `
    </h1>
    <p>Version: ${ import.meta.env.VITE_APP_DATE }
    (${ import.meta.env.VITE_APP_HASH })</p>
    `;
    if (import.meta.env.VITE_APP_BRANCH == "dev") {
      aboutText = `
      <i class="fa-solid fa-kiwi-bird fa-bounce"></i> TESTING</h1>
      <p>Version: ${ import.meta.env.VITE_APP_DATE }
      (${ import.meta.env.VITE_APP_BRANCH }
      ${ import.meta.env.VITE_APP_HASH })</p>
      `;
    }
    
    document.querySelector("#app").innerHTML = `
    <main>
      <section id="viewer">
        <article id="viewer-main">
          <canvas class="zdog-canvas"></canvas>
        </article>
        <article id="viewer-settings">
          <menu>
            <li>
              <button id="zoomIntoViewButton">
                <i class="fa-solid fa-magnifying-glass-plus"></i>
              </button>
            </li>
            <li>
              <button id="zoomOutViewButton">
                <i class="fa-solid fa-magnifying-glass-minus"></i>
              </button>
            </li>
            <li>
              <button id="playViewButton">
                <i class="fa-solid fa-play"></i> Simulate
              </button>
            </li>
          </menu>
          <menu class="menu-impressum">
            <li>
              <a href="#" id="helpLink">About</a>
            </li>
            <li>
              <a href="mailto:yanagibashi@kg.rwth-aachen.de">Feedback</a>
            </li>
            <li>
              <a href="https://www.kg.rwth-aachen.de/cms/KG/Footer/Service/~vcyq/Impressum/">Impressum</a>
            </li>
          </menu>
          <menu class="menu-right">
          <li>
            <button id="saveProjectMenuButton">
              <i class="fa-solid fa-floppy-disk"></i> Save
            </button>
          </li>
          <li>
            <button id="loadProjectMenuButton">
              <i class="fa-solid fa-folder-open"></i> Load
            </button>
          </li>
          <li>
            <button id="exportProjectMenuButton">
              <i class="fa-solid fa-file-export"></i> Export
            </button>
          </li>
            <li>
              <button id="openSettingsButton">
                <i class="fa-solid fa-gear"></i> Settings
              </button>
            </li>
          </menu>
        </article>
      </section>
      <section id="tools">
        <img id="tailorbirdLogo" src="${tailorbirdLogo}" />
        <article id="tools-edit">
          <menu>
            <li>
              <button id="addLineEditButton">
                <i class="fa-solid fa-lines-leaning"></i> Add line
              </button>
            </li>
            <li>
              <button id="addArcEditButton">
                <i class="fa-solid fa-bezier-curve"></i> Add arc
              </button>
            </li>
          </menu>
        </article>
        <article id="tools-list">
          <ul id="list-commands"></ul>
        </article>
        <article id="tools-list-buttons">
          <menu>
            <!-- <li>
              <button id="editCommandButton" disabled>
                <i class="fa-solid fa-pen-to-square"></i> Edit
              </button>
            </li> -->
            <li>
              <button id="removeCommandButton">
                <i class="fa-solid fa-trash"></i> Remove
              </button>
            </li>
          </menu>
        </article>
      </section>
    </main>
    
    <!-- Dialogs -->
    <dialog id="helpDialog">
      <h1>Tailorbird 3D ${ aboutText }
      <h2><i class="fa-solid fa-keyboard"></i> Controls</h2>
      <p>
       <ul>
        <li><i class="fa-solid fa-magnifying-glass"></i> <span class="helpControlText">Zoom: Scroll wheel</span></li>
        <li><i class="fa-solid fa-left-right"></i> <span class="helpControlText">Rotate horizontally: Drag</span></li>
        <li><i class="fa-solid fa-up-down"></i> <span class="helpControlText">Rotate vertically: Shift+Drag</span></li>
        <li><i class="fa-solid fa-up-down-left-right"></i> <span class="helpControlText">Move: Opt/Alt+Drag</span></li>
        <li><i class="fa-solid fa-share fa-rotate-90"></i> <span class="helpControlText">Jump to: O</span></li>
       </ul>
      </p>
      <form method="dialog">
        <menu>
          <button value="example1">Load example 1</button>
          <button value="example2">Load example 2</button>
          <button value="cancel">Close</button>
        </menu>
      </form>
    </dialog>
    
    <!-- Dialogs Main menu -->
    <dialog id="saveDialog">
      <form method="dialog">
        <input type="text" id="saveDialogInput" />
        <menu>
          <button value="save">Save</button>
          <button value="cancel">Cancel</button>
        </menu>
      </form>
    </dialog>
    
    <dialog id="loadDialog">
      <form method="dialog">
        <input type="text" id="loadDialogInput" />
        <br />
        <ul id="loadDialogList">
        </ul>
        <menu>
          <button value="load">Load</button>
          <button value="cancel">Cancel</button>
        </menu>
      </form>
    </dialog>
    
    <dialog id="exportDialog">
      <form method="dialog">
        <menu>
          <button value="export">Save as G-code</button>
          <button value="close">Close</button>
        </menu>
      </form>
    </dialog>
    
    <!-- Dialogs Edit menu -->
    <dialog id="addLineDialog">
      <form method="dialog" id="addLineForm">
        <h3>Add line</h3>
        <fieldset>
          <legend>Position</legend>
          <input
            type="radio"
            id="addLineInputAbsRel2"
            name="addLineInputAbsRel"
            value="rel"
            checked
          />
          <label for="addLineInputAbsRel2">Relative</label>
          <input
            type="radio"
            id="addLineInputAbsRel1"
            name="addLineInputAbsRel"
            value="abs"
          />
          <label for="addLineInputAbsRel1">Absolute</label>
          <br />
          X: <input type="text" id="addLineInputX" value="0" /><br />
          Y: <input type="text" id="addLineInputY" value="0" /><br />
          Z: <input type="text" id="addLineInputZ" value="0" /><br />
        </fieldset>
        <fieldset>
          <legend>Behavior</legend>
          <input
            type="radio"
            id="addLineInputExtPmmAbs1"
            name="addLineInputExtPmmAbs"
            value="pmm"
            checked
          />
          <label for="addLineInputExtPmmAbs1">per mm</label>
          <input
            type="radio"
            id="addLineInputExtPmmAbs2"
            name="addLineInputExtPmmAbs"
            value="abs"
          />
          <label for="addLineInputExtPmmAbs2">Absolute</label>
          <br />
          Flow (<strong>E</strong>xtrusion):
          <input
            type="text"
            id="addLineInputE"
            value="1"
          /><br />
          Speed (<strong>F</strong>eed):
          <input
            type="text"
            id="addLineInputF"
            value="1500"
          /><br />
        </fieldset>
        <menu>
          <button value="add">Add</button>
          <button value="cancel">Cancel</button>
        </menu>
      </form>
    </dialog>
    
    <dialog id="addArcDialog">
      <form method="dialog" id="addArcForm">
        <h3>Add arc</h3>
        <fieldset>
          <legend>Properties</legend>
          Size: <input type="text" id="addArcInputSize" /><br />
          Curvature:
          <br />
          <input
            type="radio"
            id="addArcInputCurvature1"
            name="addArcInputCurvature"
            value="1"
            checked
          />
          <label for="addArcInputCurvature1">Curved upwards</label>
          <input
            type="radio"
            id="addArcInputCurvature2"
            name="addArcInputCurvature"
            value="2"
          />
          <label for="addArcInputCurvature2">Curved downwards</label>
          <br />
          Direction:
          <br />
          <input
            type="radio"
            id="addArcInputDirection1"
            name="addArcInputDirection"
            value="1"
            checked
          />
          <label for="addArcInputDirection1">X pos</label>
          <input
            type="radio"
            id="addArcInputDirection2"
            name="addArcInputDirection"
            value="2"
          />
          <label for="addArcInputDirection2">Y pos</label>
          <input
            type="radio"
            id="addArcInputDirection3"
            name="addArcInputDirection"
            value="3"
          />
          <label for="addArcInputDirection3">X neg</label>
          <input
            type="radio"
            id="addArcInputDirection4"
            name="addArcInputDirection"
            value="4"
          />
          <label for="addArcInputDirection4">Y neg</label>
    
          <br />
          <input
            type="radio"
            id="addArcInputDirectionZ1"
            name="addArcInputDirectionZ"
            value="1"
            checked
          />
          <label for="addArcInputDirectionZ1">Up</label>
    
          <input
            type="radio"
            id="addArcInputDirectionZ2"
            name="addArcInputDirectionZ"
            value="2"
          />
          <label for="addArcInputDirectionZ2">Down</label>
    
          <input
            type="radio"
            id="addArcInputDirectionZ3"
            name="addArcInputDirectionZ"
            value="3"
          />
          <label for="addArcInputDirectionZ2">Flat</label>
        </fieldset>
    
        <fieldset>
          <legend>Behavior</legend>
          Flow:
          <input
            type="text"
            id="addArcInputE"
            placeholder="Extrusion rate"
            value="0"
          /><br />
          Speed:
          <input
            type="text"
            id="addArcInputF"
            placeholder="Feed rate"
            value="1500"
          /><br />
        </fieldset>
        <menu>
          <button value="add">Add</button>
          <button value="cancel">Cancel</button>
        </menu>
      </form>
    </dialog>
    
    <!-- Dialogs View menu -->
    <dialog id="settingsDialog">
      <form method="dialog" id="settingsForm">
        <fieldset>
          <legend>Grid</legend>
          <input
            type="checkbox"
            id="settingsGridDisplay"
            name="settingsGridDisplay"
            checked=""
          />
          <label for="settingsGridDisplay"> Display grid</label><br />
          <br />
          <label for="settingsGridSizeInput">Size: </label>
          <input
            type="text"
            id="settingsGridSizeInput"
            placeholder="Default: 10"
          />
          mm
        </fieldset>
        <fieldset>
          <legend>Rendering</legend>
          <input
            type="checkbox"
            id="settingsRenderNoExtrusionDisplay"
            name="settingsRenderNoExtrusionDisplay"
            checked=""
          />
          <label for="settingsRenderNoExtrusionDisplay">
            Display moves without extrusion</label
          >
        </fieldset>
        <fieldset>
          <legend>G-code Export</legend>
          <label for="settingsGcodeStartCode">Start Code</label><br />
          <textarea id="settingsGcodeStartCode" name="settingsGcodeStartCode">
    ; -- START GCODE --
    G90
    M82
    G28
    G92 E0.0000
    G1 E-4.0000 F60000
    ; -- end of START GCODE --</textarea
          >
          <br /><br />
          <label for="settingsGcodeEndCode">End Code</label><br />
          <textarea id="settingsGcodeEndCode" name="settingsGcodeEndCode">
    ; -- END GCODE --
    G92 E0.0000
    G1 E-4.0000 F60000
    G28
    ; -- end of END GCODE --</textarea
          >
        </fieldset>
        <menu>
          <button value="apply">Apply</button>
        </menu>
      </form>
    </dialog>
    `;
    
    // Old main.js
    import { Project } from "./js/project.js";
    import { Move } from "./js/command.js";
    
    function getPresetModelVase() {
      var presetProject = new Project();
      var precision = 10;
      var zoom = 30;
      const levels = 20;
      const extMultiplier = 1;
      const levelHeight = 3;
      var previousGoal = {
        x: 0,
        y: 0,
        e: -zoom,
      };
      var currentGoal = {
        x: 0,
        y: 0,
        e: 0,
      };
      for (var e = 0; e < levels; e++) {
        for (var i = 0; i < precision; i++) {
          currentGoal.x = Math.sin((1 / precision) * 2 * Math.PI * i) * zoom;
          currentGoal.y = Math.cos((1 / precision) * 2 * Math.PI * i) * zoom;
          currentGoal.e =
            previousGoal.e +
            Math.hypot(
              currentGoal.x - previousGoal.x,
              currentGoal.y - previousGoal.y
            );
          var newCommand = new Move(
            currentGoal.e * extMultiplier,
            1250,
            currentGoal.x,
            currentGoal.y,
            e * levelHeight + levelHeight / precision
          );
          presetProject.model.append(newCommand);
          previousGoal.x = currentGoal.x;
          previousGoal.y = currentGoal.y;
          previousGoal.e = currentGoal.e;
        }
        if (e < levels / 2) {
          zoom += 0.5 + (e % 10) / 10;
          precision += 1;
        } else {
          zoom -= 0.5 + (e % 10) / 10;
          precision -= 1;
        }
      }
    
      return presetProject;
    }
    
    function getPresetModelVertical() {
      var presetProject = new Project();
      var extMult = 4;
      var ext = 0;
      const startX = 35;
      var currPoint;
    
      presetProject.addLine(ext, 2500, 0, 0, 0);
    
      presetProject.addLine(ext, 1500, startX - extMult, 0, 0);
      presetProject.addLine(ext += 4, 1500, startX, 0, 0);
      presetProject.addLine(ext, 1500, startX + 15, 0, 0);
      presetProject.addLine(ext, 1500, startX - extMult, 0, 10);
    
      for (var i = 1; i < 10; i++) {
        currPoint = i * extMult;
        // Go to starting point
        presetProject.addLine(ext, 2000, startX - currPoint, 0, 0);
        presetProject.addLine(ext += extMult, 1500, startX - currPoint, 0, 0);
        // Draw arc
        presetProject.addArc(ext += currPoint * 1.8, 1500, currPoint, "1", "1", "1");
        
        presetProject.addLine(ext, 2000, startX + currPoint / 2, 0, Math.max(currPoint - 25, 0));
        presetProject.addLine(ext, 2000, startX + currPoint / 2 + 10, 0, 0);
        presetProject.addLine(ext, 1500, startX, 0, currPoint + 10);
        presetProject.addLine(ext, 1500, startX - currPoint - 10, 0, currPoint);
      }
    
      return presetProject;
    }
    
    /* Model modification */
    function editSelectedCommand() {}
    
    function removeSelectedCommand() {
      // Remove item from list in DOM
      window.selectedListEntries.forEach((entry) => {
        entry.listElement.parentNode.removeChild(entry.listElement);
        window.currentProject.model.remove(entry);
      });
      window.selectedListEntries = [];
      //window.currentProject.draw();
      /*
      document
        .getElementById("list-commands")
        .removeChild(window.selectedListEntry);
      // Remove reference from Model to Command
      window.currentProject.removeCommand(window.selectedListEntry.parentCommand);*/
    }
    
    /* 3D & UI */
    
    function initIllo() {
    
      window.illo = new Zdog.Illustration({
        element: ".zdog-canvas",
        scale: 10,
        resize: true,
        rotate: { x: Zdog.TAU * 0.2 },
      });
    
      // Wheel (zoom)
      window.illo.element.addEventListener("wheel", (e) => {
        e.preventDefault();
        // Zoom into: positive, Zoom out: negative, e.deltaY
        if (
          (window.illo.scale.x < 2 && e.deltaY > 0) ||
          (window.illo.scale.x > 80 && e.deltaY < 0)
        ) {
          return;
        }
        window.illo.scale.multiply(
          1 - Math.max(Math.min(e.deltaY / 40, 0.9), -0.9)
        );
      });
    
      // Drag (move)
      window.viewRotation = new Zdog.Vector();
      new Zdog.Dragger({
        startElement: window.illo.element,
        onDragStart: function (pointer) {
          if (pointer.buttons == 4 || pointer.altKey) {
            // Move object with middle mouse key or Meta key
            window.dragStartRX = window.illo.translate.x;
            window.dragStartRY = window.illo.translate.y;
          } else if (pointer.shiftKey) {
            // Rotate vertically with Shift key
            window.dragStartRX = window.illo.rotate.x;
          } else {
            // Rotate horizontally
            window.dragStartRX = window.illo.rotate.z;
          }
        },
        onDragMove: function (pointer, moveX, moveY) {
          if (pointer.buttons == 4 || pointer.altKey) {
            // Move object with middle mouse key or Meta key
            window.illo.translate.x = window.dragStartRX + moveX;
            window.illo.translate.y = window.dragStartRY + moveY;
          } else if (pointer.shiftKey) {
            // Rotate vertically with Shift key
            let moveRX = (moveY / illo.width) * Zdog.TAU * -1;
            window.illo.rotate.x = window.dragStartRX + moveRX;
          } else {
            // Rotate horizontally
            let moveRX = (moveX / illo.width) * Zdog.TAU * -1;
            window.illo.rotate.z = window.dragStartRX + moveRX;
          }
        },
      });
    
      // O button (jump to)
      window.addEventListener("keydown", (event) => {
        if (event.isComposing || event.key !== "o" || !window.selectedListEntries[0]) {
          return;
        }
        const jumpCmd = window.selectedListEntries[0];
        if (!"goalx" in jumpCmd || !"goaly" in jumpCmd) {
          return;
        }
        var illoTrans = new Zdog.Vector({
          x: -jumpCmd.goaly.value * window.illo.scale.x,
          y: -jumpCmd.goalx.value * window.illo.scale.y,
          z: -jumpCmd.goalz.value * window.illo.scale.z,
        });
        illoTrans.rotate(window.illo.rotate);
        console.log("jump to X: " + illoTrans.x + ", Y: " + illoTrans.y);
        window.illo.translate = illoTrans;
      });
    
      window.currentProject.renderAll();
    
      animateIllo();
    }
    
    function animateIllo() {
      window.illo.updateRenderGraph();
      window.animationReq = requestAnimationFrame(animateIllo);
    }
    
    /* Saving & loading */
    
    function saveProject(projectToSave) {
      console.log("Save project to: " + projectToSave);
      saveToStorage(window.currentProject, projectToSave);
    }
    
    function loadProject(projectToLoad) {
      console.log("Load project from: " + projectToLoad);
      const loadedProject = loadFromStorage(projectToLoad);
      //window.currentProject.unload();
      window.currentProject = loadedProject;
      //window.currentProject.load();
    }
    
    /*function loadProject() {
      const projectToLoad = document.getElementById("project-input").value;
      if (!Number.isInteger(parseInt(projectToLoad))) return;
      if (projectToLoad.length != 4) return;
      const functionName = "loadProject" + projectToLoad;
      //runFunctionByName(functionName)();
      eval(functionName + "()");
      animateIllo();
    }*/
    
    /** LocalStorage **/
    
    function loadFromStorage(modelName) {
      const loadedJson = localStorage.getItem("model" + modelName);
      if (!loadedJson) return;
    
      const loadedObject = JSON.parse(loadedJson);
      var loadedProject = new Project();
    
      loadedObject.moves.forEach((command) => {
        loadedProject.model.append(
          new Move(
            command[0].value,
            command[1].value,
            command[2].value,
            command[3].value,
            command[4].value
          )
        );
      });
    
      return loadedProject;
    }
    
    function saveToStorage(modelToSave, modelName) {
      var jsonObject = { moves: [] };
      currentProject.model.commands.forEach((command) => {
        jsonObject.moves.push([
          command.flow,
          command.speed,
          command.goalx,
          command.goaly,
          command.goalz,
        ]);
      });
      const jsonToSave = JSON.stringify(jsonObject);
      console.log(jsonToSave);
    
      localStorage.setItem("model" + modelName, jsonToSave);
    }
    
    /** Files **/
    
    function saveToFile() {
      const a = document.createElement("a");
      const file = new Blob([document.getElementById("textarea-output").value], {
        type: "text/plain",
      });
      a.href = URL.createObjectURL(file);
      a.download = "output.gcode";
      a.click();
      URL.revokeObjectURL(a.href);
    }
    
    /* Event listeners */
    
    function initEventListeners() {
      initMainEventListeners(); /* Top bar menu */
      initEditEventListeners(); /* Edit menu */
      initViewEventListeners(); /* View menu */
      initModalEventListeners(); /* All modals */
    }
    
    function initMainEventListeners() {
      document
        .getElementById("exportProjectMenuButton")
        .addEventListener("click", function onOpen() {
          document.getElementById("exportDialog").showModal();
        });
    
      document
        .getElementById("saveProjectMenuButton")
        .addEventListener("click", function onOpen() {
          document.getElementById("saveDialog").showModal();
        });
    
      document
        .getElementById("loadProjectMenuButton")
        .addEventListener("click", function onOpen() {
          const saveList = document.getElementById("loadDialogList");
          saveList.replaceChildren();
          for (let i = 0; i < localStorage.length; i++) {
            if (!localStorage.key(i).startsWith("model")) {
              continue;
            }
            var saveItem = document.createElement("li");
            saveItem.appendChild(
              document.createTextNode(localStorage.key(i).substring(5))
            );
            saveList.appendChild(saveItem);
          }
          document.getElementById("loadDialog").showModal();
        });
    
      document
        .getElementById("helpLink")
        .addEventListener("click", function onOpen() {
          document.getElementById("helpDialog").showModal();
        });
    }
    
    function initEditEventListeners() {
      // Add line / arc
      document
        .getElementById("addLineEditButton")
        .addEventListener("click", function onOpen() {
          document.getElementById("addLineDialog").showModal();
        });
    
      document
        .getElementById("addArcEditButton")
        .addEventListener("click", function onOpen() {
          document.getElementById("addArcDialog").showModal();
        });
    
      // Remove / Edit
      document
        .getElementById("removeCommandButton")
        .addEventListener("click", removeSelectedCommand);
      /*
      document
        .getElementById("editCommandButton")
        .addEventListener("click", editSelectedCommand);*/
    
      // Select Command in List
      document
        .getElementById("list-commands")
        .addEventListener("click", function (e) {
          e.preventDefault();
          if (!e.shiftKey) {
            // Remove old selection
            window.selectedListEntries.forEach((entry) => entry.removeSelection());
            window.selectedListEntries = [];
          }
          // Add selection
          if (e.target && e.target.matches("li")) {
            //window.selectedListEntry = e.target; TEST for removal
            e.target.command.addSelection();
            window.selectedListEntries.push(e.target.command);
          }
        });
    }
    
    function initViewEventListeners() {
      document
        .getElementById("zoomIntoViewButton")
        .addEventListener("click", function (e) {
          // Zoom into
          window.illo.scale.multiply(1.1);
        });
      document
        .getElementById("zoomOutViewButton")
        .addEventListener("click", function (e) {
          // Zoom out
          window.illo.scale.multiply(1 / 1.1);
        });
      document
        .getElementById("playViewButton")
        .addEventListener("click", function (e) {
          if (e.target.lastChild.nodeValue.includes("Simulate")) {
            window.currentProject.drawPlaybackStart();
          } else {
            window.currentProject.drawPlaybackStop();
          }
        });
      document
        .getElementById("openSettingsButton")
        .addEventListener("click", function (e) {
          document.getElementById("settingsDialog").showModal();
        });
    }
    
    function saveAs(blob, name) {
      const a = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
      a.download = name;
      a.rel = "noopener";
      a.href = URL.createObjectURL(blob);
      a.target = "_blank";
    
      setTimeout(() => {
        URL.revokeObjectURL(a.href);
        document.body.removeChild(a);
      }, 30000);
      setTimeout(() => {
        document.body.appendChild(a);
        a.click();
      }, 0);
    }
    
    function initModalEventListeners() {
      document
        .getElementById("exportDialog")
        .addEventListener("close", function onClose() {
          // Check if Export button was pressed
          if (event.target.returnValue != "export") return;
          console.log("Exporting to G-code...");
    
          var gcode = "";
          /*var maxVals = { x: 0, y: 0, z: 0, e: 0 };
          var minVals = { x: 0, y: 0, z: 0 };*/
    
          let initCode = {
            start: null,
            end: null,
          };
          if (
            !(
              (initCode.start = localStorage.getItem("settingsGcodeStartCode")) &&
              (initCode.end = localStorage.getItem("settingsGcodeEndCode"))
            )
          ) {
            initCode = {
              start: `; -- START GCODE --
    G90
    M82
    G28
    G92 E0.0000
    G1 E-4.0000 F60000
    ; -- end of START GCODE --
    `,
              end: `; -- END GCODE --
    G92 E0.0000
    G1 E-4.0000 F60000
    G28
    ; -- end of END GCODE --
    `,
            };
          }
    
          gcode += initCode.start;
    
          currentProject.model.commands.forEach((command) => {
            gcode += command + "\n";
            /*
            if (command instanceof Move) {
              const parameters = command.getParameters();
              const vals = {
                x: parameters[0].getValue(),
                y: parameters[1].getValue(),
                z: parameters[2].getValue(),
                e: parameters[4].getValue(),
              };
              if (vals.z < 0.0) {
                this.addStatOutput("CAUTION: Z-Axis below ground: " + command);
              }
              if (vals.e > maxVals.e) {
                if (vals.x > maxVals.x) maxVals.x = vals.x;
                if (vals.y > maxVals.y) maxVals.y = vals.y;
                if (vals.z > maxVals.z) maxVals.z = vals.z;
                if (vals.x < minVals.x) minVals.x = vals.x;
                if (vals.y < minVals.y) minVals.y = vals.y;
                if (vals.z < minVals.z) minVals.z = vals.z;
              }
              maxVals.e = vals.e;
            }*/
          });
          /*
          this.addStatOutput(
            "Max X: " +
              maxVals.x +
              ", Max Y: " +
              maxVals.y +
              ", Max Z: " +
              maxVals.z +
              ", Max E: " +
              maxVals.e
          );
          this.addStatOutput(
            "Min X: " + minVals.x + ", Min Y: " + minVals.y + ", Min Z: " + minVals.z
          );
          this.addStatOutput("G-Code generation successful.");*/
          gcode += initCode.end;
    
          // DEBUG!!
          //console.log(gcode);
          //return;
    
          var blob = new Blob([gcode], { type: "text/plain;charset=utf-8" });
          saveAs(blob, "tailorbird.gcode");
        });
    
      document
        .getElementById("settingsDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Apply button was pressed
          if (e.target.returnValue != "apply") return;
          const data = new FormData(document.querySelector("#settingsForm"));
    
          // Grid display
          if (data.has("settingsGridDisplay")) {
            localStorage.setItem("settingsGridDisplay", "on");
          } else {
            localStorage.setItem("settingsGridDisplay", "off");
          }
    
          // Grid size
          const gridSizeInput = document.getElementById("settingsGridSizeInput");
          if (Number.isInteger(parseInt(gridSizeInput.value, 10))) {
            localStorage.setItem("settingsGridSize", gridSizeInput.value);
          } else {
            localStorage.setItem("settingsGridSize", "10");
          }
          gridSizeInput.value = localStorage.getItem("settingsGridSize");
    
          // Non-extruding move display
          if (data.has("settingsRenderNoExtrusionDisplay")) {
            localStorage.setItem("settingsRenderNoExtrusionDisplay", "on");
          } else {
            localStorage.setItem("settingsRenderNoExtrusionDisplay", "off");
          }
    
          // G-code start / end code
          localStorage.setItem(
            "settingsGcodeStartCode",
            data.get("settingsGcodeStartCode")
          );
          localStorage.setItem(
            "settingsGcodeEndCode",
            data.get("settingsGcodeEndCode")
          );
    
          // Re-render everything
          window.currentProject.renderAll();
        });
    
      document
        .getElementById("saveDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Load button was pressed
          if (e.target.returnValue != "save") return;
          const inputEl = document.getElementById("saveDialogInput");
          saveProject(inputEl.value);
        });
    
      document
        .getElementById("loadDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Load button was pressed
          if (e.target.returnValue != "load") return;
          const inputEl = document.getElementById("loadDialogInput");
          const saveInputEl = document.getElementById("saveDialogInput");
          loadProject(inputEl.value);
          saveInputEl.value = inputEl.value;
          inputEl.value = "";
        });
    
      document
        .getElementById("helpDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Load button was pressed
          switch (e.target.returnValue) {
            case "example1":
              window.currentProject = getPresetModelVase();
              break;
            case "example2":
              window.currentProject = getPresetModelVertical();
              break;
            default:
              return;
          }
        });
    
      document
        .getElementById("addLineDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Add button was pressed
          if (e.target.returnValue != "add") return;
    
          let inputX = document.getElementById("addLineInputX");
          let inputY = document.getElementById("addLineInputY");
          let inputZ = document.getElementById("addLineInputZ");
          let inputE = document.getElementById("addLineInputE"); // Extrusion (E) - Flow
          let inputF = document.getElementById("addLineInputF"); // Feed rate (F) - Speed
          const data = new FormData(document.querySelector("#addLineForm"));
          const currCoords = currentProject.model.currentCoordinates();
    
          // TODO: Make this nicer
          if (data.get("addLineInputAbsRel") == "rel") {
            if (data.get("addLineInputExtPmmAbs") == "pmm") {
              window.currentProject.addLine(
                Number.parseFloat(inputE.value) *
                  Math.hypot(
                    Number.parseFloat(inputX.value),
                    Number.parseFloat(inputY.value),
                    Number.parseFloat(inputZ.value)
                  ) +
                  currCoords.e,
                Number.parseFloat(inputF.value),
                Number.parseFloat(inputX.value) + currCoords.x,
                Number.parseFloat(inputY.value) + currCoords.y,
                Number.parseFloat(inputZ.value) + currCoords.z
              );
            } else {
              window.currentProject.addLine(
                Number.parseFloat(inputE.value) + currCoords.e,
                Number.parseFloat(inputF.value),
                Number.parseFloat(inputX.value) + currCoords.x,
                Number.parseFloat(inputY.value) + currCoords.y,
                Number.parseFloat(inputZ.value) + currCoords.z
              );
            }
          } else {
            if (data.get("addLineInputExtPmmAbs") == "pmm") {
              window.currentProject.addLine(
                Number.parseFloat(inputE.value) *
                  Math.hypot(
                    Number.parseFloat(inputX.value) - currCoords.x,
                    Number.parseFloat(inputY.value) - currCoords.y,
                    Number.parseFloat(inputZ.value) - currCoords.z
                  ) +
                  currCoords.e,
                Number.parseFloat(inputF.value),
                Number.parseFloat(inputX.value),
                Number.parseFloat(inputY.value),
                Number.parseFloat(inputZ.value)
              );
            } else {
              window.currentProject.addLine(
                Number.parseFloat(inputE.value) + currCoords.e,
                Number.parseFloat(inputF.value),
                Number.parseFloat(inputX.value),
                Number.parseFloat(inputY.value),
                Number.parseFloat(inputZ.value)
              );
            }
          }
          inputX.value = "";
          inputY.value = "";
          inputZ.value = "";
        });
    
      document
        .getElementById("addArcDialog")
        .addEventListener("close", function onClose(e) {
          // Check if Add button was pressed
          if (e.target.returnValue != "add") return;
    
          const size = document.getElementById("addArcInputSize");
          const data = new FormData(document.querySelector("#addArcForm"));
          window.currentProject.addArc(
            data.get("addArcInputE"),
            data.get("addArcInputF"),
            size.value,
            data.get("addArcInputDirection"),
            data.get("addArcInputDirectionZ"),
            data.get("addArcInputCurvature")
          );
          size.value = "";
        });
    }
    
    /* Load application */
    window.onload = function () {
      window.currentProject = new Project();
      window.selectedListEntries = [];
      initIllo();
      initEventListeners();
      if (localStorage.getItem("splashSeen") !== "1") {
        localStorage.setItem("splashSeen", "1");
        document.getElementById("helpDialog").showModal();
      }
    };