Full Code of MrCrayfish/ModelCreator for AI

master b85b0da614ef cached
86 files
471.1 KB
105.6k tokens
724 symbols
1 requests
Download .txt
Showing preview only (502K chars total). Download the full file or copy to clipboard to get everything.
Repository: MrCrayfish/ModelCreator
Branch: master
Commit: b85b0da614ef
Files: 86
Total size: 471.1 KB

Directory structure:
gitextract_b14my70h/

├── .gitignore
├── LICENSE
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── mrcrayfish/
        │           └── modelcreator/
        │               ├── Actions.java
        │               ├── Animation.java
        │               ├── Camera.java
        │               ├── Constants.java
        │               ├── Exporter.java
        │               ├── ExporterJavaCode.java
        │               ├── ExporterModel.java
        │               ├── Icons.java
        │               ├── Importer.java
        │               ├── ModelCreator.java
        │               ├── Processor.java
        │               ├── ProjectManager.java
        │               ├── PropertyIdentifiers.java
        │               ├── Settings.java
        │               ├── Start.java
        │               ├── StateManager.java
        │               ├── TexturePath.java
        │               ├── component/
        │               │   ├── DisplayPropertiesDialog.java
        │               │   ├── JElementList.java
        │               │   ├── Menu.java
        │               │   ├── MenuAdapter.java
        │               │   ├── TextureEntryEditor.java
        │               │   └── TextureManager.java
        │               ├── dialog/
        │               │   └── WelcomeDialog.java
        │               ├── display/
        │               │   ├── CanvasRenderer.java
        │               │   ├── DisplayProperties.java
        │               │   └── render/
        │               │       ├── DisplayPropertyRenderer.java
        │               │       ├── FirstPersonPropertyRenderer.java
        │               │       ├── FixedPropertyRenderer.java
        │               │       ├── GroundPropertyRenderer.java
        │               │       ├── GuiPropertyRenderer.java
        │               │       ├── HeadPropertyRenderer.java
        │               │       ├── StandardRenderer.java
        │               │       └── ThirdPersonPropertyRenderer.java
        │               ├── element/
        │               │   ├── Element.java
        │               │   ├── ElementCellEntry.java
        │               │   ├── ElementCellRenderer.java
        │               │   ├── ElementManager.java
        │               │   ├── ElementManagerState.java
        │               │   └── Face.java
        │               ├── object/
        │               │   └── FaceDimension.java
        │               ├── panels/
        │               │   ├── CuboidTabbedPane.java
        │               │   ├── DisplayEntryPanel.java
        │               │   ├── ElementExtraPanel.java
        │               │   ├── FaceExtrasPanel.java
        │               │   ├── GlobalPanel.java
        │               │   ├── IElementUpdater.java
        │               │   ├── OriginPanel.java
        │               │   ├── PositionPanel.java
        │               │   ├── SidebarPanel.java
        │               │   ├── SizePanel.java
        │               │   ├── TexturePanel.java
        │               │   ├── UVPanel.java
        │               │   └── tabs/
        │               │       ├── ElementPanel.java
        │               │       ├── FacePanel.java
        │               │       └── RotationPanel.java
        │               ├── screenshot/
        │               │   ├── PendingScreenshot.java
        │               │   ├── Screenshot.java
        │               │   ├── ScreenshotCallback.java
        │               │   └── Uploader.java
        │               ├── sidebar/
        │               │   ├── Sidebar.java
        │               │   └── UVSidebar.java
        │               ├── texture/
        │               │   ├── Clipboard.java
        │               │   ├── TextureAnimation.java
        │               │   ├── TextureAtlas.java
        │               │   └── TextureEntry.java
        │               └── util/
        │                   ├── AssetsUtil.java
        │                   ├── AtlasRenderUtil.java
        │                   ├── ComponentUtil.java
        │                   ├── FontManager.java
        │                   ├── KeyboardUtil.java
        │                   ├── OperatingSystem.java
        │                   ├── Parser.java
        │                   ├── SharedLibraryLoader.java
        │                   ├── StreamUtils.java
        │                   └── Util.java
        └── resources/
            ├── bebas_neue.otf
            └── models/
                ├── cauldron.model
                └── modern_chair.model

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Windows image file caches
Thumbs.db
ehthumbs.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk

# =========================
# Operating System Files
# =========================

# OSX
# =========================

.DS_Store
.AppleDouble
.LSOverride

# Thumbnails
._*

# Files that might appear on external disk
.Spotlight-V100
.Trashes

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
/bin/

.project
.classpath
*.classpath
.classpath
test.json
/bin1/
/bin1/
out/
.idea/
*.iml
src/META-INF/MANIFEST.MF
.metadata/
/.gradle/
/build/
.settings/
.project
classes/


================================================
FILE: LICENSE
================================================
Copyright © 2016 MrCrayfish

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: build.gradle
================================================
apply plugin: "java"
apply plugin: "application"

version = "0.7.0"
mainClassName = "com.mrcrayfish.modelcreator.Start"
sourceCompatibility = targetCompatibility = 1.8

repositories {
	mavenCentral()
}

dependencies {
	compile 'com.google.code.gson:gson:2.8.5'
	compile 'org.slick2d:slick2d-core:1.0.2'
	compile 'com.jtattoo:JTattoo:1.6.11'
	
	compile 'org.lwjgl.lwjgl:lwjgl:2.9.3'
	compile 'org.lwjgl.lwjgl:lwjgl_util:2.9.3'
	compile 'org.lwjgl.lwjgl:lwjgl-platform:2.9.3'
}

jar {
	manifest {
		attributes "Main-Class": mainClassName
	}
	
	from {
		configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
	}
}

================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: gradlew
================================================
#!/usr/bin/env sh

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
  cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Actions.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.element.Element;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.Face;

/**
 * Author: MrCrayfish
 */
public class Actions
{
    public static int optimiseModel(ElementManager manager)
    {
        int count = 0;
        for(Element element : manager.getAllElements())
        {
            for(Face face : element.getAllFaces())
            {
                if(face.isEnabled() && !face.isVisible(manager))
                {
                    count++;
                    face.setEnabled(false);
                }
            }
        }
        if(count > 0)
        {
            StateManager.pushState(manager);
        }
        return count;
    }

    public static void rotateModel(ElementManager manager, boolean clockwise)
    {
        manager.getAllElements().forEach(element -> rotateElement(element, clockwise));
        manager.updateValues();
        StateManager.pushState(manager);
    }

    private static void rotateElement(Element element, boolean clockwise)
    {
        /* Calculates and sets the new starting x and y position of the element */
        double newX;
        double newZ;
        if(clockwise)
        {
            newX = element.getStartX() - 8 > 0 ? 16 - (element.getDepth() + element.getStartZ()) : 16 - element.getDepth() - element.getStartZ();
            newZ = element.getStartX();
        }
        else
        {
            newX = element.getStartZ();
            newZ = element.getStartZ() - 8 > 0 ? 16 - (element.getWidth() + element.getStartX()) : 16 - element.getWidth() - element.getStartX();
        }
        element.setStartX(newX);
        element.setStartZ(newZ);

        /* Swaps the width and depth of the element */
        double width = element.getWidth();
        element.setWidth(element.getDepth());
        element.setDepth(width);

        /* Shifts the UVs of horizontal faces to the next target face */
        Face[] faces = element.getAllFaces();
        Face tempFace = new Face(faces[clockwise ? 3 : 0]);
        if(clockwise)
        {
            for(int i = 3; i >= 1; i--)
            {
                faces[i].copyProperties(faces[i - 1]);
            }
        }
        else
        {
            for(int i = 0; i < 3; i++)
            {
                faces[i].copyProperties(faces[i + 1]);
            }
        }
        faces[clockwise ? 0 : 3].copyProperties(tempFace);

        /* Rotates the textures on the top so they match the original when rotated */
        faces[Face.UP].setRotation(getNextFaceRotation(element, Face.UP, clockwise));
        faces[Face.DOWN].setRotation(getNextFaceRotation(element, Face.DOWN, clockwise));

        /* Rotates the rotation axis. This only applies to horizontal axis */
        if(element.getRotationAxis() == 0)
        {
            element.setRotationAxis(2);
            if(!clockwise)
            {
                element.setRotation(-element.getRotation());
            }
        }
        else if(element.getRotationAxis() == 2)
        {
            element.setRotationAxis(0);
            if(clockwise)
            {
                element.setRotation(-element.getRotation());
            }
        }

        /* Rotates the origin starting x and y */
        double newOriginX;
        double newOriginZ;
        if(clockwise)
        {
            newOriginX = element.getOriginX() - 8 > 0 ? 16 - element.getOriginZ() : 16 - element.getOriginZ();
            newOriginZ = element.getOriginX();
        }
        else
        {
            newOriginX = element.getOriginZ();
            newOriginZ = element.getOriginZ() - 8 > 0 ? 16 - element.getOriginX() : 16 - element.getOriginX();
        }
        element.setOriginX(newOriginX);
        element.setOriginZ(newOriginZ);
    }

    private static int getNextFaceRotation(Element element, int side, boolean clockwise)
    {
        Face[] faces = element.getAllFaces();
        if(clockwise)
        {
            return (faces[side].getRotation() + 1) % 4;
        }
        else if(faces[side].getRotation() - 1 >= 0)
        {
            return faces[side].getRotation() - 1;
        }
        return 3;
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Animation.java
================================================
package com.mrcrayfish.modelcreator;

/**
 * Author: MrCrayfish
 */
public class Animation
{
    private static int counter;
    private static float partialTicks;

    public static void tick()
    {
        Animation.counter++;
    }

    public static void setPartialTicks(float partialTicks)
    {
        Animation.partialTicks = partialTicks;
    }

    public static int getCounter()
    {
        return Animation.counter;
    }

    public static float getPartialTicks()
    {
        return partialTicks;
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Camera.java
================================================
package com.mrcrayfish.modelcreator;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.util.glu.GLU.gluPerspective;

public class Camera
{
    private float x;
    private float y;
    private float z;
    private float rx;
    private float ry;
    private float rz;

    private float fov;
    private float aspect;
    private float near;
    private float far;

    public Camera(float fov, float aspect, float near, float far)
    {
        x = 0;
        y = 0;
        z = -20;
        rx = 30F;
        ry = 0F;
        rz = 0;

        this.fov = fov;
        this.aspect = aspect;
        this.near = near;
        this.far = far;
        initProjection();
    }

    private void initProjection()
    {
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(fov, aspect, near, far);
        glMatrixMode(GL_MODELVIEW);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glEnable(GL_DEPTH_TEST);
    }

    public void useView()
    {
        glTranslatef(x, y, z);
        glRotatef(rx, 1, 0, 0);
        glRotatef(ry, 0, 1, 0);
        glRotatef(rz, 0, 0, 1);
    }

    public float getX()
    {
        return x;
    }

    public float getY()
    {
        return y;
    }

    public float getZ()
    {
        return z;
    }

    public void setX(float x)
    {
        this.x = x;
    }

    public void setY(float y)
    {
        this.y = y;
    }

    public void setZ(float z)
    {
        this.z = z;
    }

    public float getRX()
    {
        return rx;
    }

    public float getRY()
    {
        return ry;
    }

    public float getRZ()
    {
        return rz;
    }

    public void setRX(float rx)
    {
        this.rx = rx;
    }

    public void setRY(float ry)
    {
        this.ry = ry;
    }

    public void setRZ(float rz)
    {
        this.rz = rz;
    }

    public void move(float amt, float dir)
    {
        z += amt * Math.sin(Math.toRadians(ry + 90 * dir));
        x += amt * Math.cos(Math.toRadians(ry + 90 * dir));
    }

    public void addX(float amt)
    {
        x += amt;
    }

    public void addY(float amt)
    {
        y += amt;
    }

    public void addZ(float amt)
    {
        z += amt;
    }

    public void rotateX(float amt)
    {
        rx = ((rx + amt) % 360);
    }

    public void rotateY(float amt)
    {
        ry = ((ry + amt) % 360);
    }
}

================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Constants.java
================================================
package com.mrcrayfish.modelcreator;

public class Constants
{
    public static final String NAME = "MrCrayfish's Model Creator";
    public static final String VERSION = "0.7.0";

    public static final String URL_DONATE = "https://www.patreon.com/mrcrayfish?ty=h";
    public static final String URL_TWITTER = "https://www.twitter.com/MrCraayfish";
    public static final String URL_FACEBOOK = "https://www.facebook.com/MrCrayfish";
    public static final String URL_GITHUB = "https://github.com/MrCrayfish/ModelCreator";
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Exporter.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.element.ElementManager;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

public abstract class Exporter
{
    /**
     * decimalformatter for rounding
     */
    public static final DecimalFormat FORMAT = new DecimalFormat("#.###");
    private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols();

    static
    {
        SYMBOLS.setDecimalSeparator('.');
        FORMAT.setDecimalFormatSymbols(SYMBOLS);
    }

    // Model Variables
    protected ElementManager manager;

    public Exporter(ElementManager manager)
    {
        this.manager = manager;
    }

    public File export(File file)
    {
        File path = file.getParentFile();
        if(path.exists() && path.isDirectory())
        {
            this.writeFile(file);
        }
        return file;
    }

    protected abstract void write(BufferedWriter writer) throws IOException;

    public File writeFile(File file)
    {
        try(BufferedWriter writer = new BufferedWriter(new FileWriter(file)))
        {
            if(!file.exists())
            {
                file.createNewFile();
            }
            this.write(writer);
            return file;
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    protected String space(int size)
    {
        StringBuilder builder = new StringBuilder();
        for(int i = 0; i < size; i++)
        {
            //TODO add setting to export with tabs instead
            builder.append("    ");
        }
        return builder.toString();
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/ExporterJavaCode.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.element.Element;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringWriter;

public class ExporterJavaCode extends Exporter
{
    private ModelCreator creator;
    private Version version = Version.V_1_12;
    private boolean includeFields, includeMethods, useBoundsHelper, generateRotatedBounds;

    public ExporterJavaCode(ModelCreator creator, boolean includeFields, boolean includeMethods, boolean useBoundsHelper, boolean generateRotatedBounds)
    {
        super(creator.getElementManager());
        this.creator = creator;
        this.includeFields = includeFields;
        this.includeMethods = includeMethods;
        this.useBoundsHelper = useBoundsHelper;
        this.generateRotatedBounds = useBoundsHelper && generateRotatedBounds;
    }

    public void setVersion(Version version)
    {
        this.version = version;
    }

    public void writeCodeToClipboard() throws IOException
    {
        StringWriter writerFile = new StringWriter();
        try(BufferedWriter writer = new BufferedWriter(writerFile))
        {
            write(writer);
            writer.flush();
            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(writerFile.toString()), null);
        }
    }

    @Override
    protected void write(BufferedWriter writer) throws IOException
    {
        if(version == Version.V_1_13)
        {
            if(includeFields)
            {
                /* Generates member fields */
                writeNewLine(writer, "/* Member variables */");
                if(generateRotatedBounds)
                {
                    writeNewLine(writer, "public final ImmutableMap<IBlockState, VoxelShape> SHAPES;");
                }
                else
                {
                    writeNewLine(writer, "public final VoxelShape SHAPE;");
                }
                writer.newLine();

                /* Generates logic which is to be placed into the constructor */
                writeNewLine(writer, "/* Place in Constructor */");
                if(generateRotatedBounds)
                {
                    writeNewLine(writer, "SHAPES = this.generateShapes(this.getStateContainer().getValidStates());");
                }
                else
                {
                    writeNewLine(writer, "SHAPE = this.generateShape();");
                }
                writer.newLine();
            }

            if(!includeMethods)
            {
                return;
            }

            writeNewLine(writer, "/* Methods */");

            /* Creates method for generating voxel shapes for rotatable blocks */
            if(generateRotatedBounds)
            {
                writeNewLine(writer, "private ImmutableMap<IBlockState, VoxelShape> generateShapes(ImmutableList<IBlockState> states)");
                writeNewLine(writer, "{");
                for(Element element : manager.getAllElements())
                {
                    if(element.getRotation() == 0)
                    {
                        String name = element.getName().toUpperCase().replaceAll(" ", "_");
                        double x = element.getStartX();
                        double y = element.getStartY();
                        double z = element.getStartZ();
                        writer.write("    ");
                        writeField(writer, null, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());
                    }
                    else
                    {
                        writer.write(String.format("    // Skipped '%s', as it has rotation", element.getName()));
                    }
                    writer.newLine();
                }

                writer.newLine();
                writeNewLine(writer, "    ImmutableMap.Builder<IBlockState, VoxelShape> builder = new ImmutableMap.Builder<>();");
                writeNewLine(writer, "    for(IBlockState state : states)");
                writeNewLine(writer, "    {");
                writeNewLine(writer, "        EnumFacing facing = state.getValue(HORIZONTAL_FACING);");
                writeNewLine(writer, "        List<VoxelShape> shapes = new ArrayList<>();");

                for(Element element : manager.getAllElements())
                {
                    if(element.getRotation() == 0)
                    {
                        String name = element.getName().toUpperCase().replaceAll(" ", "_");
                        writeNewLine(writer, String.format("        shapes.add(%s[facing.getHorizontalIndex()]);", name));
                    }
                }

                writeNewLine(writer, "        builder.put(state, VoxelShapeHelper.combineAll(shapes));");
                writeNewLine(writer, "    }");
                writeNewLine(writer, "    return builder.build();");
                writeNewLine(writer, "}");
                writer.newLine();
            }
            else
            {
                writeNewLine(writer, "private VoxelShape generateShape()");
                writeNewLine(writer, "{");
                writeNewLine(writer, "    List<VoxelShape> shapes = new ArrayList<>();");
                for(Element element : manager.getAllElements())
                {
                    if(element.getRotation() == 0)
                    {
                        String name = element.getName().toUpperCase().replaceAll(" ", "_");
                        double x = element.getStartX();
                        double y = element.getStartY();
                        double z = element.getStartZ();
                        writer.write("    ");
                        writeField(writer, null, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());
                    }
                    else
                    {
                        writer.write(String.format("    // Skipped '%s', as it has rotation", element.getName()));
                    }
                    writer.newLine();
                }

                if(useBoundsHelper)
                {
                    writeNewLine(writer, "    return VoxelShapeHelper.combineAll(shapes)");
                }
                else
                {
                    writer.newLine();
                    writeNewLine(writer, "    VoxelShape result = ShapeUtils.empty();");
                    writeNewLine(writer, "    for(VoxelShape shape : shapes)");
                    writeNewLine(writer, "    {");
                    writeNewLine(writer, "        result = ShapeUtils.combine(result, shape, IBooleanFunction.OR);");
                    writeNewLine(writer, "    }");
                    writeNewLine(writer, "    return result.simplify();");
                }
                writeNewLine(writer, "}");
                writer.newLine();
            }

            /* Produces the method for selection box */
            writeNewLine(writer, "@Override");
            writeNewLine(writer, "public VoxelShape getShape(IBlockState state, IBlockReader reader, BlockPos pos)");
            writeNewLine(writer, "{");
            if(generateRotatedBounds)
            {
                writeNewLine(writer, "    return SHAPES.get(state);");
            }
            else
            {
                writeNewLine(writer, "    return SHAPE;");
            }
            writeNewLine(writer, "}");
            writer.newLine();

            /* Produces the method for collisions */
            writeNewLine(writer, "@Override");
            writeNewLine(writer, "public VoxelShape getCollisionShape(IBlockState state, IBlockReader reader, BlockPos pos)");
            writeNewLine(writer, "{");
            if(generateRotatedBounds)
            {
                writeNewLine(writer, "    return SHAPES.get(state);");
            }
            else
            {
                writeNewLine(writer, "    return SHAPE;");
            }
            writeNewLine(writer, "}");
        }
        else if(version == Version.V_1_12)
        {
            if(includeFields)
            {
                StringBuilder boxList = new StringBuilder("private static final List<AxisAlignedBB>");
                boxList.append(generateRotatedBounds ? "[] COLLISION_BOXES = Bounds.getRotatedBoundLists(" : " COLLISION_BOXES = Lists.newArrayList(");
                ModelBounds bounds = useBoundsHelper ? null : new ModelBounds();
                String name = null;
                double x, y, z;
                for(Element element : manager.getAllElements())
                {
                    if(element.getRotation() != 0)
                    {
                        writer.write(String.format("// Skipped '%s', as it has roatation", element.getName()));
                    }
                    else
                    {
                        if(name != null)
                        {
                            boxList.append(", ");
                        }

                        x = element.getStartX();
                        y = element.getStartY();
                        z = element.getStartZ();
                        name = element.getName();
                        name = name.toUpperCase().replaceAll(" ", "_");
                        boxList.append(name);
                        writeField(writer, bounds, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());
                    }
                    writer.newLine();
                }

                if(name == null)
                {
                    JOptionPane.showMessageDialog(creator, "No non-rotated elements were found.", "None Found", JOptionPane.INFORMATION_MESSAGE);
                    return;
                }

                if(useBoundsHelper)
                {
                    writer.newLine();
                }
                else
                {
                    writeNewLine(writer, "/**");
                    writeNewLine(writer, String.format("* %s generated using MrCrayfish's Model Creator <a href=\"https://mrcrayfish.com/tools?id=mc\">https://mrcrayfish.com/tools?id=mc</a>", includeMethods ? "AxisAlignedBBs and methods getBoundingBox, collisionRayTrace, and collisionRayTrace" : "AxisAlignedBBs"));
                    writeNewLine(writer, "*/");
                }

                writer.write(boxList.append(");").toString());
                writer.newLine();

                if(bounds != null)
                {
                    bounds.write(writer);
                }
                else if(generateRotatedBounds)
                {
                    writer.write("private static final AxisAlignedBB[] BOUNDING_BOX = Bounds.getBoundingBoxes(COLLISION_BOXES);");
                }
                else
                {
                    writer.write("private static final AxisAlignedBB BOUNDING_BOX = Bounds.getBoundingBox(COLLISION_BOXES);");
                }
            }

            if(!includeMethods)
            {
                return;
            }

            if(includeFields)
            {
                writer.newLine();
                writer.newLine();
            }

            writeNewLine(writer, "@Override");
            writeNewLine(writer, "public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos)");
            writeNewLine(writer, "{");
            writeNewLine(writer, "    return BOUNDING_BOX%s", generateRotatedBounds ? "[state.getValue(FACING).getHorizontalIndex()];" : ";");
            writeNewLine(writer, "}");

            if(useBoundsHelper)
            {
                writer.newLine();
                writeNewLine(writer, "@Override");
                writeNewLine(writer, "protected List<AxisAlignedBB> getCollisionBoxes(IBlockState state, World world, BlockPos pos, @Nullable Entity entity, boolean isActualState)");
                writeNewLine(writer, "{");
                writeNewLine(writer, "    return COLLISION_BOXES%s", generateRotatedBounds ? "[state.getValue(FACING).getHorizontalIndex()];" : ";");
                writer.write("}");
                return;
            }

            writer.newLine();
            writeNewLine(writer, "@Override");
            writeNewLine(writer, "public void addCollisionBoxToList(IBlockState state, World world, BlockPos pos, AxisAlignedBB entityBox, List<AxisAlignedBB> collidingBoxes, @Nullable Entity entity, boolean isActualState)");
            writeNewLine(writer, "{");
            writeNewLine(writer, "    entityBox = entityBox.offset(-pos.getX(), -pos.getY(), -pos.getZ());");
            writeNewLine(writer, "    for (AxisAlignedBB box : COLLISION_BOXES)");
            writeNewLine(writer, "    {");
            writeNewLine(writer, "        if (entityBox.intersects(box))");
            writeNewLine(writer, "            collidingBoxes.add(box.offset(pos));");
            writeNewLine(writer, "    }");
            writeNewLine(writer, "}");
            writer.newLine();
            writeNewLine(writer, "@Override");
            writeNewLine(writer, "@Nullable");
            writeNewLine(writer, "public RayTraceResult collisionRayTrace(IBlockState state, World world, BlockPos pos, Vec3d start, Vec3d end)");
            writeNewLine(writer, "{");
            writeNewLine(writer, "    double distanceSq;");
            writeNewLine(writer, "    double distanceSqShortest = Double.POSITIVE_INFINITY;");
            writeNewLine(writer, "    RayTraceResult resultClosest = null;");
            writeNewLine(writer, "    RayTraceResult result;");
            writeNewLine(writer, "    start = start.subtract(pos.getX(), pos.getY(), pos.getZ());");
            writeNewLine(writer, "    end = end.subtract(pos.getX(), pos.getY(), pos.getZ());");
            writeNewLine(writer, "    for (AxisAlignedBB box : COLLISION_BOXES)");
            writeNewLine(writer, "    {");
            writeNewLine(writer, "        result = box.calculateIntercept(start, end);");
            writeNewLine(writer, "        if (result == null)");
            writeNewLine(writer, "            continue;");
            writer.newLine();
            writeNewLine(writer, "        distanceSq = result.hitVec.squareDistanceTo(start);");
            writeNewLine(writer, "        if (distanceSq < distanceSqShortest)");
            writeNewLine(writer, "        {");
            writeNewLine(writer, "            distanceSqShortest = distanceSq;");
            writeNewLine(writer, "            resultClosest = result;");
            writeNewLine(writer, "        }");
            writeNewLine(writer, "    }");
            writeNewLine(writer, "    return resultClosest == null ? null : new RayTraceResult(RayTraceResult.Type.BLOCK, resultClosest.hitVec.addVector(pos.getX(), pos.getY(), pos.getZ()), resultClosest.sideHit, pos);");
            writer.write("}");
        }
    }

    private String format(double value)
    {
        return FORMAT.format(useBoundsHelper ? value : value * 0.0625);
    }

    private void writeField(BufferedWriter writer, ModelBounds bounds, String name, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) throws IOException
    {
        if(version == Version.V_1_13)
        {
            if(generateRotatedBounds)
            {
                writer.write(String.format("final VoxelShape[] %s = VoxelShapeHelper.getRotatedVoxelShapes(Block.makeCuboidShape(%s, %s, %s, %s, %s, %s));", name, format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ)));
            }
            else
            {
                writer.write(String.format("shapes.add(Block.makeCuboidShape(%s, %s, %s, %s, %s, %s)); // %s", format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ), name));
            }
        }
        else if(version == Version.V_1_12)
        {
            StringBuilder builder = new StringBuilder("private static final AxisAlignedBB");

            if(generateRotatedBounds)
            {
                builder.append("[]");
            }

            builder.append(" %s = new ").append(useBoundsHelper ? "Bounds" : "AxisAlignedBB").append("(%s, %s, %s, %s, %s, %s)");

            if(useBoundsHelper)
            {
                builder.append(generateRotatedBounds ? ".getRotatedBounds()" : ".toAABB()");
            }

            writer.write(String.format(builder.append(";").toString(), name, format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ)));
        }

        if(bounds != null)
        {
            bounds.union(minX, minY, minZ, maxX, maxY, maxZ);
        }
    }

    private void writeNewLine(BufferedWriter writer, String line, Object... args) throws IOException
    {
        writer.write(String.format(line, args));
        writer.newLine();
    }

    private class ModelBounds
    {
        private double minX, minY, minZ, maxX, maxY, maxZ;

        private ModelBounds()
        {
            minX = minY = minZ = Double.MAX_VALUE;
            maxX = maxY = maxZ = Double.MIN_VALUE;
        }

        public void write(BufferedWriter writer) throws IOException
        {
            writeField(writer, this, "BOUNDING_BOX", minX, minY, minZ, maxX, maxY, maxZ);
        }

        private void union(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
        {
            this.minX = Math.min(this.minX, minX);
            this.minY = Math.min(this.minY, minY);
            this.minZ = Math.min(this.minZ, minZ);
            this.maxX = Math.max(this.maxX, maxX);
            this.maxY = Math.max(this.maxY, maxY);
            this.maxZ = Math.max(this.maxZ, maxZ);
        }
    }

    public enum Version
    {
        V_1_12("1.12"), V_1_13("1.13");

        private String label;

        Version(String label)
        {
            this.label = label;
        }

        @Override
        public String toString()
        {
            return label;
        }
    }
}

================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/ExporterModel.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.display.DisplayProperties;
import com.mrcrayfish.modelcreator.element.Element;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.Face;
import com.mrcrayfish.modelcreator.texture.TextureEntry;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExporterModel extends Exporter
{
    private static final String[] DISPLAY_PROPERTY_ORDER = {"gui", "ground", "fixed", "head", "firstperson_righthand", "thirdperson_righthand"};

    private Map<String, String> textureMap = new HashMap<>();
    private boolean optimize = true;
    private boolean includeNames = true;
    private boolean displayProps = true;
    private boolean includeNonTexturedFaces = false;

    public ExporterModel(ElementManager manager)
    {
        super(manager);
        compileTextureList();
    }

    public void setOptimize(boolean optimize)
    {
        this.optimize = optimize;
    }

    public void setIncludeNames(boolean includeNames)
    {
        this.includeNames = includeNames;
    }

    public void setDisplayProps(boolean displayProps)
    {
        this.displayProps = displayProps;
    }

    public void setIncludeNonTexturedFaces(boolean includeNonTexturedFaces)
    {
        this.includeNonTexturedFaces = includeNonTexturedFaces;
    }

    private void compileTextureList()
    {
        for(Element cuboid : manager.getAllElements())
        {
            for(Face face : cuboid.getAllFaces())
            {
                if(face.getTexture() != null && face.isEnabled() && (!optimize || face.isVisible(manager)))
                {
                    TextureEntry entry = face.getTexture();
                    textureMap.put(entry.getKey(), entry.getTexturePath().toString());
                }
            }
        }
    }

    @Override
    protected void write(BufferedWriter writer) throws IOException
    {
        writer.write("{");
        writer.newLine();

        writer.write(space(1) + "\"__comment\": \"Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)\",");
        writer.newLine();

        if(!manager.getAmbientOcc())
        {
            writer.write("\"ambientocclusion\": " + manager.getAmbientOcc() + ",");
            writer.newLine();
        }

        writeTextures(writer);
        writer.newLine();

        if(displayProps)
        {
            writeDisplayProperties(writer);
            writer.newLine();
        }

        writer.write(space(1) + "\"elements\": [");

        for(int i = 0; i < manager.getElementCount() - 1; i++)
        {
            Element element = manager.getElement(i);
            if(canWriteElement(element))
            {
                writeElement(writer, manager.getElement(i));
                writer.write(",");
            }
        }
        if(manager.getElementCount() > 0)
        {
            Element element = manager.getElement(manager.getElementCount() - 1);
            if(canWriteElement(element))
            {
                writeElement(writer, manager.getElement(manager.getElementCount() - 1));
            }
        }

        writer.newLine();
        writer.write(space(1) + "]");
        writer.newLine();
        writer.write("}");
    }

    private void writeTextures(BufferedWriter writer) throws IOException
    {
        writer.write(space(1) + "\"textures\": {");
        writer.newLine();
        if(manager.getParticle() != null)
        {
            TextureEntry entry = manager.getParticle();
            writer.write(space(2) + "\"particle\": \"" + entry.getModId() + ":");
            if(!entry.getDirectory().isEmpty())
            {
                writer.write(entry.getDirectory() + "/");
            }
            writer.write(entry.getName() + "\"");
            if(textureMap.size() > 0)
            {
                writer.write(",");
            }
            writer.newLine();
        }

        List<String> ids = new ArrayList<>(textureMap.keySet());
        for(int i = 0; i < ids.size() - 1; i++)
        {
            String id = ids.get(i);
            String texture = textureMap.get(id);
            writer.write(space(2) + "\"" + id + "\": \"" + texture + "\"");
            writer.write(",");
            writer.newLine();
        }
        if(ids.size() > 0)
        {
            String id = ids.get(ids.size() - 1);
            String texture = textureMap.get(id);
            writer.write(space(2) + "\"" + id + "\": \"" + texture + "\"");
            writer.newLine();
        }

        writer.write(space(1) + "},");
    }

    private void writeElement(BufferedWriter writer, Element cuboid) throws IOException
    {
        writer.newLine();
        writer.write(space(2) + "{");
        writer.newLine();
        if(includeNames)
        {
            writer.write(space(3) + "\"name\": \"" + cuboid.getName() + "\",");
            writer.newLine();
        }
        writeBounds(writer, cuboid);
        writer.newLine();
        if(!cuboid.isShaded())
        {
            writeShade(writer, cuboid);
            writer.newLine();
        }
        if(cuboid.getRotation() != 0)
        {
            writeRotation(writer, cuboid);
            writer.newLine();
        }
        writeFaces(writer, cuboid);
        writer.newLine();
        writer.write(space(2) + "}");
    }

    private void writeBounds(BufferedWriter writer, Element cuboid) throws IOException
    {
        writer.write(space(3) + "\"from\": [ " + FORMAT.format(cuboid.getStartX()) + ", " + FORMAT.format(cuboid.getStartY()) + ", " + FORMAT.format(cuboid.getStartZ()) + " ], ");
        writer.newLine();
        writer.write(space(3) + "\"to\": [ " + FORMAT.format(cuboid.getStartX() + cuboid.getWidth()) + ", " + FORMAT.format(cuboid.getStartY() + cuboid.getHeight()) + ", " + FORMAT.format(cuboid.getStartZ() + cuboid.getDepth()) + " ], ");
    }

    private void writeShade(BufferedWriter writer, Element cuboid) throws IOException
    {
        writer.write(space(3) + "\"shade\": " + cuboid.isShaded() + ",");
    }

    private void writeRotation(BufferedWriter writer, Element cuboid) throws IOException
    {
        writer.write(space(3) + "\"rotation\": { ");
        writer.write("\"origin\": [ " + FORMAT.format(cuboid.getOriginX()) + ", " + FORMAT.format(cuboid.getOriginY()) + ", " + FORMAT.format(cuboid.getOriginZ()) + " ], ");
        writer.write("\"axis\": \"" + Element.parseAxis(cuboid.getRotationAxis()) + "\", ");
        writer.write("\"angle\": " + cuboid.getRotation());
        if(cuboid.shouldRescale())
        {
            writer.write(", \"rescale\": " + cuboid.shouldRescale());
        }
        writer.write(" },");
    }

    private void writeFaces(BufferedWriter writer, Element cuboid) throws IOException
    {
        writer.write(space(3) + "\"faces\": {");
        writer.newLine();

        /* Creates a list of all the valid faces to export */
        List<Face> validFaces = new ArrayList<>();
        for(Face face : cuboid.getAllFaces())
        {
            if(face.isEnabled() && (includeNonTexturedFaces || face.getTexture() != null) && (!optimize || face.isVisible(manager)))
            {
                validFaces.add(face);
            }
        }

        /* Writes the valid faces to the writer */
        for(int i = 0; i < validFaces.size() - 1; i++)
        {
            Face face = validFaces.get(i);
            writeFace(writer, face);
            writer.write(",");
            writer.newLine();
        }
        if(validFaces.size() > 0)
        {
            writeFace(writer, validFaces.get(validFaces.size() - 1));
        }

        writer.newLine();
        writer.write(space(3) + "}");
    }

    private void writeFace(BufferedWriter writer, Face face) throws IOException
    {
        writer.write(space(4) + "\"" + Face.getFaceName(face.getSide()) + "\": { ");
        if(face.getTexture() != null)
        {
            writer.write("\"texture\": \"#" + face.getTexture().getKey() + "\"");
            writer.write(", \"uv\": [ " + FORMAT.format(face.getStartU()) + ", " + FORMAT.format(face.getStartV()) + ", " + FORMAT.format(face.getEndU()) + ", " + FORMAT.format(face.getEndV()) + " ]");
            if(face.getRotation() > 0)
            {
                writer.write(", \"rotation\": " + face.getRotation() * 90);
            }
            if(face.isCullfaced())
            {
                writer.write(", \"cullface\": \"" + Face.getFaceName(face.getSide()) + "\"");
            }
            if(face.isTintIndexEnabled() && face.getTintIndex() >= 0)
            {
                writer.write(", \"tintindex\": " + face.getTintIndex());
            }
        }
        writer.write(" }");
    }

    private void writeDisplayProperties(BufferedWriter writer) throws IOException
    {
        Map<String, DisplayProperties.Entry> entries = manager.getDisplayProperties().getEntries();
        List<String> ids = new ArrayList<>();
        for(String id : DISPLAY_PROPERTY_ORDER)
        {
            DisplayProperties.Entry entry = entries.get(id);
            if(entry != null && entry.isEnabled())
            {
                ids.add(id);
            }
        }

        writer.write(space(1) + "\"display\": {");
        writer.newLine();

        for(int i = 0; i < ids.size() - 1; i++)
        {
            String key = ids.get(i);
            writeDisplayEntry(writer, key, entries.get(key));
            writer.write(",");
            writer.newLine();
        }
        if(ids.size() > 0)
        {
            String key = ids.get(ids.size() - 1);
            writeDisplayEntry(writer, key, entries.get(key));
        }

        writer.newLine();
        writer.write(space(1) + "},");
    }

    private void writeDisplayEntry(BufferedWriter writer, String id, DisplayProperties.Entry entry) throws IOException
    {
        writer.write(space(2) + "\"" + id + "\": {");
        writer.newLine();
        writer.write(space(3) + String.format("\"rotation\": [ %s, %s, %s ],", FORMAT.format(entry.getRotationX()), FORMAT.format(entry.getRotationY()), FORMAT.format(entry.getRotationZ())));
        writer.newLine();
        writer.write(space(3) + String.format("\"translation\": [ %s, %s, %s ],", FORMAT.format(entry.getTranslationX()), FORMAT.format(entry.getTranslationY()), FORMAT.format(entry.getTranslationZ())));
        writer.newLine();
        writer.write(space(3) + String.format("\"scale\": [ %s, %s, %s ]", FORMAT.format(entry.getScaleX()), FORMAT.format(entry.getScaleY()), FORMAT.format(entry.getScaleZ())));
        writer.newLine();
        writer.write(space(2) + "}");
    }

    private boolean canWriteElement(Element element)
    {
        for(Face face : element.getAllFaces())
        {
            if(face.isEnabled() && (includeNonTexturedFaces || face.getTexture() != null) && (!optimize || face.isVisible(manager)))
            {
                return true;
            }
        }
        return false;
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Icons.java
================================================
package com.mrcrayfish.modelcreator;

import javax.swing.*;

public class Icons
{
    public static Icon bin;
    public static Icon new_;
    public static Icon import_;
    public static Icon export;
    public static Icon texture;
    public static Icon clear_texture;
    public static Icon copy;
    public static Icon copy_small;
    public static Icon clipboard;
    public static Icon clipboard_texture;
    public static Icon transparent;
    public static Icon coin;
    public static Icon load;
    public static Icon disk;
    public static Icon exit;
    public static Icon settings;
    public static Icon cube;
    public static Icon light_off;
    public static Icon light_on;
    public static Icon arrow_up;
    public static Icon arrow_down;
    public static Icon facebook;
    public static Icon twitter;
    public static Icon reddit;
    public static Icon imgur;
    public static Icon patreon;
    public static Icon planet_minecraft;
    public static Icon minecraft_forum;
    public static Icon github;
    public static Icon model_cauldron;
    public static Icon model_chair;
    public static Icon extract;
    public static Icon mojang;
    public static Icon java;
    public static Icon undo;
    public static Icon redo;
    public static Icon optimize;
    public static Icon rotate;
    public static Icon rotate_clockwise;
    public static Icon rotate_counter_clockwise;
    public static Icon refresh;
    public static Icon gallery;
    public static Icon bin2;
    public static Icon edit;
    public static Icon edit_image;

    public static void init(Class<?> clazz)
    {
        ClassLoader loader = clazz.getClassLoader();
        cube = new ImageIcon(loader.getResource("icons/cube.png"));
        bin = new ImageIcon(loader.getResource("icons/bin.png"));
        new_ = new ImageIcon(loader.getResource("icons/new.png"));
        import_ = new ImageIcon(loader.getResource("icons/import.png"));
        export = new ImageIcon(loader.getResource("icons/export.png"));
        texture = new ImageIcon(loader.getResource("icons/texture.png"));
        clear_texture = new ImageIcon(loader.getResource("icons/clear_texture.png"));
        copy = new ImageIcon(loader.getResource("icons/copy.png"));
        copy_small = new ImageIcon(loader.getResource("icons/copy_small.png"));
        clipboard = new ImageIcon(loader.getResource("icons/clipboard.png"));
        clipboard_texture = new ImageIcon(loader.getResource("icons/paste_texture.png"));
        transparent = new ImageIcon(loader.getResource("icons/transparent.png"));
        coin = new ImageIcon(loader.getResource("icons/coin.png"));
        load = new ImageIcon(loader.getResource("icons/load.png"));
        disk = new ImageIcon(loader.getResource("icons/disk.png"));
        exit = new ImageIcon(loader.getResource("icons/exit.png"));
        settings = new ImageIcon(loader.getResource("icons/settings.png"));
        extract = new ImageIcon(loader.getResource("icons/extract.png"));
        light_off = new ImageIcon(loader.getResource("icons/box_off.png"));
        light_on = new ImageIcon(loader.getResource("icons/box_on.png"));
        arrow_up = new ImageIcon(loader.getResource("icons/arrow_up.png"));
        arrow_down = new ImageIcon(loader.getResource("icons/arrow_down.png"));
        facebook = new ImageIcon(loader.getResource("icons/facebook.png"));
        twitter = new ImageIcon(loader.getResource("icons/twitter.png"));
        reddit = new ImageIcon(loader.getResource("icons/reddit.png"));
        imgur = new ImageIcon(loader.getResource("icons/imgur.png"));
        patreon = new ImageIcon(loader.getResource("icons/patreon.png"));
        planet_minecraft = new ImageIcon(loader.getResource("icons/planet_minecraft.png"));
        minecraft_forum = new ImageIcon(loader.getResource("icons/minecraft_forum.png"));
        github = new ImageIcon(loader.getResource("icons/github.png"));
        model_cauldron = new ImageIcon(loader.getResource("icons/model_cauldron.png"));
        model_chair = new ImageIcon(loader.getResource("icons/model_chair.png"));
        mojang = new ImageIcon(loader.getResource("icons/mojang.png"));
        java = new ImageIcon(loader.getResource("icons/java.png"));
        undo = new ImageIcon(loader.getResource("icons/undo.png"));
        redo = new ImageIcon(loader.getResource("icons/redo.png"));
        optimize = new ImageIcon(loader.getResource("icons/optimize.png"));
        rotate = new ImageIcon(loader.getResource("icons/rotate.png"));
        rotate_clockwise = new ImageIcon(loader.getResource("icons/rotate_clockwise.png"));
        rotate_counter_clockwise = new ImageIcon(loader.getResource("icons/rotate_anticlockwise.png"));
        refresh = new ImageIcon(loader.getResource("icons/refresh.png"));
        gallery = new ImageIcon(loader.getResource("icons/gallery.png"));
        bin2 = new ImageIcon(loader.getResource("icons/bin2.png"));
        edit = new ImageIcon(loader.getResource("icons/edit.png"));
        edit_image = new ImageIcon(loader.getResource("icons/edit_image.png"));
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Importer.java
================================================
package com.mrcrayfish.modelcreator;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mrcrayfish.modelcreator.component.TextureManager;
import com.mrcrayfish.modelcreator.display.DisplayProperties;
import com.mrcrayfish.modelcreator.element.Element;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.Face;
import com.mrcrayfish.modelcreator.texture.TextureEntry;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;

public class Importer
{
    private Map<String, String> textureMap = new HashMap<>();
    private String[] faceNames = {"north", "east", "south", "west", "up", "down"};
    private String[] displayNames = {"gui", "ground", "fixed", "head", "firstperson_righthand", "firstperson_lefthand", "thirdperson_righthand", "thirdperson_lefthand"};

    // Input File
    private String inputPath;

    // Model Variables
    private ElementManager manager;

    public Importer(ElementManager manager, String outputPath)
    {
        this.manager = manager;
        this.inputPath = outputPath;
    }

    public void importFromJSON()
    {
        File path = new File(inputPath);

        if(path.exists() && path.isFile())
        {
            FileReader fr;
            BufferedReader reader;
            try
            {
                fr = new FileReader(path);
                reader = new BufferedReader(fr);
                readComponents(reader, manager, path.getParentFile());
                reader.close();
                fr.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    private void readComponents(BufferedReader reader, ElementManager manager, File dir) throws IOException
    {
        manager.clearElements();
        manager.setParticle(null);
        manager.setDisplayProperties(DisplayProperties.MODEL_CREATOR_BLOCK);

        JsonParser parser = new JsonParser();
        JsonElement read = parser.parse(reader);

        if(read.isJsonObject())
        {
            JsonObject obj = read.getAsJsonObject();

            if(obj.has("parent") && obj.get("parent").isJsonPrimitive())
            {
                String parent = obj.get("parent").getAsString();
                File file = new File(dir, parent + ".json");
                if(!file.exists())
                {
                    parent = parent.substring(parent.lastIndexOf('/') + 1, parent.length());
                    file = new File(dir, parent + ".json");
                }

                if(file.exists())
                {
                    // load textures
                    loadTextures(dir, obj);

                    // Load Parent
                    FileReader fr = new FileReader(file);
                    reader = new BufferedReader(fr);
                    readComponents(reader, manager, file.getParentFile());
                    reader.close();
                    fr.close();
                }

                return;
            }

            // load textures
            loadTextures(dir, obj);

            // load display properties
            if(obj.has("display") && obj.get("display").isJsonObject())
            {
                readDisplayProperties(obj.getAsJsonObject("display"), manager);
            }

            // load elements
            if(obj.has("elements") && obj.get("elements").isJsonArray())
            {
                JsonArray elements = obj.get("elements").getAsJsonArray();

                for(int i = 0; i < elements.size(); i++)
                {
                    if(elements.get(i).isJsonObject())
                    {
                        readElement(elements.get(i).getAsJsonObject(), manager);
                    }
                }
            }

            manager.setAmbientOcc(true);
            if(obj.has("ambientocclusion") && obj.get("ambientocclusion").isJsonPrimitive())
            {
                manager.setAmbientOcc(obj.get("ambientocclusion").getAsBoolean());
            }
        }
    }

    private void loadTextures(File file, JsonObject obj)
    {
        if(obj.has("textures") && obj.get("textures").isJsonObject())
        {
            JsonObject textures = obj.get("textures").getAsJsonObject();

            for(Entry<String, JsonElement> entry : textures.entrySet())
            {
                if(entry.getValue().isJsonPrimitive())
                {
                    String key = entry.getKey().trim().toLowerCase(Locale.ENGLISH);
                    String value = entry.getValue().getAsString().trim().toLowerCase(Locale.ENGLISH);
                    if(!textureMap.containsKey(key))
                    {
                        if(key.equals("particle"))
                        {
                            manager.setParticle(this.loadTexture(file, key, value));
                        }
                        else if(!value.startsWith("#"))
                        {
                            textureMap.put(key, value);
                            this.loadTexture(file, key, value);
                        }
                    }
                }
            }
        }
    }

    private TextureEntry loadTexture(File project, String id, String texture)
    {
        TexturePath texturePath = new TexturePath(texture);

        /* Try loading textures as project format */
        if(project != null)
        {
            File path = new File(project, "textures");
            if(path.exists() && path.isDirectory())
            {
                File textureFile = new File(path, texturePath.getName() + ".png");
                if(textureFile.exists() && textureFile.isFile())
                {
                    return TextureManager.addImage(id, texturePath, textureFile);
                }
            }

            /* V2 of project file format uses assets folder */
            File assets = new File(project, "assets");
            if(assets.exists() && assets.isDirectory())
            {
                File textureFile = new File(assets, texturePath.toRelativePath());
                if(textureFile.exists() && textureFile.isFile())
                {
                    return TextureManager.addImage(id, texturePath, textureFile);
                }
            }
        }

        /* Try loading textures as if it was from assets */
        File parent = project;
        if(parent != null)
        {
            while((parent = parent.getParentFile()) != null)
            {
                if(parent.getName().equals("assets"))
                {
                    File textureFile = new File(parent, texturePath.toRelativePath());
                    if(textureFile.exists() && textureFile.isFile())
                    {
                        return TextureManager.addImage(id, texturePath, textureFile);
                    }
                }
                else if(parent.getName().equals(texturePath.getModId()))
                {
                    File textureFile = new File(parent, "textures" + File.separator + texturePath.getDirectory() + File.separator + texturePath.getName() + ".png");
                    if(textureFile.exists() && textureFile.isFile())
                    {
                        return TextureManager.addImage(id, texturePath, textureFile);
                    }
                }
            }
        }

        /* Try loading textures from assets directory */
        if(Settings.getAssetsDir() != null)
        {
            String path = Settings.getAssetsDir() + File.separator + texturePath.toRelativePath();
            File textureFile = new File(path);
            if(textureFile.exists())
            {
                return TextureManager.addImage(id, texturePath, textureFile);
            }
        }

        return null;
    }

    private void readDisplayProperties(JsonObject obj, ElementManager manager)
    {
        DisplayProperties properties = manager.getDisplayProperties();
        properties.getEntries().forEach((s, entry) -> entry.setEnabled(false));
        for(String displayName : displayNames)
        {
            if(obj.has(displayName) && obj.get(displayName).isJsonObject())
            {
                readEntry(obj.getAsJsonObject(displayName), displayName, properties);
            }
        }
    }

    private void readEntry(JsonObject obj, String id, DisplayProperties properties)
    {
        DisplayProperties.Entry entry = new DisplayProperties.Entry(id, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
        if(obj.has("rotation") && obj.get("rotation").isJsonArray())
        {
            JsonArray array = obj.get("rotation").getAsJsonArray();
            if(array.size() == 3)
            {
                entry.setRotationX(array.get(0).getAsDouble());
                entry.setRotationY(array.get(1).getAsDouble());
                entry.setRotationZ(array.get(2).getAsDouble());
            }
        }
        if(obj.has("translation") && obj.get("translation").isJsonArray())
        {
            JsonArray array = obj.get("translation").getAsJsonArray();
            if(array.size() == 3)
            {
                entry.setTranslationX(array.get(0).getAsDouble());
                entry.setTranslationY(array.get(1).getAsDouble());
                entry.setTranslationZ(array.get(2).getAsDouble());
            }
        }
        if(obj.has("scale") && obj.get("scale").isJsonArray())
        {
            JsonArray array = obj.get("scale").getAsJsonArray();
            if(array.size() == 3)
            {
                entry.setScaleX(array.get(0).getAsDouble());
                entry.setScaleY(array.get(1).getAsDouble());
                entry.setScaleZ(array.get(2).getAsDouble());
            }
        }
        properties.getEntries().put(id, entry);
    }

    private void readElement(JsonObject obj, ElementManager manager)
    {
        String name = "Element";
        JsonArray from = null;
        JsonArray to = null;

        if(obj.has("name") && obj.get("name").isJsonPrimitive())
        {
            name = obj.get("name").getAsString();
        }
        else if(obj.has("comment") && obj.get("comment").isJsonPrimitive())
        {
            name = obj.get("comment").getAsString();
        }
        else if(obj.has("__comment") && obj.get("__comment").isJsonPrimitive())
        {
            name = obj.get("__comment").getAsString();
        }
        if(obj.has("from") && obj.get("from").isJsonArray())
        {
            from = obj.get("from").getAsJsonArray();
        }
        if(obj.has("to") && obj.get("to").isJsonArray())
        {
            to = obj.get("to").getAsJsonArray();
        }

        if(from != null && to != null)
        {
            double x = from.get(0).getAsDouble();
            double y = from.get(1).getAsDouble();
            double z = from.get(2).getAsDouble();

            double w = to.get(0).getAsDouble() - x;
            double h = to.get(1).getAsDouble() - y;
            double d = to.get(2).getAsDouble() - z;

            Element element = new Element(w, h, d);
            element.setName(name);
            element.setStartX(x);
            element.setStartY(y);
            element.setStartZ(z);

            if(obj.has("rotation") && obj.get("rotation").isJsonObject())
            {
                JsonObject rot = obj.get("rotation").getAsJsonObject();

                if(rot.has("origin") && rot.get("origin").isJsonArray())
                {
                    JsonArray origin = rot.get("origin").getAsJsonArray();

                    double ox = origin.get(0).getAsDouble();
                    double oy = origin.get(1).getAsDouble();
                    double oz = origin.get(2).getAsDouble();

                    element.setOriginX(ox);
                    element.setOriginY(oy);
                    element.setOriginZ(oz);
                }

                if(rot.has("axis") && rot.get("axis").isJsonPrimitive())
                {
                    element.setRotationAxis(Element.parseAxisString(rot.get("axis").getAsString()));
                }

                if(rot.has("angle") && rot.get("angle").isJsonPrimitive())
                {
                    element.setRotation(rot.get("angle").getAsDouble());
                }

                if(rot.has("rescale") && rot.get("rescale").isJsonPrimitive())
                {
                    element.setRescale(rot.get("rescale").getAsBoolean());
                }
            }

            element.setShade(true);
            if(obj.has("shade") && obj.get("shade").isJsonPrimitive())
            {
                element.setShade(obj.get("shade").getAsBoolean());
            }

            for(Face face : element.getAllFaces())
            {
                face.setEnabled(false);
            }

            if(obj.has("faces") && obj.get("faces").isJsonObject())
            {
                JsonObject faces = obj.get("faces").getAsJsonObject();

                for(String faceName : faceNames)
                {
                    if(faces.has(faceName) && faces.get(faceName).isJsonObject())
                    {
                        readFace(faces.get(faceName).getAsJsonObject(), faceName, element);
                    }
                }
            }

            manager.addElement(element);
        }
    }

    private void readFace(JsonObject obj, String name, Element element)
    {
        Face face = null;
        for(Face f : element.getAllFaces())
        {
            if(f.getSide() == Face.getFaceSide(name))
            {
                face = f;
            }
        }

        if(face != null)
        {
            face.setEnabled(true);

            // automatically set uv if not specified
            face.setEndU(element.getFaceDimension(face.getSide()).getWidth());
            face.setEndV(element.getFaceDimension(face.getSide()).getHeight());
            face.setAutoUVEnabled(true);

            if(obj.has("uv") && obj.get("uv").isJsonArray())
            {
                JsonArray uv = obj.get("uv").getAsJsonArray();

                double uStart = uv.get(0).getAsDouble();
                double vStart = uv.get(1).getAsDouble();
                double uEnd = uv.get(2).getAsDouble();
                double vEnd = uv.get(3).getAsDouble();

                face.setStartU(uStart);
                face.setStartV(vStart);
                face.setEndU(uEnd);
                face.setEndV(vEnd);

                if(element.getFaceDimension(face.getSide()).getWidth() != face.getEndU() - face.getStartU() || element.getFaceDimension(face.getSide()).getHeight() != face.getEndV() - face.getStartV())
                {
                    face.setAutoUVEnabled(false);
                }
            }

            if(obj.has("texture") && obj.get("texture").isJsonPrimitive())
            {
                String id = obj.get("texture").getAsString().replace("#", "");
                TextureEntry entry = TextureManager.getTexture(id);
                if(entry != null)
                {
                    face.setTexture(entry);
                }
            }

            if(obj.has("rotation") && obj.get("rotation").isJsonPrimitive())
            {
                face.setRotation((int) obj.get("rotation").getAsDouble() / 90);
            }

            // TODO cullface with different direction than face,tintindex
            if(obj.has("cullface") && obj.get("cullface").isJsonPrimitive())
            {
                String cullface = obj.get("cullface").getAsString();

                if(cullface.equals(Face.getFaceName(face.getSide())))
                {
                    face.setCullface(true);
                }
            }

            if(obj.has("tintindex") && obj.get("tintindex").isJsonPrimitive())
            {
                int tintIndex = obj.get("tintindex").getAsInt();
                if(tintIndex >= 0)
                {
                    face.setTintIndexEnabled(true);
                    face.setTintIndex(tintIndex);
                }
            }
        }
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/ModelCreator.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.component.TextureManager;
import com.mrcrayfish.modelcreator.dialog.WelcomeDialog;
import com.mrcrayfish.modelcreator.display.CanvasRenderer;
import com.mrcrayfish.modelcreator.display.render.StandardRenderer;
import com.mrcrayfish.modelcreator.element.Element;
import com.mrcrayfish.modelcreator.element.ElementCellEntry;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.ElementManagerState;
import com.mrcrayfish.modelcreator.panels.SidebarPanel;
import com.mrcrayfish.modelcreator.screenshot.PendingScreenshot;
import com.mrcrayfish.modelcreator.screenshot.Screenshot;
import com.mrcrayfish.modelcreator.sidebar.Sidebar;
import com.mrcrayfish.modelcreator.sidebar.UVSidebar;
import com.mrcrayfish.modelcreator.texture.TextureAtlas;
import com.mrcrayfish.modelcreator.texture.TextureEntry;
import com.mrcrayfish.modelcreator.util.FontManager;
import com.mrcrayfish.modelcreator.util.KeyboardUtil;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import static org.lwjgl.opengl.GL11.*;

public class ModelCreator extends JFrame
{
    public static final Color BACKGROUND = new Color(227, 227, 234);

    // Canvas Variables
    private final static AtomicReference<Dimension> newCanvasSize = new AtomicReference<>();
    private Canvas canvas;
    private int width = 990, height = 800;

    // Swing Components
    private JScrollPane scroll;
    private Camera camera;
    private SidebarPanel manager;
    private Element grabbed = null;

    // Texture Loading Cache
    private PendingScreenshot screenshot = null;

    private int lastMouseX, lastMouseY;
    private boolean grabbing = false;
    private boolean closeRequested = false;
    private boolean performedChange = false;
    private boolean grabbingInSidebar = false;

    /* Sidebar Variables */
    private final int SIDEBAR_WIDTH = 130;
    private Sidebar activeSidebar = null;
    public static Sidebar uvSidebar;
    public static boolean isUVSidebarOpen = false;

    /* Key Events */
    private Set<Integer> keyDown = new HashSet<>();
    private List<KeyAction> keyActions = new ArrayList<>();

    private static boolean changedCanvas = false;
    private static CanvasRenderer standardRenderer = new StandardRenderer();
    private static CanvasRenderer canvasRenderer = standardRenderer;

    private boolean debugMode = false;

    public ModelCreator(String title)
    {
        super(title);

        setPreferredSize(new Dimension(1200, 815));
        setMinimumSize(new Dimension(1200, 500));
        setLayout(new BorderLayout(10, 0));
        setIconImages(getIcons());
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e ->
        {
            if(e.getID() == KeyEvent.KEY_PRESSED && !keyDown.contains(e.getKeyCode()))
            {
                keyDown.add(e.getKeyCode());
                ModelCreator.this.handleKeyAction(e.getKeyCode(), e.getModifiers(), true, true);
            }
            else if(e.getID() == KeyEvent.KEY_RELEASED)
            {
                keyDown.remove(e.getKeyCode());
                ModelCreator.this.handleKeyAction(e.getKeyCode(), e.getModifiers(), true, false);
            }
            return false;
        });

        try
        {
            canvas = new AWTGLCanvas();
            canvas.addComponentListener(new ComponentAdapter()
            {
                @Override
                public void componentResized(ComponentEvent e)
                {
                    newCanvasSize.set(canvas.getSize());
                }
            });
            addWindowFocusListener(new WindowAdapter()
            {
                @Override
                public void windowGainedFocus(WindowEvent e)
                {
                    canvas.requestFocusInWindow();
                }
            });
        }
        catch(LWJGLException e)
        {
            e.printStackTrace();
            System.exit(1);
        }

        initComponents();
        registerShortcuts();

        uvSidebar = new UVSidebar("UV Editor", manager);

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                closeRequested = true;
            }
        });

        manager.updateValues();

        pack();
        setVisible(true);
        setLocationRelativeTo(null);

        SwingUtilities.invokeLater(() -> WelcomeDialog.show(ModelCreator.this));

        createDisplay();

        loop();

        Display.destroy();
        dispose();
        System.exit(0);
    }

    private void initComponents()
    {
        Icons.init(this.getClass());
        setupMenuBar();

        canvas.setFocusable(true);
        add(canvas, BorderLayout.CENTER);

        manager = new SidebarPanel(this);
        scroll = new JScrollPane(manager);
        scroll.setBorder(BorderFactory.createEmptyBorder());
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        add(scroll, BorderLayout.EAST);
        StateManager.pushState(manager);
    }

    private void registerShortcuts()
    {
        this.keyActions.add(new KeyAction(KeyEvent.VK_E, Keyboard.KEY_E, (modifiers, pressed) ->
        {
            if(pressed && modifiers == InputEvent.CTRL_MASK)
            {
                manager.newElement();
            }
        }));
        this.keyActions.add(new KeyAction(KeyEvent.VK_D, Keyboard.KEY_D, (modifiers, pressed) ->
        {
            if(pressed && (modifiers & InputEvent.CTRL_MASK) != 0 && (modifiers & InputEvent.SHIFT_MASK) != 0 && (modifiers & InputEvent.ALT_MASK) != 0)
            {
                debugMode = !debugMode;
            }
        }));
        this.keyActions.add(new KeyAction(KeyEvent.VK_F, Keyboard.KEY_F, (modifiers, pressed) ->
        {
            if(pressed && modifiers == InputEvent.CTRL_MASK)
            {
                ElementCellEntry entry = manager.getSelectedElementEntry();
                if(entry != null)
                {
                    entry.toggleVisibility();
                    manager.getList().repaint();
                }
            }
        }));
        this.keyActions.add(new KeyAction(KeyEvent.VK_D, Keyboard.KEY_D, (modifiers, pressed) ->
        {
            if(pressed && modifiers == InputEvent.CTRL_MASK)
            {
                manager.deleteElement();
            }
        }));
    }

    private List<Image> getIcons()
    {
        List<Image> icons = new ArrayList<>();
        icons.add(Toolkit.getDefaultToolkit().getImage("res/icons/set/icon_16x.png"));
        icons.add(Toolkit.getDefaultToolkit().getImage("res/icons/set/icon_32x.png"));
        icons.add(Toolkit.getDefaultToolkit().getImage("res/icons/set/icon_64x.png"));
        icons.add(Toolkit.getDefaultToolkit().getImage("res/icons/set/icon_128x.png"));
        return icons;
    }

    public int getCanvasWidth()
    {
        return width;
    }

    public int getCanvasHeight()
    {
        return height;
    }

    public int getCanvasOffset()
    {
        return activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;
    }

    private void setupMenuBar()
    {
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
        setJMenuBar(new com.mrcrayfish.modelcreator.component.Menu(this));
    }

    private void createDisplay()
    {
        try
        {
            Display.setVSyncEnabled(true);
            Display.setInitialBackground(0.92F, 0.92F, 0.93F);
            Display.setParent(canvas);

        }
        catch(LWJGLException e)
        {
            e.printStackTrace();
        }

        try
        {
            Display.create((new PixelFormat(8, 0, 0, 4)).withDepthBits(24));
            return;
        }
        catch(LWJGLException e)
        {
            e.printStackTrace();
        }

        try
        {
            Thread.sleep(1000L);
        }
        catch(InterruptedException ignored)
        {
        }

        try
        {
            Display.create();
        }
        catch(LWJGLException e)
        {
            e.printStackTrace();
        }
    }

    private void loop()
    {
        TextureAtlas.load();

        camera = new Camera(60F, (float) Display.getWidth() / (float) Display.getHeight(), 0.3F, 1000F);

        long lastTime = System.nanoTime();
        double delta = 0.0;
        double ns = 1000000000.0 / 60.0;
        long timer = System.currentTimeMillis();
        int updates = 0;
        int frames = 0;
        while(!Display.isCloseRequested() && !getCloseRequested())
        {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            if(delta >= 1.0)
            {
                tick();
                updates++;
                delta--;
            }
            render(frames / 60.0F);
            frames++;
            if(System.currentTimeMillis() - timer > 1000)
            {
                timer += 1000;
                updates = 0;
                frames = 0;
            }
        }
    }

    private void tick()
    {
        Animation.tick();
    }

    private void render(float partialTicks)
    {
        Animation.setPartialTicks(partialTicks);
        TextureManager.processPendingTextures();

        Dimension newDim = newCanvasSize.getAndSet(null);
        if (newDim != null)
        {
            width = newDim.width;
            height = newDim.height;
        }

        this.handleKeyboardInput();

        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

        this.draw();

        Display.update();

        if(screenshot != null)
        {
            if(screenshot.getFile() != null)
            {
                Screenshot.getScreenshot(width, height, screenshot.getCallback(), screenshot.getFile());
            }
            else
            {
                Screenshot.getScreenshot(width, height, screenshot.getCallback());
            }
            screenshot = null;
        }
    }

    private void handleKeyboardInput()
    {
        while(Keyboard.next())
        {
            int modifiers = 0;
            if(KeyboardUtil.isCtrlKeyDown())
            {
                modifiers += InputEvent.CTRL_MASK;
            }
            if(KeyboardUtil.isShiftKeyDown())
            {
                modifiers += InputEvent.SHIFT_MASK;
            }
            if(KeyboardUtil.isAltKeyDown())
            {
                modifiers += InputEvent.ALT_MASK;
            }

            int code = Keyboard.getEventKey();
            int finalModifiers = modifiers;
            if(Keyboard.getEventKeyState())
            {
                SwingUtilities.invokeLater(() -> this.handleKeyAction(code, finalModifiers, false, true));
            }
            else
            {
                SwingUtilities.invokeLater(() -> this.handleKeyAction(code, finalModifiers, false, false));
            }
        }
    }

    private void draw()
    {
        if(changedCanvas)
        {
            canvasRenderer.onInit(camera);
            changedCanvas = false;
        }

        int offset = activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;

        glViewport(offset, 0, width - offset, height);

        this.handleInput(offset);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        GLU.gluPerspective(60F, (float) (width - offset) / (float) height, 0.3F, 1000F);

        canvasRenderer.onRenderPerspective(this, manager, camera);

        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        GLU.gluOrtho2D(0, width, height, 0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        canvasRenderer.onRenderOverlay(manager, camera, this);

        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        GLU.gluOrtho2D(0, width, height, 0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        drawOverlay(offset);
    }

    private void drawOverlay(int offset)
    {
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_DEPTH_TEST);
        glPushMatrix();
        {
            glColor3f(0.58F, 0.58F, 0.58F);
            glLineWidth(2F);
            glBegin(GL_LINES);
            {
                glVertex2i(offset, 0);
                glVertex2i(width, 0);
                glVertex2i(width, 0);
                glVertex2i(width, height);
                glVertex2i(offset, height);
                glVertex2i(offset, 0);
                glVertex2i(offset, height);
                glVertex2i(width, height);
            }
            glEnd();
        }
        glPopMatrix();

        if(debugMode)
        {
            glPushMatrix();
            {
                List<ElementManagerState> states = StateManager.getStates();
                for(int i = 0; i < states.size(); i++)
                {
                    ElementManagerState managerState = states.get(i);
                    String text = "No Elements";
                    if(managerState.getElements().size() > 0)
                    {
                        text = managerState.getElements().toString();
                    }

                    if(StateManager.getTailIndex() == i)
                    {
                        text = text + " <<<";
                    }

                    GL11.glEnable(GL11.GL_BLEND);
                    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
                    FontManager.BEBAS_NEUE_20.drawString(10, 10 + i * 20, text, new org.newdawn.slick.Color(1, 1, 1));
                    GL11.glDisable(GL11.GL_BLEND);
                }
            }
            glPopMatrix();
        }

        if(activeSidebar != null)
        {
            activeSidebar.draw(offset, width, height, getHeight());
        }

        if(canvasRenderer == standardRenderer)
        {
            glPushMatrix();
            {
                glTranslatef(width - 80, height - 80, 0);
                glLineWidth(2F);
                glRotated(-camera.getRY(), 0, 0, 1);

                GL11.glEnable(GL11.GL_BLEND);
                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
                FontManager.BEBAS_NEUE_20.drawString(-5, -75, "N", new org.newdawn.slick.Color(1, 1, 1));
                GL11.glDisable(GL11.GL_BLEND);

                glColor3d(0.6, 0.6, 0.6);
                glBegin(GL_LINES);
                {
                    glVertex2i(0, -50);
                    glVertex2i(0, 50);
                    glVertex2i(-50, 0);
                    glVertex2i(50, 0);
                }
                glEnd();

                glColor3d(0.3, 0.3, 0.6);
                glBegin(GL_TRIANGLES);
                {
                    glVertex2i(-5, -45);
                    glVertex2i(0, -50);
                    glVertex2i(5, -45);

                    glVertex2i(-5, 45);
                    glVertex2i(0, 50);
                    glVertex2i(5, 45);

                    glVertex2i(-45, -5);
                    glVertex2i(-50, 0);
                    glVertex2i(-45, 5);

                    glVertex2i(45, -5);
                    glVertex2i(50, 0);
                    glVertex2i(45, 5);
                }
                glEnd();
            }
            glPopMatrix();
        }

        /*glColor3f(1.0F, 1.0F, 1.0F);
        glBindTexture(GL_TEXTURE_2D, 6);
        glBegin(GL_QUADS);
        {
            glTexCoord2d(0.0, 0.0);
            glVertex2f(0, 0);
            glTexCoord2d(1.0, 0.0);
            glVertex2f(300, 0);
            glTexCoord2d(1.0, 1.0);
            glVertex2f(300, 300);
            glTexCoord2d(0.0, 1.0);
            glVertex2f(0, 300);
        }
        glEnd();*/
    }

    private void handleInput(int offset)
    {
        final float cameraMod = Math.abs(camera.getZ());

        if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1))
        {
            if(!grabbingInSidebar)
            {
                if(!grabbing && Mouse.getX() < offset)
                {
                    grabbingInSidebar = true;
                }
                if(!grabbing)
                {
                    lastMouseX = Mouse.getX();
                    lastMouseY = Mouse.getY();
                    grabbing = true;
                    uvSidebar.handleMouseInput(0, Mouse.getX(), Mouse.getY(), true);
                }
            }
        }
        else if(grabbing)
        {
            if(grabbed != null && performedChange)
            {
                StateManager.pushState(manager);
                performedChange = false;
            }
            grabbing = false;
            grabbed = null;
        }
        else if(grabbingInSidebar)
        {
            uvSidebar.handleMouseInput(0, Mouse.getX(), Mouse.getY(), false);
            grabbingInSidebar = false;
        }

        if(grabbingInSidebar)
        {
            activeSidebar.handleInput(getHeight());
        }
        else
        {
            if(Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) && canvasRenderer == standardRenderer)
            {
                if(grabbed == null)
                {
                    if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1))
                    {
                        int sel = select(Mouse.getX(), Mouse.getY());
                        if(sel >= 0)
                        {
                            grabbed = manager.getAllElements().get(sel);
                            manager.setSelectedElement(sel);
                        }
                        else
                        {
                            grabbed = null;
                            manager.setSelectedElement(-1);
                        }
                    }
                }
                else
                {
                    Element element = grabbed;
                    int state = getCameraState(camera);

                    int newMouseX = Mouse.getX();
                    int newMouseY = Mouse.getY();

                    int xMovement = (newMouseX - lastMouseX) / 20;
                    int yMovement = (newMouseY - lastMouseY) / 20;

                    if(xMovement != 0 | yMovement != 0)
                    {
                        if(Mouse.isButtonDown(0))
                        {
                            switch(state)
                            {
                                case 0:
                                    element.addStartX(xMovement);
                                    element.addStartY(yMovement);
                                    break;
                                case 1:
                                    element.addStartZ(xMovement);
                                    element.addStartY(yMovement);
                                    break;
                                case 2:
                                    element.addStartX(-xMovement);
                                    element.addStartY(yMovement);
                                    break;
                                case 3:
                                    element.addStartZ(-xMovement);
                                    element.addStartY(yMovement);
                                    break;
                                case 4:
                                    element.addStartX(xMovement);
                                    element.addStartZ(-yMovement);
                                    break;
                                case 5:
                                    element.addStartX(yMovement);
                                    element.addStartZ(xMovement);
                                    break;
                                case 6:
                                    element.addStartX(-xMovement);
                                    element.addStartZ(yMovement);
                                    break;
                                case 7:
                                    element.addStartX(-yMovement);
                                    element.addStartZ(-xMovement);
                                    break;
                            }
                        }
                        else if(Mouse.isButtonDown(1))
                        {
                            switch(state)
                            {
                                case 0:
                                    element.addHeight(yMovement);
                                    element.addWidth(xMovement);
                                    break;
                                case 1:
                                    element.addHeight(yMovement);
                                    element.addDepth(xMovement);
                                    break;
                                case 2:
                                    element.addHeight(yMovement);
                                    element.addWidth(-xMovement);
                                    break;
                                case 3:
                                    element.addHeight(yMovement);
                                    element.addDepth(-xMovement);
                                    break;
                                case 4:
                                    element.addDepth(-yMovement);
                                    element.addWidth(xMovement);
                                    break;
                                case 5:
                                    element.addDepth(xMovement);
                                    element.addWidth(yMovement);
                                    break;
                                case 6:
                                    element.addDepth(yMovement);
                                    element.addWidth(-xMovement);
                                    break;
                                case 7:
                                    element.addDepth(-xMovement);
                                    element.addWidth(-yMovement);
                                    break;
                                case 8:
                                    element.addDepth(-yMovement);
                                    element.addWidth(xMovement);
                                    break;
                            }
                        }

                        if(xMovement != 0)
                        {
                            lastMouseX = newMouseX;
                        }
                        if(yMovement != 0)
                        {
                            lastMouseY = newMouseY;
                        }

                        manager.updateValues();
                        element.updateEndUVs();

                        performedChange = true;
                    }
                }
            }
            else
            {
                if(Mouse.isButtonDown(0))
                {
                    final float modifier = (cameraMod * 0.05f);
                    camera.addX(Mouse.getDX() * 0.01F * modifier);
                    camera.addY(Mouse.getDY() * 0.01F * modifier);
                }
                else if(Mouse.isButtonDown(1))
                {
                    final float modifier = applyLimit(cameraMod * 0.1f);
                    camera.rotateX(-(Mouse.getDY() * 0.5F) * modifier);
                    final float rxAbs = Math.abs(camera.getRX());
                    camera.rotateY((rxAbs >= 90 && rxAbs < 270 ? -1 : 1) * Mouse.getDX() * 0.5F * modifier);
                }

                final float wheel = Mouse.getDWheel();
                if(wheel != 0)
                {
                    camera.addZ(wheel * (cameraMod / 5000F));
                }
            }
        }
    }

    private int select(int x, int y)
    {
        IntBuffer selBuffer = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder()).asIntBuffer();
        int[] buffer = new int[256];

        IntBuffer viewBuffer = ByteBuffer.allocateDirect(64).order(ByteOrder.nativeOrder()).asIntBuffer();
        int[] viewport = new int[4];

        int hits;
        GL11.glGetInteger(GL11.GL_VIEWPORT, viewBuffer);
        viewBuffer.get(viewport);

        GL11.glSelectBuffer(selBuffer);
        GL11.glRenderMode(GL11.GL_SELECT);
        GL11.glInitNames();
        GL11.glPushName(0);
        GL11.glPushMatrix();
        {
            GL11.glMatrixMode(GL11.GL_PROJECTION);
            GL11.glLoadIdentity();
            GLU.gluPickMatrix(x, y, 1, 1, IntBuffer.wrap(viewport));

            int offset = activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;
            GLU.gluPerspective(60F, (float) (width - offset) / (float) height, 0.3F, 1000F);
            canvasRenderer.onRenderPerspective(this, manager, camera);
        }
        GL11.glPopMatrix();
        hits = GL11.glRenderMode(GL11.GL_RENDER);

        selBuffer.get(buffer);
        if(hits > 0)
        {
            int choose = buffer[3];
            int depth = buffer[1];

            for(int i = 1; i < hits; i++)
            {
                if((buffer[i * 4 + 1] < depth || choose == 0) && buffer[i * 4 + 3] != 0)
                {
                    choose = buffer[i * 4 + 3];
                    depth = buffer[i * 4 + 1];
                }
            }

            if(choose > 0)
            {
                return choose - 1;
            }
        }

        return -1;
    }

    private float applyLimit(float value)
    {
        if(value > 0.4F)
        {
            value = 0.4F;
        }
        else if(value < 0.15F)
        {
            value = 0.15F;
        }
        return value;
    }

    private int getCameraState(Camera camera)
    {
        int cameraRotY = (int) (camera.getRY() >= 0 ? camera.getRY() : 360 + camera.getRY());
        int state = (int) ((cameraRotY * 4.0F / 360.0F) + 0.5D) & 3;

        if(camera.getRX() > 45)
        {
            state += 4;
        }
        if(camera.getRX() < -45)
        {
            state += 8;
        }
        return state;
    }

    public void startScreenshot(PendingScreenshot screenshot)
    {
        this.screenshot = screenshot;
    }

    public void setSidebar(Sidebar s)
    {
        activeSidebar = s;
        if(s == null)
        {
            isUVSidebarOpen = false;
        }
    }

    public Sidebar getActiveSidebar()
    {
        return activeSidebar;
    }

    public ElementManager getElementManager()
    {
        return manager;
    }

    public void close()
    {
        this.closeRequested = true;
    }

    private boolean getCloseRequested()
    {
        return closeRequested;
    }

    private void handleKeyAction(int code, int modifiers, boolean awt, boolean pressed)
    {
        if(!this.isActive())
            return;

        keyActions.forEach(keyAction ->
        {
            if(awt)
            {
                if(keyAction.awtCode == code)
                {
                    keyAction.handler.process(modifiers, pressed);
                }
            }
            else
            {
                if(keyAction.keyboardCode == code)
                {
                    keyAction.handler.process(modifiers, pressed);
                }
            }
        });
    }

    public static void setCanvasRenderer(CanvasRenderer displayRenderer)
    {
        canvasRenderer = displayRenderer;
        changedCanvas = true;
    }

    public static void restoreStandardRenderer()
    {
        setCanvasRenderer(standardRenderer);
    }

    public void registerKeyAction(KeyAction keyAction)
    {
        this.keyActions.add(keyAction);
    }

    public static class KeyAction
    {
        private final int awtCode;
        private final int keyboardCode;
        private final Handler handler;

        public KeyAction(int awtCode, int keyboardCode, Handler handler)
        {
            this.awtCode = awtCode;
            this.keyboardCode = keyboardCode;
            this.handler = handler;
        }

        public interface Handler
        {
            void process(int modifiers, boolean pressed);
        }
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Processor.java
================================================
package com.mrcrayfish.modelcreator;

/**
 * Author: MrCrayfish
 */
public interface Processor<T>
{
    /**
     * Processes the given argument.
     *
     * @param t the object to process
     * @return if the object was processed successfully
     */
    boolean run(T t);
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/ProjectManager.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.component.TextureManager;
import com.mrcrayfish.modelcreator.element.Element;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.Face;
import com.mrcrayfish.modelcreator.texture.TextureEntry;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class ProjectManager
{
    private static final Pattern TEXTURE_FIX = Pattern.compile("\\d+$"); //Matches numbers at the end of line

    public static void loadProject(ElementManager manager, String modelFile)
    {
        TextureManager.clear();
        manager.clearElements();
        manager.setParticle(null);

        File projectFolder = extractFiles(modelFile);
        if(projectFolder != null)
        {
            Project project = new Project(projectFolder);
            Importer importer = new Importer(manager, project.getModel().getPath());
            importer.importFromJSON();
        }
        deleteFolder(projectFolder);
    }

    private static void deleteFolder(File file)
    {
        Runtime.getRuntime().addShutdownHook(new Thread(() ->
        {
            try
            {
                Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }));
    }

    private static File extractFiles(String modelFile)
    {
        try
        {
            Path path = Files.createTempDirectory("ModelCreator");
            File folder = path.toFile();

            ZipInputStream zis = new ZipInputStream(new FileInputStream(modelFile));
            ZipEntry ze;
            while((ze = zis.getNextEntry()) != null)
            {
                String fileName = ze.getName();

                /* Fixes old project texture files extracting with numbers on the file name */
                Matcher matcher = TEXTURE_FIX.matcher(ze.getName());
                if(matcher.find())
                {
                    String numbers = matcher.group(0);
                    fileName = fileName.replace(numbers, "");
                }

                File file = new File(folder, fileName);
                file.getParentFile().mkdirs();
                file.createNewFile();

                byte[] buffer = new byte[1024];
                FileOutputStream fos = new FileOutputStream(file);

                int len;
                while((len = zis.read(buffer)) > 0)
                {
                    fos.write(buffer, 0, len);
                }

                fos.flush();
                fos.close();
                zis.closeEntry();
            }
            zis.close();

            return folder;
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    public static void saveProject(ElementManager manager, String name)
    {
        try
        {
            FileOutputStream fos = new FileOutputStream(name);
            ZipOutputStream zos = new ZipOutputStream(fos);

            File file = getSaveFile(manager);
            addToZipFile(file, zos, "model.json");
            file.delete();

            for(TextureEntry entry : getAllTextures(manager))
            {
                File temp = File.createTempFile(entry.getName(), "");
                BufferedImage image = entry.getSource();
                ImageIO.write(image, "PNG", temp);
                addToZipFile(temp, zos, "assets/" + entry.getModId() + "/textures/" + entry.getDirectory() + "/", entry.getName() + ".png");
                temp.delete();
            }

            //TODO make output animation properties
            /*for(String metaLocation : getMetaLocations(manager))
            {
                if(metaLocation != null)
                {
                    File texture = new File(metaLocation);
                    if(texture.exists())
                    {
                        addToZipFile(texture, zos, "textures/", texture.getName());
                    }
                }
            }*/

            zos.close();
            fos.close();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }

    private static Set<TextureEntry> getAllTextures(ElementManager manager)
    {
        Set<TextureEntry> textureEntries = new HashSet<>();
        for(Element element : manager.getAllElements())
        {
            for(Face face : element.getAllFaces())
            {
                if(face.getTexture() != null)
                {
                    textureEntries.add(face.getTexture());
                }
            }
        }
        return textureEntries;
    }

    private static File getSaveFile(ElementManager manager) throws IOException
    {
        ExporterModel exporter = new ExporterModel(manager);
        exporter.setOptimize(false);
        exporter.setIncludeNonTexturedFaces(true);
        return exporter.writeFile(File.createTempFile("model.json", ""));
    }

    private static void addToZipFile(File file, ZipOutputStream zos, String name) throws IOException
    {
        addToZipFile(file, zos, "", name);
    }

    private static void addToZipFile(File file, ZipOutputStream zos, String folder, String name) throws IOException
    {
        FileInputStream fis = new FileInputStream(file);
        ZipEntry zipEntry = new ZipEntry(folder + name);
        zos.putNextEntry(zipEntry);

        byte[] bytes = new byte[1024];
        int length;
        while((length = fis.read(bytes)) >= 0)
        {
            zos.write(bytes, 0, length);
        }

        zos.closeEntry();
        fis.close();
    }

    private static class Project
    {
        public File model;
        public File textures;

        public Project(File folder)
        {
            File[] files = folder.listFiles();
            if(files != null)
            {
                for(File file : files)
                {
                    String name = file.getName();
                    if(file.isFile() && name.equals("model.json"))
                    {
                        this.model = file;
                    }
                    else if(file.isDirectory() && name.equals("textures"))
                    {
                        this.textures = file;
                    }
                }
            }
        }

        public File getModel()
        {
            return model;
        }

        public File getTextures()
        {
            return textures;
        }
    }

    private static class ProjectTexture
    {
        private File texture;
        private File meta;

        public ProjectTexture(File texture, File meta)
        {
            this.texture = texture;
            this.meta = meta;
        }

        public File getTexture()
        {
            return texture;
        }

        public File getMeta()
        {
            return meta;
        }
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/PropertyIdentifiers.java
================================================
package com.mrcrayfish.modelcreator;

/**
 * Author: MrCrayfish
 */
public class PropertyIdentifiers
{
    public static final int SIZE_X = 0;
    public static final int SIZE_Y = 1;
    public static final int SIZE_Z = 2;
    public static final int POS_X = 3;
    public static final int POS_Y = 4;
    public static final int POS_Z = 5;
    public static final int ORIGIN_X = 6;
    public static final int ORIGIN_Y = 7;
    public static final int ORIGIN_Z = 8;
    public static final int START_U = 9;
    public static final int START_V = 10;
    public static final int END_U = 11;
    public static final int END_V = 12;
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Settings.java
================================================
package com.mrcrayfish.modelcreator;

import java.util.prefs.Preferences;

public class Settings
{
    private static final String IMAGE_IMPORT_DIR = "image_import_dir";
    private static final String SCREENSHOT_DIR = "screenshot_dir";
    private static final String MODEL_DIR = "model_dir";
    private static final String JSON_DIR = "json_dir";
    private static final String EXPORT_JSON_DIR = "export_json_dir";
    private static final String UNDO_LIMIT = "undo_limit";
    private static final String RENDER_CARDINAL_POINTS = "cardinal_points";
    private static final String ASSESTS_DIR = "assets_dir";
    private static final String FACE_COLORS = "face_colors";
    private static final String IMAGE_EDITOR = "image_editor";
    private static final String IMAGE_EDITOR_ARGS = "image_editor_args";

    public static final int[] DEFAULT_FACE_COLORS = {16711680, 65280, 255, 16776960, 16711935, 65535};

    public static String getImageImportDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(IMAGE_IMPORT_DIR, null);
    }

    public static void setImageImportDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(IMAGE_IMPORT_DIR, dir);
    }

    public static String getScreenshotDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(SCREENSHOT_DIR, null);
    }

    public static void setScreenshotDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(SCREENSHOT_DIR, dir);
    }

    public static String getModelDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(MODEL_DIR, null);
    }

    public static void setModelDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(MODEL_DIR, dir);
    }

    public static String getJSONDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(JSON_DIR, null);
    }

    public static void setJSONDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(JSON_DIR, dir);
    }

    public static String getExportJSONDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(EXPORT_JSON_DIR, null);
    }

    public static void setExportJSONDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(EXPORT_JSON_DIR, dir);
    }

    public static String getAssetsDir()
    {
        Preferences prefs = getPreferences();
        return prefs.get(ASSESTS_DIR, null);
    }

    public static void setAssetsDir(String dir)
    {
        Preferences prefs = getPreferences();
        prefs.put(ASSESTS_DIR, dir);
    }

    public static int getUndoLimit()
    {
        Preferences prefs = getPreferences();
        String s = prefs.get(UNDO_LIMIT, null);
        try
        {
            return Math.max(1, Integer.parseInt(s));
        }
        catch(NumberFormatException e)
        {
            return 50;
        }
    }

    public static void setUndoLimit(int limit)
    {
        Preferences prefs = getPreferences();
        prefs.put(UNDO_LIMIT, Integer.toString(Math.max(1, limit)));
    }

    public static boolean getCardinalPoints()
    {
        Preferences prefs = getPreferences();
        String s = prefs.get(RENDER_CARDINAL_POINTS, "true");
        return Boolean.parseBoolean(s);
    }

    public static void setCardinalPoints(boolean renderCardinalPoints)
    {
        Preferences prefs = getPreferences();
        prefs.put(RENDER_CARDINAL_POINTS, Boolean.toString(renderCardinalPoints));
    }

    public static int[] getFaceColors()
    {
        Preferences prefs = getPreferences();
        String s = prefs.get(FACE_COLORS, "");
        String[] values = s.split(",");
        if(values.length == 6)
        {
            int[] colors = new int[6];
            for(int i = 0; i < values.length; i++)
            {
                int color = Integer.parseInt(values[i]);
                colors[i] = color;
            }
            return colors;
        }
        return DEFAULT_FACE_COLORS;
    }

    public static void setFaceColors(int[] colors)
    {
        StringBuilder builder = new StringBuilder();
        for(int value : colors)
        {
            builder.append(value);
            builder.append(",");
        }
        builder.setLength(builder.length() - 1);
        Preferences prefs = getPreferences();
        prefs.put(FACE_COLORS, builder.toString());
    }

    public static String getImageEditor()
    {
        Preferences prefs = getPreferences();
        return prefs.get(IMAGE_EDITOR, null);
    }

    public static void setImageEditor(String file)
    {
        Preferences prefs = getPreferences();
        prefs.put(IMAGE_EDITOR, file);
    }

    public static String getImageEditorArgs()
    {
        Preferences prefs = getPreferences();
        return prefs.get(IMAGE_EDITOR_ARGS, "\"%s\"");
    }

    public static void setImageEditorArgs(String args)
    {
        Preferences prefs = getPreferences();
        prefs.put(IMAGE_EDITOR_ARGS, args);
    }

    private static Preferences getPreferences()
    {
        return Preferences.userNodeForPackage(Settings.class);
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/Start.java
================================================
package com.mrcrayfish.modelcreator;

import com.jtattoo.plaf.fast.FastLookAndFeel;
import com.mrcrayfish.modelcreator.util.SharedLibraryLoader;

import javax.swing.*;
import java.util.Properties;

public class Start
{
    public static void main(String[] args)
    {
        SharedLibraryLoader.load(false);

        Double version = Double.parseDouble(System.getProperty("java.specification.version"));
        if(version < 1.8)
        {
            JOptionPane.showMessageDialog(null, "You need Java 1.8 or higher to run this program.");
            return;
        }

        System.setProperty("org.lwjgl.util.Debug", "true");

        try
        {
            Properties props = new Properties();
            props.put("logoString", "");
            props.put("centerWindowTitle", "on");
            props.put("buttonBackgroundColor", "127 132 145");
            props.put("buttonForegroundColor", "255 255 255");
            props.put("windowTitleBackgroundColor", "97 102 115");
            props.put("windowTitleForegroundColor", "255 255 255");
            props.put("backgroundColor", "221 221 228");
            props.put("menuBackgroundColor", "221 221 228");
            props.put("controlForegroundColor", "120 120 120");
            props.put("windowBorderColor", "97 102 110");
            FastLookAndFeel.setTheme(props);
            UIManager.setLookAndFeel("com.jtattoo.plaf.fast.FastLookAndFeel");
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }

        new ModelCreator(Constants.NAME + " v" + Constants.VERSION);
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/StateManager.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.element.ElementManagerState;

import javax.swing.*;
import java.awt.event.ActionListener;
import java.util.Stack;

/**
 * Author: MrCrayfish
 */
public class StateManager
{
    /* Undo/Redo Stack */
    private static Stack<ElementManagerState> states = new Stack<>();
    private static int tailIndex = -1;
    private static int lastId = -1;
    private static Timer timer;

    public static void pushState(ElementManager manager)
    {
        pushState(manager.createState());
    }

    private static void pushState(ElementManagerState state)
    {
        if(timer != null && timer.isRunning())
        {
            for(ActionListener listener : timer.getActionListeners())
            {
                listener.actionPerformed(null);
            }
            timer.stop();
            timer = null;
        }
        pushManagerState(state);
    }

    private static void pushManagerState(ElementManagerState state)
    {
        while(tailIndex < states.size() - 1)
        {
            states.pop();
        }

        if(states.size() >= 50) //Make configurable
        {
            while(states.size() > 50)
            {
                states.remove(0);
            }
        }
        states.push(state);
        tailIndex = states.size() - 1;
    }

    public static void restorePreviousState(ElementManager manager)
    {
        if(canRestorePreviousState())
        {
            ElementManagerState state = states.get(tailIndex - 1);
            manager.restoreState(state);
            tailIndex--;
        }
    }

    public static void restoreNextState(ElementManager manager)
    {
        if(canRestoreNextState())
        {
            ElementManagerState state = states.get(tailIndex + 1);
            manager.restoreState(state);
            tailIndex++;
        }
    }

    public static boolean canRestorePreviousState()
    {
        return tailIndex > 0;
    }

    public static boolean canRestoreNextState()
    {
        return tailIndex < states.size() - 1;
    }

    public static void clear()
    {
        if(timer != null && timer.isRunning())
        {
            timer.stop();
            timer = null;
        }
        states.clear();
        tailIndex = -1;
    }

    public static Stack<ElementManagerState> getStates()
    {
        return states;
    }

    public static int getTailIndex()
    {
        return tailIndex;
    }

    public static void pushStateDelayed(ElementManager manager, int id)
    {
        if(lastId != id)
        {
            if(timer != null && timer.isRunning())
            {
                for(ActionListener listener : timer.getActionListeners())
                {
                    listener.actionPerformed(null);
                }
                timer.stop();
            }
        }
        else
        {
            if(timer != null && timer.isRunning())
            {
                timer.stop();
            }
        }

        ElementManagerState state = manager.createState();
        ActionListener listener = e -> pushManagerState(state);
        timer = new Timer(400, listener);
        timer.setRepeats(false);
        timer.start();
        lastId = id;
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/TexturePath.java
================================================
package com.mrcrayfish.modelcreator;

import com.mrcrayfish.modelcreator.util.AssetsUtil;

import java.io.File;
import java.util.regex.Pattern;

/**
 * Author: MrCrayfish
 */
public class TexturePath
{
    public static final Pattern PATTERN = Pattern.compile("([a-z_0-9]+:)?([a-z_0-9]+/)*[a-z_0-9]+");

    private String modId = "minecraft";
    private String directory;
    private String name;

    public TexturePath(String s)
    {
        String[] split = s.split(":");
        if(split.length == 2)
        {
            this.modId = split[0];
        }
        String assetPath = split[split.length - 1];
        this.directory = assetPath.substring(0, Math.max(0, assetPath.lastIndexOf("/")));
        this.name = assetPath.replace(this.directory, "").substring(1);
    }

    public TexturePath(File file)
    {
        this.modId = AssetsUtil.getModId(file);
        this.directory = AssetsUtil.getTextureDirectory(file);
        this.name = file.getName().substring(0, file.getName().indexOf("."));
    }

    public String getModId()
    {
        return modId;
    }

    public String getDirectory()
    {
        return directory;
    }

    public String getName()
    {
        return name;
    }

    @Override
    public String toString()
    {
        return modId + ":" + directory + "/" + name;
    }

    public String toRelativePath()
    {
        return modId + File.separator + "textures" + File.separator + directory + File.separator + name + ".png";
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/component/DisplayPropertiesDialog.java
================================================
package com.mrcrayfish.modelcreator.component;

import com.mrcrayfish.modelcreator.ModelCreator;
import com.mrcrayfish.modelcreator.display.CanvasRenderer;
import com.mrcrayfish.modelcreator.display.DisplayProperties;
import com.mrcrayfish.modelcreator.element.ElementManager;
import com.mrcrayfish.modelcreator.panels.DisplayEntryPanel;
import com.mrcrayfish.modelcreator.util.ComponentUtil;

import javax.swing.*;
import java.awt.*;

/**
 * Author: MrCrayfish
 */
public class DisplayPropertiesDialog extends JDialog
{
    private ModelCreator creator;

    private JTabbedPane tabbedPane;

    public DisplayPropertiesDialog(ModelCreator creator)
    {
        super(creator, "Display Properties", Dialog.ModalityType.MODELESS);
        this.creator = creator;
        this.init();
        this.pack();
        this.setResizable(false);
    }


    private void init()
    {
        SpringLayout layout = new SpringLayout();
        JPanel panel = new JPanel(layout);
        panel.setPreferredSize(new Dimension(400, 490));
        this.add(panel);

        JLabel labelProperties = new JLabel("Presets");
        panel.add(labelProperties);

        JComboBox<DisplayProperties> comboBoxProperties = new JComboBox<>();
        comboBoxProperties.addItem(DisplayProperties.MODEL_CREATOR_BLOCK);
        comboBoxProperties.addItem(DisplayProperties.DEFAULT_BLOCK);
        comboBoxProperties.addItem(DisplayProperties.DEFAULT_ITEM);
        comboBoxProperties.setPreferredSize(new Dimension(0, 24));
        panel.add(comboBoxProperties);

        DisplayProperties properties = creator.getElementManager().getDisplayProperties();

        tabbedPane = new JTabbedPane();
        tabbedPane.addTab("GUI", new DisplayEntryPanel(properties.getEntry("gui")));
        tabbedPane.addTab("Ground", new DisplayEntryPanel(properties.getEntry("ground")));
        tabbedPane.addTab("Fixed", new DisplayEntryPanel(properties.getEntry("fixed")));
        tabbedPane.addTab("Head", new DisplayEntryPanel(properties.getEntry("head")));
        tabbedPane.addTab("First Person", new DisplayEntryPanel(properties.getEntry("firstperson_righthand")));
        tabbedPane.addTab("Third Person", new DisplayEntryPanel(properties.getEntry("thirdperson_righthand")));
        tabbedPane.addTab("First Person (Left)", new DisplayEntryPanel(properties.getEntry("firstperson_lefthand")));
        tabbedPane.addTab("Third Person (Left)", new DisplayEntryPanel(properties.getEntry("thirdperson_lefthand")));
        tabbedPane.addChangeListener(e ->
        {
            Component c = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());
            if(c instanceof DisplayEntryPanel)
            {
                DisplayEntryPanel entryPanel = (DisplayEntryPanel) c;
                CanvasRenderer render = DisplayProperties.RENDER_MAP.get(entryPanel.getEntry().getId());
                if(render != null)
                {
                    ModelCreator.setCanvasRenderer(render);
                }
                else
                {
                    ModelCreator.restoreStandardRenderer();
                }
            }
        });
        panel.add(tabbedPane);

        JButton btnApplyProperties = new JButton("Apply");
        btnApplyProperties.setPreferredSize(new Dimension(80, 24));
        btnApplyProperties.addActionListener(e ->
        {
            creator.getElementManager().setDisplayProperties((DisplayProperties) comboBoxProperties.getSelectedItem());
            this.updateValues(creator.getElementManager().getDisplayProperties());
        });
        panel.add(btnApplyProperties);

        JCheckBox checkBoxShowGrid = ComponentUtil.createCheckBox("Show Grid", "Determines whether the grid should render", Menu.shouldRenderGrid);
        checkBoxShowGrid.addActionListener(e -> Menu.shouldRenderGrid = checkBoxShowGrid.isSelected());
        panel.add(checkBoxShowGrid);

        layout.putConstraint(SpringLayout.WEST, labelProperties, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, labelProperties, 2, SpringLayout.NORTH, comboBoxProperties);
        layout.putConstraint(SpringLayout.EAST, comboBoxProperties, -10, SpringLayout.WEST, btnApplyProperties);
        layout.putConstraint(SpringLayout.NORTH, comboBoxProperties, 10, SpringLayout.NORTH, panel);
        layout.putConstraint(SpringLayout.WEST, comboBoxProperties, 10, SpringLayout.EAST, labelProperties);
        layout.putConstraint(SpringLayout.NORTH, btnApplyProperties, 10, SpringLayout.NORTH, panel);
        layout.putConstraint(SpringLayout.EAST, btnApplyProperties, -10, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.WEST, checkBoxShowGrid, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, checkBoxShowGrid, 5, SpringLayout.SOUTH, comboBoxProperties);
        layout.putConstraint(SpringLayout.EAST, tabbedPane, -10, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.NORTH, tabbedPane, 5, SpringLayout.SOUTH, checkBoxShowGrid);
        layout.putConstraint(SpringLayout.WEST, tabbedPane, 10, SpringLayout.WEST, panel);
    }

    public void updateValues(DisplayProperties displayProperties)
    {
        Component[] components = tabbedPane.getComponents();
        for(Component c : components)
        {
            if(c instanceof DisplayEntryPanel)
            {
                DisplayEntryPanel entryPanel = (DisplayEntryPanel) c;
                DisplayProperties.Entry oldEntry = entryPanel.getEntry();
                DisplayProperties.Entry newEntry = displayProperties.getEntry(oldEntry.getId());
                if(newEntry != null)
                {
                    entryPanel.updateValues(newEntry);
                }
            }
        }
    }

    public static void update(ModelCreator creator)
    {
        if(Menu.displayPropertiesDialog != null)
        {
            Menu.displayPropertiesDialog.updateValues(creator.getElementManager().getDisplayProperties());
        }
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/component/JElementList.java
================================================
package com.mrcrayfish.modelcreator.component;

import com.mrcrayfish.modelcreator.element.ElementCellEntry;
import com.mrcrayfish.modelcreator.util.ComponentUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

/**
 * Author: MrCrayfish
 */
public class JElementList extends JList<ElementCellEntry>
{
    private boolean processInput;

    @Override
    protected void processMouseEvent(MouseEvent e)
    {
        if(e.getID() == MouseEvent.MOUSE_FIRST)
        {
            return;
        }

        if(e.getID() == MouseEvent.MOUSE_RELEASED)
        {
            processInput = true;
            return;
        }

        if(!processInput)
        {
            return;
        }

        if(e.getButton() == MouseEvent.BUTTON1)
        {
            int index = this.locationToIndex(e.getPoint());
            if(index != -1)
            {
                Rectangle rectangle = this.getCellBounds(index, index);
                if(rectangle.contains(e.getPoint()))
                {
                    Point relativePoint = new Point((int) e.getPoint().getX(), (int) (e.getPoint().getY() - rectangle.getY()));
                    ElementCellEntry entry = this.getModel().getElementAt(index);
                    Rectangle buttonBounds = ComponentUtil.expandRectangle(entry.getVisibility().getBounds(), 4);
                    if(buttonBounds.contains(relativePoint))
                    {
                        entry.toggleVisibility();
                        this.repaint();
                        e.consume();
                        processInput = false;
                        return;
                    }
                }
            }
        }

        super.processMouseEvent(e);
    }

    @Override
    protected void processMouseMotionEvent(MouseEvent e)
    {
        if(processInput)
        {
            super.processMouseMotionEvent(e);
        }
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/component/Menu.java
================================================
package com.mrcrayfish.modelcreator.component;

import com.mrcrayfish.modelcreator.*;
import com.mrcrayfish.modelcreator.display.CanvasRenderer;
import com.mrcrayfish.modelcreator.display.DisplayProperties;
import com.mrcrayfish.modelcreator.element.Face;
import com.mrcrayfish.modelcreator.panels.DisplayEntryPanel;
import com.mrcrayfish.modelcreator.screenshot.PendingScreenshot;
import com.mrcrayfish.modelcreator.screenshot.Screenshot;
import com.mrcrayfish.modelcreator.screenshot.Uploader;
import com.mrcrayfish.modelcreator.util.ComponentUtil;
import com.mrcrayfish.modelcreator.util.KeyboardUtil;
import com.mrcrayfish.modelcreator.util.Util;
import org.lwjgl.input.Keyboard;

import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.Set;

public class Menu extends JMenuBar
{
    private ModelCreator creator;

    /* File */
    private JMenu menuFile;
    private JMenuItem itemNew;
    private JMenuItem itemLoad;
    private JMenuItem itemSave;
    private JMenuItem itemImport;
    private JMenuItem itemExport;
    private JMenuItem itemSettings;
    private JMenuItem itemExit;

    /* Edit */
    private JMenu menuEdit;
    private JMenuItem itemUndo;
    private JMenuItem itemRedo;

    /* Model */
    private JMenu menuModel;
    private JMenuItem itemTextureManager;
    private JMenuItem itemDisplayProps;
    private JMenuItem itemOptimise;
    private JMenu menuRotate;
    private JMenuItem itemRotateClockwise;
    private JMenuItem itemRotateCounterClockwise;

    /* Share */
    private JMenu menuScreenshot;
    private JMenuItem itemSaveToDisk;
    private JMenuItem itemShareFacebook;
    private JMenuItem itemShareTwitter;
    private JMenuItem itemShareReddit;
    private JMenuItem itemImgurLink;

    /* Extras */
    private JMenu menuMore;
    private JMenuItem itemExtractAssets;
    private JMenu menuDeveloper;
    private JMenuItem itemJavaCode;
    private JMenu menuExamples;
    private JMenuItem itemModelCauldron;
    private JMenuItem itemModelChair;
    private JMenuItem itemDonate;
    private JMenuItem itemGitHub;

    public static DisplayPropertiesDialog displayPropertiesDialog = null;
    public static boolean shouldRenderGrid = false;

    public Menu(ModelCreator creator)
    {
        this.creator = creator;
        this.initMenu();
    }

    private void initMenu()
    {
        menuFile = new JMenu("File");
        {
            itemNew = createMenuItem("New", "New Model", KeyEvent.VK_N, Icons.new_, KeyEvent.VK_N, Keyboard.KEY_N, InputEvent.CTRL_MASK);
            itemLoad = createMenuItem("Load Project...", "Load Project from File", KeyEvent.VK_S, Icons.load, KeyEvent.VK_O, Keyboard.KEY_O, InputEvent.CTRL_MASK);
            itemSave = createMenuItem("Save Project...", "Save Project to File", KeyEvent.VK_S, Icons.disk, KeyEvent.VK_S, Keyboard.KEY_S, InputEvent.CTRL_MASK);
            itemImport = createMenuItem("Import JSON...", "Import Model from JSON", KeyEvent.VK_I, Icons.import_);
            itemExport = createMenuItem("Export JSON...", "Export Model to JSON", KeyEvent.VK_E, Icons.export);
            itemSettings = createMenuItem("Settings", "Change the settings of the Model Creator", KeyEvent.VK_S, Icons.settings, KeyEvent.VK_S, Keyboard.KEY_S, InputEvent.CTRL_MASK + InputEvent.ALT_MASK);
            itemExit = createMenuItem("Exit", "Exit Application", KeyEvent.VK_E, Icons.exit);
        }

        menuEdit = new JMenu("Edit");
        {
            itemUndo = createMenuItem("Undo", "Undos the previous action", KeyEvent.VK_U, Icons.undo, KeyEvent.VK_Z, Keyboard.KEY_Z, InputEvent.CTRL_MASK);
            itemRedo = createMenuItem("Redo", "Redos the previous action", KeyEvent.VK_R, Icons.redo, KeyEvent.VK_Y, Keyboard.KEY_Y, InputEvent.CTRL_MASK);
        }

        menuModel = new JMenu("Model");
        {
            itemTextureManager = createMenuItem("Texture Manager", "Manage the textures entries for the model", KeyEvent.VK_T, Icons.texture, KeyEvent.VK_T, Keyboard.KEY_T, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK);
            itemDisplayProps = createMenuItem("Display Properties", "Change the display properties of the model", KeyEvent.VK_D, Icons.gallery, KeyEvent.VK_D, Keyboard.KEY_D, InputEvent.CTRL_MASK + InputEvent.ALT_MASK);
            itemOptimise = createMenuItem("Optimize", "Performs basic optimizion by disabling faces that aren't visible", KeyEvent.VK_O, Icons.optimize, KeyEvent.VK_N, Keyboard.KEY_N, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK);
            menuRotate = new JMenu("Rotate");
            menuRotate.setMnemonic(KeyEvent.VK_R);
            menuRotate.setIcon(Icons.rotate);
            {
                itemRotateClockwise = createMenuItem("90\u00B0 Clockwise", "Rotates all elements clockwise by 90\u00B0", KeyEvent.VK_C, Icons.rotate_clockwise, KeyEvent.VK_RIGHT, Keyboard.KEY_RIGHT, InputEvent.CTRL_MASK);
                itemRotateCounterClockwise = createMenuItem("90\u00B0 Counter Clockwise", "Rotates all elements counter clockwise by 90\u00B0", KeyEvent.VK_C, Icons.rotate_counter_clockwise, KeyEvent.VK_LEFT, Keyboard.KEY_LEFT, InputEvent.CTRL_MASK);
            }
        }

        menuScreenshot = new JMenu("Screenshot");
        {
            itemSaveToDisk = createMenuItem("Save to Disk...", "Save screenshot to disk.", KeyEvent.VK_D, Icons.disk);
            itemShareFacebook = createMenuItem("Share to Facebook", "Share a screenshot of your model Facebook.", KeyEvent.VK_S, Icons.facebook);
            itemShareTwitter = createMenuItem("Share to Twitter", "Share a screenshot of your model to Twitter.", KeyEvent.VK_S, Icons.twitter);
            itemShareReddit = createMenuItem("Share to Minecraft Subreddit", "Share a screenshot of your model to Minecraft Reddit.", KeyEvent.VK_S, Icons.reddit);
            itemImgurLink = createMenuItem("Get Imgur Link", "Get an Imgur link of your screenshot to share.", KeyEvent.VK_I, Icons.imgur);
        }

        menuMore = new JMenu("More");
        {
            itemExtractAssets = createMenuItem("Extract Assets...", "Extract Minecraft assets so you can get access to block and item textures", KeyEvent.VK_E, Icons.extract);
            menuDeveloper = new JMenu("Mod Developer");
            menuDeveloper.setMnemonic(KeyEvent.VK_M);
            menuDeveloper.setIcon(Icons.mojang);
            {
                itemJavaCode = createMenuItem("Generate Java Code...", "Generate Java code for selection and collisions boxes", KeyEvent.VK_J, Icons.java);
            }
            menuExamples = new JMenu("Examples");
            menuExamples.setMnemonic(KeyEvent.VK_E);
            menuExamples.setIcon(Icons.new_);
            {
                itemModelCauldron = createMenuItem("Cauldron", "<html>Model by MrCrayfish<br><b>Private use only</b></html>", KeyEvent.VK_C, Icons.model_cauldron);
                itemModelChair = createMenuItem("Chair", "<html>Model by MrCrayfish<br><b>Private use only</b></html>", KeyEvent.VK_C, Icons.model_chair);
            }
            itemDonate = createMenuItem("Donate (Patreon)", "Pledge to MrCrayfish", KeyEvent.VK_D, Icons.patreon);
            itemGitHub = createMenuItem("Source Code", "View Source Code", KeyEvent.VK_S, Icons.github);
        }

        this.initActions();

        /* Menu File */
        menuFile.add(itemNew);
        menuFile.addSeparator();
        menuFile.add(itemLoad);
        menuFile.add(itemSave);
        menuFile.addSeparator();
        menuFile.add(itemImport);
        menuFile.add(itemExport);
        menuFile.addSeparator();
        menuFile.add(itemSettings);
        menuFile.addSeparator();
        menuFile.add(itemExit);
        this.add(menuFile);

        /* Menu Edit */
        menuEdit.add(itemUndo);
        menuEdit.add(itemRedo);
        menuEdit.addMenuListener(new MenuAdapter()
        {
            @Override
            public void menuSelected(MenuEvent e)
            {
                itemRedo.setEnabled(StateManager.canRestoreNextState());
                itemUndo.setEnabled(StateManager.canRestorePreviousState());
            }
        });
        this.add(menuEdit);

        /* Menu Model Sub Menus */
        menuRotate.add(itemRotateClockwise);
        menuRotate.add(itemRotateCounterClockwise);

        /* Menu Model */
        menuModel.add(itemTextureManager);
        menuModel.add(itemDisplayProps);
        menuModel.add(itemOptimise);
        menuModel.addSeparator();
        menuModel.add(menuRotate);
        this.add(menuModel);

        /* Menu Screenshots */
        menuScreenshot.add(itemSaveToDisk);
        menuScreenshot.add(itemShareFacebook);
        menuScreenshot.add(itemShareTwitter);
        menuScreenshot.add(itemShareReddit);
        menuScreenshot.add(itemImgurLink);
        this.add(menuScreenshot);

        /* Menu More Sub Menus */
        menuDeveloper.add(itemJavaCode);
        menuExamples.add(itemModelCauldron);
        menuExamples.add(itemModelChair);

        /* Menu More */
        menuMore.add(itemExtractAssets);
        menuMore.add(menuDeveloper);
        menuMore.addSeparator();
        menuMore.add(menuExamples);
        menuMore.addSeparator();
        menuMore.add(itemGitHub);
        menuMore.add(itemDonate);
        this.add(menuMore);
    }

    private void initActions()
    {
        itemNew.addActionListener(a -> newProject(creator));

        itemLoad.addActionListener(a -> loadProject(creator));

        itemSave.addActionListener(a -> saveProject(creator));

        itemImport.addActionListener(a -> showImportJson(creator));

        itemExport.addActionListener(a -> showExportJson(creator));

        itemJavaCode.addActionListener(a -> showExportJavaCode(creator, a));

        itemSettings.addActionListener(a -> showSettings(creator));

        itemExit.addActionListener(a -> creator.close());

        itemTextureManager.addActionListener(a ->
        {
            TextureManager manager = new TextureManager(creator, creator.getElementManager(), Dialog.ModalityType.APPLICATION_MODAL, false);
            manager.setLocationRelativeTo(null);
            manager.setVisible(true);
        });

        itemDisplayProps.addActionListener(a -> showDisplayProperties(creator));

        itemOptimise.addActionListener(a -> optimizeModel(creator));

        itemRotateClockwise.addActionListener(a -> Actions.rotateModel(creator.getElementManager(), true));

        itemRotateCounterClockwise.addActionListener(a -> Actions.rotateModel(creator.getElementManager(), false));

        itemSaveToDisk.addActionListener(a ->
        {
            JFileChooser chooser = new JFileChooser();
            chooser.setDialogTitle("Output Directory");
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            chooser.setApproveButtonText("Save");

            FileNameExtensionFilter filter = new FileNameExtensionFilter("PNG (.png)", "png");
            chooser.setFileFilter(filter);

            String dir = Settings.getScreenshotDir();

            if(dir != null)
            {
                chooser.setCurrentDirectory(new File(dir));
            }

            int returnVal = chooser.showSaveDialog(null);
            if(returnVal == JFileChooser.APPROVE_OPTION)
            {
                if(chooser.getSelectedFile().exists())
                {
                    returnVal = JOptionPane.showConfirmDialog(null, "A file already exists with that name, are you sure you want to override?", "Warning", JOptionPane.YES_NO_OPTION);
                }
                if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)
                {
                    File location = chooser.getSelectedFile().getParentFile();
                    Settings.setScreenshotDir(location.toString());

                    String filePath = chooser.getSelectedFile().getAbsolutePath();
                    if(!filePath.endsWith(".png"))
                    {
                        chooser.setSelectedFile(new File(filePath + ".png"));
                    }
                    creator.setSidebar(null);
                    creator.startScreenshot(new PendingScreenshot(chooser.getSelectedFile(), null));
                }
            }
        });

        itemShareFacebook.addActionListener(a ->
        {
            creator.setSidebar(null);
            creator.startScreenshot(new PendingScreenshot(null, file ->
            {
                try
                {
                    String url = Uploader.upload(file);
                    Screenshot.shareToFacebook(url);
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }));
        });

        itemShareTwitter.addActionListener(a ->
        {
            creator.setSidebar(null);
            creator.startScreenshot(new PendingScreenshot(null, file ->
            {
                try
                {
                    String url = Uploader.upload(file);
                    Screenshot.shareToTwitter(url);
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }));
        });

        itemShareReddit.addActionListener(a ->
        {
            creator.setSidebar(null);
            creator.startScreenshot(new PendingScreenshot(null, file ->
            {
                try
                {
                    String url = Uploader.upload(file);
                    Screenshot.shareToReddit(url);
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            }));
        });

        itemImgurLink.addActionListener(a ->
        {
            creator.setSidebar(null);
            creator.startScreenshot(new PendingScreenshot(null, file -> SwingUtilities.invokeLater(() ->
            {
                try
                {
                    String url = Uploader.upload(file);

                    JOptionPane message = new JOptionPane();
                    String title;

                    if(url != null && !url.equals("null"))
                    {
                        StringSelection text = new StringSelection(url);
                        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(text, null);
                        title = "Success";
                        message.setMessage("<html><b>" + url + "</b> has been copied to your clipboard.</html>");
                    }
                    else
                    {
                        title = "Error";
                        message.setMessage("Failed to upload screenshot. Check your internet connection then try again.");
                    }

                    JDialog dialog = message.createDialog(this, title);
                    dialog.setLocationRelativeTo(null);
                    dialog.setModal(false);
                    dialog.setVisible(true);
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                }
            })));
        });

        itemGitHub.addActionListener(a -> Util.openUrl(Constants.URL_GITHUB));

        itemDonate.addActionListener(a -> Util.openUrl(Constants.URL_DONATE));

        itemExtractAssets.addActionListener(a -> showExtractAssets(creator));

        itemModelCauldron.addActionListener(a ->
        {
            StateManager.clear();
            Util.loadModelFromJar(creator.getElementManager(), getClass(), "models/cauldron");
            StateManager.pushState(creator.getElementManager());
        });

        itemModelChair.addActionListener(a ->
        {
            StateManager.clear();
            Util.loadModelFromJar(creator.getElementManager(), getClass(), "models/modern_chair");
            StateManager.pushState(creator.getElementManager());
        });

        itemUndo.addActionListener(a -> StateManager.restorePreviousState(creator.getElementManager()));

        itemRedo.addActionListener(a -> StateManager.restoreNextState(creator.getElementManager()));
    }

    private JMenuItem createMenuItem(String name, String tooltip, int mnemonic, Icon icon)
    {
        return createMenuItem(name, tooltip, mnemonic, icon, -1, -1, -1);
    }

    private JMenuItem createMenuItem(String name, String tooltip, int mnemonic, Icon icon, int awtCode, int keyCode, int modifiers)
    {
        JMenuItem item = new JMenuItem(name);
        item.setToolTipText(tooltip);
        item.setMnemonic(mnemonic);
        item.setIcon(icon);

        if(awtCode != -1 && keyCode != -1 && modifiers != -1)
        {
            KeyStroke shortcut = KeyStroke.getKeyStroke(awtCode, modifiers);
            if(shortcut != null)
            {
                String shortcutText = KeyboardUtil.convertKeyStokeToString(shortcut);
                item.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));
                JLabel label = new JLabel("<html><p style='color:#666666;font-size:9px'>" + shortcutText + "<p></html>");
                item.add(label);
                Dimension size = new Dimension((int) Math.ceil(item.getPreferredSize().getWidth() + label.getPreferredSize().getWidth()) + 10, 20);
                item.setPreferredSize(size);
            }

            if(shortcut != null)
            {
                creator.registerKeyAction(new ModelCreator.KeyAction(awtCode, keyCode, (modifier, pressed) ->
                {
                    if(pressed && modifier == modifiers)
                    {
                        for(ActionListener listener : item.getActionListeners())
                        {
                            listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, item.getActionCommand(), modifier));
                        }
                    }
                }));
            }
        }

        return item;
    }

    public static void newProject(ModelCreator creator)
    {
        int returnVal = JOptionPane.showConfirmDialog(creator, "You current work will be cleared, are you sure?", "Note", JOptionPane.YES_NO_OPTION);
        if(returnVal == JOptionPane.YES_OPTION)
        {
            TextureManager.clear();
            StateManager.clear();
            creator.getElementManager().reset();
            creator.getElementManager().updateValues();
            DisplayPropertiesDialog.update(creator);
            StateManager.pushState(creator.getElementManager());
        }
    }

    public static void loadProject(ModelCreator creator)
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Load Project");
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setApproveButtonText("Load");

        FileNameExtensionFilter filter = new FileNameExtensionFilter("Model (.model)", "model");
        chooser.setFileFilter(filter);

        String dir = Settings.getModelDir();

        if(dir != null)
        {
            chooser.setCurrentDirectory(new File(dir));
        }

        int returnVal = chooser.showOpenDialog(null);
        if(returnVal == JFileChooser.APPROVE_OPTION)
        {
            if(creator.getElementManager().getElementCount() > 0)
            {
                returnVal = JOptionPane.showConfirmDialog(null, "Your current project will be cleared, are you sure you want to continue?", "Warning", JOptionPane.YES_NO_OPTION);
            }
            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)
            {
                File location = chooser.getSelectedFile().getParentFile();
                Settings.setModelDir(location.toString());

                TextureManager.clear();
                StateManager.clear();
                ProjectManager.loadProject(creator.getElementManager(), chooser.getSelectedFile().getAbsolutePath());
                DisplayPropertiesDialog.update(creator);
                StateManager.pushState(creator.getElementManager());
            }
        }
    }

    public static void saveProject(ModelCreator creator)
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Save Project");
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setApproveButtonText("Save");

        FileNameExtensionFilter filter = new FileNameExtensionFilter("Model (.model)", "model");
        chooser.setFileFilter(filter);
        String dir = Settings.getModelDir();

        if(dir != null)
        {
            chooser.setCurrentDirectory(new File(dir));
        }

        int returnVal = chooser.showSaveDialog(null);
        if(returnVal == JFileChooser.APPROVE_OPTION)
        {
            if(chooser.getSelectedFile().exists())
            {
                returnVal = JOptionPane.showConfirmDialog(null, "A file already exists with that name, are you sure you want to override?", "Warning", JOptionPane.YES_NO_OPTION);
            }
            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)
            {
                File location = chooser.getSelectedFile().getParentFile();
                Settings.setModelDir(location.toString());

                String filePath = chooser.getSelectedFile().getAbsolutePath();
                if(!filePath.endsWith(".model"))
                {
                    filePath += ".model";
                }
                ProjectManager.saveProject(creator.getElementManager(), filePath);
            }
        }
    }

    public static void optimizeModel(ModelCreator creator)
    {
        int result = JOptionPane.showConfirmDialog(null, "<html>Are you sure you want to optimize the model?<br/>It is recommended you save the project before running this<br/>action, otherwise you will have to re-enable the disabled faces.<html>", "Optimize Confirmation", JOptionPane.YES_NO_OPTION);
        if(result == JOptionPane.YES_OPTION)
        {
            int count = Actions.optimiseModel(creator.getElementManager());
            JOptionPane.showMessageDialog(null, "<html>Optimizing the model disabled <b>" + count + "</b> faces</html>", "Optimization Success", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    public static void showImportJson(ModelCreator creator)
    {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Import JSON Model");
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setApproveButtonText("Import");

        FileNameExtensionFilter filter = new FileNameExtensionFilter("JSON (.json)", "json");
        chooser.setFileFilter(filter);

        String dir = Settings.getJSONDir();
        if(dir != null)
        {
            chooser.setCurrentDirectory(new File(dir));
        }

        int returnVal = chooser.showOpenDialog(null);
        if(returnVal == JFileChooser.APPROVE_OPTION)
        {
            if(creator.getElementManager().getElementCount() > 0)
            {
                returnVal = JOptionPane.showConfirmDialog(null, "Your current project will be cleared, are you sure you want to continue?", "Warning", JOptionPane.YES_NO_OPTION);
            }
            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)
            {
                File location = chooser.getSelectedFile().getParentFile();
                Settings.setJSONDir(location.toString());

                TextureManager.clear();
                StateManager.clear();
                Importer importer = new Importer(creator.getElementManager(), chooser.getSelectedFile().getAbsolutePath());
                importer.importFromJSON();
                StateManager.pushState(creator.getElementManager());
            }
            creator.getElementManager().updateValues();
        }
    }

    public static void showExportJson(ModelCreator creator)
    {
        JDialog dialog = new JDialog(creator, "Export JSON Model", Dialog.ModalityType.APPLICATION_MODAL);

        JPanel panel = new JPanel(new BorderLayout());
        panel.setPreferredSize(new Dimension(500, 225));

        SpringLayout springLayout = new SpringLayout();
        JPanel exportDir = new JPanel(springLayout);

        JLabel labelName = new JLabel("Name");
        labelName.setHorizontalAlignment(SwingConstants.RIGHT);
        exportDir.add(labelName);

        JTextField textFieldName = new JTextField();
        textFieldName.setPreferredSize(new Dimension(100, 24));
        textFieldName.setCaretPosition(0);
        exportDir.add(textFieldName);

        JTextField textFieldDestination = new JTextField();
        textFieldDestination.setPreferredSize(new Dimension(100, 24));

        String exportJsonDir = Settings.getExportJSONDir();
        if(exportJsonDir != null)
        {
            textFieldDestination.setText(exportJsonDir);
        }
        else
        {
            String userHome = System.getProperty("user.home", ".");
            textFieldDestination.setText(userHome);
        }

        textFieldDestination.setEditable(false);
        textFieldDestination.setFocusable(false);
        textFieldDestination.setCaretPosition(0);
        exportDir.add(textFieldDestination);

        JButton btnBrowserDir = new JButton("Browse");
        btnBrowserDir.setPreferredSize(new Dimension(80, 24));
        btnBrowserDir.setIcon(Icons.load);
        btnBrowserDir.addActionListener(e ->
        {
            JFileChooser chooser = new JFileChooser();
            chooser.setDialogTitle("Export Destination");
            chooser.setCurrentDirectory(new File(textFieldDestination.getText()));
            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            chooser.setApproveButtonText("Select");
            int returnVal = chooser.showOpenDialog(dialog);
            if(returnVal == JFileChooser.APPROVE_OPTION)
            {
                File file = chooser.getSelectedFile();
                if(file != null)
                {
                    textFieldDestination.setText(file.getAbsolutePath());
                }
            }
        });
        exportDir.add(btnBrowserDir);

        JLabel labelExportDir = new JLabel("Destination");
        exportDir.add(labelExportDir);

        //JComponent optionSeparator = DefaultComponentFactory.getInstance().createSeparator("Export Options");
        JComponent optionSeparator = new JSeparator();
        exportDir.add(optionSeparator);

        JCheckBox checkBoxOptimize = ComponentUtil.createCheckBox("Optimize Model", "Removes unnecessary faces that can't been seen in the model", true);
        exportDir.add(checkBoxOptimize);

        JCheckBox checkBoxDisplayProps = ComponentUtil.createCheckBox("Include Display Properties", "Adds the display definitions (first-person, third-person, etc) to the model file", true);
        exportDir.add(checkBoxDisplayProps);

        JCheckBox checkBoxElementNames = ComponentUtil.createCheckBox("Include Element Names", "The name of each element will be added to it's entry in the json model elements array. Useful for identifying elements, and when importing back into Model Creator, it will use those names", true);
        exportDir.add(checkBoxElementNames);

        JSeparator separator = new JSeparator();
        exportDir.add(separator);

		/* Constraints */

        springLayout.putConstraint(SpringLayout.NORTH, labelName, 3, SpringLayout.NORTH, textFieldName);
        springLayout.putConstraint(SpringLayout.WEST, labelName, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.EAST, labelName, -5, SpringLayout.WEST, textFieldDestination);
        springLayout.putConstraint(SpringLayout.NORTH, textFieldName, 10, SpringLayout.NORTH, exportDir);
        springLayout.putConstraint(SpringLayout.WEST, textFieldName, 0, SpringLayout.WEST, textFieldDestination);
        springLayout.putConstraint(SpringLayout.EAST, textFieldName, 0, SpringLayout.EAST, textFieldDestination);
        springLayout.putConstraint(SpringLayout.WEST, optionSeparator, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.EAST, optionSeparator, -10, SpringLayout.EAST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, optionSeparator, 10, SpringLayout.SOUTH, textFieldDestination);
        springLayout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);
        springLayout.putConstraint(SpringLayout.EAST, btnBrowserDir, -10, SpringLayout.EAST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, textFieldDestination, 10, SpringLayout.SOUTH, textFieldName);
        springLayout.putConstraint(SpringLayout.WEST, textFieldDestination, 5, SpringLayout.EAST, labelExportDir);
        springLayout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);
        springLayout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);
        springLayout.putConstraint(SpringLayout.WEST, labelExportDir, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, checkBoxOptimize, 5, SpringLayout.SOUTH, optionSeparator);
        springLayout.putConstraint(SpringLayout.WEST, checkBoxOptimize, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, checkBoxDisplayProps, 0, SpringLayout.SOUTH, checkBoxOptimize);
        springLayout.putConstraint(SpringLayout.WEST, checkBoxDisplayProps, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, checkBoxElementNames, 0, SpringLayout.SOUTH, checkBoxDisplayProps);
        springLayout.putConstraint(SpringLayout.WEST, checkBoxElementNames, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.WEST, separator, 10, SpringLayout.WEST, exportDir);
        springLayout.putConstraint(SpringLayout.EAST, separator, -10, SpringLayout.EAST, exportDir);
        springLayout.putConstraint(SpringLayout.NORTH, separator, 5, SpringLayout.SOUTH, checkBoxElementNames);
        springLayout.putConstraint(SpringLayout.SOUTH, separator, 5, SpringLayout.SOUTH, exportDir);

        panel.setPreferredSize(panel.getPreferredSize());
        panel.add(exportDir, BorderLayout.CENTER);

        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10));

        JButton btnCancel = new JButton("Cancel");
        btnCancel.addActionListener(new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                dialog.dispose();
            }
        });
        buttons.add(btnCancel);

        JButton btnExport = new JButton("Export");
        btnExport.addActionListener(e ->
        {
            String name = textFieldName.getText().trim();
            if(!textFieldDestination.getText().isEmpty() && !name.isEmpty())
            {
                File destination = new File(textFieldDestination.getText());
                destination.mkdirs();

                File modelFile = new File(destination, textFieldName.getText() + ".json");
                if(modelFile.exists())
                {
                    int returnVal = JOptionPane.showConfirmDialog(dialog, "A file for that name already exists in the directory. Are you sure you want to override it?", "Warning", JOptionPane.YES_NO_OPTION);
                    if(returnVal != JOptionPane.YES_OPTION)
                    {
                        return;
                    }
                }

                try
                {
                    modelFile.createNewFile();
                }
                catch(IOException e1)
                {
                    JOptionPane.showMessageDialog(dialog, "Unable to create the file. Check that your destination folder is writable", "Error", JOptionPane.ERROR_MESSAGE);
                }

                dialog.dispose();

                ExporterModel exporter = new ExporterModel(creator.getElementManager());
                exporter.setOptimize(checkBoxOptimize.isSelected());
                exporter.setDisplayProps(checkBoxDisplayProps.isSelected());
                exporter.setIncludeNames(checkBoxElementNames.isSelected());
                if(exporter.writeFile(modelFile) == null)
                {
                    modelFile.delete();
                    JOptionPane.showMessageDialog(dialog, "An error occured while exporting the model. Please try again", "Error", JOptionPane.ERROR_MESSAGE);
                }
                else
                {
                    Settings.setExportJSONDir(textFieldDestination.getText());
                    int returnVal = JOptionPane.showOptionDialog(dialog, "Model exported successfully!", "Success", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, new Object[]{"Open Folder", "Close"}, "Close");
                    if(returnVal == 0)
                    {
                        Desktop desktop = Desktop.getDesktop();
                        try
                        {
                            desktop.open(destination);
                        }
                        catch(IOException e1)
                        {
                            e1.printStackTrace();
                        }
                    }
                }
            }
        });
        buttons.add(btnExport);

        panel.add(buttons, BorderLayout.SOUTH);

        dialog.add(panel);

        dialog.pack();
        dialog.setResizable(false);
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
    }

    private static void showExportJavaCode(ModelCreator creator, ActionEvent actionEvent)
    {
        JCheckBox includeFields = ComponentUtil.createCheckBox("Generate Fields", "Include decelerations of the AABBs fields that represent the model's elements", true);
        JCheckBox includeMethods = ComponentUtil.createCheckBox("Generate Methods", "Include bounds, raytracing, & collision methods", true);
        JCheckBox useBoundsHelper = ComponentUtil.createCheckBox("Use Bounds Helper", "Fields and methods use MrCrayfish's Bounds helper class, and target his code-base", false);
        JCheckBox generateRotatedBounds = ComponentUtil.createCheckBox("Make Rotatable", "Use Bounds helper class to create AABB rotation arrays for each element", false);

        useBoundsHelper.addActionListener(e -> generateRotatedBounds.setEnabled(useBoundsHelper.isSelected()));

        String mcTooltip = "Select Minecraft version";
        JPanel panelMC = new JPanel(new FlowLayout(FlowLayout.LEFT));

        JLabel mcLabel = new JLabel("MC Version");
        mcLabel.setToolTipText(mcTooltip);
        panelMC.add(mcLabel);

        JComboBox<ExporterJavaCode.Version> mcVersion = new JComboBox<>(ExporterJavaCode.Version.values());
        mcVersion.setToolTipText(mcTooltip);
        mcVersion.setPreferredSize(new Dimension(60, 24));
        mcVersion.addActionListener(e ->
        {
            ExporterJavaCode.Version version = (ExporterJavaCode.Version) mcVersion.getSelectedItem();
            switch(version)
            {
                case V_1_12:
                    useBoundsHelper.setText("Use Bounds Helper");
                    break;
                default:
                    useBoundsHelper.setText("Use VoxelShapeHelper");
                    break;
            }
        });
        panelMC.add(mcVersion);

        JPanel panelMain = new JPanel();
        panelMain.setLayout(new GridLayout(1, 2));
        panelMain.add(includeFields);
        panelMain.add(includeMethods);

        JPanel parent = new JPanel();
        parent.setLayout(new BoxLayout(parent, BoxLayout.Y_AXIS));
        parent.add(panelMC);

        JPanel controls = new JPanel(new GridLayout(1, 1));
        controls.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), "<html><b>Options</b></html>"));
        controls.add(panelMain);
        parent.add(controls);

        if((actionEvent.getModifiers() & ActionEvent.SHIFT_MASK) > 0 && (actionEvent.getModifiers() & ActionEvent.CTRL_MASK) > 0)
        {
            includeMethods.setToolTipText(includeMethods.getToolTipText().replace(", raytracing", ""));
            useBoundsHelper.setForeground(Color.BLACK);
            useBoundsHelper.setSelected(true);
            generateRotatedBounds.setSelected(true);

            JPanel panelCray = new JPanel();
            panelCray.setLayout(new GridLayout(1, 2));
            panelCray.add(useBoundsHelper);
            panelCray.add(generateRotatedBounds);

            controls.setLayout(new GridLayout(2, 1));
            controls.add(panelCray);
        }

        JLabel infoLabel = new JLabel("<html><body><p style='width:260px'>" +
                "Use this tool to generate Java code for selection and collision boxes. " +
                "This includes AxisAlignedBB fields and the required methods for the " +
                "Block class to apply them. It should be noted that elements in the model " +
                "that have been rotated will be ignored when generating." +
                "</p></body></html>");
        JLabel questionLabel = new JLabel("<html><body><p style='width:260px'>" +
                "Would you like the code to be copied to your clipboard or saved to a text file?" +
                "</p></body></html>");

        int returnValDestination = JOptionPane.showOptionDialog(creator, new Object[] {infoLabel, Box.createHorizontalStrut(20), Box.createHorizontalStrut(20), new JSeparator(),
                        parent, new JSeparator(), Box.createHorizontalStrut(20), Box.createHorizontalStrut(20),
                        questionLabel}, "Generate Java Code", JOptionPane.YES_NO_OPTION,
                        JOptionPane.INFORMATION_MESSAGE, null, new String[] {"Clipboard", "Text File"}, "Clipboard");
        if (!includeFields.isSelected() && !includeMethods.isSelected())
        {
            JOptionPane.showMessageDialog(creator, "Either AxisAlignedBBs or methods must be selected.", "None Selected", JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        ExporterJavaCode exporter = new ExporterJavaCode(creator, includeFields.isSelected(), includeMethods.isSelected(), useBoundsHelper.isSelected(), generateRotatedBounds.isSelected());
        exporter.setVersion((ExporterJavaCode.Version) mcVersion.getSelectedItem());
        if (returnValDestination == JOptionPane.CLOSED_OPTION)
            return;

        if (returnValDestination == JOptionPane.OK_OPTION)
        {
            try
            {
                exporter.writeCodeToClipboard();
            }
            catch (Exception exception)
            {
                JOptionPane.showMessageDialog(creator, "An error occured while copying code to your clipboard.", "Error", JOptionPane.ERROR_MESSAGE);
            }
            return;
        }

        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Output Directory");
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        chooser.setApproveButtonText("Export");

        FileNameExtensionFilter filter = new FileNameExtensionFilter("TXT (.txt)", "txt");
        chooser.setFileFilter(filter);

        String dir = Settings.getJSONDir();

        if (dir != null)
        {
            chooser.setCurrentDirectory(new File(dir));
        }

        int returnVal = chooser.showSaveDialog(null);
        if (returnVal == JFileChooser.APPROVE_OPTION)
        {
            if (chooser.getSelectedFile().exists())
            {
                returnVal = JOptionPane.showConfirmDialog(null, "A file already exists with that name, are you sure you want to override?", "Warning", JOptionPane.YES_NO_OPTION);
            }
            if (returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)
            {
                File location = chooser.getSelectedFile().getParentFile();
                Settings.setJSONDir(location.toString());
                String filePath = chooser.getSelectedFile().getAbsolutePath();
                if (!filePath.endsWith(".txt"))
                    chooser.setSelectedFile(new File(filePath + ".txt"));

                exporter.export(chooser.getSelectedFile());
            }
        }
    }

    public static void showSettings(ModelCreator creator)
    {
        JDialog dialog = new JDialog(creator, "Settings", Dialog.ModalityType.APPLICATION_MODAL);

        JPanel panel = new JPanel(new BorderLayout());
        panel.setPreferredSize(new Dimension(500, 300));
        dialog.add(panel);

        JTabbedPane tabbedPane = new JTabbedPane();
        panel.add(tabbedPane, BorderLayout.CENTER);

        SpringLayout generalSpringLayout = new SpringLayout();
        JPanel generalPanel = new JPanel(generalSpringLayout);
        tabbedPane.addTab("General", generalPanel);

        JPanel optionsPanel = new JPanel(new GridLayout(1, 2));
        generalPanel.add(optionsPanel);

        JPanel undoPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        optionsPanel.add(undoPanel);

        JLabel labelUndoLimit = new JLabel("Undo / Redo Limit");
        undoPanel.add(labelUndoLimit);

        final Boolean[] changed = {false};
        SpinnerNumberModel undoSpinnerNumberModel = new SpinnerNumberModel();
        undoSpinnerNumberModel.setMinimum(1);
        JSpinner undoLimitSpinner = new JSpinner(undoSpinnerNumberModel);
        undoLimitSpinner.setPreferredSize(new Dimension(40, 24));
        undoLimitSpinner.setValue(Settings.getUndoLimit());
        undoLimitSpinner.addChangeListener(e ->
        {
            if(!changed[0])
            {
                JOptionPane.showMessageDialog(dialog, "Increasing the undo/redo limit will increase the amount of memory the program use. Change this setting with caution.", "Warning", JOptionPane.WARNING_MESSAGE);
                changed[0] = true;
            }
        });
        undoPanel.add(undoLimitSpinner);

        JCheckBox checkBoxCardinalPoints = ComponentUtil.createCheckBox("Show Cardinal Points", "", Settings.getCardinalPoints());
        optionsPanel.add(checkBoxCardinalPoints);

        JSeparator separator = new JSeparator();
        generalPanel.add(separator);

        String assetsPath = Settings.getAssetsDir() != null ? Settings.getAssetsDir() : "";
        JPanel texturePathPanel = createDirectorySelector("Assets Path", dialog, assetsPath);
        generalPanel.add(texturePathPanel);

        JSeparator separator2 = new JSeparator();
        generalPanel.add(separator2);

        String imageEditorPath = Settings.getImageEditor() != null ? Settings.getImageEditor() : "";
        JPanel imageEditorPanel = ComponentUtil.createFileSelector("Image Editor", dialog, imageEditorPath, null, null);
        generalPanel.add(imageEditorPanel);

        JLabel labelArguments = new JLabel("Arguments");
        generalPanel.add(labelArguments);

        String imageEditorArgs = Settings.getImageEditorArgs() != null ? Settings.getImageEditorArgs() : "\"%s\"";
        JTextField textFieldArguments = new JTextField(imageEditorArgs);
        textFieldArguments.setPreferredSize(new Dimension(0, 24));
        generalPanel.add(textFieldArguments);

        generalSpringLayout.putConstraint(SpringLayout.WEST, optionsPanel, 5, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, optionsPanel, 5, SpringLayout.NORTH, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.EAST, optionsPanel, 5, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.WEST, separator, 0, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.EAST, separator, 0, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, separator, 5, SpringLayout.SOUTH, optionsPanel);
        generalSpringLayout.putConstraint(SpringLayout.EAST, texturePathPanel, -10, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.WEST, texturePathPanel, 10, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, texturePathPanel, 10, SpringLayout.SOUTH, separator);
        generalSpringLayout.putConstraint(SpringLayout.EAST, separator2, 0, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.WEST, separator2, 0, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, separator2, 10, SpringLayout.SOUTH, texturePathPanel);
        generalSpringLayout.putConstraint(SpringLayout.EAST, imageEditorPanel, -10, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.WEST, imageEditorPanel, 10, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, imageEditorPanel, 10, SpringLayout.SOUTH, separator2);
        generalSpringLayout.putConstraint(SpringLayout.WEST, labelArguments, 10, SpringLayout.WEST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, labelArguments, 2, SpringLayout.NORTH, textFieldArguments);
        generalSpringLayout.putConstraint(SpringLayout.EAST, textFieldArguments, -10, SpringLayout.EAST, generalPanel);
        generalSpringLayout.putConstraint(SpringLayout.WEST, textFieldArguments, 20, SpringLayout.EAST, labelArguments);
        generalSpringLayout.putConstraint(SpringLayout.NORTH, textFieldArguments, 10, SpringLayout.SOUTH, imageEditorPanel);

        JPanel colorGrid = new JPanel();
        colorGrid.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        JScrollPane colorScrollPane = new JScrollPane(colorGrid);
        tabbedPane.addTab("Appearance", colorScrollPane);

        colorGrid.add(createColorSelector(dialog, "North Face", Face.getFaceColour(Face.NORTH), createFaceColorProcessor(Face.NORTH)));
        colorGrid.add(createColorSelector(dialog, "East Face", Face.getFaceColour(Face.EAST), createFaceColorProcessor(Face.EAST)));
        colorGrid.add(createColorSelector(dialog, "South Face", Face.getFaceColour(Face.SOUTH), createFaceColorProcessor(Face.SOUTH)));
        colorGrid.add(createColorSelector(dialog, "West Face", Face.getFaceColour(Face.WEST), createFaceColorProcessor(Face.WEST)));
        colorGrid.add(createColorSelector(dialog, "Up Face", Face.getFaceColour(Face.UP), createFaceColorProcessor(Face.UP)));
        colorGrid.add(createColorSelector(dialog, "Down Face", Face.getFaceColour(Face.DOWN), createFaceColorProcessor(Face.DOWN)));

        JButton btnReset = new JButton("Reset Colors");
        btnReset.addActionListener(a ->
        {
            Face.setFaceColors(Settings.DEFAULT_FACE_COLORS);
            dialog.dispose();
            JOptionPane.showMessageDialog(creator, "Colors reset");
        });
        colorGrid.add(btnReset);

        colorGrid.setLayout(new GridLayout(colorGrid.getComponentCount(), 1, 20, 10));

        dialog.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosed(WindowEvent e)
            {
                Settings.setAssetsDir(getDirectoryFromSelector(texturePathPanel));
                Settings.setUndoLimit((int) undoLimitSpinner.getValue());
                Settings.setFaceColors(Face.getFaceColors());
                Settings.setImageEditor(getDirectoryFromSelector(imageEditorPanel));
                Settings.setImageEditorArgs(textFieldArguments.getText());
                Settings.setCardinalPoints(checkBoxCardinalPoints.isSelected());
            }
        });

        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        dialog.requestFocus();
        dialog.pack();
        dialog.setResizable(false);
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
    }

    private static JPanel createDirectorySelector(String label, Component parent, String defaultDir)
    {
        SpringLayout layout = new SpringLayout();
        JPanel panel = new JPanel(layout);
        panel.setPreferredSize(new Dimension(100, 24));

        JTextField textFieldDestination = new JTextField();
        textFieldDestination.setPreferredSize(new Dimension(100, 24));
        textFieldDestination.setText(defaultDir);
        textFieldDestination.setEditable(false);
        textFieldDestination.setFocusable(false);
        textFieldDestination.setCaretPosition(0);
        panel.add(textFieldDestination);

        JButton btnBrowserDir = new JButton("Browse");
        btnBrowserDir.setPreferredSize(new Dimension(80, 24));
        btnBrowserDir.setIcon(Icons.load);
        btnBrowserDir.addActionListener(e ->
        {
            JFileChooser chooser = new JFileChooser();
            chooser.setDialogTitle("Select a Folder");
            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            chooser.setApproveButtonText("Select");
            chooser.setCurrentDirectory(new File(defaultDir));
            int returnVal = chooser.showOpenDialog(parent);
            if(returnVal == JFileChooser.APPROVE_OPTION)
            {
                File file = chooser.getSelectedFile();
                if(file != null)
                {
                    textFieldDestination.setText(file.getAbsolutePath());
                }
            }
        });
        panel.add(btnBrowserDir);

        JLabel labelExportDir = new JLabel(label);
        panel.add(labelExportDir);

        layout.putConstraint(SpringLayout.NORTH, textFieldDestination, 0, SpringLayout.NORTH, panel);
        layout.putConstraint(SpringLayout.WEST, textFieldDestination, 10, SpringLayout.EAST, labelExportDir);
        layout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);
        layout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);
        layout.putConstraint(SpringLayout.WEST, labelExportDir, 0, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);
        layout.putConstraint(SpringLayout.EAST, btnBrowserDir, 0, SpringLayout.EAST, panel);

        return panel;
    }

    public static String getDirectoryFromSelector(JPanel panel)
    {
        for(Component component : panel.getComponents())
        {
            if(component instanceof JTextField)
            {
                return ((JTextField) component).getText();
            }
        }
        return "";
    }

    public static void showExtractAssets(ModelCreator creator)
    {
        JDialog dialog = new JDialog(creator, "Extract Assets", Dialog.ModalityType.APPLICATION_MODAL);
        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        SpringLayout layout = new SpringLayout();
        JPanel panel = new JPanel(layout);
        panel.setPreferredSize(new Dimension(300, 150));
        dialog.add(panel);

        JLabel labelInfo = new JLabel("<html>This tool allows you to extract Minecraft's assets. The versions listed below are the ones you have downloaded with the Java edition of the game.</html>");
        panel.add(labelInfo);

        JLabel labelMinecraftAssets = new JLabel("Minecraft Version");
        panel.add(labelMinecraftAssets);

        JComboBox<String> comboBoxMinecraftVersions = new JComboBox<>();
        comboBoxMinecraftVersions.setPreferredSize(new Dimension(40, 24));
        Util.getMinecraftVersions().forEach(comboBoxMinecraftVersions::addItem);
        panel.add(comboBoxMinecraftVersions);

        JButton btnExtract = new JButton("Extract");
        btnExtract.setIcon(Icons.extract);
        btnExtract.setPreferredSize(new Dimension(80, 24));
        btnExtract.addActionListener(e ->
        {
            Util.extractMinecraftAssets((String) comboBoxMinecraftVersions.getSelectedItem(), dialog);
            dialog.dispose();
        });
        panel.add(btnExtract);

        layout.putConstraint(SpringLayout.NORTH, labelInfo, 10, SpringLayout.NORTH, panel);
        layout.putConstraint(SpringLayout.EAST, labelInfo, -10, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.WEST, labelInfo, 10, SpringLayout.WEST, panel);

        layout.putConstraint(SpringLayout.NORTH, labelMinecraftAssets, 2, SpringLayout.NORTH, comboBoxMinecraftVersions);
        layout.putConstraint(SpringLayout.WEST, labelMinecraftAssets, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, comboBoxMinecraftVersions, 15, SpringLayout.SOUTH, labelInfo);
        layout.putConstraint(SpringLayout.WEST, comboBoxMinecraftVersions, 10, SpringLayout.EAST, labelMinecraftAssets);
        layout.putConstraint(SpringLayout.EAST, comboBoxMinecraftVersions, -10, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.SOUTH, btnExtract, -10, SpringLayout.SOUTH, panel);
        layout.putConstraint(SpringLayout.EAST, btnExtract, -10, SpringLayout.EAST, panel);

        dialog.pack();
        dialog.setResizable(false);
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);
    }

    public static JPanel createColorSelector(Window parent, String labelText, int startColor, Processor<Integer> processor)
    {
        SpringLayout layout = new SpringLayout();
        JPanel panel = new JPanel(layout);
        panel.setPreferredSize(new Dimension(200, 30));
        panel.setBackground(new Color(0, 0, 0, 0));

        JLabel label = new JLabel(labelText);
        panel.add(label);

        JPanel colorPanel = new JPanel();
        colorPanel.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
        colorPanel.setBackground(new Color(startColor));
        colorPanel.setPreferredSize(new Dimension(24, 24));
        panel.add(colorPanel);

        JButton button = new JButton("Change");
        button.setPreferredSize(new Dimension(80, 24));
        button.addActionListener(e ->
        {
            int color = selectColor(parent, startColor);
            if(processor.run(color))
            {
                colorPanel.setBackground(new Color(color));
            }
        });
        panel.add(button);

        layout.putConstraint(SpringLayout.WEST, label, 0, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.VERTICAL_CENTER, label, 0, SpringLayout.VERTICAL_CENTER, panel);
        layout.putConstraint(SpringLayout.EAST, label, 5, SpringLayout.WEST, colorPanel);
        layout.putConstraint(SpringLayout.WEST, colorPanel, 80, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.VERTICAL_CENTER, colorPanel, 0, SpringLayout.VERTICAL_CENTER, panel);
        layout.putConstraint(SpringLayout.EAST, colorPanel, -10, SpringLayout.WEST, button);
        layout.putConstraint(SpringLayout.EAST, button, 0, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.VERTICAL_CENTER, button, 0, SpringLayout.VERTICAL_CENTER, panel);
        return panel;
    }

    private static int selectColor(Window parent, int startColor)
    {
        JDialog dialog = new JDialog(parent, "Select a Color", Dialog.ModalityType.APPLICATION_MODAL);
        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

        JPanel panel = new JPanel(new BorderLayout());
        dialog.add(panel);

        JColorChooser colorChooser = new JColorChooser();
        colorChooser.setColor(startColor);
        panel.add(colorChooser, BorderLayout.CENTER);

        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        panel.add(buttons, BorderLayout.SOUTH);

        JButton btnExtract = new JButton("Select");
        btnExtract.setIcon(Icons.extract);
        btnExtract.setPreferredSize(new Dimension(80, 24));
        btnExtract.addActionListener(e -> dialog.dispose());
        buttons.add(btnExtract);

        dialog.pack();
        dialog.setResizable(false);
        dialog.setLocationRelativeTo(null);
        dialog.setVisible(true);

        return colorChooser.getColor().getRGB();
    }

    private static Processor<Integer> createFaceColorProcessor(int side)
    {
        return integer ->
        {
            if(Face.getFaceColour(side) != integer)
            {
                Face.setFaceColor(side, integer);
                return true;
            }
            return false;
        };
    }

    private static void showDisplayProperties(ModelCreator creator)
    {
        DisplayPropertiesDialog dialog = new DisplayPropertiesDialog(creator);
        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        dialog.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosed(WindowEvent e)
            {
                Menu.displayPropertiesDialog = null;
                ModelCreator.restoreStandardRenderer();
            }
        });
        dialog.setLocationRelativeTo(null);
        dialog.setLocation(dialog.getLocation().x - 500, dialog.getLocation().y);
        dialog.setVisible(true);
        dialog.requestFocus();

        Menu.displayPropertiesDialog = dialog;
        ModelCreator.setCanvasRenderer(DisplayProperties.RENDER_MAP.get("gui"));
    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/component/MenuAdapter.java
================================================
package com.mrcrayfish.modelcreator.component;

import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;

/**
 * Author: MrCrayfish
 */
public abstract class MenuAdapter implements MenuListener
{
    @Override
    public void menuSelected(MenuEvent e)
    {

    }

    @Override
    public void menuDeselected(MenuEvent e)
    {

    }

    @Override
    public void menuCanceled(MenuEvent e)
    {

    }
}


================================================
FILE: src/main/java/com/mrcrayfish/modelcreator/component/TextureEntryEditor.java
================================================
package com.mrcrayfish.modelcreator.component;

import com.mrcrayfish.modelcreator.Icons;
import com.mrcrayfish.modelcreator.Settings;
import com.mrcrayfish.modelcreator.TexturePath;
import com.mrcrayfish.modelcreator.texture.TextureEntry;
import com.mrcrayfish.modelcreator.util.ComponentUtil;
import com.mrcrayfish.modelcreator.util.Util;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Locale;

/**
 * Author: MrCrayfish
 */
public class TextureEntryEditor extends JDialog
{
    private TextureEntry entry;

    private JLabel icon;
    private JTextField textFieldKey;
    private JTextField textFieldValue;
    private boolean triedEditingValue = false;

    private File texture = null;

    public TextureEntryEditor(Window owner, TextureEntry entry, ModalityType type)
    {
        super(owner, "Edit Texture Entry", type);
        this.entry = entry;
        this.setPreferredSize(new Dimension(300, 370));
        this.setResizable(false);
        this.initComponents();
        this.pack();
    }

    private void initComponents()
    {
        JPanel panel = new JPanel();
        panel.setLayout(new SpringLayout());
        this.add(panel);

        JPanel image = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        image.setPreferredSize(new Dimension(150, 150));
        image.setBackground(Color.WHITE);
        icon = new JLabel(resize(entry.getSource(), 150));
        image.add(icon);
        panel.add(image);

        JPanel fileSelector = ComponentUtil.createFileSelector("File", this, entry.getTextureFile().getAbsolutePath(), new FileNameExtensionFilter("PNG Image", "png"), file ->
        {
            if(this.setTexture(file))
            {
                TexturePath texturePath = new TexturePath(file);
                textFieldValue.setText(texturePath.toString());
                return true;
            }
            return false;
        });
        panel.add(fileSelector);

        JSeparator separator1 = new JSeparator();
        panel.add(separator1);

        JLabel labelKey = new JLabel("Key");
        panel.add(labelKey);

        textFieldKey = new JTextField();
        textFieldKey.setPreferredSize(new Dimension(0, 24));
        textFieldKey.setText(entry.getKey());
        panel.add(textFieldKey);

        JLabel labelValue = new JLabel("Value");
        panel.add(labelValue);

        textFieldValue = new JTextField();
        textFieldValue.setPreferredSize(new Dimension(0, 24));
        textFieldValue.setText(entry.getTexturePath().toString());
        textFieldValue.addFocusListener(new FocusAdapter()
        {
            @Override
            public void focusGained(FocusEvent e)
            {
                if(!triedEditingValue)
                {
                    JOptionPane.showMessageDialog(TextureEntryEditor.this, "Only edit this value if you are an advanced user. Changing this may cause the texture to not load in Minecraft", "Important", JOptionPane.INFORMATION_MESSAGE);
                    triedEditingValue = true;
                }
            }
        });
        panel.add(textFieldValue);

        JSeparator separator2 = new JSeparator();
        panel.add(separator2);

        JButton btnRefresh = new JButton();
        btnRefresh.setIcon(Icons.refresh);
        btnRefresh.addActionListener(e -> this.setTexture(entry.getTextureFile()));
        panel.add(btnRefresh);

        JButton btnEdit = new JButton("Edit");
        btnEdit.setIcon(Icons.edit_image);
        btnEdit.addActionListener(a ->
        {
            String program = Settings.getImageEditor();
            if(!program.isEmpty())
            {
                try
                {
                    String command = program + " " + String.format(Settings.getImageEditorArgs(), entry.getTextureFile().getAbsolutePath());
                    Runtime.getRuntime().exec(command);
                }
                catch(IOException e)
                {
                    e.printStackTrace();
                }
            }
            else
            {
                int returnVal = JOptionPane.showConfirmDialog(this, "Image Editor has not been configured. Do you want to open with default editor?", "Message", JOptionPane.YES_NO_OPTION);
                if(returnVal == JOptionPane.YES_OPTION)
                {
                    try
                    {
                        Desktop.getDesktop().edit(entry.getTextureFile());
                    }
                    catch(IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        });
        panel.add(btnEdit);

        JButton btnCancel = new JButton("Cancel");
        btnCancel.addActionListener(e -> this.dispose());
        panel.add(btnCancel);

        JButton btnSave = new JButton("Save");
        btnSave.setIcon(Icons.disk);
        btnSave.addActionListener(e ->
        {
            String key = textFieldKey.getText().trim().toLowerCase(Locale.ENGLISH);
            String value = textFieldValue.getText().trim().toLowerCase(Locale.ENGLISH);

            if(!TextureEntry.KEY_PATTERN.matcher(key).matches())
            {
                JOptionPane.showMessageDialog(this, "Invalid key format. It may only contain lowercase letters, numbers, and underscore.", "Key Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            TextureEntry foundEntry = TextureManager.getTexture(key);
            if(foundEntry != null && foundEntry != entry)
            {
                JOptionPane.showMessageDialog(this, "The key entered is already in use by another texture. Please choose a different key", "Key Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            if("particle".equals(key))
            {
                JOptionPane.showMessageDialog(this, "The key 'particle' is reserved. Please choose a different key", "Key Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            if(!TexturePath.PATTERN.matcher(value).matches())
            {
                JOptionPane.showMessageDialog(this, "Invalid value format", "Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            if(texture != null)
            {
                try
                {
                    Dimension dimension = Util.getImageDimension(texture);
                    if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)
                    {
                        JOptionPane.showMessageDialog(this, "Image size must be multiple of 16", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                }
                catch(IOException e1)
                {
                    JOptionPane.showMessageDialog(this, "Unable to determine image dimensions", "Error", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                entry.setTextureFile(texture);
            }
            entry.setTexturePath(new TexturePath(value));
            entry.setKey(key);
            this.dispose();
        });
        panel.add(btnSave);

        SpringLayout layout = (SpringLayout) panel.getLayout();
        layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, image, 0, SpringLayout.HORIZONTAL_CENTER, panel);
        layout.putConstraint(SpringLayout.NORTH, image, 10, SpringLayout.NORTH, panel);

        layout.putConstraint(SpringLayout.WEST, fileSelector, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, fileSelector, 20, SpringLayout.SOUTH, image);
        layout.putConstraint(SpringLayout.EAST, fileSelector, -10, SpringLayout.EAST, panel);

        layout.putConstraint(SpringLayout.WEST, separator1, 0, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, separator1, 10, SpringLayout.SOUTH, fileSelector);
        layout.putConstraint(SpringLayout.EAST, separator1, 0, SpringLayout.EAST, panel);

        layout.putConstraint(SpringLayout.WEST, labelKey, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, labelKey, 2, SpringLayout.NORTH, textFieldKey);

        layout.putConstraint(SpringLayout.WEST, textFieldKey, 40, SpringLayout.WEST, labelKey);
        layout.putConstraint(SpringLayout.NORTH, textFieldKey, 10, SpringLayout.SOUTH, separator1);
        layout.putConstraint(SpringLayout.EAST, textFieldKey, -10, SpringLayout.EAST, panel);

        layout.putConstraint(SpringLayout.WEST, labelValue, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, labelValue, 2, SpringLayout.NORTH, textFieldValue);

        layout.putConstraint(SpringLayout.WEST, textFieldValue, 40, SpringLayout.WEST, labelValue);
        layout.putConstraint(SpringLayout.NORTH, textFieldValue, 10, SpringLayout.SOUTH, textFieldKey);
        layout.putConstraint(SpringLayout.EAST, textFieldValue, -10, SpringLayout.EAST, panel);

        layout.putConstraint(SpringLayout.WEST, separator2, 0, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.NORTH, separator2, 10, SpringLayout.SOUTH, textFieldValue);
        layout.putConstraint(SpringLayout.EAST, separator2, 0, SpringLayout.EAST, panel);

        layout.putConstraint(SpringLayout.WEST, btnRefresh, 10, SpringLayout.WEST, panel);
        layout.putConstraint(SpringLayout.SOUTH, btnRefresh, -10, SpringLayout.SOUTH, panel);

        layout.putConstraint(SpringLayout.WEST, btnEdit, 10, SpringLayout.EAST, btnRefresh);
        layout.putConstraint(SpringLayout.SOUTH, btnEdit, -10, SpringLayout.SOUTH, panel);

        layout.putConstraint(SpringLayout.EAST, btnCancel, -10, SpringLayout.WEST, btnSave);
        layout.putConstraint(SpringLayout.SOUTH, btnCancel, -10, SpringLayout.SOUTH, panel);

        layout.putConstraint(SpringLayout.EAST, btnSave, -10, SpringLayout.EAST, panel);
        layout.putConstraint(SpringLayout.SOUTH, btnSave, -10, SpringLayout.SOUTH, panel);
    }

    private static ImageIcon resize(BufferedImage source, int size)
    {
        Image scaledImage = source.getScaledInstance(size, size, java.awt.Image.SCALE_FAST);
        return new ImageIcon(scaledImage);
    }

    private boolean setTexture(File file)
    {
        try
        {
            Dimension dimension = Util.getImageDimension(file);
            if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)
            {
                JOptionPane.showMessageDialog(this, "Image size must be multiple of 16", "Error", JOptionPane.ERROR_MESSAGE);
                return false;
            }
        }
        catch(IOException e1)
        {
            JOptionPane.showMessageDialog(this, "Unable to determine image dimensions", "Error", JOptionPane.ERROR_MESSAGE);
            return false;
        }
        try
Download .txt
gitextract_b14my70h/

├── .gitignore
├── LICENSE
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── mrcrayfish/
        │           └── modelcreator/
        │               ├── Actions.java
        │               ├── Animation.java
        │               ├── Camera.java
        │               ├── Constants.java
        │               ├── Exporter.java
        │               ├── ExporterJavaCode.java
        │               ├── ExporterModel.java
        │               ├── Icons.java
        │               ├── Importer.java
        │               ├── ModelCreator.java
        │               ├── Processor.java
        │               ├── ProjectManager.java
        │               ├── PropertyIdentifiers.java
        │               ├── Settings.java
        │               ├── Start.java
        │               ├── StateManager.java
        │               ├── TexturePath.java
        │               ├── component/
        │               │   ├── DisplayPropertiesDialog.java
        │               │   ├── JElementList.java
        │               │   ├── Menu.java
        │               │   ├── MenuAdapter.java
        │               │   ├── TextureEntryEditor.java
        │               │   └── TextureManager.java
        │               ├── dialog/
        │               │   └── WelcomeDialog.java
        │               ├── display/
        │               │   ├── CanvasRenderer.java
        │               │   ├── DisplayProperties.java
        │               │   └── render/
        │               │       ├── DisplayPropertyRenderer.java
        │               │       ├── FirstPersonPropertyRenderer.java
        │               │       ├── FixedPropertyRenderer.java
        │               │       ├── GroundPropertyRenderer.java
        │               │       ├── GuiPropertyRenderer.java
        │               │       ├── HeadPropertyRenderer.java
        │               │       ├── StandardRenderer.java
        │               │       └── ThirdPersonPropertyRenderer.java
        │               ├── element/
        │               │   ├── Element.java
        │               │   ├── ElementCellEntry.java
        │               │   ├── ElementCellRenderer.java
        │               │   ├── ElementManager.java
        │               │   ├── ElementManagerState.java
        │               │   └── Face.java
        │               ├── object/
        │               │   └── FaceDimension.java
        │               ├── panels/
        │               │   ├── CuboidTabbedPane.java
        │               │   ├── DisplayEntryPanel.java
        │               │   ├── ElementExtraPanel.java
        │               │   ├── FaceExtrasPanel.java
        │               │   ├── GlobalPanel.java
        │               │   ├── IElementUpdater.java
        │               │   ├── OriginPanel.java
        │               │   ├── PositionPanel.java
        │               │   ├── SidebarPanel.java
        │               │   ├── SizePanel.java
        │               │   ├── TexturePanel.java
        │               │   ├── UVPanel.java
        │               │   └── tabs/
        │               │       ├── ElementPanel.java
        │               │       ├── FacePanel.java
        │               │       └── RotationPanel.java
        │               ├── screenshot/
        │               │   ├── PendingScreenshot.java
        │               │   ├── Screenshot.java
        │               │   ├── ScreenshotCallback.java
        │               │   └── Uploader.java
        │               ├── sidebar/
        │               │   ├── Sidebar.java
        │               │   └── UVSidebar.java
        │               ├── texture/
        │               │   ├── Clipboard.java
        │               │   ├── TextureAnimation.java
        │               │   ├── TextureAtlas.java
        │               │   └── TextureEntry.java
        │               └── util/
        │                   ├── AssetsUtil.java
        │                   ├── AtlasRenderUtil.java
        │                   ├── ComponentUtil.java
        │                   ├── FontManager.java
        │                   ├── KeyboardUtil.java
        │                   ├── OperatingSystem.java
        │                   ├── Parser.java
        │                   ├── SharedLibraryLoader.java
        │                   ├── StreamUtils.java
        │                   └── Util.java
        └── resources/
            ├── bebas_neue.otf
            └── models/
                ├── cauldron.model
                └── modern_chair.model
Download .txt
SYMBOL INDEX (724 symbols across 76 files)

FILE: src/main/java/com/mrcrayfish/modelcreator/Actions.java
  class Actions (line 10) | public class Actions
    method optimiseModel (line 12) | public static int optimiseModel(ElementManager manager)
    method rotateModel (line 33) | public static void rotateModel(ElementManager manager, boolean clockwise)
    method rotateElement (line 40) | private static void rotateElement(Element element, boolean clockwise)
    method getNextFaceRotation (line 121) | private static int getNextFaceRotation(Element element, int side, bool...

FILE: src/main/java/com/mrcrayfish/modelcreator/Animation.java
  class Animation (line 6) | public class Animation
    method tick (line 11) | public static void tick()
    method setPartialTicks (line 16) | public static void setPartialTicks(float partialTicks)
    method getCounter (line 21) | public static int getCounter()
    method getPartialTicks (line 26) | public static float getPartialTicks()

FILE: src/main/java/com/mrcrayfish/modelcreator/Camera.java
  class Camera (line 6) | public class Camera
    method Camera (line 20) | public Camera(float fov, float aspect, float near, float far)
    method initProjection (line 36) | private void initProjection()
    method useView (line 46) | public void useView()
    method getX (line 54) | public float getX()
    method getY (line 59) | public float getY()
    method getZ (line 64) | public float getZ()
    method setX (line 69) | public void setX(float x)
    method setY (line 74) | public void setY(float y)
    method setZ (line 79) | public void setZ(float z)
    method getRX (line 84) | public float getRX()
    method getRY (line 89) | public float getRY()
    method getRZ (line 94) | public float getRZ()
    method setRX (line 99) | public void setRX(float rx)
    method setRY (line 104) | public void setRY(float ry)
    method setRZ (line 109) | public void setRZ(float rz)
    method move (line 114) | public void move(float amt, float dir)
    method addX (line 120) | public void addX(float amt)
    method addY (line 125) | public void addY(float amt)
    method addZ (line 130) | public void addZ(float amt)
    method rotateX (line 135) | public void rotateX(float amt)
    method rotateY (line 140) | public void rotateY(float amt)

FILE: src/main/java/com/mrcrayfish/modelcreator/Constants.java
  class Constants (line 3) | public class Constants

FILE: src/main/java/com/mrcrayfish/modelcreator/Exporter.java
  class Exporter (line 12) | public abstract class Exporter
    method Exporter (line 29) | public Exporter(ElementManager manager)
    method export (line 34) | public File export(File file)
    method write (line 44) | protected abstract void write(BufferedWriter writer) throws IOException;
    method writeFile (line 46) | public File writeFile(File file)
    method space (line 64) | protected String space(int size)

FILE: src/main/java/com/mrcrayfish/modelcreator/ExporterJavaCode.java
  class ExporterJavaCode (line 12) | public class ExporterJavaCode extends Exporter
    method ExporterJavaCode (line 18) | public ExporterJavaCode(ModelCreator creator, boolean includeFields, b...
    method setVersion (line 28) | public void setVersion(Version version)
    method writeCodeToClipboard (line 33) | public void writeCodeToClipboard() throws IOException
    method write (line 44) | @Override
    method format (line 333) | private String format(double value)
    method writeField (line 338) | private void writeField(BufferedWriter writer, ModelBounds bounds, Str...
    method writeNewLine (line 376) | private void writeNewLine(BufferedWriter writer, String line, Object.....
    class ModelBounds (line 382) | private class ModelBounds
      method ModelBounds (line 386) | private ModelBounds()
      method write (line 392) | public void write(BufferedWriter writer) throws IOException
      method union (line 397) | private void union(double minX, double minY, double minZ, double max...
    type Version (line 408) | public enum Version
      method Version (line 414) | Version(String label)
      method toString (line 419) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/ExporterModel.java
  class ExporterModel (line 16) | public class ExporterModel extends Exporter
    method ExporterModel (line 26) | public ExporterModel(ElementManager manager)
    method setOptimize (line 32) | public void setOptimize(boolean optimize)
    method setIncludeNames (line 37) | public void setIncludeNames(boolean includeNames)
    method setDisplayProps (line 42) | public void setDisplayProps(boolean displayProps)
    method setIncludeNonTexturedFaces (line 47) | public void setIncludeNonTexturedFaces(boolean includeNonTexturedFaces)
    method compileTextureList (line 52) | private void compileTextureList()
    method write (line 67) | @Override
    method writeTextures (line 117) | private void writeTextures(BufferedWriter writer) throws IOException
    method writeElement (line 157) | private void writeElement(BufferedWriter writer, Element cuboid) throw...
    method writeBounds (line 184) | private void writeBounds(BufferedWriter writer, Element cuboid) throws...
    method writeShade (line 191) | private void writeShade(BufferedWriter writer, Element cuboid) throws ...
    method writeRotation (line 196) | private void writeRotation(BufferedWriter writer, Element cuboid) thro...
    method writeFaces (line 209) | private void writeFaces(BufferedWriter writer, Element cuboid) throws ...
    method writeFace (line 241) | private void writeFace(BufferedWriter writer, Face face) throws IOExce...
    method writeDisplayProperties (line 264) | private void writeDisplayProperties(BufferedWriter writer) throws IOEx...
    method writeDisplayEntry (line 297) | private void writeDisplayEntry(BufferedWriter writer, String id, Displ...
    method canWriteElement (line 310) | private boolean canWriteElement(Element element)

FILE: src/main/java/com/mrcrayfish/modelcreator/Icons.java
  class Icons (line 5) | public class Icons
    method init (line 53) | public static void init(Class<?> clazz)

FILE: src/main/java/com/mrcrayfish/modelcreator/Importer.java
  class Importer (line 24) | public class Importer
    method Importer (line 36) | public Importer(ElementManager manager, String outputPath)
    method importFromJSON (line 42) | public void importFromJSON()
    method readComponents (line 65) | private void readComponents(BufferedReader reader, ElementManager mana...
    method loadTextures (line 135) | private void loadTextures(File file, JsonObject obj)
    method loadTexture (line 164) | private TextureEntry loadTexture(File project, String id, String texture)
    method readDisplayProperties (line 232) | private void readDisplayProperties(JsonObject obj, ElementManager mana...
    method readEntry (line 245) | private void readEntry(JsonObject obj, String id, DisplayProperties pr...
    method readElement (line 281) | private void readElement(JsonObject obj, ElementManager manager)
    method readFace (line 385) | private void readFace(JsonObject obj, String name, Element element)

FILE: src/main/java/com/mrcrayfish/modelcreator/ModelCreator.java
  class ModelCreator (line 43) | public class ModelCreator extends JFrame
    method ModelCreator (line 83) | public ModelCreator(String title)
    method initComponents (line 165) | private void initComponents()
    method registerShortcuts (line 182) | private void registerShortcuts()
    method getIcons (line 219) | private List<Image> getIcons()
    method getCanvasWidth (line 229) | public int getCanvasWidth()
    method getCanvasHeight (line 234) | public int getCanvasHeight()
    method getCanvasOffset (line 239) | public int getCanvasOffset()
    method setupMenuBar (line 244) | private void setupMenuBar()
    method createDisplay (line 250) | private void createDisplay()
    method loop (line 292) | private void loop()
    method tick (line 326) | private void tick()
    method render (line 331) | private void render(float partialTicks)
    method handleKeyboardInput (line 365) | private void handleKeyboardInput()
    method draw (line 396) | private void draw()
    method drawOverlay (line 435) | private void drawOverlay(int offset)
    method handleInput (line 554) | private void handleInput(int offset)
    method select (line 751) | private int select(int x, int y)
    method applyLimit (line 804) | private float applyLimit(float value)
    method getCameraState (line 817) | private int getCameraState(Camera camera)
    method startScreenshot (line 833) | public void startScreenshot(PendingScreenshot screenshot)
    method setSidebar (line 838) | public void setSidebar(Sidebar s)
    method getActiveSidebar (line 847) | public Sidebar getActiveSidebar()
    method getElementManager (line 852) | public ElementManager getElementManager()
    method close (line 857) | public void close()
    method getCloseRequested (line 862) | private boolean getCloseRequested()
    method handleKeyAction (line 867) | private void handleKeyAction(int code, int modifiers, boolean awt, boo...
    method setCanvasRenderer (line 891) | public static void setCanvasRenderer(CanvasRenderer displayRenderer)
    method restoreStandardRenderer (line 897) | public static void restoreStandardRenderer()
    method registerKeyAction (line 902) | public void registerKeyAction(KeyAction keyAction)
    class KeyAction (line 907) | public static class KeyAction
      method KeyAction (line 913) | public KeyAction(int awtCode, int keyboardCode, Handler handler)
      type Handler (line 920) | public interface Handler
        method process (line 922) | void process(int modifiers, boolean pressed);

FILE: src/main/java/com/mrcrayfish/modelcreator/Processor.java
  type Processor (line 6) | public interface Processor<T>
    method run (line 14) | boolean run(T t);

FILE: src/main/java/com/mrcrayfish/modelcreator/ProjectManager.java
  class ProjectManager (line 26) | public class ProjectManager
    method loadProject (line 30) | public static void loadProject(ElementManager manager, String modelFile)
    method deleteFolder (line 46) | private static void deleteFolder(File file)
    method extractFiles (line 61) | private static File extractFiles(String modelFile)
    method saveProject (line 110) | public static void saveProject(ElementManager manager, String name)
    method getAllTextures (line 152) | private static Set<TextureEntry> getAllTextures(ElementManager manager)
    method getSaveFile (line 168) | private static File getSaveFile(ElementManager manager) throws IOExcep...
    method addToZipFile (line 176) | private static void addToZipFile(File file, ZipOutputStream zos, Strin...
    method addToZipFile (line 181) | private static void addToZipFile(File file, ZipOutputStream zos, Strin...
    class Project (line 198) | private static class Project
      method Project (line 203) | public Project(File folder)
      method getModel (line 223) | public File getModel()
      method getTextures (line 228) | public File getTextures()
    class ProjectTexture (line 234) | private static class ProjectTexture
      method ProjectTexture (line 239) | public ProjectTexture(File texture, File meta)
      method getTexture (line 245) | public File getTexture()
      method getMeta (line 250) | public File getMeta()

FILE: src/main/java/com/mrcrayfish/modelcreator/PropertyIdentifiers.java
  class PropertyIdentifiers (line 6) | public class PropertyIdentifiers

FILE: src/main/java/com/mrcrayfish/modelcreator/Settings.java
  class Settings (line 5) | public class Settings
    method getImageImportDir (line 21) | public static String getImageImportDir()
    method setImageImportDir (line 27) | public static void setImageImportDir(String dir)
    method getScreenshotDir (line 33) | public static String getScreenshotDir()
    method setScreenshotDir (line 39) | public static void setScreenshotDir(String dir)
    method getModelDir (line 45) | public static String getModelDir()
    method setModelDir (line 51) | public static void setModelDir(String dir)
    method getJSONDir (line 57) | public static String getJSONDir()
    method setJSONDir (line 63) | public static void setJSONDir(String dir)
    method getExportJSONDir (line 69) | public static String getExportJSONDir()
    method setExportJSONDir (line 75) | public static void setExportJSONDir(String dir)
    method getAssetsDir (line 81) | public static String getAssetsDir()
    method setAssetsDir (line 87) | public static void setAssetsDir(String dir)
    method getUndoLimit (line 93) | public static int getUndoLimit()
    method setUndoLimit (line 107) | public static void setUndoLimit(int limit)
    method getCardinalPoints (line 113) | public static boolean getCardinalPoints()
    method setCardinalPoints (line 120) | public static void setCardinalPoints(boolean renderCardinalPoints)
    method getFaceColors (line 126) | public static int[] getFaceColors()
    method setFaceColors (line 144) | public static void setFaceColors(int[] colors)
    method getImageEditor (line 157) | public static String getImageEditor()
    method setImageEditor (line 163) | public static void setImageEditor(String file)
    method getImageEditorArgs (line 169) | public static String getImageEditorArgs()
    method setImageEditorArgs (line 175) | public static void setImageEditorArgs(String args)
    method getPreferences (line 181) | private static Preferences getPreferences()

FILE: src/main/java/com/mrcrayfish/modelcreator/Start.java
  class Start (line 9) | public class Start
    method main (line 11) | public static void main(String[] args)

FILE: src/main/java/com/mrcrayfish/modelcreator/StateManager.java
  class StateManager (line 13) | public class StateManager
    method pushState (line 21) | public static void pushState(ElementManager manager)
    method pushState (line 26) | private static void pushState(ElementManagerState state)
    method pushManagerState (line 40) | private static void pushManagerState(ElementManagerState state)
    method restorePreviousState (line 58) | public static void restorePreviousState(ElementManager manager)
    method restoreNextState (line 68) | public static void restoreNextState(ElementManager manager)
    method canRestorePreviousState (line 78) | public static boolean canRestorePreviousState()
    method canRestoreNextState (line 83) | public static boolean canRestoreNextState()
    method clear (line 88) | public static void clear()
    method getStates (line 99) | public static Stack<ElementManagerState> getStates()
    method getTailIndex (line 104) | public static int getTailIndex()
    method pushStateDelayed (line 109) | public static void pushStateDelayed(ElementManager manager, int id)

FILE: src/main/java/com/mrcrayfish/modelcreator/TexturePath.java
  class TexturePath (line 11) | public class TexturePath
    method TexturePath (line 19) | public TexturePath(String s)
    method TexturePath (line 31) | public TexturePath(File file)
    method getModId (line 38) | public String getModId()
    method getDirectory (line 43) | public String getDirectory()
    method getName (line 48) | public String getName()
    method toString (line 53) | @Override
    method toRelativePath (line 59) | public String toRelativePath()

FILE: src/main/java/com/mrcrayfish/modelcreator/component/DisplayPropertiesDialog.java
  class DisplayPropertiesDialog (line 16) | public class DisplayPropertiesDialog extends JDialog
    method DisplayPropertiesDialog (line 22) | public DisplayPropertiesDialog(ModelCreator creator)
    method init (line 32) | private void init()
    method updateValues (line 106) | public void updateValues(DisplayProperties displayProperties)
    method update (line 124) | public static void update(ModelCreator creator)

FILE: src/main/java/com/mrcrayfish/modelcreator/component/JElementList.java
  class JElementList (line 13) | public class JElementList extends JList<ElementCellEntry>
    method processMouseEvent (line 17) | @Override
    method processMouseMotionEvent (line 62) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/component/Menu.java
  class Menu (line 26) | public class Menu extends JMenuBar
    method Menu (line 76) | public Menu(ModelCreator creator)
    method initMenu (line 82) | private void initMenu()
    method initActions (line 210) | private void initActions()
    method createMenuItem (line 396) | private JMenuItem createMenuItem(String name, String tooltip, int mnem...
    method createMenuItem (line 401) | private JMenuItem createMenuItem(String name, String tooltip, int mnem...
    method newProject (line 439) | public static void newProject(ModelCreator creator)
    method loadProject (line 453) | public static void loadProject(ModelCreator creator)
    method saveProject (line 491) | public static void saveProject(ModelCreator creator)
    method optimizeModel (line 529) | public static void optimizeModel(ModelCreator creator)
    method showImportJson (line 539) | public static void showImportJson(ModelCreator creator)
    method showExportJson (line 577) | public static void showExportJson(ModelCreator creator)
    method showExportJavaCode (line 771) | private static void showExportJavaCode(ModelCreator creator, ActionEve...
    method showSettings (line 907) | public static void showSettings(ModelCreator creator)
    method createDirectorySelector (line 1038) | private static JPanel createDirectorySelector(String label, Component ...
    method getDirectoryFromSelector (line 1088) | public static String getDirectoryFromSelector(JPanel panel)
    method showExtractAssets (line 1100) | public static void showExtractAssets(ModelCreator creator)
    method createColorSelector (line 1149) | public static JPanel createColorSelector(Window parent, String labelTe...
    method selectColor (line 1188) | private static int selectColor(Window parent, int startColor)
    method createFaceColorProcessor (line 1217) | private static Processor<Integer> createFaceColorProcessor(int side)
    method showDisplayProperties (line 1230) | private static void showDisplayProperties(ModelCreator creator)

FILE: src/main/java/com/mrcrayfish/modelcreator/component/MenuAdapter.java
  class MenuAdapter (line 9) | public abstract class MenuAdapter implements MenuListener
    method menuSelected (line 11) | @Override
    method menuDeselected (line 17) | @Override
    method menuCanceled (line 23) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/component/TextureEntryEditor.java
  class TextureEntryEditor (line 24) | public class TextureEntryEditor extends JDialog
    method TextureEntryEditor (line 35) | public TextureEntryEditor(Window owner, TextureEntry entry, ModalityTy...
    method initComponents (line 45) | private void initComponents()
    method resize (line 247) | private static ImageIcon resize(BufferedImage source, int size)
    method setTexture (line 253) | private boolean setTexture(File file)

FILE: src/main/java/com/mrcrayfish/modelcreator/component/TextureManager.java
  class TextureManager (line 24) | public class TextureManager extends JDialog
    method TextureManager (line 46) | public TextureManager(Frame owner, ElementManager manager, ModalityTyp...
    method initComponents (line 57) | private void initComponents()
    method getSelectedTexture (line 185) | public TextureEntry getSelectedTexture()
    method getResult (line 194) | public int getResult()
    method showFileChooser (line 199) | public void showFileChooser()
    method addImage (line 243) | private void addImage(File image)
    method addImage (line 273) | public static TextureEntry addImage(String id, TexturePath path, File ...
    method getTexture (line 309) | public static TextureEntry getTexture(String id)
    class TextureCellRenderer (line 321) | public static class TextureCellRenderer implements ListCellRenderer<Te...
      method getListCellRendererComponent (line 323) | @Override
    method display (line 361) | public static TextureEntry display(Frame owner, ElementManager manager...
    method loadTexture (line 373) | public static void loadTexture(TextureEntry entry)
    method removeTexture (line 381) | public static void removeTexture(TextureEntry entry)
    method processPendingTextures (line 389) | public static void processPendingTextures()
    method clear (line 417) | public static void clear()

FILE: src/main/java/com/mrcrayfish/modelcreator/dialog/WelcomeDialog.java
  class WelcomeDialog (line 11) | public class WelcomeDialog
    method show (line 13) | public static void show(JFrame parent)
    method getDialogContent (line 22) | private static JPanel getDialogContent(JFrame parent)
    method getLeftPanel (line 38) | private static JPanel getLeftPanel()
    method getScrollableMessage (line 50) | private static JScrollPane getScrollableMessage()
    method getButtonGrid (line 67) | private static JPanel getButtonGrid()
    method openUrl (line 91) | private static void openUrl(String url)
    method getWelcomeDialog (line 107) | private static JDialog getWelcomeDialog(JFrame parent, JPanel dialogCo...
    method showDialog (line 117) | private static void showDialog(JDialog dialog)

FILE: src/main/java/com/mrcrayfish/modelcreator/display/CanvasRenderer.java
  class CanvasRenderer (line 14) | public abstract class CanvasRenderer
    method onInit (line 18) | public void onInit(Camera camera) {}
    method onRenderPerspective (line 20) | public void onRenderPerspective(ModelCreator creator, ElementManager m...
    method onRenderOverlay (line 22) | public void onRenderOverlay(ElementManager manager, Camera camera, Mod...

FILE: src/main/java/com/mrcrayfish/modelcreator/display/DisplayProperties.java
  class DisplayProperties (line 11) | public class DisplayProperties
    method DisplayProperties (line 64) | private DisplayProperties(String name, boolean isDefault)
    method DisplayProperties (line 70) | public DisplayProperties(DisplayProperties properties)
    method DisplayProperties (line 76) | public DisplayProperties(String name, DisplayProperties properties)
    method add (line 82) | public void add(String id, double rotationX, double rotationY, double ...
    method getName (line 89) | public String getName()
    method isDefault (line 94) | public boolean isDefault()
    method getEntry (line 99) | public Entry getEntry(String id)
    method getEntries (line 104) | public Map<String, Entry> getEntries()
    method toString (line 109) | @Override
    class Entry (line 115) | public static class Entry
      method Entry (line 123) | public Entry(String id, double rotationX, double rotationY, double r...
      method Entry (line 137) | public Entry(Entry entry)
      method setEnabled (line 152) | public void setEnabled(boolean enabled)
      method isEnabled (line 157) | public boolean isEnabled()
      method getId (line 162) | public String getId()
      method getRotationX (line 167) | public double getRotationX()
      method setRotationX (line 172) | public void setRotationX(double rotationX)
      method getRotationY (line 177) | public double getRotationY()
      method setRotationY (line 182) | public void setRotationY(double rotationY)
      method getRotationZ (line 187) | public double getRotationZ()
      method setRotationZ (line 192) | public void setRotationZ(double rotationZ)
      method getTranslationX (line 197) | public double getTranslationX()
      method setTranslationX (line 202) | public void setTranslationX(double translationX)
      method getTranslationY (line 207) | public double getTranslationY()
      method setTranslationY (line 212) | public void setTranslationY(double translationY)
      method getTranslationZ (line 217) | public double getTranslationZ()
      method setTranslationZ (line 222) | public void setTranslationZ(double translationZ)
      method getScaleX (line 227) | public double getScaleX()
      method setScaleX (line 232) | public void setScaleX(double scaleX)
      method getScaleY (line 237) | public double getScaleY()
      method setScaleY (line 242) | public void setScaleY(double scaleY)
      method getScaleZ (line 247) | public double getScaleZ()
      method setScaleZ (line 252) | public void setScaleZ(double scaleZ)

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/DisplayPropertyRenderer.java
  class DisplayPropertyRenderer (line 11) | public abstract class DisplayPropertyRenderer extends StandardRenderer
    method drawElements (line 13) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/FirstPersonPropertyRenderer.java
  class FirstPersonPropertyRenderer (line 18) | public class FirstPersonPropertyRenderer extends DisplayPropertyRenderer
    method FirstPersonPropertyRenderer (line 22) | public FirstPersonPropertyRenderer(boolean leftHanded)
    method onRenderPerspective (line 27) | @Override
    method onRenderOverlay (line 30) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/FixedPropertyRenderer.java
  class FixedPropertyRenderer (line 14) | public class FixedPropertyRenderer extends DisplayPropertyRenderer
    method FixedPropertyRenderer (line 16) | public FixedPropertyRenderer()
    method addElements (line 21) | private void addElements()
    method onInit (line 54) | @Override
    method onRenderPerspective (line 65) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/GroundPropertyRenderer.java
  class GroundPropertyRenderer (line 15) | public class GroundPropertyRenderer extends DisplayPropertyRenderer
    method GroundPropertyRenderer (line 17) | public GroundPropertyRenderer()
    method addElements (line 22) | private void addElements()
    method onInit (line 31) | @Override
    method onRenderPerspective (line 42) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/GuiPropertyRenderer.java
  class GuiPropertyRenderer (line 16) | public class GuiPropertyRenderer extends DisplayPropertyRenderer
    method onRenderPerspective (line 18) | @Override
    method onRenderOverlay (line 21) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/HeadPropertyRenderer.java
  class HeadPropertyRenderer (line 14) | public class HeadPropertyRenderer extends DisplayPropertyRenderer
    method HeadPropertyRenderer (line 16) | public HeadPropertyRenderer()
    method addElements (line 21) | private void addElements()
    method onInit (line 83) | @Override
    method onRenderPerspective (line 94) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/StandardRenderer.java
  class StandardRenderer (line 21) | public class StandardRenderer extends CanvasRenderer
    method onInit (line 23) | @Override
    method onRenderPerspective (line 34) | @Override
    method drawElements (line 52) | protected void drawElements(ElementManager manager)
    method drawGrid (line 74) | protected void drawGrid(Camera camera, boolean renderCardinalPoints)

FILE: src/main/java/com/mrcrayfish/modelcreator/display/render/ThirdPersonPropertyRenderer.java
  class ThirdPersonPropertyRenderer (line 14) | public class ThirdPersonPropertyRenderer extends DisplayPropertyRenderer
    method ThirdPersonPropertyRenderer (line 18) | public ThirdPersonPropertyRenderer(boolean leftHanded)
    method addElements (line 24) | private void addElements()
    method onInit (line 85) | @Override
    method onRenderPerspective (line 96) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/element/Element.java
  class Element (line 13) | public class Element
    method Element (line 38) | public Element(double width, double height, double depth)
    method Element (line 47) | public Element(Element cuboid)
    method initFaces (line 84) | private void initFaces()
    method setSelectedFace (line 90) | public void setSelectedFace(int face)
    method getSelectedFace (line 95) | public Face getSelectedFace()
    method getSelectedFaceIndex (line 100) | public int getSelectedFaceIndex()
    method getAllFaces (line 105) | public Face[] getAllFaces()
    method getFaceDimension (line 110) | public FaceDimension getFaceDimension(int side)
    method setAllTextures (line 130) | public void setAllTextures(TextureEntry entry)
    method draw (line 138) | public void draw()
    method drawExtras (line 187) | public void drawExtras(ElementManager manager)
    method drawOutline (line 216) | public void drawOutline()
    method addStartX (line 403) | public void addStartX(double amt)
    method addStartY (line 408) | public void addStartY(double amt)
    method addStartZ (line 413) | public void addStartZ(double amt)
    method getStartX (line 418) | public double getStartX()
    method getStartY (line 423) | public double getStartY()
    method getStartZ (line 428) | public double getStartZ()
    method setStartX (line 433) | public void setStartX(double amt)
    method setStartY (line 438) | public void setStartY(double amt)
    method setStartZ (line 443) | public void setStartZ(double amt)
    method getWidth (line 448) | public double getWidth()
    method getHeight (line 453) | public double getHeight()
    method getDepth (line 458) | public double getDepth()
    method addWidth (line 463) | public void addWidth(double amt)
    method addHeight (line 468) | public void addHeight(double amt)
    method addDepth (line 473) | public void addDepth(double amt)
    method setWidth (line 478) | public void setWidth(double width)
    method setHeight (line 483) | public void setHeight(double height)
    method setDepth (line 488) | public void setDepth(double depth)
    method getOriginX (line 493) | public double getOriginX()
    method getOriginY (line 498) | public double getOriginY()
    method getOriginZ (line 503) | public double getOriginZ()
    method addOriginX (line 508) | public void addOriginX(double amt)
    method addOriginY (line 513) | public void addOriginY(double amt)
    method addOriginZ (line 518) | public void addOriginZ(double amt)
    method setOriginX (line 523) | public void setOriginX(double amt)
    method setOriginY (line 528) | public void setOriginY(double amt)
    method setOriginZ (line 533) | public void setOriginZ(double amt)
    method getRotation (line 538) | public double getRotation()
    method setRotation (line 543) | public void setRotation(double rotation)
    method getRotationAxis (line 548) | public int getRotationAxis()
    method setRotationAxis (line 553) | public void setRotationAxis(int axis)
    method setRescale (line 558) | public void setRescale(boolean rescale)
    method shouldRescale (line 563) | public boolean shouldRescale()
    method isShaded (line 568) | public boolean isShaded()
    method setShade (line 573) | public void setShade(boolean shade)
    method setName (line 578) | public void setName(String name)
    method getName (line 583) | public String getName()
    method toString (line 588) | @Override
    method updateStartUVs (line 594) | public void updateStartUVs()
    method updateEndUVs (line 602) | public void updateEndUVs()
    method rotateAxis (line 610) | private void rotateAxis()
    method parseAxis (line 626) | public static String parseAxis(int axis)
    method parseAxisString (line 640) | public static int parseAxisString(String axis)
    method isVisible (line 654) | public boolean isVisible()
    method setVisible (line 659) | public void setVisible(boolean visible)

FILE: src/main/java/com/mrcrayfish/modelcreator/element/ElementCellEntry.java
  class ElementCellEntry (line 11) | public class ElementCellEntry
    method ElementCellEntry (line 18) | public ElementCellEntry(Element element)
    method createPanel (line 24) | private void createPanel()
    method getElement (line 37) | public Element getElement()
    method getPanel (line 42) | public JPanel getPanel()
    method getVisibility (line 47) | public JLabel getVisibility()
    method getName (line 52) | public JLabel getName()
    method toggleVisibility (line 57) | public void toggleVisibility()

FILE: src/main/java/com/mrcrayfish/modelcreator/element/ElementCellRenderer.java
  class ElementCellRenderer (line 9) | public class ElementCellRenderer extends DefaultListCellRenderer
    method getListCellRendererComponent (line 11) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/element/ElementManager.java
  type ElementManager (line 8) | public interface ElementManager
    method getSelectedElement (line 10) | Element getSelectedElement();
    method setSelectedElement (line 12) | void setSelectedElement(int pos);
    method getAllElements (line 14) | List<Element> getAllElements();
    method getElement (line 16) | Element getElement(int index);
    method getElementCount (line 18) | int getElementCount();
    method clearElements (line 20) | void clearElements();
    method updateName (line 22) | void updateName();
    method updateValues (line 24) | void updateValues();
    method getAmbientOcc (line 26) | boolean getAmbientOcc();
    method setAmbientOcc (line 28) | void setAmbientOcc(boolean occ);
    method addElement (line 30) | void addElement(Element e);
    method setParticle (line 32) | void setParticle(TextureEntry entry);
    method getParticle (line 34) | TextureEntry getParticle();
    method reset (line 36) | void reset();
    method createState (line 38) | default ElementManagerState createState()
    method restoreState (line 43) | void restoreState(ElementManagerState state);
    method setDisplayProperties (line 45) | void setDisplayProperties(DisplayProperties properties);
    method getDisplayProperties (line 47) | DisplayProperties getDisplayProperties();

FILE: src/main/java/com/mrcrayfish/modelcreator/element/ElementManagerState.java
  class ElementManagerState (line 11) | public class ElementManagerState
    method ElementManagerState (line 18) | public ElementManagerState(ElementManager manager)
    method getElements (line 26) | public List<Element> getElements()
    method getSelectedIndex (line 31) | public int getSelectedIndex()
    method isAmbientOcclusion (line 36) | public boolean isAmbientOcclusion()
    method getParticleTexture (line 41) | public TextureEntry getParticleTexture()

FILE: src/main/java/com/mrcrayfish/modelcreator/element/Face.java
  class Face (line 12) | public class Face
    method Face (line 45) | public Face(Element cuboid, int side)
    method Face (line 51) | public Face(Face face)
    method copyProperties (line 56) | public void copyProperties(Face face)
    method renderNorth (line 71) | public void renderNorth()
    method renderNorth (line 80) | private void renderNorth(int pass, int mode)
    method renderEast (line 120) | public void renderEast()
    method renderEast (line 129) | private void renderEast(int pass, int mode)
    method renderSouth (line 169) | public void renderSouth()
    method renderSouth (line 178) | private void renderSouth(int pass, int mode)
    method renderWest (line 218) | public void renderWest()
    method renderWest (line 227) | private void renderWest(int pass, int mode)
    method renderUp (line 267) | public void renderUp()
    method renderUp (line 276) | private void renderUp(int pass, int mode)
    method renderDown (line 315) | public void renderDown()
    method renderDown (line 324) | private void renderDown(int pass, int mode)
    method setTexCoord (line 364) | private void setTexCoord(int corner)
    method setTexCoord (line 369) | private void setTexCoord(int corner, boolean forceFit)
    method startRender (line 390) | private void startRender(int pass)
    method finishRender (line 401) | private void finishRender()
    method applyShade (line 406) | private void applyShade(float reduction)
    method setTexture (line 416) | public void setTexture(TextureEntry texture)
    method bindTexture (line 421) | public void bindTexture(int pass)
    method moveTextureU (line 451) | public void moveTextureU(double amt)
    method moveTextureV (line 457) | public void moveTextureV(double amt)
    method getMinX (line 463) | private double getMinX()
    method getMinY (line 472) | private double getMinY()
    method getMinZ (line 481) | private double getMinZ()
    method getMaxX (line 490) | private double getMaxX()
    method getMaxY (line 499) | private double getMaxY()
    method getMaxZ (line 508) | private double getMaxZ()
    method addTextureX (line 517) | public void addTextureX(double amt)
    method addTextureY (line 522) | public void addTextureY(double amt)
    method addTextureXEnd (line 527) | public void addTextureXEnd(double amt)
    method addTextureYEnd (line 532) | public void addTextureYEnd(double amt)
    method getStartU (line 537) | public double getStartU()
    method getStartV (line 542) | public double getStartV()
    method getEndU (line 547) | public double getEndU()
    method getEndV (line 552) | public double getEndV()
    method setStartU (line 557) | public void setStartU(double u)
    method setStartV (line 562) | public void setStartV(double v)
    method setEndU (line 567) | public void setEndU(double ue)
    method setEndV (line 572) | public void setEndV(double ve)
    method getTexture (line 577) | public TextureEntry getTexture()
    method fitTexture (line 582) | public void fitTexture(boolean fitTexture)
    method shouldFitTexture (line 587) | public boolean shouldFitTexture()
    method getSide (line 592) | public int getSide()
    method isCullfaced (line 597) | public boolean isCullfaced()
    method setCullface (line 602) | public void setCullface(boolean cullface)
    method isEnabled (line 607) | public boolean isEnabled()
    method setEnabled (line 612) | public void setEnabled(boolean enabled)
    method isAutoUVEnabled (line 617) | public boolean isAutoUVEnabled()
    method setAutoUVEnabled (line 622) | public void setAutoUVEnabled(boolean enabled)
    method isBinded (line 627) | public boolean isBinded()
    method updateStartUV (line 632) | public void updateStartUV()
    method updateEndUV (line 641) | public void updateEndUV()
    method getFaceName (line 650) | public static String getFaceName(int face)
    method getFaceSide (line 670) | public static int getFaceSide(String name)
    method getFaceColour (line 690) | public static int getFaceColour(int side)
    method getFaceColors (line 699) | public static int[] getFaceColors()
    method setFaceColors (line 704) | public static void setFaceColors(int[] colors)
    method setFaceColor (line 712) | public static void setFaceColor(int side, int color)
    method getRotation (line 717) | public int getRotation()
    method setRotation (line 722) | public void setRotation(int rotation)
    method setTintIndexEnabled (line 727) | public void setTintIndexEnabled(boolean enabled)
    method isTintIndexEnabled (line 732) | public boolean isTintIndexEnabled()
    method getTintIndex (line 737) | public int getTintIndex()
    method setTintIndex (line 742) | public void setTintIndex(int tintIndex)
    method isVisible (line 747) | public boolean isVisible(ElementManager manager)

FILE: src/main/java/com/mrcrayfish/modelcreator/object/FaceDimension.java
  class FaceDimension (line 3) | public class FaceDimension
    method FaceDimension (line 7) | public FaceDimension(double width, double height)
    method getWidth (line 13) | public double getWidth()
    method getHeight (line 18) | public double getHeight()

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/CuboidTabbedPane.java
  class CuboidTabbedPane (line 8) | public class CuboidTabbedPane extends JTabbedPane
    method CuboidTabbedPane (line 12) | public CuboidTabbedPane(ElementManager manager)
    method updateValues (line 17) | public void updateValues()

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/DisplayEntryPanel.java
  class DisplayEntryPanel (line 17) | public class DisplayEntryPanel extends JPanel
    method DisplayEntryPanel (line 31) | public DisplayEntryPanel(DisplayProperties.Entry entry)
    method getEntry (line 39) | public DisplayProperties.Entry getEntry()
    method initComponents (line 44) | private void initComponents()
    method updateValues (line 460) | public void updateValues(DisplayProperties.Entry entry)
    method createRotationSlider (line 475) | private JSlider createRotationSlider(String labelText, JComponent parent)

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/ElementExtraPanel.java
  class ElementExtraPanel (line 12) | public class ElementExtraPanel extends JPanel implements IElementUpdater
    method ElementExtraPanel (line 18) | public ElementExtraPanel(ElementManager manager)
    method initComponents (line 29) | private void initComponents()
    method addComponents (line 44) | private void addComponents()
    method updateValues (line 49) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/FaceExtrasPanel.java
  class FaceExtrasPanel (line 13) | public class FaceExtrasPanel extends JPanel implements IElementUpdater
    method FaceExtrasPanel (line 26) | public FaceExtrasPanel(ElementManager manager)
    method initComponents (line 37) | private void initComponents()
    method addComponents (line 127) | private void addComponents()
    method updateValues (line 132) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/GlobalPanel.java
  class GlobalPanel (line 15) | public class GlobalPanel extends JPanel implements IElementUpdater
    method GlobalPanel (line 22) | public GlobalPanel(ElementManager manager)
    method initComponents (line 33) | private void initComponents()
    method addComponents (line 58) | private void addComponents()
    method updateValues (line 64) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/IElementUpdater.java
  type IElementUpdater (line 5) | public interface IElementUpdater
    method updateValues (line 7) | void updateValues(Element cube);

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/OriginPanel.java
  class OriginPanel (line 12) | public class OriginPanel extends JPanel implements IElementUpdater
    method OriginPanel (line 26) | public OriginPanel(ElementManager manager)
    method initComponents (line 38) | private void initComponents()
    method initProperties (line 51) | private void initProperties()
    method addComponents (line 271) | private void addComponents()
    method updateValues (line 284) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/PositionPanel.java
  class PositionPanel (line 12) | public class PositionPanel extends JPanel implements IElementUpdater
    method PositionPanel (line 26) | public PositionPanel(ElementManager manager)
    method initComponents (line 39) | private void initComponents()
    method initProperties (line 52) | private void initProperties()
    method addComponents (line 283) | private void addComponents()
    method updateValues (line 296) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/SidebarPanel.java
  class SidebarPanel (line 24) | public class SidebarPanel extends JPanel implements ElementManager
    method SidebarPanel (line 45) | public SidebarPanel(ModelCreator creator)
    method initComponents (line 54) | private void initComponents()
    method initIncrementButton (line 172) | public static void initIncrementButton(JButton button, Font defaultFon...
    method initIncrementableField (line 179) | public static void initIncrementableField(JTextField field, Font defau...
    method setLayoutConstaints (line 186) | private void setLayoutConstaints()
    method getList (line 193) | public JList<ElementCellEntry> getList()
    method getSelectedElement (line 198) | @Override
    method getSelectedElementEntry (line 209) | public ElementCellEntry getSelectedElementEntry()
    method setSelectedElement (line 219) | @Override
    method getAllElements (line 236) | @Override
    method getElement (line 247) | @Override
    method getElementCount (line 254) | @Override
    method updateName (line 260) | @Override
    method updateValues (line 278) | @Override
    method getCreator (line 284) | public ModelCreator getCreator()
    method getAmbientOcc (line 289) | @Override
    method setAmbientOcc (line 295) | @Override
    method clearElements (line 301) | @Override
    method addElement (line 307) | @Override
    method setParticle (line 313) | @Override
    method getParticle (line 319) | @Override
    method reset (line 325) | @Override
    method restoreState (line 333) | @Override
    method setDisplayProperties (line 347) | @Override
    method getDisplayProperties (line 353) | @Override
    method newElement (line 359) | public void newElement()
    method deleteElement (line 366) | public void deleteElement()

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/SizePanel.java
  class SizePanel (line 12) | public class SizePanel extends JPanel implements IElementUpdater
    method SizePanel (line 26) | public SizePanel(ElementManager manager)
    method initComponents (line 38) | private void initComponents()
    method initProperties (line 51) | private void initProperties()
    method addComponents (line 286) | private void addComponents()
    method updateValues (line 299) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/TexturePanel.java
  class TexturePanel (line 17) | public class TexturePanel extends JPanel
    method TexturePanel (line 26) | public TexturePanel(ElementManager manager)
    method initComponents (line 37) | private void initComponents()
    method addComponents (line 115) | private void addComponents()

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/UVPanel.java
  class UVPanel (line 13) | public class UVPanel extends JPanel implements IElementUpdater
    method UVPanel (line 30) | public UVPanel(ElementManager manager)
    method initComponents (line 42) | private void initComponents()
    method initProperties (line 59) | private void initProperties()
    method addComponents (line 386) | private void addComponents()
    method updateValues (line 402) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/tabs/ElementPanel.java
  class ElementPanel (line 11) | public class ElementPanel extends JPanel implements IElementUpdater
    method ElementPanel (line 20) | public ElementPanel(ElementManager manager)
    method initComponents (line 29) | private void initComponents()
    method addComponents (line 37) | private void addComponents()
    method updateValues (line 50) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/tabs/FacePanel.java
  class FacePanel (line 19) | public class FacePanel extends JPanel implements IElementUpdater
    method FacePanel (line 37) | public FacePanel(ElementManager manager)
    method initMenu (line 47) | private void initMenu()
    method initComponents (line 58) | private void initComponents()
    method addComponents (line 119) | private void addComponents()
    method updateValues (line 129) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/panels/tabs/RotationPanel.java
  class RotationPanel (line 18) | public class RotationPanel extends JPanel implements IElementUpdater
    method RotationPanel (line 36) | public RotationPanel(ElementManager manager)
    method initMenu (line 46) | private void initMenu()
    method initComponents (line 54) | private void initComponents()
    method addComponents (line 138) | private void addComponents()
    method updateValues (line 147) | @Override

FILE: src/main/java/com/mrcrayfish/modelcreator/screenshot/PendingScreenshot.java
  class PendingScreenshot (line 5) | public class PendingScreenshot
    method PendingScreenshot (line 10) | public PendingScreenshot(File file, ScreenshotCallback callback)
    method getFile (line 16) | public File getFile()
    method getCallback (line 21) | public ScreenshotCallback getCallback()

FILE: src/main/java/com/mrcrayfish/modelcreator/screenshot/Screenshot.java
  class Screenshot (line 15) | public class Screenshot
    method getScreenshot (line 17) | public static void getScreenshot(int width, int height, ScreenshotCall...
    method getScreenshot (line 29) | public static void getScreenshot(int width, int height, ScreenshotCall...
    method isUrl (line 65) | private static boolean isUrl(String url)
    method shareToFacebook (line 80) | public static void shareToFacebook(String link)
    method shareToTwitter (line 97) | public static void shareToTwitter(String link)
    method shareToReddit (line 115) | public static void shareToReddit(String link)

FILE: src/main/java/com/mrcrayfish/modelcreator/screenshot/ScreenshotCallback.java
  type ScreenshotCallback (line 5) | public interface ScreenshotCallback
    method callback (line 7) | void callback(File file);

FILE: src/main/java/com/mrcrayfish/modelcreator/screenshot/Uploader.java
  class Uploader (line 12) | public class Uploader
    method upload (line 17) | public static String upload(File file)
    method upload (line 46) | private static void upload(InputStream input, OutputStream output) thr...
    method getImageLink (line 57) | private static String getImageLink(InputStream input) throws IOException

FILE: src/main/java/com/mrcrayfish/modelcreator/sidebar/Sidebar.java
  class Sidebar (line 8) | public abstract class Sidebar
    method Sidebar (line 12) | public Sidebar(String title)
    method draw (line 17) | public void draw(int sidebarWidth, int canvasWidth, int canvasHeight, ...
    method drawTitle (line 32) | private void drawTitle()
    method handleMouseInput (line 40) | public abstract void handleMouseInput(int button, int mouseX, int mous...
    method handleInput (line 42) | public abstract void handleInput(int canvasHeight);

FILE: src/main/java/com/mrcrayfish/modelcreator/sidebar/UVSidebar.java
  class UVSidebar (line 14) | public class UVSidebar extends Sidebar
    method UVSidebar (line 30) | public UVSidebar(String title, ElementManager manager)
    method draw (line 36) | @Override
    method handleMouseInput (line 184) | @Override
    method handleInput (line 210) | @Override
    method getFace (line 279) | private int getFace(int canvasHeight, int mouseX, int mouseY)
    method getHoveredFace (line 294) | public int getHoveredFace()
    method isGrabbing (line 299) | public boolean isGrabbing()

FILE: src/main/java/com/mrcrayfish/modelcreator/texture/Clipboard.java
  class Clipboard (line 5) | public class Clipboard
    method copyTexture (line 10) | public static void copyTexture(Face face)
    method getTexture (line 15) | public static TextureEntry getTexture()

FILE: src/main/java/com/mrcrayfish/modelcreator/texture/TextureAnimation.java
  class TextureAnimation (line 17) | public class TextureAnimation
    method setSize (line 26) | public void setSize(int width, int height)
    method getWidth (line 32) | public int getWidth()
    method getHeight (line 37) | public int getHeight()
    method setFrameTime (line 42) | public void setFrameTime(int frametime)
    method setFrames (line 47) | public void setFrames(List<Integer> frameList)
    method setCustomTimes (line 53) | public void setCustomTimes(Map<Integer, Integer> times)
    method setInterpolate (line 59) | public void setInterpolate(boolean interpolate)
    method isInterpolated (line 64) | public boolean isInterpolated()
    method getFrameCount (line 69) | public int getFrameCount()
    method getMaxTime (line 74) | public long getMaxTime()
    method getCurrentAnimationFrame (line 84) | public int getCurrentAnimationFrame()
    method getNextAnimationFrame (line 102) | public int getNextAnimationFrame()
    method getFrameTime (line 136) | public long getFrameTime(int frame)
    method getFrameInterpolation (line 145) | public double getFrameInterpolation()
    method getPasses (line 167) | public int getPasses()
    method getAnimationForTexture (line 176) | public static TextureAnimation getAnimationForTexture(File file, int w...

FILE: src/main/java/com/mrcrayfish/modelcreator/texture/TextureAtlas.java
  class TextureAtlas (line 17) | public class TextureAtlas
    method load (line 32) | public static void load()
    method bind (line 49) | public static void bind()
    class Entry (line 54) | public static class Entry
      method Entry (line 59) | private Entry(int u, int v, int width, int height)
      method getU (line 67) | public double getU()
      method getV (line 72) | public double getV()
      method getWidth (line 77) | public double getWidth()
      method getHeight (line 82) | public double getHeight()
    method loadTexture (line 88) | private static int loadTexture(BufferedImage image)

FILE: src/main/java/com/mrcrayfish/modelcreator/texture/TextureEntry.java
  class TextureEntry (line 21) | public class TextureEntry
    method TextureEntry (line 37) | public TextureEntry(File texture) throws IOException
    method TextureEntry (line 52) | public TextureEntry(String key, File texture) throws IOException
    method TextureEntry (line 67) | public TextureEntry(String key, TexturePath path, File texture) throws...
    method setTexturePath (line 82) | public void setTexturePath(TexturePath path)
    method getTexturePath (line 87) | public TexturePath getTexturePath()
    method getKey (line 92) | public String getKey()
    method setKey (line 97) | public void setKey(String key)
    method getModId (line 102) | public String getModId()
    method getDirectory (line 107) | public String getDirectory()
    method getName (line 112) | public String getName()
    method bindTexture (line 117) | public void bindTexture()
    method bindNextTexture (line 125) | public void bindNextTexture()
    method getSource (line 133) | public BufferedImage getSource()
    method getIcon (line 138) | public ImageIcon getIcon()
    method getAnimation (line 143) | public TextureAnimation getAnimation()
    method isAnimated (line 148) | public boolean isAnimated()
    method getPasses (line 153) | public int getPasses()
    method getTextureFile (line 162) | public File getTextureFile()
    method setTextureFile (line 167) | public void setTextureFile(File texture)
    method deleteTexture (line 183) | public void deleteTexture()
    method createIcon (line 195) | private static ImageIcon createIcon(BufferedImage source)
    method loadTexture (line 202) | public void loadTexture()
    method loadTexture (line 232) | private int loadTexture(BufferedImage image)

FILE: src/main/java/com/mrcrayfish/modelcreator/util/AssetsUtil.java
  class AssetsUtil (line 8) | public class AssetsUtil
    method getTextureDirectory (line 10) | public static String getTextureDirectory(File file)
    method getModId (line 26) | public static String getModId(File file)

FILE: src/main/java/com/mrcrayfish/modelcreator/util/AtlasRenderUtil.java
  class AtlasRenderUtil (line 10) | public class AtlasRenderUtil
    method bindTexture (line 14) | public static void bindTexture(TextureAtlas.Entry entry)
    method drawQuad (line 19) | public static void drawQuad(int startX, int startY, int endX, int endY)

FILE: src/main/java/com/mrcrayfish/modelcreator/util/ComponentUtil.java
  class ComponentUtil (line 11) | public class ComponentUtil
    method createRadioButton (line 13) | public static JRadioButton createRadioButton(String label, String tool...
    method createCheckBox (line 24) | public static JCheckBox createCheckBox(String text, String tooltip, bo...
    method createDirectorySelector (line 36) | public static JPanel createDirectorySelector(String label, Component p...
    method createFileSelector (line 93) | public static JPanel createFileSelector(String label, Component parent...
    method expandRectangle (line 154) | public static Rectangle expandRectangle(Rectangle r, int amount)

FILE: src/main/java/com/mrcrayfish/modelcreator/util/FontManager.java
  type FontManager (line 10) | public enum FontManager
    method FontManager (line 17) | FontManager(String name, float size)
    method loadFont (line 22) | private void loadFont(String name, float size)
    method drawString (line 37) | public void drawString(int x, int y, String text, Color color)
    method getWidth (line 42) | public int getWidth(String s)
    method getHeight (line 47) | public int getHeight()

FILE: src/main/java/com/mrcrayfish/modelcreator/util/KeyboardUtil.java
  class KeyboardUtil (line 11) | public class KeyboardUtil
    method convertKeyStokeToString (line 13) | public static String convertKeyStokeToString(KeyStroke keyStroke)
    method isCtrlKeyDown (line 26) | public static boolean isCtrlKeyDown()
    method isShiftKeyDown (line 38) | public static boolean isShiftKeyDown()
    method isAltKeyDown (line 43) | public static boolean isAltKeyDown()

FILE: src/main/java/com/mrcrayfish/modelcreator/util/OperatingSystem.java
  type OperatingSystem (line 6) | public enum OperatingSystem
    method get (line 49) | public static OperatingSystem get()

FILE: src/main/java/com/mrcrayfish/modelcreator/util/Parser.java
  class Parser (line 7) | public class Parser
    method parseDouble (line 9) | public static double parseDouble(String text, double def)
    method parseInt (line 22) | public static int parseInt(String text, int def)

FILE: src/main/java/com/mrcrayfish/modelcreator/util/SharedLibraryLoader.java
  class SharedLibraryLoader (line 33) | public class SharedLibraryLoader
    method load (line 70) | public static synchronized void load(boolean disableOpenAL)
    method SharedLibraryLoader (line 125) | private SharedLibraryLoader()
    method crc (line 130) | private String crc(InputStream input)
    method readFile (line 161) | private InputStream readFile(String path)
    method extractFile (line 210) | private File extractFile(String sourcePath, String dirName) throws IOE...
    method getExtractedFile (line 235) | private File getExtractedFile(String dirName, String fileName)
    method canWrite (line 276) | private boolean canWrite(File file)
    method canExecute (line 311) | private boolean canExecute(File file)
    method extractFile (line 328) | private File extractFile(String sourcePath, String sourceCrc, File ext...

FILE: src/main/java/com/mrcrayfish/modelcreator/util/StreamUtils.java
  class StreamUtils (line 5) | public class StreamUtils
    method convertToString (line 7) | public static String convertToString(InputStream inputStream) throws I...

FILE: src/main/java/com/mrcrayfish/modelcreator/util/Util.java
  class Util (line 28) | public class Util
    method getMinecraftVersions (line 65) | public static List<String> getMinecraftVersions()
    method getImageDimension (line 70) | public static Dimension getImageDimension(File image) throws IOException
    method openUrl (line 103) | public static void openUrl(String url)
    method loadModelFromJar (line 119) | public static void loadModelFromJar(ElementManager manager, Class<?> c...
    method extractMinecraftAssets (line 148) | public static void extractMinecraftAssets(String version, Window window)
    method extractZipFiles (line 167) | private static void extractZipFiles(File zipFile, Predicate<ZipEntry> ...
    method getMinecraftDirectory (line 289) | public static File getMinecraftDirectory()
    method getSubFolders (line 310) | private static File[] getSubFolders(File parent)
    method hasFile (line 315) | private static boolean hasFile(File parent, String targetName)
    method getFile (line 321) | private static File getFile(File parent, String targetName)
    method hasFolder (line 331) | public static boolean hasFolder(File parent, String targetName)
    method isLegacyAssets (line 337) | private static boolean isLegacyAssets(File file)
    class VersionProperties (line 353) | private static class VersionProperties
    class AssetIndex (line 358) | private static class AssetIndex
Condensed preview — 86 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (508K chars).
[
  {
    "path": ".gitignore",
    "chars": 749,
    "preview": "# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$"
  },
  {
    "path": "LICENSE",
    "chars": 554,
    "preview": "Copyright © 2016 MrCrayfish\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file "
  },
  {
    "path": "build.gradle",
    "chars": 626,
    "preview": "apply plugin: \"java\"\napply plugin: \"application\"\n\nversion = \"0.7.0\"\nmainClassName = \"com.mrcrayfish.modelcreator.Start\"\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 200,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradlew",
    "chars": 5296,
    "preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up"
  },
  {
    "path": "gradlew.bat",
    "chars": 2176,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Actions.java",
    "chars": 4247,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcre"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Animation.java",
    "chars": 523,
    "preview": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic class Animation\n{\n    private static int coun"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Camera.java",
    "chars": 2409,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport static org.lwjgl.opengl.GL11.*;\nimport static org.lwjgl.util.glu.GLU.gluPer"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Constants.java",
    "chars": 530,
    "preview": "package com.mrcrayfish.modelcreator;\n\npublic class Constants\n{\n    public static final String NAME = \"MrCrayfish's Model"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Exporter.java",
    "chars": 1776,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport java.io.Buffere"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ExporterJavaCode.java",
    "chars": 18158,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.Element;\n\nimport javax.swing.*;\nimport "
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ExporterModel.java",
    "chars": 11148,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfis"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Icons.java",
    "chars": 5085,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport javax.swing.*;\n\npublic class Icons\n{\n    public static Icon bin;\n    public"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Importer.java",
    "chars": 16354,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.g"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ModelCreator.java",
    "chars": 28868,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Processor.java",
    "chars": 278,
    "preview": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic interface Processor<T>\n{\n    /**\n     * Proce"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ProjectManager.java",
    "chars": 7453,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/PropertyIdentifiers.java",
    "chars": 631,
    "preview": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic class PropertyIdentifiers\n{\n    public static"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Settings.java",
    "chars": 5216,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport java.util.prefs.Preferences;\n\npublic class Settings\n{\n    private static fi"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Start.java",
    "chars": 1589,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.jtattoo.plaf.fast.FastLookAndFeel;\nimport com.mrcrayfish.modelcreator.u"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/StateManager.java",
    "chars": 3310,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.m"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/TexturePath.java",
    "chars": 1490,
    "preview": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.util.AssetsUtil;\n\nimport java.io.File;\nimport j"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/DisplayPropertiesDialog.java",
    "chars": 6012,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.m"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/JElementList.java",
    "chars": 1917,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.element.ElementCellEntry;\nimport com."
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/Menu.java",
    "chars": 57497,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/MenuAdapter.java",
    "chars": 427,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport javax.swing.event.MenuEvent;\nimport javax.swing.event.MenuListene"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/TextureEntryEditor.java",
    "chars": 11398,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcre"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/TextureManager.java",
    "chars": 14598,
    "preview": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcre"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/dialog/WelcomeDialog.java",
    "chars": 4520,
    "preview": "package com.mrcrayfish.modelcreator.dialog;\n\nimport com.mrcrayfish.modelcreator.Constants;\nimport com.mrcrayfish.modelcr"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/CanvasRenderer.java",
    "chars": 671,
    "preview": "package com.mrcrayfish.modelcreator.display;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcrea"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/DisplayProperties.java",
    "chars": 8358,
    "preview": "package com.mrcrayfish.modelcreator.display;\n\nimport com.mrcrayfish.modelcreator.display.render.*;\n\nimport java.util.Has"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/DisplayPropertyRenderer.java",
    "chars": 697,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcr"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/FirstPersonPropertyRenderer.java",
    "chars": 4146,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/FixedPropertyRenderer.java",
    "chars": 3342,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/GroundPropertyRenderer.java",
    "chars": 2761,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Animation;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/GuiPropertyRenderer.java",
    "chars": 3413,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/HeadPropertyRenderer.java",
    "chars": 4156,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/StandardRenderer.java",
    "chars": 5994,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/ThirdPersonPropertyRenderer.java",
    "chars": 4990,
    "preview": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.mo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/Element.java",
    "chars": 16572,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.mod"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementCellEntry.java",
    "chars": 1230,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.Icons;\n\nimport javax.swing.*;\nimport ja"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementCellRenderer.java",
    "chars": 625,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * Author: MrCrayfish\n */\npu"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementManager.java",
    "chars": 938,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.m"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementManagerState.java",
    "chars": 1110,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport java.util"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/Face.java",
    "chars": 20012,
    "preview": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcr"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/object/FaceDimension.java",
    "chars": 365,
    "preview": "package com.mrcrayfish.modelcreator.object;\n\npublic class FaceDimension\n{\n    private double width, height;\n\n    public "
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/CuboidTabbedPane.java",
    "chars": 800,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport javax.sw"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/DisplayEntryPanel.java",
    "chars": 22166,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Exporter;\nimport com.mrcrayfish.modelcre"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/ElementExtraPanel.java",
    "chars": 1897,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.mode"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/FaceExtrasPanel.java",
    "chars": 6936,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.mode"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/GlobalPanel.java",
    "chars": 2534,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreato"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/IElementUpdater.java",
    "chars": 172,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.element.Element;\n\npublic interface IElem"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/OriginPanel.java",
    "chars": 11497,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.el"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/PositionPanel.java",
    "chars": 12023,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.el"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/SidebarPanel.java",
    "chars": 11318,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreato"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/SizePanel.java",
    "chars": 11900,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.el"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/TexturePanel.java",
    "chars": 4352,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreato"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/UVPanel.java",
    "chars": 16006,
    "preview": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.el"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/ElementPanel.java",
    "chars": 1717,
    "preview": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/FacePanel.java",
    "chars": 5470,
    "preview": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/RotationPanel.java",
    "chars": 6281,
    "preview": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/PendingScreenshot.java",
    "chars": 461,
    "preview": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport java.io.File;\n\npublic class PendingScreenshot\n{\n    private File"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/Screenshot.java",
    "chars": 3938,
    "preview": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport com.mrcrayfish.modelcreator.util.Util;\nimport org.lwjgl.BufferUt"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/ScreenshotCallback.java",
    "chars": 140,
    "preview": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport java.io.File;\n\npublic interface ScreenshotCallback\n{\n    void ca"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/Uploader.java",
    "chars": 2121,
    "preview": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/sidebar/Sidebar.java",
    "chars": 1104,
    "preview": "package com.mrcrayfish.modelcreator.sidebar;\n\nimport com.mrcrayfish.modelcreator.util.FontManager;\nimport org.newdawn.sl"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/sidebar/UVSidebar.java",
    "chars": 10767,
    "preview": "package com.mrcrayfish.modelcreator.sidebar;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.mod"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/Clipboard.java",
    "chars": 446,
    "preview": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.mrcrayfish.modelcreator.element.Face;\n\npublic class Clipboard\n{"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureAnimation.java",
    "chars": 8091,
    "preview": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimpo"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureAtlas.java",
    "chars": 3411,
    "preview": "package com.mrcrayfish.modelcreator.texture;\n\nimport org.lwjgl.BufferUtils;\nimport org.lwjgl.opengl.GL11;\nimport org.lwj"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureEntry.java",
    "chars": 7383,
    "preview": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.mrcrayfish.modelcreator.TexturePath;\nimport com.mrcrayfish.mode"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/AssetsUtil.java",
    "chars": 1069,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport java.io.File;\n\n/**\n * Author: MrCrayfish\n */\npublic class AssetsUtil\n{"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/AtlasRenderUtil.java",
    "chars": 1238,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.texture.TextureAtlas;\n\nimport static org.l"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/ComponentUtil.java",
    "chars": 6722,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator."
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/FontManager.java",
    "chars": 1120,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport org.newdawn.slick.Col"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/KeyboardUtil.java",
    "chars": 1282,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport org.lwjgl.input.Keyboard;\n\nimport javax.swing.*;\nimport java.awt.event"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/OperatingSystem.java",
    "chars": 1044,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\n/**\n * Author: MrCrayfish\n */\npublic enum OperatingSystem\n{\n    WINDOWS,\n    "
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/Parser.java",
    "chars": 697,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.Exporter;\n\nimport java.text.ParseException"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/SharedLibraryLoader.java",
    "chars": 9138,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\n/****************************************************************************"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/StreamUtils.java",
    "chars": 821,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport java.io.*;\n\npublic class StreamUtils\n{\n    public static String conver"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/Util.java",
    "chars": 11926,
    "preview": "package com.mrcrayfish.modelcreator.util;\n\nimport com.google.gson.Gson;\nimport com.mrcrayfish.modelcreator.ProjectManage"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the MrCrayfish/ModelCreator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 86 files (471.1 KB), approximately 105.6k tokens, and a symbol index with 724 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!