Browse Source

line texture and material

Marcel Zickler 3 years ago
parent
commit
0f4b6ff12d
100 changed files with 5836 additions and 3 deletions
  1. 1 1
      Assembly-CSharp-Editor.csproj
  2. 1 1
      Assembly-CSharp.csproj
  3. 100 0
      Assets/Materials/Route_Line.mat
  4. 8 0
      Assets/Materials/Route_Line.mat.meta
  5. 274 0
      Assets/Models/SM_Bld_OfficeOld_Large_Base_Stairs_01.fbx
  6. 97 0
      Assets/Models/SM_Bld_OfficeOld_Large_Base_Stairs_01.fbx.meta
  7. 92 0
      Assets/Plotting/.idea/workspace.xml
  8. 1 1
      Assets/Plotting/plots.meta
  9. 8 0
      Assets/Plotting/venv.meta
  10. 8 0
      Assets/Plotting/venv/bin.meta
  11. 76 0
      Assets/Plotting/venv/bin/activate
  12. 37 0
      Assets/Plotting/venv/bin/activate.csh
  13. 7 0
      Assets/Plotting/venv/bin/activate.csh.meta
  14. 75 0
      Assets/Plotting/venv/bin/activate.fish
  15. 7 0
      Assets/Plotting/venv/bin/activate.fish.meta
  16. 7 0
      Assets/Plotting/venv/bin/activate.meta
  17. 8 0
      Assets/Plotting/venv/bin/easy_install
  18. 8 0
      Assets/Plotting/venv/bin/easy_install-3.7
  19. 7 0
      Assets/Plotting/venv/bin/easy_install-3.7.meta
  20. 7 0
      Assets/Plotting/venv/bin/easy_install.meta
  21. 8 0
      Assets/Plotting/venv/bin/f2py
  22. 7 0
      Assets/Plotting/venv/bin/f2py.meta
  23. 8 0
      Assets/Plotting/venv/bin/f2py3
  24. 8 0
      Assets/Plotting/venv/bin/f2py3.7
  25. 7 0
      Assets/Plotting/venv/bin/f2py3.7.meta
  26. 7 0
      Assets/Plotting/venv/bin/f2py3.meta
  27. 8 0
      Assets/Plotting/venv/bin/pip
  28. 7 0
      Assets/Plotting/venv/bin/pip.meta
  29. 8 0
      Assets/Plotting/venv/bin/pip3
  30. 8 0
      Assets/Plotting/venv/bin/pip3.7
  31. 7 0
      Assets/Plotting/venv/bin/pip3.7.meta
  32. 7 0
      Assets/Plotting/venv/bin/pip3.meta
  33. 1 0
      Assets/Plotting/venv/bin/python
  34. 7 0
      Assets/Plotting/venv/bin/python.meta
  35. 1 0
      Assets/Plotting/venv/bin/python3
  36. 7 0
      Assets/Plotting/venv/bin/python3.meta
  37. 8 0
      Assets/Plotting/venv/include.meta
  38. 8 0
      Assets/Plotting/venv/lib.meta
  39. 8 0
      Assets/Plotting/venv/lib/python3.7.meta
  40. 8 0
      Assets/Plotting/venv/lib/python3.7/site-packages.meta
  41. 8 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL.meta
  42. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libXau.6.dylib
  43. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libfreetype.6.dylib
  44. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libjpeg.9.dylib
  45. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/liblcms2.2.dylib
  46. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/liblzma.5.dylib
  47. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libopenjp2.2.3.1.dylib
  48. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libpng16.16.dylib
  49. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libtiff.5.dylib
  50. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebp.7.dylib
  51. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebpdemux.2.dylib
  52. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebpmux.3.dylib
  53. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libxcb.1.1.0.dylib
  54. BIN
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libz.1.2.11.dylib
  55. 110 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BdfFontFile.py
  56. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BdfFontFile.py.meta
  57. 422 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BlpImagePlugin.py
  58. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BlpImagePlugin.py.meta
  59. 381 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BmpImagePlugin.py
  60. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BmpImagePlugin.py.meta
  61. 73 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BufrStubImagePlugin.py
  62. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BufrStubImagePlugin.py.meta
  63. 120 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ContainerIO.py
  64. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ContainerIO.py.meta
  65. 76 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/CurImagePlugin.py
  66. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/CurImagePlugin.py.meta
  67. 89 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DcxImagePlugin.py
  68. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DcxImagePlugin.py.meta
  69. 178 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DdsImagePlugin.py
  70. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DdsImagePlugin.py.meta
  71. 419 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/EpsImagePlugin.py
  72. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/EpsImagePlugin.py.meta
  73. 318 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ExifTags.py
  74. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ExifTags.py.meta
  75. 76 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FitsStubImagePlugin.py
  76. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FitsStubImagePlugin.py.meta
  77. 172 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FliImagePlugin.py
  78. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FliImagePlugin.py.meta
  79. 111 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FontFile.py
  80. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FontFile.py.meta
  81. 243 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FpxImagePlugin.py
  82. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FpxImagePlugin.py.meta
  83. 106 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FtexImagePlugin.py
  84. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FtexImagePlugin.py.meta
  85. 100 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GbrImagePlugin.py
  86. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GbrImagePlugin.py.meta
  87. 91 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GdImageFile.py
  88. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GdImageFile.py.meta
  89. 888 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GifImagePlugin.py
  90. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GifImagePlugin.py.meta
  91. 140 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpGradientFile.py
  92. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpGradientFile.py.meta
  93. 56 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpPaletteFile.py
  94. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpPaletteFile.py.meta
  95. 74 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GribStubImagePlugin.py
  96. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GribStubImagePlugin.py.meta
  97. 73 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/Hdf5StubImagePlugin.py
  98. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/Hdf5StubImagePlugin.py.meta
  99. 384 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/IcnsImagePlugin.py
  100. 7 0
      Assets/Plotting/venv/lib/python3.7/site-packages/PIL/IcnsImagePlugin.py.meta

File diff suppressed because it is too large
+ 1 - 1
Assembly-CSharp-Editor.csproj


File diff suppressed because it is too large
+ 1 - 1
Assembly-CSharp.csproj


+ 100 - 0
Assets/Materials/Route_Line.mat

@@ -0,0 +1,100 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: Route_Line
+  m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: 3050
+  stringTagMap:
+    RenderType: Transparent
+  disabledShaderPasses:
+  - SHADOWCASTER
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BaseMap:
+        m_Texture: {fileID: 2800000, guid: 148b1aa8d3b42469d8af5ceb308c1f65, type: 3}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 2800000, guid: 148b1aa8d3b42469d8af5ceb308c1f65, type: 3}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _SpecGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Floats:
+    - _AlphaClip: 0
+    - _Blend: 0
+    - _BumpScale: 1
+    - _ColorMask: 15
+    - _Cull: 2
+    - _Cutoff: 0.5
+    - _DstBlend: 10
+    - _EnvironmentReflections: 1
+    - _GlossMapScale: 0
+    - _Glossiness: 0
+    - _GlossyReflections: 0
+    - _Metallic: 0
+    - _OcclusionStrength: 1
+    - _QueueOffset: 0
+    - _ReceiveShadows: 1
+    - _SampleGI: 0
+    - _Smoothness: 0.5
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 5
+    - _Stencil: 0
+    - _StencilComp: 8
+    - _StencilOp: 0
+    - _StencilReadMask: 255
+    - _StencilWriteMask: 255
+    - _Surface: 1
+    - _UseUIAlphaClip: 0
+    - _WorkflowMode: 1
+    - _ZWrite: 0
+    m_Colors:
+    - _BaseColor: {r: 1, g: 1, b: 1, a: 1}
+    - _Color: {r: 1, g: 1, b: 1, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+    - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
+    - _Specular: {r: 0, g: 0, b: 0, a: 0}
+--- !u!114 &2049640972875789400
+MonoBehaviour:
+  m_ObjectHideFlags: 11
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 0}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  version: 1

+ 8 - 0
Assets/Materials/Route_Line.mat.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 86feb68e3c44c4b0f8f98f18fa25dcc1
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

File diff suppressed because it is too large
+ 274 - 0
Assets/Models/SM_Bld_OfficeOld_Large_Base_Stairs_01.fbx


+ 97 - 0
Assets/Models/SM_Bld_OfficeOld_Large_Base_Stairs_01.fbx.meta

@@ -0,0 +1,97 @@
+fileFormatVersion: 2
+guid: 9acfd8da2c53549eba1de798fa31196d
+ModelImporter:
+  serializedVersion: 19301
+  internalIDToNameTable: []
+  externalObjects: {}
+  materials:
+    materialImportMode: 1
+    materialName: 0
+    materialSearch: 1
+    materialLocation: 1
+  animations:
+    legacyGenerateAnimations: 4
+    bakeSimulation: 0
+    resampleCurves: 1
+    optimizeGameObjects: 0
+    motionNodeName: 
+    rigImportErrors: 
+    rigImportWarnings: 
+    animationImportErrors: 
+    animationImportWarnings: 
+    animationRetargetingWarnings: 
+    animationDoRetargetingWarnings: 0
+    importAnimatedCustomProperties: 0
+    importConstraints: 0
+    animationCompression: 1
+    animationRotationError: 0.5
+    animationPositionError: 0.5
+    animationScaleError: 0.5
+    animationWrapMode: 0
+    extraExposedTransformPaths: []
+    extraUserProperties: []
+    clipAnimations: []
+    isReadable: 0
+  meshes:
+    lODScreenPercentages: []
+    globalScale: 1
+    meshCompression: 0
+    addColliders: 0
+    useSRGBMaterialColor: 1
+    sortHierarchyByName: 1
+    importVisibility: 1
+    importBlendShapes: 1
+    importCameras: 1
+    importLights: 1
+    fileIdsGeneration: 2
+    swapUVChannels: 0
+    generateSecondaryUV: 0
+    useFileUnits: 1
+    keepQuads: 0
+    weldVertices: 1
+    preserveHierarchy: 0
+    skinWeightsMode: 0
+    maxBonesPerVertex: 4
+    minBoneWeight: 0.001
+    meshOptimizationFlags: -1
+    indexFormat: 0
+    secondaryUVAngleDistortion: 8
+    secondaryUVAreaDistortion: 15.000001
+    secondaryUVHardAngle: 88
+    secondaryUVPackMargin: 4
+    useFileScale: 1
+  tangentSpace:
+    normalSmoothAngle: 60
+    normalImportMode: 0
+    tangentImportMode: 3
+    normalCalculationMode: 4
+    legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
+    blendShapeNormalImportMode: 1
+    normalSmoothingSource: 0
+  referencedClips: []
+  importAnimation: 1
+  humanDescription:
+    serializedVersion: 3
+    human: []
+    skeleton: []
+    armTwist: 0.5
+    foreArmTwist: 0.5
+    upperLegTwist: 0.5
+    legTwist: 0.5
+    armStretch: 0.05
+    legStretch: 0.05
+    feetSpacing: 0
+    globalScale: 1
+    rootMotionBoneName: 
+    hasTranslationDoF: 0
+    hasExtraRoot: 0
+    skeletonHasParents: 1
+  lastHumanDescriptionAvatarSource: {instanceID: 0}
+  autoGenerateAvatarMappingIfUnspecified: 1
+  animationType: 2
+  humanoidOversampling: 1
+  avatarSetup: 0
+  additionalBone: 0
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 92 - 0
Assets/Plotting/.idea/workspace.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="4b5a9a03-46af-4440-8c54-acff031ba513" name="Default Changelist" comment="">
+      <change beforePath="$PROJECT_DIR$/../../Assembly-CSharp-Editor.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../Assembly-CSharp-Editor.csproj" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../Assembly-CSharp.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../Assembly-CSharp.csproj" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.idea/Plotting.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/Plotting.iml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/plot.py" beforeDir="false" afterPath="$PROJECT_DIR$/plot.py" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/plots.meta" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/../PolygonCity/Textures/PolygonCity_Texture_01_A.tx" beforeDir="false" afterPath="$PROJECT_DIR$/../PolygonCity/Textures/PolygonCity_Texture_01_A.tx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../PolygonCity/Textures/PolygonCity_Texture_Normal.tx" beforeDir="false" afterPath="$PROJECT_DIR$/../PolygonCity/Textures/PolygonCity_Texture_Normal.tx" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../ProjectSettings/FbxExportSettings.asset" beforeDir="false" afterPath="$PROJECT_DIR$/../../ProjectSettings/FbxExportSettings.asset" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../ProjectSettings/ProjectVersion.txt" beforeDir="false" afterPath="$PROJECT_DIR$/../../ProjectSettings/ProjectVersion.txt" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../SteamVR.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../SteamVR.csproj" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../SteamVR_Editor.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../SteamVR_Editor.csproj" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../SteamVR_Input_Editor.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../SteamVR_Input_Editor.csproj" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/../../SteamVR_Windows_EditorHelper.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/../../SteamVR_Windows_EditorHelper.csproj" afterDir="false" />
+    </list>
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../.." />
+  </component>
+  <component name="ProjectId" id="1m0kQeGokybjWhLLZ0nKmCjyf4d" />
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
+  </component>
+  <component name="PropertiesComponent">
+    <property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
+    <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+    <property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
+  </component>
+  <component name="RecentsManager">
+    <key name="MoveFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/plots" />
+    </key>
+  </component>
+  <component name="RunManager">
+    <configuration name="plot" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
+      <module name="Plotting" />
+      <option name="INTERPRETER_OPTIONS" value="" />
+      <option name="PARENT_ENVS" value="true" />
+      <envs>
+        <env name="PYTHONUNBUFFERED" value="1" />
+      </envs>
+      <option name="SDK_HOME" value="" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="IS_MODULE_SDK" value="true" />
+      <option name="ADD_CONTENT_ROOTS" value="true" />
+      <option name="ADD_SOURCE_ROOTS" value="true" />
+      <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
+      <option name="SCRIPT_NAME" value="$PROJECT_DIR$/plot.py" />
+      <option name="PARAMETERS" value="" />
+      <option name="SHOW_COMMAND_LINE" value="false" />
+      <option name="EMULATE_TERMINAL" value="false" />
+      <option name="MODULE_MODE" value="false" />
+      <option name="REDIRECT_INPUT" value="false" />
+      <option name="INPUT_FILE" value="" />
+      <method v="2" />
+    </configuration>
+    <recent_temporary>
+      <list>
+        <item itemvalue="Python.plot" />
+      </list>
+    </recent_temporary>
+  </component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="4b5a9a03-46af-4440-8c54-acff031ba513" name="Default Changelist" comment="" />
+      <created>1608637880885</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1608637880885</updated>
+      <workItem from="1608637882355" duration="1665000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="3" />
+  </component>
+  <component name="com.intellij.coverage.CoverageDataManagerImpl">
+    <SUITE FILE_PATH="coverage/Plotting$plot.coverage" NAME="plot Coverage Results" MODIFIED="1608638556987" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
+  </component>
+</project>

+ 1 - 1
Assets/Logs.meta → Assets/Plotting/plots.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 416fa2706e19dda41815b9fd27b8fd23
+guid: 3ae3e6584e8a5416da6df33d6446528f
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 8 - 0
Assets/Plotting/venv.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 48328747ec0bd44bab2913d9818ce8a5
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 20e28e4957eff40fe9c04a5a612f79bd
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Assets/Plotting/venv/bin/activate

@@ -0,0 +1,76 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+    # reset old environment variables
+    if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
+        PATH="${_OLD_VIRTUAL_PATH:-}"
+        export PATH
+        unset _OLD_VIRTUAL_PATH
+    fi
+    if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
+        PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
+        export PYTHONHOME
+        unset _OLD_VIRTUAL_PYTHONHOME
+    fi
+
+    # This should detect bash and zsh, which have a hash command that must
+    # be called to get it to forget past commands.  Without forgetting
+    # past commands the $PATH changes we made may not be respected
+    if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+        hash -r
+    fi
+
+    if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
+        PS1="${_OLD_VIRTUAL_PS1:-}"
+        export PS1
+        unset _OLD_VIRTUAL_PS1
+    fi
+
+    unset VIRTUAL_ENV
+    if [ ! "${1:-}" = "nondestructive" ] ; then
+    # Self destruct!
+        unset -f deactivate
+    fi
+}
+
+# unset irrelevant variables
+deactivate nondestructive
+
+VIRTUAL_ENV="/Users/marcel/Unity/VRCyling/Assets/Plotting/venv"
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/bin:$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
+# could use `if (set -u; : $PYTHONHOME) ;` in bash
+if [ -n "${PYTHONHOME:-}" ] ; then
+    _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
+    unset PYTHONHOME
+fi
+
+if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
+    _OLD_VIRTUAL_PS1="${PS1:-}"
+    if [ "x(venv) " != x ] ; then
+	PS1="(venv) ${PS1:-}"
+    else
+    if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
+        # special case for Aspen magic directories
+        # see http://www.zetadev.com/software/aspen/
+        PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
+    else
+        PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
+    fi
+    fi
+    export PS1
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands.  Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+    hash -r
+fi

+ 37 - 0
Assets/Plotting/venv/bin/activate.csh

@@ -0,0 +1,37 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi <davidedb@gmail.com>.
+# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV "/Users/marcel/Unity/VRCyling/Assets/Plotting/venv"
+
+set _OLD_VIRTUAL_PATH="$PATH"
+setenv PATH "$VIRTUAL_ENV/bin:$PATH"
+
+
+set _OLD_VIRTUAL_PROMPT="$prompt"
+
+if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
+    if ("venv" != "") then
+        set env_name = "venv"
+    else
+        if (`basename "VIRTUAL_ENV"` == "__") then
+            # special case for Aspen magic directories
+            # see http://www.zetadev.com/software/aspen/
+            set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
+        else
+            set env_name = `basename "$VIRTUAL_ENV"`
+        endif
+    endif
+    set prompt = "[$env_name] $prompt"
+    unset env_name
+endif
+
+alias pydoc python -m pydoc
+
+rehash

+ 7 - 0
Assets/Plotting/venv/bin/activate.csh.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8cba1404136f4460fa23e6ac5d0d2beb
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 75 - 0
Assets/Plotting/venv/bin/activate.fish

@@ -0,0 +1,75 @@
+# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
+# you cannot run it directly
+
+function deactivate  -d "Exit virtualenv and return to normal shell environment"
+    # reset old environment variables
+    if test -n "$_OLD_VIRTUAL_PATH"
+        set -gx PATH $_OLD_VIRTUAL_PATH
+        set -e _OLD_VIRTUAL_PATH
+    end
+    if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+        set -e _OLD_VIRTUAL_PYTHONHOME
+    end
+
+    if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
+        functions -e fish_prompt
+        set -e _OLD_FISH_PROMPT_OVERRIDE
+        functions -c _old_fish_prompt fish_prompt
+        functions -e _old_fish_prompt
+    end
+
+    set -e VIRTUAL_ENV
+    if test "$argv[1]" != "nondestructive"
+        # Self destruct!
+        functions -e deactivate
+    end
+end
+
+# unset irrelevant variables
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV "/Users/marcel/Unity/VRCyling/Assets/Plotting/venv"
+
+set -gx _OLD_VIRTUAL_PATH $PATH
+set -gx PATH "$VIRTUAL_ENV/bin" $PATH
+
+# unset PYTHONHOME if set
+if set -q PYTHONHOME
+    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+    set -e PYTHONHOME
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+    # fish uses a function instead of an env var to generate the prompt.
+
+    # save the current fish_prompt function as the function _old_fish_prompt
+    functions -c fish_prompt _old_fish_prompt
+
+    # with the original prompt function renamed, we can override with our own.
+    function fish_prompt
+        # Save the return status of the last command
+        set -l old_status $status
+
+        # Prompt override?
+        if test -n "(venv) "
+            printf "%s%s" "(venv) " (set_color normal)
+        else
+            # ...Otherwise, prepend env
+            set -l _checkbase (basename "$VIRTUAL_ENV")
+            if test $_checkbase = "__"
+                # special case for Aspen magic directories
+                # see http://www.zetadev.com/software/aspen/
+                printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
+            else
+                printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
+            end
+        end
+
+        # Restore the return status of the previous command.
+        echo "exit $old_status" | .
+        _old_fish_prompt
+    end
+
+    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+end

+ 7 - 0
Assets/Plotting/venv/bin/activate.fish.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 84378701a8db94664a390e4760537d2b
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
Assets/Plotting/venv/bin/activate.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f83a1a2d1f8474e16b85d721683d1a8d
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin/easy_install

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from setuptools.command.easy_install import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
Assets/Plotting/venv/bin/easy_install-3.7

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from setuptools.command.easy_install import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 7 - 0
Assets/Plotting/venv/bin/easy_install-3.7.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6dd6d4a1cca624b8f91c0f61774df25a
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
Assets/Plotting/venv/bin/easy_install.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 88d7bb36faf9049f7a6c0005c10bb0bd
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin/f2py

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 7 - 0
Assets/Plotting/venv/bin/f2py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a59dcd9e83d9f4592b18302983b237a7
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin/f2py3

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
Assets/Plotting/venv/bin/f2py3.7

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from numpy.f2py.f2py2e import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 7 - 0
Assets/Plotting/venv/bin/f2py3.7.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8e868454d45c54ffebfb47fd433109d0
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
Assets/Plotting/venv/bin/f2py3.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a84f030f89dcd4fee97cb99f6ac85eaa
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin/pip

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 7 - 0
Assets/Plotting/venv/bin/pip.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4133e6e8a5a2a451e818bd4b047addc9
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/bin/pip3

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
Assets/Plotting/venv/bin/pip3.7

@@ -0,0 +1,8 @@
+#!/Users/marcel/Unity/VRCyling/Assets/Plotting/venv/bin/python
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 7 - 0
Assets/Plotting/venv/bin/pip3.7.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f39810bc523844123b6f4b477288516b
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
Assets/Plotting/venv/bin/pip3.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: e08c181c4bf304f8483e26aab9212c87
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1 - 0
Assets/Plotting/venv/bin/python

@@ -0,0 +1 @@
+python3

+ 7 - 0
Assets/Plotting/venv/bin/python.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 2ca32f4d2e15440819884cd1f0bec7c6
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1 - 0
Assets/Plotting/venv/bin/python3

@@ -0,0 +1 @@
+/usr/local/Cellar/python/3.7.7/bin/python3

+ 7 - 0
Assets/Plotting/venv/bin/python3.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 365c8b94899644f8eaaef8573f673243
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/include.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 40f8c1865487c44d7b1f297dd9801cb4
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/lib.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b5df9630233464f8090fefe05578b00d
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/lib/python3.7.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8e89407068895468ebb692b6a0b4e58f
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/lib/python3.7/site-packages.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fd8ca44976c9a4074ba1980f60ea12df
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6e3f6e587f7424143922b50ce9087dd5
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libXau.6.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libfreetype.6.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libjpeg.9.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/liblcms2.2.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/liblzma.5.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libopenjp2.2.3.1.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libpng16.16.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libtiff.5.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebp.7.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebpdemux.2.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libwebpmux.3.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libxcb.1.1.0.dylib


BIN
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/.dylibs/libz.1.2.11.dylib


+ 110 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BdfFontFile.py

@@ -0,0 +1,110 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# bitmap distribution font (bdf) file parser
+#
+# history:
+# 1996-05-16 fl   created (as bdf2pil)
+# 1997-08-25 fl   converted to FontFile driver
+# 2001-05-25 fl   removed bogus __init__ call
+# 2002-11-20 fl   robustification (from Kevin Cazabon, Dmitry Vasiliev)
+# 2003-04-22 fl   more robustification (from Graham Dumpleton)
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1997-2003 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+"""
+Parse X Bitmap Distribution Format (BDF)
+"""
+
+
+from . import FontFile, Image
+
+bdf_slant = {
+    "R": "Roman",
+    "I": "Italic",
+    "O": "Oblique",
+    "RI": "Reverse Italic",
+    "RO": "Reverse Oblique",
+    "OT": "Other",
+}
+
+bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
+
+
+def bdf_char(f):
+    # skip to STARTCHAR
+    while True:
+        s = f.readline()
+        if not s:
+            return None
+        if s[:9] == b"STARTCHAR":
+            break
+    id = s[9:].strip().decode("ascii")
+
+    # load symbol properties
+    props = {}
+    while True:
+        s = f.readline()
+        if not s or s[:6] == b"BITMAP":
+            break
+        i = s.find(b" ")
+        props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
+
+    # load bitmap
+    bitmap = []
+    while True:
+        s = f.readline()
+        if not s or s[:7] == b"ENDCHAR":
+            break
+        bitmap.append(s[:-1])
+    bitmap = b"".join(bitmap)
+
+    [x, y, l, d] = [int(p) for p in props["BBX"].split()]
+    [dx, dy] = [int(p) for p in props["DWIDTH"].split()]
+
+    bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y)
+
+    try:
+        im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
+    except ValueError:
+        # deal with zero-width characters
+        im = Image.new("1", (x, y))
+
+    return id, int(props["ENCODING"]), bbox, im
+
+
+class BdfFontFile(FontFile.FontFile):
+    """Font file plugin for the X11 BDF format."""
+
+    def __init__(self, fp):
+        super().__init__()
+
+        s = fp.readline()
+        if s[:13] != b"STARTFONT 2.1":
+            raise SyntaxError("not a valid BDF file")
+
+        props = {}
+        comments = []
+
+        while True:
+            s = fp.readline()
+            if not s or s[:13] == b"ENDPROPERTIES":
+                break
+            i = s.find(b" ")
+            props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
+            if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
+                if s.find(b"LogicalFontDescription") < 0:
+                    comments.append(s[i + 1 : -1].decode("ascii"))
+
+        while True:
+            c = bdf_char(fp)
+            if not c:
+                break
+            id, ch, (xy, dst, src), im = c
+            if 0 <= ch < len(self.glyph):
+                self.glyph[ch] = xy, dst, src, im

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BdfFontFile.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 81fc70148fb6f4512ae1e42c0fa8673e
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 422 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BlpImagePlugin.py

@@ -0,0 +1,422 @@
+"""
+Blizzard Mipmap Format (.blp)
+Jerome Leclanche <jerome@leclan.ch>
+
+The contents of this file are hereby released in the public domain (CC0)
+Full text of the CC0 license:
+  https://creativecommons.org/publicdomain/zero/1.0/
+
+BLP1 files, used mostly in Warcraft III, are not fully supported.
+All types of BLP2 files used in World of Warcraft are supported.
+
+The BLP file structure consists of a header, up to 16 mipmaps of the
+texture
+
+Texture sizes must be powers of two, though the two dimensions do
+not have to be equal; 512x256 is valid, but 512x200 is not.
+The first mipmap (mipmap #0) is the full size image; each subsequent
+mipmap halves both dimensions. The final mipmap should be 1x1.
+
+BLP files come in many different flavours:
+* JPEG-compressed (type == 0) - only supported for BLP1.
+* RAW images (type == 1, encoding == 1). Each mipmap is stored as an
+  array of 8-bit values, one per pixel, left to right, top to bottom.
+  Each value is an index to the palette.
+* DXT-compressed (type == 1, encoding == 2):
+- DXT1 compression is used if alpha_encoding == 0.
+  - An additional alpha bit is used if alpha_depth == 1.
+  - DXT3 compression is used if alpha_encoding == 1.
+  - DXT5 compression is used if alpha_encoding == 7.
+"""
+
+import struct
+from io import BytesIO
+
+from . import Image, ImageFile
+
+BLP_FORMAT_JPEG = 0
+
+BLP_ENCODING_UNCOMPRESSED = 1
+BLP_ENCODING_DXT = 2
+BLP_ENCODING_UNCOMPRESSED_RAW_BGRA = 3
+
+BLP_ALPHA_ENCODING_DXT1 = 0
+BLP_ALPHA_ENCODING_DXT3 = 1
+BLP_ALPHA_ENCODING_DXT5 = 7
+
+
+def unpack_565(i):
+    return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3)
+
+
+def decode_dxt1(data, alpha=False):
+    """
+    input: one "row" of data (i.e. will produce 4*width pixels)
+    """
+
+    blocks = len(data) // 8  # number of blocks in row
+    ret = (bytearray(), bytearray(), bytearray(), bytearray())
+
+    for block in range(blocks):
+        # Decode next 8-byte block.
+        idx = block * 8
+        color0, color1, bits = struct.unpack_from("<HHI", data, idx)
+
+        r0, g0, b0 = unpack_565(color0)
+        r1, g1, b1 = unpack_565(color1)
+
+        # Decode this block into 4x4 pixels
+        # Accumulate the results onto our 4 row accumulators
+        for j in range(4):
+            for i in range(4):
+                # get next control op and generate a pixel
+
+                control = bits & 3
+                bits = bits >> 2
+
+                a = 0xFF
+                if control == 0:
+                    r, g, b = r0, g0, b0
+                elif control == 1:
+                    r, g, b = r1, g1, b1
+                elif control == 2:
+                    if color0 > color1:
+                        r = (2 * r0 + r1) // 3
+                        g = (2 * g0 + g1) // 3
+                        b = (2 * b0 + b1) // 3
+                    else:
+                        r = (r0 + r1) // 2
+                        g = (g0 + g1) // 2
+                        b = (b0 + b1) // 2
+                elif control == 3:
+                    if color0 > color1:
+                        r = (2 * r1 + r0) // 3
+                        g = (2 * g1 + g0) // 3
+                        b = (2 * b1 + b0) // 3
+                    else:
+                        r, g, b, a = 0, 0, 0, 0
+
+                if alpha:
+                    ret[j].extend([r, g, b, a])
+                else:
+                    ret[j].extend([r, g, b])
+
+    return ret
+
+
+def decode_dxt3(data):
+    """
+    input: one "row" of data (i.e. will produce 4*width pixels)
+    """
+
+    blocks = len(data) // 16  # number of blocks in row
+    ret = (bytearray(), bytearray(), bytearray(), bytearray())
+
+    for block in range(blocks):
+        idx = block * 16
+        block = data[idx : idx + 16]
+        # Decode next 16-byte block.
+        bits = struct.unpack_from("<8B", block)
+        color0, color1 = struct.unpack_from("<HH", block, 8)
+
+        (code,) = struct.unpack_from("<I", block, 12)
+
+        r0, g0, b0 = unpack_565(color0)
+        r1, g1, b1 = unpack_565(color1)
+
+        for j in range(4):
+            high = False  # Do we want the higher bits?
+            for i in range(4):
+                alphacode_index = (4 * j + i) // 2
+                a = bits[alphacode_index]
+                if high:
+                    high = False
+                    a >>= 4
+                else:
+                    high = True
+                    a &= 0xF
+                a *= 17  # We get a value between 0 and 15
+
+                color_code = (code >> 2 * (4 * j + i)) & 0x03
+
+                if color_code == 0:
+                    r, g, b = r0, g0, b0
+                elif color_code == 1:
+                    r, g, b = r1, g1, b1
+                elif color_code == 2:
+                    r = (2 * r0 + r1) // 3
+                    g = (2 * g0 + g1) // 3
+                    b = (2 * b0 + b1) // 3
+                elif color_code == 3:
+                    r = (2 * r1 + r0) // 3
+                    g = (2 * g1 + g0) // 3
+                    b = (2 * b1 + b0) // 3
+
+                ret[j].extend([r, g, b, a])
+
+    return ret
+
+
+def decode_dxt5(data):
+    """
+    input: one "row" of data (i.e. will produce 4 * width pixels)
+    """
+
+    blocks = len(data) // 16  # number of blocks in row
+    ret = (bytearray(), bytearray(), bytearray(), bytearray())
+
+    for block in range(blocks):
+        idx = block * 16
+        block = data[idx : idx + 16]
+        # Decode next 16-byte block.
+        a0, a1 = struct.unpack_from("<BB", block)
+
+        bits = struct.unpack_from("<6B", block, 2)
+        alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
+        alphacode2 = bits[0] | (bits[1] << 8)
+
+        color0, color1 = struct.unpack_from("<HH", block, 8)
+
+        (code,) = struct.unpack_from("<I", block, 12)
+
+        r0, g0, b0 = unpack_565(color0)
+        r1, g1, b1 = unpack_565(color1)
+
+        for j in range(4):
+            for i in range(4):
+                # get next control op and generate a pixel
+                alphacode_index = 3 * (4 * j + i)
+
+                if alphacode_index <= 12:
+                    alphacode = (alphacode2 >> alphacode_index) & 0x07
+                elif alphacode_index == 15:
+                    alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
+                else:  # alphacode_index >= 18 and alphacode_index <= 45
+                    alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
+
+                if alphacode == 0:
+                    a = a0
+                elif alphacode == 1:
+                    a = a1
+                elif a0 > a1:
+                    a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
+                elif alphacode == 6:
+                    a = 0
+                elif alphacode == 7:
+                    a = 255
+                else:
+                    a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
+
+                color_code = (code >> 2 * (4 * j + i)) & 0x03
+
+                if color_code == 0:
+                    r, g, b = r0, g0, b0
+                elif color_code == 1:
+                    r, g, b = r1, g1, b1
+                elif color_code == 2:
+                    r = (2 * r0 + r1) // 3
+                    g = (2 * g0 + g1) // 3
+                    b = (2 * b0 + b1) // 3
+                elif color_code == 3:
+                    r = (2 * r1 + r0) // 3
+                    g = (2 * g1 + g0) // 3
+                    b = (2 * b1 + b0) // 3
+
+                ret[j].extend([r, g, b, a])
+
+    return ret
+
+
+class BLPFormatError(NotImplementedError):
+    pass
+
+
+class BlpImageFile(ImageFile.ImageFile):
+    """
+    Blizzard Mipmap Format
+    """
+
+    format = "BLP"
+    format_description = "Blizzard Mipmap Format"
+
+    def _open(self):
+        self.magic = self.fp.read(4)
+        self._read_blp_header()
+
+        if self.magic == b"BLP1":
+            decoder = "BLP1"
+            self.mode = "RGB"
+        elif self.magic == b"BLP2":
+            decoder = "BLP2"
+            self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
+        else:
+            raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}")
+
+        self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
+
+    def _read_blp_header(self):
+        (self._blp_compression,) = struct.unpack("<i", self.fp.read(4))
+
+        (self._blp_encoding,) = struct.unpack("<b", self.fp.read(1))
+        (self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
+        (self._blp_alpha_encoding,) = struct.unpack("<b", self.fp.read(1))
+        (self._blp_mips,) = struct.unpack("<b", self.fp.read(1))
+
+        self._size = struct.unpack("<II", self.fp.read(8))
+
+        if self.magic == b"BLP1":
+            # Only present for BLP1
+            (self._blp_encoding,) = struct.unpack("<i", self.fp.read(4))
+            (self._blp_subtype,) = struct.unpack("<i", self.fp.read(4))
+
+        self._blp_offsets = struct.unpack("<16I", self.fp.read(16 * 4))
+        self._blp_lengths = struct.unpack("<16I", self.fp.read(16 * 4))
+
+
+class _BLPBaseDecoder(ImageFile.PyDecoder):
+    _pulls_fd = True
+
+    def decode(self, buffer):
+        try:
+            self.fd.seek(0)
+            self.magic = self.fd.read(4)
+            self._read_blp_header()
+            self._load()
+        except struct.error as e:
+            raise OSError("Truncated Blp file") from e
+        return 0, 0
+
+    def _read_palette(self):
+        ret = []
+        for i in range(256):
+            try:
+                b, g, r, a = struct.unpack("<4B", self.fd.read(4))
+            except struct.error:
+                break
+            ret.append((b, g, r, a))
+        return ret
+
+    def _read_blp_header(self):
+        (self._blp_compression,) = struct.unpack("<i", self.fd.read(4))
+
+        (self._blp_encoding,) = struct.unpack("<b", self.fd.read(1))
+        (self._blp_alpha_depth,) = struct.unpack("<b", self.fd.read(1))
+        (self._blp_alpha_encoding,) = struct.unpack("<b", self.fd.read(1))
+        (self._blp_mips,) = struct.unpack("<b", self.fd.read(1))
+
+        self.size = struct.unpack("<II", self.fd.read(8))
+
+        if self.magic == b"BLP1":
+            # Only present for BLP1
+            (self._blp_encoding,) = struct.unpack("<i", self.fd.read(4))
+            (self._blp_subtype,) = struct.unpack("<i", self.fd.read(4))
+
+        self._blp_offsets = struct.unpack("<16I", self.fd.read(16 * 4))
+        self._blp_lengths = struct.unpack("<16I", self.fd.read(16 * 4))
+
+
+class BLP1Decoder(_BLPBaseDecoder):
+    def _load(self):
+        if self._blp_compression == BLP_FORMAT_JPEG:
+            self._decode_jpeg_stream()
+
+        elif self._blp_compression == 1:
+            if self._blp_encoding in (4, 5):
+                data = bytearray()
+                palette = self._read_palette()
+                _data = BytesIO(self.fd.read(self._blp_lengths[0]))
+                while True:
+                    try:
+                        (offset,) = struct.unpack("<B", _data.read(1))
+                    except struct.error:
+                        break
+                    b, g, r, a = palette[offset]
+                    data.extend([r, g, b])
+
+                self.set_as_raw(bytes(data))
+            else:
+                raise BLPFormatError(
+                    f"Unsupported BLP encoding {repr(self._blp_encoding)}"
+                )
+        else:
+            raise BLPFormatError(
+                f"Unsupported BLP compression {repr(self._blp_encoding)}"
+            )
+
+    def _decode_jpeg_stream(self):
+        from PIL.JpegImagePlugin import JpegImageFile
+
+        (jpeg_header_size,) = struct.unpack("<I", self.fd.read(4))
+        jpeg_header = self.fd.read(jpeg_header_size)
+        self.fd.read(self._blp_offsets[0] - self.fd.tell())  # What IS this?
+        data = self.fd.read(self._blp_lengths[0])
+        data = jpeg_header + data
+        data = BytesIO(data)
+        image = JpegImageFile(data)
+        self.tile = image.tile  # :/
+        self.fd = image.fp
+        self.mode = image.mode
+
+
+class BLP2Decoder(_BLPBaseDecoder):
+    def _load(self):
+        palette = self._read_palette()
+
+        data = bytearray()
+        self.fd.seek(self._blp_offsets[0])
+
+        if self._blp_compression == 1:
+            # Uncompressed or DirectX compression
+
+            if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED:
+                _data = BytesIO(self.fd.read(self._blp_lengths[0]))
+                while True:
+                    try:
+                        (offset,) = struct.unpack("<B", _data.read(1))
+                    except struct.error:
+                        break
+                    b, g, r, a = palette[offset]
+                    data.extend((r, g, b))
+
+            elif self._blp_encoding == BLP_ENCODING_DXT:
+                if self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT1:
+                    linesize = (self.size[0] + 3) // 4 * 8
+                    for yb in range((self.size[1] + 3) // 4):
+                        for d in decode_dxt1(
+                            self.fd.read(linesize), alpha=bool(self._blp_alpha_depth)
+                        ):
+                            data += d
+
+                elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3:
+                    linesize = (self.size[0] + 3) // 4 * 16
+                    for yb in range((self.size[1] + 3) // 4):
+                        for d in decode_dxt3(self.fd.read(linesize)):
+                            data += d
+
+                elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5:
+                    linesize = (self.size[0] + 3) // 4 * 16
+                    for yb in range((self.size[1] + 3) // 4):
+                        for d in decode_dxt5(self.fd.read(linesize)):
+                            data += d
+                else:
+                    raise BLPFormatError(
+                        f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
+                    )
+            else:
+                raise BLPFormatError(f"Unknown BLP encoding {repr(self._blp_encoding)}")
+
+        else:
+            raise BLPFormatError(
+                f"Unknown BLP compression {repr(self._blp_compression)}"
+            )
+
+        self.set_as_raw(bytes(data))
+
+
+Image.register_open(
+    BlpImageFile.format, BlpImageFile, lambda p: p[:4] in (b"BLP1", b"BLP2")
+)
+Image.register_extension(BlpImageFile.format, ".blp")
+
+Image.register_decoder("BLP1", BLP1Decoder)
+Image.register_decoder("BLP2", BLP2Decoder)

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BlpImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 243af4b5cdd6f468e92736c44c059e9d
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 381 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BmpImagePlugin.py

@@ -0,0 +1,381 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# BMP file handler
+#
+# Windows (and OS/2) native bitmap storage format.
+#
+# history:
+# 1995-09-01 fl   Created
+# 1996-04-30 fl   Added save
+# 1997-08-27 fl   Fixed save of 1-bit images
+# 1998-03-06 fl   Load P images as L where possible
+# 1998-07-03 fl   Load P images as 1 where possible
+# 1998-12-29 fl   Handle small palettes
+# 2002-12-30 fl   Fixed load of 1-bit palette images
+# 2003-04-21 fl   Fixed load of 1-bit monochrome images
+# 2003-04-23 fl   Added limited support for BI_BITFIELDS compression
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+from . import Image, ImageFile, ImagePalette
+from ._binary import i8
+from ._binary import i16le as i16
+from ._binary import i32le as i32
+from ._binary import o8
+from ._binary import o16le as o16
+from ._binary import o32le as o32
+
+#
+# --------------------------------------------------------------------
+# Read BMP file
+
+BIT2MODE = {
+    # bits => mode, rawmode
+    1: ("P", "P;1"),
+    4: ("P", "P;4"),
+    8: ("P", "P"),
+    16: ("RGB", "BGR;15"),
+    24: ("RGB", "BGR"),
+    32: ("RGB", "BGRX"),
+}
+
+
+def _accept(prefix):
+    return prefix[:2] == b"BM"
+
+
+def _dib_accept(prefix):
+    return i32(prefix[:4]) in [12, 40, 64, 108, 124]
+
+
+# =============================================================================
+# Image plugin for the Windows BMP format.
+# =============================================================================
+class BmpImageFile(ImageFile.ImageFile):
+    """ Image plugin for the Windows Bitmap format (BMP) """
+
+    # ------------------------------------------------------------- Description
+    format_description = "Windows Bitmap"
+    format = "BMP"
+
+    # -------------------------------------------------- BMP Compression values
+    COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
+    for k, v in COMPRESSIONS.items():
+        vars()[k] = v
+
+    def _bitmap(self, header=0, offset=0):
+        """ Read relevant info about the BMP """
+        read, seek = self.fp.read, self.fp.seek
+        if header:
+            seek(header)
+        file_info = {}
+        # read bmp header size @offset 14 (this is part of the header size)
+        file_info["header_size"] = i32(read(4))
+        file_info["direction"] = -1
+
+        # -------------------- If requested, read header at a specific position
+        # read the rest of the bmp header, without its size
+        header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
+
+        # -------------------------------------------------- IBM OS/2 Bitmap v1
+        # ----- This format has different offsets because of width/height types
+        if file_info["header_size"] == 12:
+            file_info["width"] = i16(header_data[0:2])
+            file_info["height"] = i16(header_data[2:4])
+            file_info["planes"] = i16(header_data[4:6])
+            file_info["bits"] = i16(header_data[6:8])
+            file_info["compression"] = self.RAW
+            file_info["palette_padding"] = 3
+
+        # --------------------------------------------- Windows Bitmap v2 to v5
+        # v3, OS/2 v2, v4, v5
+        elif file_info["header_size"] in (40, 64, 108, 124):
+            file_info["y_flip"] = i8(header_data[7]) == 0xFF
+            file_info["direction"] = 1 if file_info["y_flip"] else -1
+            file_info["width"] = i32(header_data[0:4])
+            file_info["height"] = (
+                i32(header_data[4:8])
+                if not file_info["y_flip"]
+                else 2 ** 32 - i32(header_data[4:8])
+            )
+            file_info["planes"] = i16(header_data[8:10])
+            file_info["bits"] = i16(header_data[10:12])
+            file_info["compression"] = i32(header_data[12:16])
+            # byte size of pixel data
+            file_info["data_size"] = i32(header_data[16:20])
+            file_info["pixels_per_meter"] = (
+                i32(header_data[20:24]),
+                i32(header_data[24:28]),
+            )
+            file_info["colors"] = i32(header_data[28:32])
+            file_info["palette_padding"] = 4
+            self.info["dpi"] = tuple(
+                int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"]
+            )
+            if file_info["compression"] == self.BITFIELDS:
+                if len(header_data) >= 52:
+                    for idx, mask in enumerate(
+                        ["r_mask", "g_mask", "b_mask", "a_mask"]
+                    ):
+                        file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4])
+                else:
+                    # 40 byte headers only have the three components in the
+                    # bitfields masks, ref:
+                    # https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
+                    # See also
+                    # https://github.com/python-pillow/Pillow/issues/1293
+                    # There is a 4th component in the RGBQuad, in the alpha
+                    # location, but it is listed as a reserved component,
+                    # and it is not generally an alpha channel
+                    file_info["a_mask"] = 0x0
+                    for mask in ["r_mask", "g_mask", "b_mask"]:
+                        file_info[mask] = i32(read(4))
+                file_info["rgb_mask"] = (
+                    file_info["r_mask"],
+                    file_info["g_mask"],
+                    file_info["b_mask"],
+                )
+                file_info["rgba_mask"] = (
+                    file_info["r_mask"],
+                    file_info["g_mask"],
+                    file_info["b_mask"],
+                    file_info["a_mask"],
+                )
+        else:
+            raise OSError(f"Unsupported BMP header type ({file_info['header_size']})")
+
+        # ------------------ Special case : header is reported 40, which
+        # ---------------------- is shorter than real size for bpp >= 16
+        self._size = file_info["width"], file_info["height"]
+
+        # ------- If color count was not found in the header, compute from bits
+        file_info["colors"] = (
+            file_info["colors"]
+            if file_info.get("colors", 0)
+            else (1 << file_info["bits"])
+        )
+
+        # ---------------------- Check bit depth for unusual unsupported values
+        self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
+        if self.mode is None:
+            raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})")
+
+        # ---------------- Process BMP with Bitfields compression (not palette)
+        if file_info["compression"] == self.BITFIELDS:
+            SUPPORTED = {
+                32: [
+                    (0xFF0000, 0xFF00, 0xFF, 0x0),
+                    (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
+                    (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
+                    (0x0, 0x0, 0x0, 0x0),
+                    (0xFF000000, 0xFF0000, 0xFF00, 0x0),
+                ],
+                24: [(0xFF0000, 0xFF00, 0xFF)],
+                16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
+            }
+            MASK_MODES = {
+                (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
+                (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
+                (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
+                (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
+                (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
+                (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
+                (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
+                (16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
+            }
+            if file_info["bits"] in SUPPORTED:
+                if (
+                    file_info["bits"] == 32
+                    and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
+                ):
+                    raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
+                    self.mode = "RGBA" if "A" in raw_mode else self.mode
+                elif (
+                    file_info["bits"] in (24, 16)
+                    and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
+                ):
+                    raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
+                else:
+                    raise OSError("Unsupported BMP bitfields layout")
+            else:
+                raise OSError("Unsupported BMP bitfields layout")
+        elif file_info["compression"] == self.RAW:
+            if file_info["bits"] == 32 and header == 22:  # 32-bit .cur offset
+                raw_mode, self.mode = "BGRA", "RGBA"
+        else:
+            raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
+
+        # --------------- Once the header is processed, process the palette/LUT
+        if self.mode == "P":  # Paletted for 1, 4 and 8 bit images
+
+            # ---------------------------------------------------- 1-bit images
+            if not (0 < file_info["colors"] <= 65536):
+                raise OSError(f"Unsupported BMP Palette size ({file_info['colors']})")
+            else:
+                padding = file_info["palette_padding"]
+                palette = read(padding * file_info["colors"])
+                greyscale = True
+                indices = (
+                    (0, 255)
+                    if file_info["colors"] == 2
+                    else list(range(file_info["colors"]))
+                )
+
+                # ----------------- Check if greyscale and ignore palette if so
+                for ind, val in enumerate(indices):
+                    rgb = palette[ind * padding : ind * padding + 3]
+                    if rgb != o8(val) * 3:
+                        greyscale = False
+
+                # ------- If all colors are grey, white or black, ditch palette
+                if greyscale:
+                    self.mode = "1" if file_info["colors"] == 2 else "L"
+                    raw_mode = self.mode
+                else:
+                    self.mode = "P"
+                    self.palette = ImagePalette.raw(
+                        "BGRX" if padding == 4 else "BGR", palette
+                    )
+
+        # ---------------------------- Finally set the tile data for the plugin
+        self.info["compression"] = file_info["compression"]
+        self.tile = [
+            (
+                "raw",
+                (0, 0, file_info["width"], file_info["height"]),
+                offset or self.fp.tell(),
+                (
+                    raw_mode,
+                    ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
+                    file_info["direction"],
+                ),
+            )
+        ]
+
+    def _open(self):
+        """ Open file, check magic number and read header """
+        # read 14 bytes: magic number, filesize, reserved, header final offset
+        head_data = self.fp.read(14)
+        # choke if the file does not have the required magic bytes
+        if not _accept(head_data):
+            raise SyntaxError("Not a BMP file")
+        # read the start position of the BMP image data (u32)
+        offset = i32(head_data[10:14])
+        # load bitmap information (offset=raster info)
+        self._bitmap(offset=offset)
+
+
+# =============================================================================
+# Image plugin for the DIB format (BMP alias)
+# =============================================================================
+class DibImageFile(BmpImageFile):
+
+    format = "DIB"
+    format_description = "Windows Bitmap"
+
+    def _open(self):
+        self._bitmap()
+
+
+#
+# --------------------------------------------------------------------
+# Write BMP file
+
+
+SAVE = {
+    "1": ("1", 1, 2),
+    "L": ("L", 8, 256),
+    "P": ("P", 8, 256),
+    "RGB": ("BGR", 24, 0),
+    "RGBA": ("BGRA", 32, 0),
+}
+
+
+def _dib_save(im, fp, filename):
+    _save(im, fp, filename, False)
+
+
+def _save(im, fp, filename, bitmap_header=True):
+    try:
+        rawmode, bits, colors = SAVE[im.mode]
+    except KeyError as e:
+        raise OSError(f"cannot write mode {im.mode} as BMP") from e
+
+    info = im.encoderinfo
+
+    dpi = info.get("dpi", (96, 96))
+
+    # 1 meter == 39.3701 inches
+    ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
+
+    stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
+    header = 40  # or 64 for OS/2 version 2
+    image = stride * im.size[1]
+
+    # bitmap header
+    if bitmap_header:
+        offset = 14 + header + colors * 4
+        file_size = offset + image
+        if file_size > 2 ** 32 - 1:
+            raise ValueError("File size is too large for the BMP format")
+        fp.write(
+            b"BM"  # file type (magic)
+            + o32(file_size)  # file size
+            + o32(0)  # reserved
+            + o32(offset)  # image data offset
+        )
+
+    # bitmap info header
+    fp.write(
+        o32(header)  # info header size
+        + o32(im.size[0])  # width
+        + o32(im.size[1])  # height
+        + o16(1)  # planes
+        + o16(bits)  # depth
+        + o32(0)  # compression (0=uncompressed)
+        + o32(image)  # size of bitmap
+        + o32(ppm[0])  # resolution
+        + o32(ppm[1])  # resolution
+        + o32(colors)  # colors used
+        + o32(colors)  # colors important
+    )
+
+    fp.write(b"\0" * (header - 40))  # padding (for OS/2 format)
+
+    if im.mode == "1":
+        for i in (0, 255):
+            fp.write(o8(i) * 4)
+    elif im.mode == "L":
+        for i in range(256):
+            fp.write(o8(i) * 4)
+    elif im.mode == "P":
+        fp.write(im.im.getpalette("RGB", "BGRX"))
+
+    ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
+
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+
+Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
+Image.register_save(BmpImageFile.format, _save)
+
+Image.register_extension(BmpImageFile.format, ".bmp")
+
+Image.register_mime(BmpImageFile.format, "image/bmp")
+
+Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
+Image.register_save(DibImageFile.format, _dib_save)
+
+Image.register_extension(DibImageFile.format, ".dib")
+
+Image.register_mime(DibImageFile.format, "image/bmp")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BmpImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 69bf4f4ee213d4810bcdbaefc0668944
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 73 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BufrStubImagePlugin.py

@@ -0,0 +1,73 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# BUFR stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+    """
+    Install application-specific BUFR image handler.
+
+    :param handler: Handler object.
+    """
+    global _handler
+    _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+    return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
+
+
+class BufrStubImageFile(ImageFile.StubImageFile):
+
+    format = "BUFR"
+    format_description = "BUFR"
+
+    def _open(self):
+
+        offset = self.fp.tell()
+
+        if not _accept(self.fp.read(4)):
+            raise SyntaxError("Not a BUFR file")
+
+        self.fp.seek(offset)
+
+        # make something up
+        self.mode = "F"
+        self._size = 1, 1
+
+        loader = self._load()
+        if loader:
+            loader.open(self)
+
+    def _load(self):
+        return _handler
+
+
+def _save(im, fp, filename):
+    if _handler is None or not hasattr("_handler", "save"):
+        raise OSError("BUFR save handler not installed")
+    _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
+Image.register_save(BufrStubImageFile.format, _save)
+
+Image.register_extension(BufrStubImageFile.format, ".bufr")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/BufrStubImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1ebfe6b60c4004d72bdc374e113c0185
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 120 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ContainerIO.py

@@ -0,0 +1,120 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# a class to read from a container file
+#
+# History:
+# 1995-06-18 fl     Created
+# 1995-09-07 fl     Added readline(), readlines()
+#
+# Copyright (c) 1997-2001 by Secret Labs AB
+# Copyright (c) 1995 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+import io
+
+
+class ContainerIO:
+    """
+    A file object that provides read access to a part of an existing
+    file (for example a TAR file).
+    """
+
+    def __init__(self, file, offset, length):
+        """
+        Create file object.
+
+        :param file: Existing file.
+        :param offset: Start of region, in bytes.
+        :param length: Size of region, in bytes.
+        """
+        self.fh = file
+        self.pos = 0
+        self.offset = offset
+        self.length = length
+        self.fh.seek(offset)
+
+    ##
+    # Always false.
+
+    def isatty(self):
+        return False
+
+    def seek(self, offset, mode=io.SEEK_SET):
+        """
+        Move file pointer.
+
+        :param offset: Offset in bytes.
+        :param mode: Starting position. Use 0 for beginning of region, 1
+           for current offset, and 2 for end of region.  You cannot move
+           the pointer outside the defined region.
+        """
+        if mode == 1:
+            self.pos = self.pos + offset
+        elif mode == 2:
+            self.pos = self.length + offset
+        else:
+            self.pos = offset
+        # clamp
+        self.pos = max(0, min(self.pos, self.length))
+        self.fh.seek(self.offset + self.pos)
+
+    def tell(self):
+        """
+        Get current file pointer.
+
+        :returns: Offset from start of region, in bytes.
+        """
+        return self.pos
+
+    def read(self, n=0):
+        """
+        Read data.
+
+        :param n: Number of bytes to read. If omitted or zero,
+            read until end of region.
+        :returns: An 8-bit string.
+        """
+        if n:
+            n = min(n, self.length - self.pos)
+        else:
+            n = self.length - self.pos
+        if not n:  # EOF
+            return b"" if "b" in self.fh.mode else ""
+        self.pos = self.pos + n
+        return self.fh.read(n)
+
+    def readline(self):
+        """
+        Read a line of text.
+
+        :returns: An 8-bit string.
+        """
+        s = b"" if "b" in self.fh.mode else ""
+        newline_character = b"\n" if "b" in self.fh.mode else "\n"
+        while True:
+            c = self.read(1)
+            if not c:
+                break
+            s = s + c
+            if c == newline_character:
+                break
+        return s
+
+    def readlines(self):
+        """
+        Read multiple lines of text.
+
+        :returns: A list of 8-bit strings.
+        """
+        lines = []
+        while True:
+            s = self.readline()
+            if not s:
+                break
+            lines.append(s)
+        return lines

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ContainerIO.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 59afeda3dcc9a4386b3d218eda8e34cc
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/CurImagePlugin.py

@@ -0,0 +1,76 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# Windows Cursor support for PIL
+#
+# notes:
+#       uses BmpImagePlugin.py to read the bitmap data.
+#
+# history:
+#       96-05-27 fl     Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+#
+# See the README file for information on usage and redistribution.
+#
+from . import BmpImagePlugin, Image
+from ._binary import i8
+from ._binary import i16le as i16
+from ._binary import i32le as i32
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+    return prefix[:4] == b"\0\0\2\0"
+
+
+##
+# Image plugin for Windows Cursor files.
+
+
+class CurImageFile(BmpImagePlugin.BmpImageFile):
+
+    format = "CUR"
+    format_description = "Windows Cursor"
+
+    def _open(self):
+
+        offset = self.fp.tell()
+
+        # check magic
+        s = self.fp.read(6)
+        if not _accept(s):
+            raise SyntaxError("not a CUR file")
+
+        # pick the largest cursor in the file
+        m = b""
+        for i in range(i16(s[4:])):
+            s = self.fp.read(16)
+            if not m:
+                m = s
+            elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
+                m = s
+        if not m:
+            raise TypeError("No cursors were found")
+
+        # load as bitmap
+        self._bitmap(i32(m[12:]) + offset)
+
+        # patch up the bitmap height
+        self._size = self.size[0], self.size[1] // 2
+        d, e, o, a = self.tile[0]
+        self.tile[0] = d, (0, 0) + self.size, o, a
+
+        return
+
+
+#
+# --------------------------------------------------------------------
+
+Image.register_open(CurImageFile.format, CurImageFile, _accept)
+
+Image.register_extension(CurImageFile.format, ".cur")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/CurImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1b3af07e55d274edd9157173ef1411d4
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 89 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DcxImagePlugin.py

@@ -0,0 +1,89 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# DCX file handling
+#
+# DCX is a container file format defined by Intel, commonly used
+# for fax applications.  Each DCX file consists of a directory
+# (a list of file offsets) followed by a set of (usually 1-bit)
+# PCX files.
+#
+# History:
+# 1995-09-09 fl   Created
+# 1996-03-20 fl   Properly derived from PcxImageFile.
+# 1998-07-15 fl   Renamed offset attribute to avoid name clash
+# 2002-07-30 fl   Fixed file handling
+#
+# Copyright (c) 1997-98 by Secret Labs AB.
+# Copyright (c) 1995-96 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+from . import Image
+from ._binary import i32le as i32
+from .PcxImagePlugin import PcxImageFile
+
+MAGIC = 0x3ADE68B1  # QUIZ: what's this value, then?
+
+
+def _accept(prefix):
+    return len(prefix) >= 4 and i32(prefix) == MAGIC
+
+
+##
+# Image plugin for the Intel DCX format.
+
+
+class DcxImageFile(PcxImageFile):
+
+    format = "DCX"
+    format_description = "Intel DCX"
+    _close_exclusive_fp_after_loading = False
+
+    def _open(self):
+
+        # Header
+        s = self.fp.read(4)
+        if not _accept(s):
+            raise SyntaxError("not a DCX file")
+
+        # Component directory
+        self._offset = []
+        for i in range(1024):
+            offset = i32(self.fp.read(4))
+            if not offset:
+                break
+            self._offset.append(offset)
+
+        self.__fp = self.fp
+        self.frame = None
+        self.n_frames = len(self._offset)
+        self.is_animated = self.n_frames > 1
+        self.seek(0)
+
+    def seek(self, frame):
+        if not self._seek_check(frame):
+            return
+        self.frame = frame
+        self.fp = self.__fp
+        self.fp.seek(self._offset[frame])
+        PcxImageFile._open(self)
+
+    def tell(self):
+        return self.frame
+
+    def _close__fp(self):
+        try:
+            if self.__fp != self.fp:
+                self.__fp.close()
+        except AttributeError:
+            pass
+        finally:
+            self.__fp = None
+
+
+Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
+
+Image.register_extension(DcxImageFile.format, ".dcx")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DcxImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 05216b337235a4152b435a4850b20ff5
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 178 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DdsImagePlugin.py

@@ -0,0 +1,178 @@
+"""
+A Pillow loader for .dds files (S3TC-compressed aka DXTC)
+Jerome Leclanche <jerome@leclan.ch>
+
+Documentation:
+  https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
+
+The contents of this file are hereby released in the public domain (CC0)
+Full text of the CC0 license:
+  https://creativecommons.org/publicdomain/zero/1.0/
+"""
+
+import struct
+from io import BytesIO
+
+from . import Image, ImageFile
+
+# Magic ("DDS ")
+DDS_MAGIC = 0x20534444
+
+# DDS flags
+DDSD_CAPS = 0x1
+DDSD_HEIGHT = 0x2
+DDSD_WIDTH = 0x4
+DDSD_PITCH = 0x8
+DDSD_PIXELFORMAT = 0x1000
+DDSD_MIPMAPCOUNT = 0x20000
+DDSD_LINEARSIZE = 0x80000
+DDSD_DEPTH = 0x800000
+
+# DDS caps
+DDSCAPS_COMPLEX = 0x8
+DDSCAPS_TEXTURE = 0x1000
+DDSCAPS_MIPMAP = 0x400000
+
+DDSCAPS2_CUBEMAP = 0x200
+DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
+DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
+DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
+DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
+DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
+DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
+DDSCAPS2_VOLUME = 0x200000
+
+# Pixel Format
+DDPF_ALPHAPIXELS = 0x1
+DDPF_ALPHA = 0x2
+DDPF_FOURCC = 0x4
+DDPF_PALETTEINDEXED8 = 0x20
+DDPF_RGB = 0x40
+DDPF_LUMINANCE = 0x20000
+
+
+# dds.h
+
+DDS_FOURCC = DDPF_FOURCC
+DDS_RGB = DDPF_RGB
+DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
+DDS_LUMINANCE = DDPF_LUMINANCE
+DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
+DDS_ALPHA = DDPF_ALPHA
+DDS_PAL8 = DDPF_PALETTEINDEXED8
+
+DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
+DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
+DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
+DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
+DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
+
+DDS_HEIGHT = DDSD_HEIGHT
+DDS_WIDTH = DDSD_WIDTH
+
+DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
+DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
+DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
+
+DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
+DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
+DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
+DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
+DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
+DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
+
+
+# DXT1
+DXT1_FOURCC = 0x31545844
+
+# DXT3
+DXT3_FOURCC = 0x33545844
+
+# DXT5
+DXT5_FOURCC = 0x35545844
+
+
+# dxgiformat.h
+
+DXGI_FORMAT_BC7_TYPELESS = 97
+DXGI_FORMAT_BC7_UNORM = 98
+DXGI_FORMAT_BC7_UNORM_SRGB = 99
+
+
+class DdsImageFile(ImageFile.ImageFile):
+    format = "DDS"
+    format_description = "DirectDraw Surface"
+
+    def _open(self):
+        magic, header_size = struct.unpack("<II", self.fp.read(8))
+        if header_size != 124:
+            raise OSError(f"Unsupported header size {repr(header_size)}")
+        header_bytes = self.fp.read(header_size - 4)
+        if len(header_bytes) != 120:
+            raise OSError(f"Incomplete header: {len(header_bytes)} bytes")
+        header = BytesIO(header_bytes)
+
+        flags, height, width = struct.unpack("<3I", header.read(12))
+        self._size = (width, height)
+        self.mode = "RGBA"
+
+        pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
+        struct.unpack("<11I", header.read(44))  # reserved
+
+        # pixel format
+        pfsize, pfflags = struct.unpack("<2I", header.read(8))
+        fourcc = header.read(4)
+        (bitcount,) = struct.unpack("<I", header.read(4))
+        masks = struct.unpack("<4I", header.read(16))
+        if pfflags & 0x40:
+            # DDPF_RGB - Texture contains uncompressed RGB data
+            masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
+            rawmode = ""
+            if bitcount == 32:
+                rawmode += masks[0xFF000000]
+            rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
+
+            self.tile = [("raw", (0, 0) + self.size, 0, (rawmode, 0, 1))]
+        else:
+            data_start = header_size + 4
+            n = 0
+            if fourcc == b"DXT1":
+                self.pixel_format = "DXT1"
+                n = 1
+            elif fourcc == b"DXT3":
+                self.pixel_format = "DXT3"
+                n = 2
+            elif fourcc == b"DXT5":
+                self.pixel_format = "DXT5"
+                n = 3
+            elif fourcc == b"DX10":
+                data_start += 20
+                # ignoring flags which pertain to volume textures and cubemaps
+                dxt10 = BytesIO(self.fp.read(20))
+                dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
+                if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
+                    self.pixel_format = "BC7"
+                    n = 7
+                elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
+                    self.pixel_format = "BC7"
+                    self.info["gamma"] = 1 / 2.2
+                    n = 7
+                else:
+                    raise NotImplementedError(
+                        f"Unimplemented DXGI format {dxgi_format}"
+                    )
+            else:
+                raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}")
+
+            self.tile = [("bcn", (0, 0) + self.size, data_start, (n))]
+
+    def load_seek(self, pos):
+        pass
+
+
+def _validate(prefix):
+    return prefix[:4] == b"DDS "
+
+
+Image.register_open(DdsImageFile.format, DdsImageFile, _validate)
+Image.register_extension(DdsImageFile.format, ".dds")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/DdsImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 40bccfb30cdcd41629bd0ac2fad2afc2
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 419 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/EpsImagePlugin.py

@@ -0,0 +1,419 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# EPS file handling
+#
+# History:
+# 1995-09-01 fl   Created (0.1)
+# 1996-05-18 fl   Don't choke on "atend" fields, Ghostscript interface (0.2)
+# 1996-08-22 fl   Don't choke on floating point BoundingBox values
+# 1996-08-23 fl   Handle files from Macintosh (0.3)
+# 2001-02-17 fl   Use 're' instead of 'regex' (Python 2.1) (0.4)
+# 2003-09-07 fl   Check gs.close status (from Federico Di Gregorio) (0.5)
+# 2014-05-07 e    Handling of EPS with binary preview and fixed resolution
+#                 resizing
+#
+# Copyright (c) 1997-2003 by Secret Labs AB.
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import io
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+from . import Image, ImageFile
+from ._binary import i32le as i32
+
+#
+# --------------------------------------------------------------------
+
+split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
+field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
+
+gs_windows_binary = None
+if sys.platform.startswith("win"):
+    import shutil
+
+    for binary in ("gswin32c", "gswin64c", "gs"):
+        if shutil.which(binary) is not None:
+            gs_windows_binary = binary
+            break
+    else:
+        gs_windows_binary = False
+
+
+def has_ghostscript():
+    if gs_windows_binary:
+        return True
+    if not sys.platform.startswith("win"):
+        try:
+            subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
+            return True
+        except OSError:
+            # No Ghostscript
+            pass
+    return False
+
+
+def Ghostscript(tile, size, fp, scale=1):
+    """Render an image using Ghostscript"""
+
+    # Unpack decoder tile
+    decoder, tile, offset, data = tile[0]
+    length, bbox = data
+
+    # Hack to support hi-res rendering
+    scale = int(scale) or 1
+    # orig_size = size
+    # orig_bbox = bbox
+    size = (size[0] * scale, size[1] * scale)
+    # resolution is dependent on bbox and size
+    res = (
+        72.0 * size[0] / (bbox[2] - bbox[0]),
+        72.0 * size[1] / (bbox[3] - bbox[1]),
+    )
+
+    out_fd, outfile = tempfile.mkstemp()
+    os.close(out_fd)
+
+    infile_temp = None
+    if hasattr(fp, "name") and os.path.exists(fp.name):
+        infile = fp.name
+    else:
+        in_fd, infile_temp = tempfile.mkstemp()
+        os.close(in_fd)
+        infile = infile_temp
+
+        # Ignore length and offset!
+        # Ghostscript can read it
+        # Copy whole file to read in Ghostscript
+        with open(infile_temp, "wb") as f:
+            # fetch length of fp
+            fp.seek(0, io.SEEK_END)
+            fsize = fp.tell()
+            # ensure start position
+            # go back
+            fp.seek(0)
+            lengthfile = fsize
+            while lengthfile > 0:
+                s = fp.read(min(lengthfile, 100 * 1024))
+                if not s:
+                    break
+                lengthfile -= len(s)
+                f.write(s)
+
+    # Build Ghostscript command
+    command = [
+        "gs",
+        "-q",  # quiet mode
+        "-g%dx%d" % size,  # set output geometry (pixels)
+        "-r%fx%f" % res,  # set input DPI (dots per inch)
+        "-dBATCH",  # exit after processing
+        "-dNOPAUSE",  # don't pause between pages
+        "-dSAFER",  # safe mode
+        "-sDEVICE=ppmraw",  # ppm driver
+        f"-sOutputFile={outfile}",  # output file
+        # adjust for image origin
+        "-c",
+        f"{-bbox[0]} {-bbox[1]} translate",
+        "-f",
+        infile,  # input file
+        # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
+        "-c",
+        "showpage",
+    ]
+
+    if gs_windows_binary is not None:
+        if not gs_windows_binary:
+            raise OSError("Unable to locate Ghostscript on paths")
+        command[0] = gs_windows_binary
+
+    # push data through Ghostscript
+    try:
+        startupinfo = None
+        if sys.platform.startswith("win"):
+            startupinfo = subprocess.STARTUPINFO()
+            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+        subprocess.check_call(command, startupinfo=startupinfo)
+        out_im = Image.open(outfile)
+        out_im.load()
+    finally:
+        try:
+            os.unlink(outfile)
+            if infile_temp:
+                os.unlink(infile_temp)
+        except OSError:
+            pass
+
+    im = out_im.im.copy()
+    out_im.close()
+    return im
+
+
+class PSFile:
+    """
+    Wrapper for bytesio object that treats either CR or LF as end of line.
+    """
+
+    def __init__(self, fp):
+        self.fp = fp
+        self.char = None
+
+    def seek(self, offset, whence=io.SEEK_SET):
+        self.char = None
+        self.fp.seek(offset, whence)
+
+    def readline(self):
+        s = self.char or b""
+        self.char = None
+
+        c = self.fp.read(1)
+        while c not in b"\r\n":
+            s = s + c
+            c = self.fp.read(1)
+
+        self.char = self.fp.read(1)
+        # line endings can be 1 or 2 of \r \n, in either order
+        if self.char in b"\r\n":
+            self.char = None
+
+        return s.decode("latin-1")
+
+
+def _accept(prefix):
+    return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
+
+
+##
+# Image plugin for Encapsulated PostScript.  This plugin supports only
+# a few variants of this format.
+
+
+class EpsImageFile(ImageFile.ImageFile):
+    """EPS File Parser for the Python Imaging Library"""
+
+    format = "EPS"
+    format_description = "Encapsulated Postscript"
+
+    mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
+
+    def _open(self):
+        (length, offset) = self._find_offset(self.fp)
+
+        # Rewrap the open file pointer in something that will
+        # convert line endings and decode to latin-1.
+        fp = PSFile(self.fp)
+
+        # go to offset - start of "%!PS"
+        fp.seek(offset)
+
+        box = None
+
+        self.mode = "RGB"
+        self._size = 1, 1  # FIXME: huh?
+
+        #
+        # Load EPS header
+
+        s_raw = fp.readline()
+        s = s_raw.strip("\r\n")
+
+        while s_raw:
+            if s:
+                if len(s) > 255:
+                    raise SyntaxError("not an EPS file")
+
+                try:
+                    m = split.match(s)
+                except re.error as e:
+                    raise SyntaxError("not an EPS file") from e
+
+                if m:
+                    k, v = m.group(1, 2)
+                    self.info[k] = v
+                    if k == "BoundingBox":
+                        try:
+                            # Note: The DSC spec says that BoundingBox
+                            # fields should be integers, but some drivers
+                            # put floating point values there anyway.
+                            box = [int(float(i)) for i in v.split()]
+                            self._size = box[2] - box[0], box[3] - box[1]
+                            self.tile = [
+                                ("eps", (0, 0) + self.size, offset, (length, box))
+                            ]
+                        except Exception:
+                            pass
+
+                else:
+                    m = field.match(s)
+                    if m:
+                        k = m.group(1)
+
+                        if k == "EndComments":
+                            break
+                        if k[:8] == "PS-Adobe":
+                            self.info[k[:8]] = k[9:]
+                        else:
+                            self.info[k] = ""
+                    elif s[0] == "%":
+                        # handle non-DSC PostScript comments that some
+                        # tools mistakenly put in the Comments section
+                        pass
+                    else:
+                        raise OSError("bad EPS header")
+
+            s_raw = fp.readline()
+            s = s_raw.strip("\r\n")
+
+            if s and s[:1] != "%":
+                break
+
+        #
+        # Scan for an "ImageData" descriptor
+
+        while s[:1] == "%":
+
+            if len(s) > 255:
+                raise SyntaxError("not an EPS file")
+
+            if s[:11] == "%ImageData:":
+                # Encoded bitmapped image.
+                x, y, bi, mo = s[11:].split(None, 7)[:4]
+
+                if int(bi) != 8:
+                    break
+                try:
+                    self.mode = self.mode_map[int(mo)]
+                except ValueError:
+                    break
+
+                self._size = int(x), int(y)
+                return
+
+            s = fp.readline().strip("\r\n")
+            if not s:
+                break
+
+        if not box:
+            raise OSError("cannot determine EPS bounding box")
+
+    def _find_offset(self, fp):
+
+        s = fp.read(160)
+
+        if s[:4] == b"%!PS":
+            # for HEAD without binary preview
+            fp.seek(0, io.SEEK_END)
+            length = fp.tell()
+            offset = 0
+        elif i32(s[0:4]) == 0xC6D3D0C5:
+            # FIX for: Some EPS file not handled correctly / issue #302
+            # EPS can contain binary data
+            # or start directly with latin coding
+            # more info see:
+            # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
+            offset = i32(s[4:8])
+            length = i32(s[8:12])
+        else:
+            raise SyntaxError("not an EPS file")
+
+        return (length, offset)
+
+    def load(self, scale=1):
+        # Load EPS via Ghostscript
+        if not self.tile:
+            return
+        self.im = Ghostscript(self.tile, self.size, self.fp, scale)
+        self.mode = self.im.mode
+        self._size = self.im.size
+        self.tile = []
+
+    def load_seek(self, *args, **kwargs):
+        # we can't incrementally load, so force ImageFile.parser to
+        # use our custom load method by defining this method.
+        pass
+
+
+#
+# --------------------------------------------------------------------
+
+
+def _save(im, fp, filename, eps=1):
+    """EPS Writer for the Python Imaging Library."""
+
+    #
+    # make sure image data is available
+    im.load()
+
+    #
+    # determine PostScript image mode
+    if im.mode == "L":
+        operator = (8, 1, "image")
+    elif im.mode == "RGB":
+        operator = (8, 3, "false 3 colorimage")
+    elif im.mode == "CMYK":
+        operator = (8, 4, "false 4 colorimage")
+    else:
+        raise ValueError("image mode is not supported")
+
+    base_fp = fp
+    wrapped_fp = False
+    if fp != sys.stdout:
+        fp = io.TextIOWrapper(fp, encoding="latin-1")
+        wrapped_fp = True
+
+    try:
+        if eps:
+            #
+            # write EPS header
+            fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
+            fp.write("%%Creator: PIL 0.1 EpsEncode\n")
+            # fp.write("%%CreationDate: %s"...)
+            fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
+            fp.write("%%Pages: 1\n")
+            fp.write("%%EndComments\n")
+            fp.write("%%Page: 1 1\n")
+            fp.write("%%ImageData: %d %d " % im.size)
+            fp.write('%d %d 0 1 1 "%s"\n' % operator)
+
+        #
+        # image header
+        fp.write("gsave\n")
+        fp.write("10 dict begin\n")
+        fp.write(f"/buf {im.size[0] * operator[1]} string def\n")
+        fp.write("%d %d scale\n" % im.size)
+        fp.write("%d %d 8\n" % im.size)  # <= bits
+        fp.write(f"[{im.size[0]} 0 0 -{im.size[1]} 0 {im.size[1]}]\n")
+        fp.write("{ currentfile buf readhexstring pop } bind\n")
+        fp.write(operator[2] + "\n")
+        if hasattr(fp, "flush"):
+            fp.flush()
+
+        ImageFile._save(im, base_fp, [("eps", (0, 0) + im.size, 0, None)])
+
+        fp.write("\n%%%%EndBinary\n")
+        fp.write("grestore end\n")
+        if hasattr(fp, "flush"):
+            fp.flush()
+    finally:
+        if wrapped_fp:
+            fp.detach()
+
+
+#
+# --------------------------------------------------------------------
+
+
+Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
+
+Image.register_save(EpsImageFile.format, _save)
+
+Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
+
+Image.register_mime(EpsImageFile.format, "application/postscript")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/EpsImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: de95b45ef2cef445ca5a0a5a1be71657
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 318 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ExifTags.py

@@ -0,0 +1,318 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# EXIF tags
+#
+# Copyright (c) 2003 by Secret Labs AB
+#
+# See the README file for information on usage and redistribution.
+#
+
+"""
+This module provides constants and clear-text names for various
+well-known EXIF tags.
+"""
+
+
+TAGS = {
+    # possibly incomplete
+    0x000B: "ProcessingSoftware",
+    0x00FE: "NewSubfileType",
+    0x00FF: "SubfileType",
+    0x0100: "ImageWidth",
+    0x0101: "ImageLength",
+    0x0102: "BitsPerSample",
+    0x0103: "Compression",
+    0x0106: "PhotometricInterpretation",
+    0x0107: "Thresholding",
+    0x0108: "CellWidth",
+    0x0109: "CellLength",
+    0x010A: "FillOrder",
+    0x010D: "DocumentName",
+    0x010E: "ImageDescription",
+    0x010F: "Make",
+    0x0110: "Model",
+    0x0111: "StripOffsets",
+    0x0112: "Orientation",
+    0x0115: "SamplesPerPixel",
+    0x0116: "RowsPerStrip",
+    0x0117: "StripByteCounts",
+    0x0118: "MinSampleValue",
+    0x0119: "MaxSampleValue",
+    0x011A: "XResolution",
+    0x011B: "YResolution",
+    0x011C: "PlanarConfiguration",
+    0x011D: "PageName",
+    0x0120: "FreeOffsets",
+    0x0121: "FreeByteCounts",
+    0x0122: "GrayResponseUnit",
+    0x0123: "GrayResponseCurve",
+    0x0124: "T4Options",
+    0x0125: "T6Options",
+    0x0128: "ResolutionUnit",
+    0x0129: "PageNumber",
+    0x012D: "TransferFunction",
+    0x0131: "Software",
+    0x0132: "DateTime",
+    0x013B: "Artist",
+    0x013C: "HostComputer",
+    0x013D: "Predictor",
+    0x013E: "WhitePoint",
+    0x013F: "PrimaryChromaticities",
+    0x0140: "ColorMap",
+    0x0141: "HalftoneHints",
+    0x0142: "TileWidth",
+    0x0143: "TileLength",
+    0x0144: "TileOffsets",
+    0x0145: "TileByteCounts",
+    0x014A: "SubIFDs",
+    0x014C: "InkSet",
+    0x014D: "InkNames",
+    0x014E: "NumberOfInks",
+    0x0150: "DotRange",
+    0x0151: "TargetPrinter",
+    0x0152: "ExtraSamples",
+    0x0153: "SampleFormat",
+    0x0154: "SMinSampleValue",
+    0x0155: "SMaxSampleValue",
+    0x0156: "TransferRange",
+    0x0157: "ClipPath",
+    0x0158: "XClipPathUnits",
+    0x0159: "YClipPathUnits",
+    0x015A: "Indexed",
+    0x015B: "JPEGTables",
+    0x015F: "OPIProxy",
+    0x0200: "JPEGProc",
+    0x0201: "JpegIFOffset",
+    0x0202: "JpegIFByteCount",
+    0x0203: "JpegRestartInterval",
+    0x0205: "JpegLosslessPredictors",
+    0x0206: "JpegPointTransforms",
+    0x0207: "JpegQTables",
+    0x0208: "JpegDCTables",
+    0x0209: "JpegACTables",
+    0x0211: "YCbCrCoefficients",
+    0x0212: "YCbCrSubSampling",
+    0x0213: "YCbCrPositioning",
+    0x0214: "ReferenceBlackWhite",
+    0x02BC: "XMLPacket",
+    0x1000: "RelatedImageFileFormat",
+    0x1001: "RelatedImageWidth",
+    0x1002: "RelatedImageLength",
+    0x4746: "Rating",
+    0x4749: "RatingPercent",
+    0x800D: "ImageID",
+    0x828D: "CFARepeatPatternDim",
+    0x828E: "CFAPattern",
+    0x828F: "BatteryLevel",
+    0x8298: "Copyright",
+    0x829A: "ExposureTime",
+    0x829D: "FNumber",
+    0x83BB: "IPTCNAA",
+    0x8649: "ImageResources",
+    0x8769: "ExifOffset",
+    0x8773: "InterColorProfile",
+    0x8822: "ExposureProgram",
+    0x8824: "SpectralSensitivity",
+    0x8825: "GPSInfo",
+    0x8827: "ISOSpeedRatings",
+    0x8828: "OECF",
+    0x8829: "Interlace",
+    0x882A: "TimeZoneOffset",
+    0x882B: "SelfTimerMode",
+    0x9000: "ExifVersion",
+    0x9003: "DateTimeOriginal",
+    0x9004: "DateTimeDigitized",
+    0x9101: "ComponentsConfiguration",
+    0x9102: "CompressedBitsPerPixel",
+    0x9201: "ShutterSpeedValue",
+    0x9202: "ApertureValue",
+    0x9203: "BrightnessValue",
+    0x9204: "ExposureBiasValue",
+    0x9205: "MaxApertureValue",
+    0x9206: "SubjectDistance",
+    0x9207: "MeteringMode",
+    0x9208: "LightSource",
+    0x9209: "Flash",
+    0x920A: "FocalLength",
+    0x920B: "FlashEnergy",
+    0x920C: "SpatialFrequencyResponse",
+    0x920D: "Noise",
+    0x9211: "ImageNumber",
+    0x9212: "SecurityClassification",
+    0x9213: "ImageHistory",
+    0x9214: "SubjectLocation",
+    0x9215: "ExposureIndex",
+    0x9216: "TIFF/EPStandardID",
+    0x927C: "MakerNote",
+    0x9286: "UserComment",
+    0x9290: "SubsecTime",
+    0x9291: "SubsecTimeOriginal",
+    0x9292: "SubsecTimeDigitized",
+    0x9400: "AmbientTemperature",
+    0x9401: "Humidity",
+    0x9402: "Pressure",
+    0x9403: "WaterDepth",
+    0x9404: "Acceleration",
+    0x9405: "CameraElevationAngle",
+    0x9C9B: "XPTitle",
+    0x9C9C: "XPComment",
+    0x9C9D: "XPAuthor",
+    0x9C9E: "XPKeywords",
+    0x9C9F: "XPSubject",
+    0xA000: "FlashPixVersion",
+    0xA001: "ColorSpace",
+    0xA002: "ExifImageWidth",
+    0xA003: "ExifImageHeight",
+    0xA004: "RelatedSoundFile",
+    0xA005: "ExifInteroperabilityOffset",
+    0xA20B: "FlashEnergy",
+    0xA20C: "SpatialFrequencyResponse",
+    0xA20E: "FocalPlaneXResolution",
+    0xA20F: "FocalPlaneYResolution",
+    0xA210: "FocalPlaneResolutionUnit",
+    0xA214: "SubjectLocation",
+    0xA215: "ExposureIndex",
+    0xA217: "SensingMethod",
+    0xA300: "FileSource",
+    0xA301: "SceneType",
+    0xA302: "CFAPattern",
+    0xA401: "CustomRendered",
+    0xA402: "ExposureMode",
+    0xA403: "WhiteBalance",
+    0xA404: "DigitalZoomRatio",
+    0xA405: "FocalLengthIn35mmFilm",
+    0xA406: "SceneCaptureType",
+    0xA407: "GainControl",
+    0xA408: "Contrast",
+    0xA409: "Saturation",
+    0xA40A: "Sharpness",
+    0xA40B: "DeviceSettingDescription",
+    0xA40C: "SubjectDistanceRange",
+    0xA420: "ImageUniqueID",
+    0xA430: "CameraOwnerName",
+    0xA431: "BodySerialNumber",
+    0xA432: "LensSpecification",
+    0xA433: "LensMake",
+    0xA434: "LensModel",
+    0xA435: "LensSerialNumber",
+    0xA500: "Gamma",
+    0xC4A5: "PrintImageMatching",
+    0xC612: "DNGVersion",
+    0xC613: "DNGBackwardVersion",
+    0xC614: "UniqueCameraModel",
+    0xC615: "LocalizedCameraModel",
+    0xC616: "CFAPlaneColor",
+    0xC617: "CFALayout",
+    0xC618: "LinearizationTable",
+    0xC619: "BlackLevelRepeatDim",
+    0xC61A: "BlackLevel",
+    0xC61B: "BlackLevelDeltaH",
+    0xC61C: "BlackLevelDeltaV",
+    0xC61D: "WhiteLevel",
+    0xC61E: "DefaultScale",
+    0xC61F: "DefaultCropOrigin",
+    0xC620: "DefaultCropSize",
+    0xC621: "ColorMatrix1",
+    0xC622: "ColorMatrix2",
+    0xC623: "CameraCalibration1",
+    0xC624: "CameraCalibration2",
+    0xC625: "ReductionMatrix1",
+    0xC626: "ReductionMatrix2",
+    0xC627: "AnalogBalance",
+    0xC628: "AsShotNeutral",
+    0xC629: "AsShotWhiteXY",
+    0xC62A: "BaselineExposure",
+    0xC62B: "BaselineNoise",
+    0xC62C: "BaselineSharpness",
+    0xC62D: "BayerGreenSplit",
+    0xC62E: "LinearResponseLimit",
+    0xC62F: "CameraSerialNumber",
+    0xC630: "LensInfo",
+    0xC631: "ChromaBlurRadius",
+    0xC632: "AntiAliasStrength",
+    0xC633: "ShadowScale",
+    0xC634: "DNGPrivateData",
+    0xC635: "MakerNoteSafety",
+    0xC65A: "CalibrationIlluminant1",
+    0xC65B: "CalibrationIlluminant2",
+    0xC65C: "BestQualityScale",
+    0xC65D: "RawDataUniqueID",
+    0xC68B: "OriginalRawFileName",
+    0xC68C: "OriginalRawFileData",
+    0xC68D: "ActiveArea",
+    0xC68E: "MaskedAreas",
+    0xC68F: "AsShotICCProfile",
+    0xC690: "AsShotPreProfileMatrix",
+    0xC691: "CurrentICCProfile",
+    0xC692: "CurrentPreProfileMatrix",
+    0xC6BF: "ColorimetricReference",
+    0xC6F3: "CameraCalibrationSignature",
+    0xC6F4: "ProfileCalibrationSignature",
+    0xC6F6: "AsShotProfileName",
+    0xC6F7: "NoiseReductionApplied",
+    0xC6F8: "ProfileName",
+    0xC6F9: "ProfileHueSatMapDims",
+    0xC6FA: "ProfileHueSatMapData1",
+    0xC6FB: "ProfileHueSatMapData2",
+    0xC6FC: "ProfileToneCurve",
+    0xC6FD: "ProfileEmbedPolicy",
+    0xC6FE: "ProfileCopyright",
+    0xC714: "ForwardMatrix1",
+    0xC715: "ForwardMatrix2",
+    0xC716: "PreviewApplicationName",
+    0xC717: "PreviewApplicationVersion",
+    0xC718: "PreviewSettingsName",
+    0xC719: "PreviewSettingsDigest",
+    0xC71A: "PreviewColorSpace",
+    0xC71B: "PreviewDateTime",
+    0xC71C: "RawImageDigest",
+    0xC71D: "OriginalRawFileDigest",
+    0xC71E: "SubTileBlockSize",
+    0xC71F: "RowInterleaveFactor",
+    0xC725: "ProfileLookTableDims",
+    0xC726: "ProfileLookTableData",
+    0xC740: "OpcodeList1",
+    0xC741: "OpcodeList2",
+    0xC74E: "OpcodeList3",
+    0xC761: "NoiseProfile",
+}
+"""Maps EXIF tags to tag names."""
+
+
+GPSTAGS = {
+    0: "GPSVersionID",
+    1: "GPSLatitudeRef",
+    2: "GPSLatitude",
+    3: "GPSLongitudeRef",
+    4: "GPSLongitude",
+    5: "GPSAltitudeRef",
+    6: "GPSAltitude",
+    7: "GPSTimeStamp",
+    8: "GPSSatellites",
+    9: "GPSStatus",
+    10: "GPSMeasureMode",
+    11: "GPSDOP",
+    12: "GPSSpeedRef",
+    13: "GPSSpeed",
+    14: "GPSTrackRef",
+    15: "GPSTrack",
+    16: "GPSImgDirectionRef",
+    17: "GPSImgDirection",
+    18: "GPSMapDatum",
+    19: "GPSDestLatitudeRef",
+    20: "GPSDestLatitude",
+    21: "GPSDestLongitudeRef",
+    22: "GPSDestLongitude",
+    23: "GPSDestBearingRef",
+    24: "GPSDestBearing",
+    25: "GPSDestDistanceRef",
+    26: "GPSDestDistance",
+    27: "GPSProcessingMethod",
+    28: "GPSAreaInformation",
+    29: "GPSDateStamp",
+    30: "GPSDifferential",
+    31: "GPSHPositioningError",
+}
+"""Maps EXIF GPS tags to tag names."""

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/ExifTags.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8aabb15e392a245f8aee46ebc68d91ba
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FitsStubImagePlugin.py

@@ -0,0 +1,76 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# FITS stub adapter
+#
+# Copyright (c) 1998-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+    """
+    Install application-specific FITS image handler.
+
+    :param handler: Handler object.
+    """
+    global _handler
+    _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+    return prefix[:6] == b"SIMPLE"
+
+
+class FITSStubImageFile(ImageFile.StubImageFile):
+
+    format = "FITS"
+    format_description = "FITS"
+
+    def _open(self):
+
+        offset = self.fp.tell()
+
+        if not _accept(self.fp.read(6)):
+            raise SyntaxError("Not a FITS file")
+
+        # FIXME: add more sanity checks here; mandatory header items
+        # include SIMPLE, BITPIX, NAXIS, etc.
+
+        self.fp.seek(offset)
+
+        # make something up
+        self.mode = "F"
+        self._size = 1, 1
+
+        loader = self._load()
+        if loader:
+            loader.open(self)
+
+    def _load(self):
+        return _handler
+
+
+def _save(im, fp, filename):
+    if _handler is None or not hasattr("_handler", "save"):
+        raise OSError("FITS save handler not installed")
+    _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept)
+Image.register_save(FITSStubImageFile.format, _save)
+
+Image.register_extensions(FITSStubImageFile.format, [".fit", ".fits"])

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FitsStubImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 674a906f3ee4c408792d6500ab09952a
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 172 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FliImagePlugin.py

@@ -0,0 +1,172 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# FLI/FLC file handling.
+#
+# History:
+#       95-09-01 fl     Created
+#       97-01-03 fl     Fixed parser, setup decoder tile
+#       98-07-15 fl     Renamed offset attribute to avoid name clash
+#
+# Copyright (c) Secret Labs AB 1997-98.
+# Copyright (c) Fredrik Lundh 1995-97.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+from . import Image, ImageFile, ImagePalette
+from ._binary import i8
+from ._binary import i16le as i16
+from ._binary import i32le as i32
+from ._binary import o8
+
+#
+# decoder
+
+
+def _accept(prefix):
+    return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
+
+
+##
+# Image plugin for the FLI/FLC animation format.  Use the <b>seek</b>
+# method to load individual frames.
+
+
+class FliImageFile(ImageFile.ImageFile):
+
+    format = "FLI"
+    format_description = "Autodesk FLI/FLC Animation"
+    _close_exclusive_fp_after_loading = False
+
+    def _open(self):
+
+        # HEAD
+        s = self.fp.read(128)
+        if not (
+            _accept(s)
+            and i16(s[14:16]) in [0, 3]  # flags
+            and s[20:22] == b"\x00\x00"  # reserved
+        ):
+            raise SyntaxError("not an FLI/FLC file")
+
+        # frames
+        self.n_frames = i16(s[6:8])
+        self.is_animated = self.n_frames > 1
+
+        # image characteristics
+        self.mode = "P"
+        self._size = i16(s[8:10]), i16(s[10:12])
+
+        # animation speed
+        duration = i32(s[16:20])
+        magic = i16(s[4:6])
+        if magic == 0xAF11:
+            duration = (duration * 1000) // 70
+        self.info["duration"] = duration
+
+        # look for palette
+        palette = [(a, a, a) for a in range(256)]
+
+        s = self.fp.read(16)
+
+        self.__offset = 128
+
+        if i16(s[4:6]) == 0xF100:
+            # prefix chunk; ignore it
+            self.__offset = self.__offset + i32(s)
+            s = self.fp.read(16)
+
+        if i16(s[4:6]) == 0xF1FA:
+            # look for palette chunk
+            s = self.fp.read(6)
+            if i16(s[4:6]) == 11:
+                self._palette(palette, 2)
+            elif i16(s[4:6]) == 4:
+                self._palette(palette, 0)
+
+        palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
+        self.palette = ImagePalette.raw("RGB", b"".join(palette))
+
+        # set things up to decode first frame
+        self.__frame = -1
+        self.__fp = self.fp
+        self.__rewind = self.fp.tell()
+        self.seek(0)
+
+    def _palette(self, palette, shift):
+        # load palette
+
+        i = 0
+        for e in range(i16(self.fp.read(2))):
+            s = self.fp.read(2)
+            i = i + i8(s[0])
+            n = i8(s[1])
+            if n == 0:
+                n = 256
+            s = self.fp.read(n * 3)
+            for n in range(0, len(s), 3):
+                r = i8(s[n]) << shift
+                g = i8(s[n + 1]) << shift
+                b = i8(s[n + 2]) << shift
+                palette[i] = (r, g, b)
+                i += 1
+
+    def seek(self, frame):
+        if not self._seek_check(frame):
+            return
+        if frame < self.__frame:
+            self._seek(0)
+
+        for f in range(self.__frame + 1, frame + 1):
+            self._seek(f)
+
+    def _seek(self, frame):
+        if frame == 0:
+            self.__frame = -1
+            self.__fp.seek(self.__rewind)
+            self.__offset = 128
+        else:
+            # ensure that the previous frame was loaded
+            self.load()
+
+        if frame != self.__frame + 1:
+            raise ValueError(f"cannot seek to frame {frame}")
+        self.__frame = frame
+
+        # move to next frame
+        self.fp = self.__fp
+        self.fp.seek(self.__offset)
+
+        s = self.fp.read(4)
+        if not s:
+            raise EOFError
+
+        framesize = i32(s)
+
+        self.decodermaxblock = framesize
+        self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
+
+        self.__offset += framesize
+
+    def tell(self):
+        return self.__frame
+
+    def _close__fp(self):
+        try:
+            if self.__fp != self.fp:
+                self.__fp.close()
+        except AttributeError:
+            pass
+        finally:
+            self.__fp = None
+
+
+#
+# registry
+
+Image.register_open(FliImageFile.format, FliImageFile, _accept)
+
+Image.register_extensions(FliImageFile.format, [".fli", ".flc"])

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FliImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 869289d8b90d848fabfbe6ea72333ee8
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 111 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FontFile.py

@@ -0,0 +1,111 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# base class for raster font file parsers
+#
+# history:
+# 1997-06-05 fl   created
+# 1997-08-19 fl   restrict image width
+#
+# Copyright (c) 1997-1998 by Secret Labs AB
+# Copyright (c) 1997-1998 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+import os
+
+from . import Image, _binary
+
+WIDTH = 800
+
+
+def puti16(fp, values):
+    """Write network order (big-endian) 16-bit sequence"""
+    for v in values:
+        if v < 0:
+            v += 65536
+        fp.write(_binary.o16be(v))
+
+
+class FontFile:
+    """Base class for raster font file handlers."""
+
+    bitmap = None
+
+    def __init__(self):
+
+        self.info = {}
+        self.glyph = [None] * 256
+
+    def __getitem__(self, ix):
+        return self.glyph[ix]
+
+    def compile(self):
+        """Create metrics and bitmap"""
+
+        if self.bitmap:
+            return
+
+        # create bitmap large enough to hold all data
+        h = w = maxwidth = 0
+        lines = 1
+        for glyph in self:
+            if glyph:
+                d, dst, src, im = glyph
+                h = max(h, src[3] - src[1])
+                w = w + (src[2] - src[0])
+                if w > WIDTH:
+                    lines += 1
+                    w = src[2] - src[0]
+                maxwidth = max(maxwidth, w)
+
+        xsize = maxwidth
+        ysize = lines * h
+
+        if xsize == 0 and ysize == 0:
+            return ""
+
+        self.ysize = h
+
+        # paste glyphs into bitmap
+        self.bitmap = Image.new("1", (xsize, ysize))
+        self.metrics = [None] * 256
+        x = y = 0
+        for i in range(256):
+            glyph = self[i]
+            if glyph:
+                d, dst, src, im = glyph
+                xx = src[2] - src[0]
+                # yy = src[3] - src[1]
+                x0, y0 = x, y
+                x = x + xx
+                if x > WIDTH:
+                    x, y = 0, y + h
+                    x0, y0 = x, y
+                    x = xx
+                s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
+                self.bitmap.paste(im.crop(src), s)
+                self.metrics[i] = d, dst, s
+
+    def save(self, filename):
+        """Save font"""
+
+        self.compile()
+
+        # font data
+        self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
+
+        # font metrics
+        with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
+            fp.write(b"PILfont\n")
+            fp.write(f";;;;;;{self.ysize};\n".encode("ascii"))  # HACK!!!
+            fp.write(b"DATA\n")
+            for id in range(256):
+                m = self.metrics[id]
+                if not m:
+                    puti16(fp, [0] * 10)
+                else:
+                    puti16(fp, m[0] + m[1] + m[2])

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FontFile.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: cc66a6eb8c6944881b749a77291315d7
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 243 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FpxImagePlugin.py

@@ -0,0 +1,243 @@
+#
+# THIS IS WORK IN PROGRESS
+#
+# The Python Imaging Library.
+# $Id$
+#
+# FlashPix support for PIL
+#
+# History:
+# 97-01-25 fl   Created (reads uncompressed RGB images only)
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+import olefile
+
+from . import Image, ImageFile
+from ._binary import i8
+from ._binary import i32le as i32
+
+# we map from colour field tuples to (mode, rawmode) descriptors
+MODES = {
+    # opacity
+    (0x00007FFE): ("A", "L"),
+    # monochrome
+    (0x00010000,): ("L", "L"),
+    (0x00018000, 0x00017FFE): ("RGBA", "LA"),
+    # photo YCC
+    (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
+    (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
+    # standard RGB (NIFRGB)
+    (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
+    (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
+}
+
+
+#
+# --------------------------------------------------------------------
+
+
+def _accept(prefix):
+    return prefix[:8] == olefile.MAGIC
+
+
+##
+# Image plugin for the FlashPix images.
+
+
+class FpxImageFile(ImageFile.ImageFile):
+
+    format = "FPX"
+    format_description = "FlashPix"
+
+    def _open(self):
+        #
+        # read the OLE directory and see if this is a likely
+        # to be a FlashPix file
+
+        try:
+            self.ole = olefile.OleFileIO(self.fp)
+        except OSError as e:
+            raise SyntaxError("not an FPX file; invalid OLE file") from e
+
+        if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
+            raise SyntaxError("not an FPX file; bad root CLSID")
+
+        self._open_index(1)
+
+    def _open_index(self, index=1):
+        #
+        # get the Image Contents Property Set
+
+        prop = self.ole.getproperties(
+            [f"Data Object Store {index:06d}", "\005Image Contents"]
+        )
+
+        # size (highest resolution)
+
+        self._size = prop[0x1000002], prop[0x1000003]
+
+        size = max(self.size)
+        i = 1
+        while size > 64:
+            size = size / 2
+            i += 1
+        self.maxid = i - 1
+
+        # mode.  instead of using a single field for this, flashpix
+        # requires you to specify the mode for each channel in each
+        # resolution subimage, and leaves it to the decoder to make
+        # sure that they all match.  for now, we'll cheat and assume
+        # that this is always the case.
+
+        id = self.maxid << 16
+
+        s = prop[0x2000002 | id]
+
+        colors = []
+        bands = i32(s, 4)
+        if bands > 4:
+            raise OSError("Invalid number of bands")
+        for i in range(bands):
+            # note: for now, we ignore the "uncalibrated" flag
+            colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
+
+        self.mode, self.rawmode = MODES[tuple(colors)]
+
+        # load JPEG tables, if any
+        self.jpeg = {}
+        for i in range(256):
+            id = 0x3000001 | (i << 16)
+            if id in prop:
+                self.jpeg[i] = prop[id]
+
+        self._open_subimage(1, self.maxid)
+
+    def _open_subimage(self, index=1, subimage=0):
+        #
+        # setup tile descriptors for a given subimage
+
+        stream = [
+            f"Data Object Store {index:06d}",
+            f"Resolution {subimage:04d}",
+            "Subimage 0000 Header",
+        ]
+
+        fp = self.ole.openstream(stream)
+
+        # skip prefix
+        fp.read(28)
+
+        # header stream
+        s = fp.read(36)
+
+        size = i32(s, 4), i32(s, 8)
+        # tilecount = i32(s, 12)
+        tilesize = i32(s, 16), i32(s, 20)
+        # channels = i32(s, 24)
+        offset = i32(s, 28)
+        length = i32(s, 32)
+
+        if size != self.size:
+            raise OSError("subimage mismatch")
+
+        # get tile descriptors
+        fp.seek(28 + offset)
+        s = fp.read(i32(s, 12) * length)
+
+        x = y = 0
+        xsize, ysize = size
+        xtile, ytile = tilesize
+        self.tile = []
+
+        for i in range(0, len(s), length):
+
+            compression = i32(s, i + 8)
+
+            if compression == 0:
+                self.tile.append(
+                    (
+                        "raw",
+                        (x, y, x + xtile, y + ytile),
+                        i32(s, i) + 28,
+                        (self.rawmode),
+                    )
+                )
+
+            elif compression == 1:
+
+                # FIXME: the fill decoder is not implemented
+                self.tile.append(
+                    (
+                        "fill",
+                        (x, y, x + xtile, y + ytile),
+                        i32(s, i) + 28,
+                        (self.rawmode, s[12:16]),
+                    )
+                )
+
+            elif compression == 2:
+
+                internal_color_conversion = i8(s[14])
+                jpeg_tables = i8(s[15])
+                rawmode = self.rawmode
+
+                if internal_color_conversion:
+                    # The image is stored as usual (usually YCbCr).
+                    if rawmode == "RGBA":
+                        # For "RGBA", data is stored as YCbCrA based on
+                        # negative RGB. The following trick works around
+                        # this problem :
+                        jpegmode, rawmode = "YCbCrK", "CMYK"
+                    else:
+                        jpegmode = None  # let the decoder decide
+
+                else:
+                    # The image is stored as defined by rawmode
+                    jpegmode = rawmode
+
+                self.tile.append(
+                    (
+                        "jpeg",
+                        (x, y, x + xtile, y + ytile),
+                        i32(s, i) + 28,
+                        (rawmode, jpegmode),
+                    )
+                )
+
+                # FIXME: jpeg tables are tile dependent; the prefix
+                # data must be placed in the tile descriptor itself!
+
+                if jpeg_tables:
+                    self.tile_prefix = self.jpeg[jpeg_tables]
+
+            else:
+                raise OSError("unknown/invalid compression")
+
+            x = x + xtile
+            if x >= xsize:
+                x, y = 0, y + ytile
+                if y >= ysize:
+                    break  # isn't really required
+
+        self.stream = stream
+        self.fp = None
+
+    def load(self):
+
+        if not self.fp:
+            self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
+
+        return ImageFile.ImageFile.load(self)
+
+
+#
+# --------------------------------------------------------------------
+
+
+Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
+
+Image.register_extension(FpxImageFile.format, ".fpx")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FpxImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 309d762aa956040bc8da79473b8e843d
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 106 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FtexImagePlugin.py

@@ -0,0 +1,106 @@
+"""
+A Pillow loader for .ftc and .ftu files (FTEX)
+Jerome Leclanche <jerome@leclan.ch>
+
+The contents of this file are hereby released in the public domain (CC0)
+Full text of the CC0 license:
+  https://creativecommons.org/publicdomain/zero/1.0/
+
+Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
+
+The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
+packed custom format called FTEX. This file format uses file extensions FTC
+and FTU.
+* FTC files are compressed textures (using standard texture compression).
+* FTU files are not compressed.
+Texture File Format
+The FTC and FTU texture files both use the same format. This
+has the following structure:
+{header}
+{format_directory}
+{data}
+Where:
+{header} = {
+    u32:magic,
+    u32:version,
+    u32:width,
+    u32:height,
+    u32:mipmap_count,
+    u32:format_count
+}
+
+* The "magic" number is "FTEX".
+* "width" and "height" are the dimensions of the texture.
+* "mipmap_count" is the number of mipmaps in the texture.
+* "format_count" is the number of texture formats (different versions of the
+same texture) in this file.
+
+{format_directory} = format_count * { u32:format, u32:where }
+
+The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
+uncompressed textures.
+The texture data for a format starts at the position "where" in the file.
+
+Each set of texture data in the file has the following structure:
+{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
+* "mipmap_size" is the number of bytes in that mip level. For compressed
+textures this is the size of the texture data compressed with DXT1. For 24 bit
+uncompressed textures, this is 3 * width * height. Following this are the image
+bytes for that mipmap level.
+
+Note: All data is stored in little-Endian (Intel) byte order.
+"""
+
+import struct
+from io import BytesIO
+
+from . import Image, ImageFile
+
+MAGIC = b"FTEX"
+FORMAT_DXT1 = 0
+FORMAT_UNCOMPRESSED = 1
+
+
+class FtexImageFile(ImageFile.ImageFile):
+    format = "FTEX"
+    format_description = "Texture File Format (IW2:EOC)"
+
+    def _open(self):
+        struct.unpack("<I", self.fp.read(4))  # magic
+        struct.unpack("<i", self.fp.read(4))  # version
+        self._size = struct.unpack("<2i", self.fp.read(8))
+        mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
+
+        self.mode = "RGB"
+
+        # Only support single-format files.
+        # I don't know of any multi-format file.
+        assert format_count == 1
+
+        format, where = struct.unpack("<2i", self.fp.read(8))
+        self.fp.seek(where)
+        (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
+
+        data = self.fp.read(mipmap_size)
+
+        if format == FORMAT_DXT1:
+            self.mode = "RGBA"
+            self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
+        elif format == FORMAT_UNCOMPRESSED:
+            self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
+        else:
+            raise ValueError(f"Invalid texture compression format: {repr(format)}")
+
+        self.fp.close()
+        self.fp = BytesIO(data)
+
+    def load_seek(self, pos):
+        pass
+
+
+def _validate(prefix):
+    return prefix[:4] == MAGIC
+
+
+Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
+Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/FtexImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 3dba91bced771426fa910bf0746853b9
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 100 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GbrImagePlugin.py

@@ -0,0 +1,100 @@
+#
+# The Python Imaging Library
+#
+# load a GIMP brush file
+#
+# History:
+#       96-03-14 fl     Created
+#       16-01-08 es     Version 2
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1996.
+# Copyright (c) Eric Soroos 2016.
+#
+# See the README file for information on usage and redistribution.
+#
+#
+# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for
+# format documentation.
+#
+# This code Interprets version 1 and 2 .gbr files.
+# Version 1 files are obsolete, and should not be used for new
+#   brushes.
+# Version 2 files are saved by GIMP v2.8 (at least)
+# Version 3 files have a format specifier of 18 for 16bit floats in
+#   the color depth field. This is currently unsupported by Pillow.
+
+from . import Image, ImageFile
+from ._binary import i32be as i32
+
+
+def _accept(prefix):
+    return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
+
+
+##
+# Image plugin for the GIMP brush format.
+
+
+class GbrImageFile(ImageFile.ImageFile):
+
+    format = "GBR"
+    format_description = "GIMP brush file"
+
+    def _open(self):
+        header_size = i32(self.fp.read(4))
+        version = i32(self.fp.read(4))
+        if header_size < 20:
+            raise SyntaxError("not a GIMP brush")
+        if version not in (1, 2):
+            raise SyntaxError(f"Unsupported GIMP brush version: {version}")
+
+        width = i32(self.fp.read(4))
+        height = i32(self.fp.read(4))
+        color_depth = i32(self.fp.read(4))
+        if width <= 0 or height <= 0:
+            raise SyntaxError("not a GIMP brush")
+        if color_depth not in (1, 4):
+            raise SyntaxError(f"Unsupported GIMP brush color depth: {color_depth}")
+
+        if version == 1:
+            comment_length = header_size - 20
+        else:
+            comment_length = header_size - 28
+            magic_number = self.fp.read(4)
+            if magic_number != b"GIMP":
+                raise SyntaxError("not a GIMP brush, bad magic number")
+            self.info["spacing"] = i32(self.fp.read(4))
+
+        comment = self.fp.read(comment_length)[:-1]
+
+        if color_depth == 1:
+            self.mode = "L"
+        else:
+            self.mode = "RGBA"
+
+        self._size = width, height
+
+        self.info["comment"] = comment
+
+        # Image might not be small
+        Image._decompression_bomb_check(self.size)
+
+        # Data is an uncompressed block of w * h * bytes/pixel
+        self._data_size = width * height * color_depth
+
+    def load(self):
+        if self.im:
+            # Already loaded
+            return
+
+        self.im = Image.core.new(self.mode, self.size)
+        self.frombytes(self.fp.read(self._data_size))
+
+
+#
+# registry
+
+
+Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
+Image.register_extension(GbrImageFile.format, ".gbr")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GbrImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 301073250571b40cd8abf94f79552adf
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 91 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GdImageFile.py

@@ -0,0 +1,91 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# GD file handling
+#
+# History:
+# 1996-04-12 fl   Created
+#
+# Copyright (c) 1997 by Secret Labs AB.
+# Copyright (c) 1996 by Fredrik Lundh.
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+"""
+.. note::
+    This format cannot be automatically recognized, so the
+    class is not registered for use with :py:func:`PIL.Image.open()`.  To open a
+    gd file, use the :py:func:`PIL.GdImageFile.open()` function instead.
+
+.. warning::
+    THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE.  This
+    implementation is provided for convenience and demonstrational
+    purposes only.
+"""
+
+
+from . import ImageFile, ImagePalette, UnidentifiedImageError
+from ._binary import i8
+from ._binary import i16be as i16
+from ._binary import i32be as i32
+
+
+class GdImageFile(ImageFile.ImageFile):
+    """
+    Image plugin for the GD uncompressed format.  Note that this format
+    is not supported by the standard :py:func:`PIL.Image.open()` function.  To use
+    this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
+    use the :py:func:`PIL.GdImageFile.open()` function.
+    """
+
+    format = "GD"
+    format_description = "GD uncompressed images"
+
+    def _open(self):
+
+        # Header
+        s = self.fp.read(1037)
+
+        if not i16(s[:2]) in [65534, 65535]:
+            raise SyntaxError("Not a valid GD 2.x .gd file")
+
+        self.mode = "L"  # FIXME: "P"
+        self._size = i16(s[2:4]), i16(s[4:6])
+
+        trueColor = i8(s[6])
+        trueColorOffset = 2 if trueColor else 0
+
+        # transparency index
+        tindex = i32(s[7 + trueColorOffset : 7 + trueColorOffset + 4])
+        if tindex < 256:
+            self.info["transparency"] = tindex
+
+        self.palette = ImagePalette.raw(
+            "XBGR", s[7 + trueColorOffset + 4 : 7 + trueColorOffset + 4 + 256 * 4]
+        )
+
+        self.tile = [
+            ("raw", (0, 0) + self.size, 7 + trueColorOffset + 4 + 256 * 4, ("L", 0, 1))
+        ]
+
+
+def open(fp, mode="r"):
+    """
+    Load texture from a GD image file.
+
+    :param filename: GD file name, or an opened file handle.
+    :param mode: Optional mode.  In this version, if the mode argument
+        is given, it must be "r".
+    :returns: An image instance.
+    :raises OSError: If the image could not be read.
+    """
+    if mode != "r":
+        raise ValueError("bad mode")
+
+    try:
+        return GdImageFile(fp)
+    except SyntaxError as e:
+        raise UnidentifiedImageError("cannot identify this image file") from e

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GdImageFile.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 87d0a30ad0bf44169be6ed69351c77fa
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 888 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GifImagePlugin.py

@@ -0,0 +1,888 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# GIF file handling
+#
+# History:
+# 1995-09-01 fl   Created
+# 1996-12-14 fl   Added interlace support
+# 1996-12-30 fl   Added animation support
+# 1997-01-05 fl   Added write support, fixed local colour map bug
+# 1997-02-23 fl   Make sure to load raster data in getdata()
+# 1997-07-05 fl   Support external decoder (0.4)
+# 1998-07-09 fl   Handle all modes when saving (0.5)
+# 1998-07-15 fl   Renamed offset attribute to avoid name clash
+# 2001-04-16 fl   Added rewind support (seek to frame 0) (0.6)
+# 2001-04-17 fl   Added palette optimization (0.7)
+# 2002-06-06 fl   Added transparency support for save (0.8)
+# 2004-02-24 fl   Disable interlacing for small images
+#
+# Copyright (c) 1997-2004 by Secret Labs AB
+# Copyright (c) 1995-2004 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+import itertools
+import math
+import os
+import subprocess
+
+from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
+from ._binary import i8
+from ._binary import i16le as i16
+from ._binary import o8
+from ._binary import o16le as o16
+
+# --------------------------------------------------------------------
+# Identify/read GIF files
+
+
+def _accept(prefix):
+    return prefix[:6] in [b"GIF87a", b"GIF89a"]
+
+
+##
+# Image plugin for GIF images.  This plugin supports both GIF87 and
+# GIF89 images.
+
+
+class GifImageFile(ImageFile.ImageFile):
+
+    format = "GIF"
+    format_description = "Compuserve GIF"
+    _close_exclusive_fp_after_loading = False
+
+    global_palette = None
+
+    def data(self):
+        s = self.fp.read(1)
+        if s and i8(s):
+            return self.fp.read(i8(s))
+        return None
+
+    def _open(self):
+
+        # Screen
+        s = self.fp.read(13)
+        if not _accept(s):
+            raise SyntaxError("not a GIF file")
+
+        self.info["version"] = s[:6]
+        self._size = i16(s[6:]), i16(s[8:])
+        self.tile = []
+        flags = i8(s[10])
+        bits = (flags & 7) + 1
+
+        if flags & 128:
+            # get global palette
+            self.info["background"] = i8(s[11])
+            # check if palette contains colour indices
+            p = self.fp.read(3 << bits)
+            for i in range(0, len(p), 3):
+                if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])):
+                    p = ImagePalette.raw("RGB", p)
+                    self.global_palette = self.palette = p
+                    break
+
+        self.__fp = self.fp  # FIXME: hack
+        self.__rewind = self.fp.tell()
+        self._n_frames = None
+        self._is_animated = None
+        self._seek(0)  # get ready to read first frame
+
+    @property
+    def n_frames(self):
+        if self._n_frames is None:
+            current = self.tell()
+            try:
+                while True:
+                    self.seek(self.tell() + 1)
+            except EOFError:
+                self._n_frames = self.tell() + 1
+            self.seek(current)
+        return self._n_frames
+
+    @property
+    def is_animated(self):
+        if self._is_animated is None:
+            if self._n_frames is not None:
+                self._is_animated = self._n_frames != 1
+            else:
+                current = self.tell()
+
+                try:
+                    self.seek(1)
+                    self._is_animated = True
+                except EOFError:
+                    self._is_animated = False
+
+                self.seek(current)
+        return self._is_animated
+
+    def seek(self, frame):
+        if not self._seek_check(frame):
+            return
+        if frame < self.__frame:
+            if frame != 0:
+                self.im = None
+            self._seek(0)
+
+        last_frame = self.__frame
+        for f in range(self.__frame + 1, frame + 1):
+            try:
+                self._seek(f)
+            except EOFError as e:
+                self.seek(last_frame)
+                raise EOFError("no more images in GIF file") from e
+
+    def _seek(self, frame):
+
+        if frame == 0:
+            # rewind
+            self.__offset = 0
+            self.dispose = None
+            self.dispose_extent = [0, 0, 0, 0]  # x0, y0, x1, y1
+            self.__frame = -1
+            self.__fp.seek(self.__rewind)
+            self._prev_im = None
+            self.disposal_method = 0
+        else:
+            # ensure that the previous frame was loaded
+            if not self.im:
+                self.load()
+
+        if frame != self.__frame + 1:
+            raise ValueError(f"cannot seek to frame {frame}")
+        self.__frame = frame
+
+        self.tile = []
+
+        self.fp = self.__fp
+        if self.__offset:
+            # backup to last frame
+            self.fp.seek(self.__offset)
+            while self.data():
+                pass
+            self.__offset = 0
+
+        if self.dispose:
+            self.im.paste(self.dispose, self.dispose_extent)
+
+        from copy import copy
+
+        self.palette = copy(self.global_palette)
+
+        info = {}
+        while True:
+
+            s = self.fp.read(1)
+            if not s or s == b";":
+                break
+
+            elif s == b"!":
+                #
+                # extensions
+                #
+                s = self.fp.read(1)
+                block = self.data()
+                if i8(s) == 249:
+                    #
+                    # graphic control extension
+                    #
+                    flags = i8(block[0])
+                    if flags & 1:
+                        info["transparency"] = i8(block[3])
+                    info["duration"] = i16(block[1:3]) * 10
+
+                    # disposal method - find the value of bits 4 - 6
+                    dispose_bits = 0b00011100 & flags
+                    dispose_bits = dispose_bits >> 2
+                    if dispose_bits:
+                        # only set the dispose if it is not
+                        # unspecified. I'm not sure if this is
+                        # correct, but it seems to prevent the last
+                        # frame from looking odd for some animations
+                        self.disposal_method = dispose_bits
+                elif i8(s) == 254:
+                    #
+                    # comment extension
+                    #
+                    while block:
+                        if "comment" in info:
+                            info["comment"] += block
+                        else:
+                            info["comment"] = block
+                        block = self.data()
+                    continue
+                elif i8(s) == 255:
+                    #
+                    # application extension
+                    #
+                    info["extension"] = block, self.fp.tell()
+                    if block[:11] == b"NETSCAPE2.0":
+                        block = self.data()
+                        if len(block) >= 3 and i8(block[0]) == 1:
+                            info["loop"] = i16(block[1:3])
+                while self.data():
+                    pass
+
+            elif s == b",":
+                #
+                # local image
+                #
+                s = self.fp.read(9)
+
+                # extent
+                x0, y0 = i16(s[0:]), i16(s[2:])
+                x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
+                if x1 > self.size[0] or y1 > self.size[1]:
+                    self._size = max(x1, self.size[0]), max(y1, self.size[1])
+                self.dispose_extent = x0, y0, x1, y1
+                flags = i8(s[8])
+
+                interlace = (flags & 64) != 0
+
+                if flags & 128:
+                    bits = (flags & 7) + 1
+                    self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits))
+
+                # image data
+                bits = i8(self.fp.read(1))
+                self.__offset = self.fp.tell()
+                self.tile = [
+                    ("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
+                ]
+                break
+
+            else:
+                pass
+                # raise OSError, "illegal GIF tag `%x`" % i8(s)
+
+        try:
+            if self.disposal_method < 2:
+                # do not dispose or none specified
+                self.dispose = None
+            elif self.disposal_method == 2:
+                # replace with background colour
+                Image._decompression_bomb_check(self.size)
+                self.dispose = Image.core.fill("P", self.size, self.info["background"])
+            else:
+                # replace with previous contents
+                if self.im:
+                    self.dispose = self.im.copy()
+
+            # only dispose the extent in this frame
+            if self.dispose:
+                self.dispose = self._crop(self.dispose, self.dispose_extent)
+        except (AttributeError, KeyError):
+            pass
+
+        if not self.tile:
+            # self.__fp = None
+            raise EOFError
+
+        for k in ["transparency", "duration", "comment", "extension", "loop"]:
+            if k in info:
+                self.info[k] = info[k]
+            elif k in self.info:
+                del self.info[k]
+
+        self.mode = "L"
+        if self.palette:
+            self.mode = "P"
+
+    def tell(self):
+        return self.__frame
+
+    def load_end(self):
+        ImageFile.ImageFile.load_end(self)
+
+        # if the disposal method is 'do not dispose', transparent
+        # pixels should show the content of the previous frame
+        if self._prev_im and self.disposal_method == 1:
+            # we do this by pasting the updated area onto the previous
+            # frame which we then use as the current image content
+            updated = self._crop(self.im, self.dispose_extent)
+            self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
+            self.im = self._prev_im
+        self._prev_im = self.im.copy()
+
+    def _close__fp(self):
+        try:
+            if self.__fp != self.fp:
+                self.__fp.close()
+        except AttributeError:
+            pass
+        finally:
+            self.__fp = None
+
+
+# --------------------------------------------------------------------
+# Write GIF files
+
+
+RAWMODE = {"1": "L", "L": "L", "P": "P"}
+
+
+def _normalize_mode(im, initial_call=False):
+    """
+    Takes an image (or frame), returns an image in a mode that is appropriate
+    for saving in a Gif.
+
+    It may return the original image, or it may return an image converted to
+    palette or 'L' mode.
+
+    UNDONE: What is the point of mucking with the initial call palette, for
+    an image that shouldn't have a palette, or it would be a mode 'P' and
+    get returned in the RAWMODE clause.
+
+    :param im: Image object
+    :param initial_call: Default false, set to true for a single frame.
+    :returns: Image object
+    """
+    if im.mode in RAWMODE:
+        im.load()
+        return im
+    if Image.getmodebase(im.mode) == "RGB":
+        if initial_call:
+            palette_size = 256
+            if im.palette:
+                palette_size = len(im.palette.getdata()[1]) // 3
+            return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size)
+        else:
+            return im.convert("P")
+    return im.convert("L")
+
+
+def _normalize_palette(im, palette, info):
+    """
+    Normalizes the palette for image.
+      - Sets the palette to the incoming palette, if provided.
+      - Ensures that there's a palette for L mode images
+      - Optimizes the palette if necessary/desired.
+
+    :param im: Image object
+    :param palette: bytes object containing the source palette, or ....
+    :param info: encoderinfo
+    :returns: Image object
+    """
+    source_palette = None
+    if palette:
+        # a bytes palette
+        if isinstance(palette, (bytes, bytearray, list)):
+            source_palette = bytearray(palette[:768])
+        if isinstance(palette, ImagePalette.ImagePalette):
+            source_palette = bytearray(
+                itertools.chain.from_iterable(
+                    zip(
+                        palette.palette[:256],
+                        palette.palette[256:512],
+                        palette.palette[512:768],
+                    )
+                )
+            )
+
+    if im.mode == "P":
+        if not source_palette:
+            source_palette = im.im.getpalette("RGB")[:768]
+    else:  # L-mode
+        if not source_palette:
+            source_palette = bytearray(i // 3 for i in range(768))
+        im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
+
+    used_palette_colors = _get_optimize(im, info)
+    if used_palette_colors is not None:
+        return im.remap_palette(used_palette_colors, source_palette)
+
+    im.palette.palette = source_palette
+    return im
+
+
+def _write_single_frame(im, fp, palette):
+    im_out = _normalize_mode(im, True)
+    for k, v in im_out.info.items():
+        im.encoderinfo.setdefault(k, v)
+    im_out = _normalize_palette(im_out, palette, im.encoderinfo)
+
+    for s in _get_global_header(im_out, im.encoderinfo):
+        fp.write(s)
+
+    # local image header
+    flags = 0
+    if get_interlace(im):
+        flags = flags | 64
+    _write_local_header(fp, im, (0, 0), flags)
+
+    im_out.encoderconfig = (8, get_interlace(im))
+    ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
+
+    fp.write(b"\0")  # end of image data
+
+
+def _write_multiple_frames(im, fp, palette):
+
+    duration = im.encoderinfo.get("duration", im.info.get("duration"))
+    disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
+
+    im_frames = []
+    frame_count = 0
+    background_im = None
+    for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
+        for im_frame in ImageSequence.Iterator(imSequence):
+            # a copy is required here since seek can still mutate the image
+            im_frame = _normalize_mode(im_frame.copy())
+            if frame_count == 0:
+                for k, v in im_frame.info.items():
+                    im.encoderinfo.setdefault(k, v)
+            im_frame = _normalize_palette(im_frame, palette, im.encoderinfo)
+
+            encoderinfo = im.encoderinfo.copy()
+            if isinstance(duration, (list, tuple)):
+                encoderinfo["duration"] = duration[frame_count]
+            if isinstance(disposal, (list, tuple)):
+                encoderinfo["disposal"] = disposal[frame_count]
+            frame_count += 1
+
+            if im_frames:
+                # delta frame
+                previous = im_frames[-1]
+                if encoderinfo.get("disposal") == 2:
+                    if background_im is None:
+                        background = _get_background(
+                            im,
+                            im.encoderinfo.get("background", im.info.get("background")),
+                        )
+                        background_im = Image.new("P", im_frame.size, background)
+                        background_im.putpalette(im_frames[0]["im"].palette)
+                    base_im = background_im
+                else:
+                    base_im = previous["im"]
+                if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im):
+                    delta = ImageChops.subtract_modulo(im_frame, base_im)
+                else:
+                    delta = ImageChops.subtract_modulo(
+                        im_frame.convert("RGB"), base_im.convert("RGB")
+                    )
+                bbox = delta.getbbox()
+                if not bbox:
+                    # This frame is identical to the previous frame
+                    if duration:
+                        previous["encoderinfo"]["duration"] += encoderinfo["duration"]
+                    continue
+            else:
+                bbox = None
+            im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+
+    if len(im_frames) > 1:
+        for frame_data in im_frames:
+            im_frame = frame_data["im"]
+            if not frame_data["bbox"]:
+                # global header
+                for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
+                    fp.write(s)
+                offset = (0, 0)
+            else:
+                # compress difference
+                frame_data["encoderinfo"]["include_color_table"] = True
+
+                im_frame = im_frame.crop(frame_data["bbox"])
+                offset = frame_data["bbox"][:2]
+            _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
+        return True
+    elif "duration" in im.encoderinfo and isinstance(
+        im.encoderinfo["duration"], (list, tuple)
+    ):
+        # Since multiple frames will not be written, add together the frame durations
+        im.encoderinfo["duration"] = sum(im.encoderinfo["duration"])
+
+
+def _save_all(im, fp, filename):
+    _save(im, fp, filename, save_all=True)
+
+
+def _save(im, fp, filename, save_all=False):
+    # header
+    if "palette" in im.encoderinfo or "palette" in im.info:
+        palette = im.encoderinfo.get("palette", im.info.get("palette"))
+    else:
+        palette = None
+        im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
+
+    if not save_all or not _write_multiple_frames(im, fp, palette):
+        _write_single_frame(im, fp, palette)
+
+    fp.write(b";")  # end of file
+
+    if hasattr(fp, "flush"):
+        fp.flush()
+
+
+def get_interlace(im):
+    interlace = im.encoderinfo.get("interlace", 1)
+
+    # workaround for @PIL153
+    if min(im.size) < 16:
+        interlace = 0
+
+    return interlace
+
+
+def _write_local_header(fp, im, offset, flags):
+    transparent_color_exists = False
+    try:
+        transparency = im.encoderinfo["transparency"]
+    except KeyError:
+        pass
+    else:
+        transparency = int(transparency)
+        # optimize the block away if transparent color is not used
+        transparent_color_exists = True
+
+        used_palette_colors = _get_optimize(im, im.encoderinfo)
+        if used_palette_colors is not None:
+            # adjust the transparency index after optimize
+            try:
+                transparency = used_palette_colors.index(transparency)
+            except ValueError:
+                transparent_color_exists = False
+
+    if "duration" in im.encoderinfo:
+        duration = int(im.encoderinfo["duration"] / 10)
+    else:
+        duration = 0
+
+    disposal = int(im.encoderinfo.get("disposal", 0))
+
+    if transparent_color_exists or duration != 0 or disposal:
+        packed_flag = 1 if transparent_color_exists else 0
+        packed_flag |= disposal << 2
+        if not transparent_color_exists:
+            transparency = 0
+
+        fp.write(
+            b"!"
+            + o8(249)  # extension intro
+            + o8(4)  # length
+            + o8(packed_flag)  # packed fields
+            + o16(duration)  # duration
+            + o8(transparency)  # transparency index
+            + o8(0)
+        )
+
+    if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]):
+        fp.write(b"!" + o8(254))  # extension intro
+        comment = im.encoderinfo["comment"]
+        if isinstance(comment, str):
+            comment = comment.encode()
+        for i in range(0, len(comment), 255):
+            subblock = comment[i : i + 255]
+            fp.write(o8(len(subblock)) + subblock)
+        fp.write(o8(0))
+    if "loop" in im.encoderinfo:
+        number_of_loops = im.encoderinfo["loop"]
+        fp.write(
+            b"!"
+            + o8(255)  # extension intro
+            + o8(11)
+            + b"NETSCAPE2.0"
+            + o8(3)
+            + o8(1)
+            + o16(number_of_loops)  # number of loops
+            + o8(0)
+        )
+    include_color_table = im.encoderinfo.get("include_color_table")
+    if include_color_table:
+        palette_bytes = _get_palette_bytes(im)
+        color_table_size = _get_color_table_size(palette_bytes)
+        if color_table_size:
+            flags = flags | 128  # local color table flag
+            flags = flags | color_table_size
+
+    fp.write(
+        b","
+        + o16(offset[0])  # offset
+        + o16(offset[1])
+        + o16(im.size[0])  # size
+        + o16(im.size[1])
+        + o8(flags)  # flags
+    )
+    if include_color_table and color_table_size:
+        fp.write(_get_header_palette(palette_bytes))
+    fp.write(o8(8))  # bits
+
+
+def _save_netpbm(im, fp, filename):
+
+    # Unused by default.
+    # To use, uncomment the register_save call at the end of the file.
+    #
+    # If you need real GIF compression and/or RGB quantization, you
+    # can use the external NETPBM/PBMPLUS utilities.  See comments
+    # below for information on how to enable this.
+    tempfile = im._dump()
+
+    try:
+        with open(filename, "wb") as f:
+            if im.mode != "RGB":
+                subprocess.check_call(
+                    ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
+                )
+            else:
+                # Pipe ppmquant output into ppmtogif
+                # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
+                quant_cmd = ["ppmquant", "256", tempfile]
+                togif_cmd = ["ppmtogif"]
+                quant_proc = subprocess.Popen(
+                    quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
+                )
+                togif_proc = subprocess.Popen(
+                    togif_cmd,
+                    stdin=quant_proc.stdout,
+                    stdout=f,
+                    stderr=subprocess.DEVNULL,
+                )
+
+                # Allow ppmquant to receive SIGPIPE if ppmtogif exits
+                quant_proc.stdout.close()
+
+                retcode = quant_proc.wait()
+                if retcode:
+                    raise subprocess.CalledProcessError(retcode, quant_cmd)
+
+                retcode = togif_proc.wait()
+                if retcode:
+                    raise subprocess.CalledProcessError(retcode, togif_cmd)
+    finally:
+        try:
+            os.unlink(tempfile)
+        except OSError:
+            pass
+
+
+# Force optimization so that we can test performance against
+# cases where it took lots of memory and time previously.
+_FORCE_OPTIMIZE = False
+
+
+def _get_optimize(im, info):
+    """
+    Palette optimization is a potentially expensive operation.
+
+    This function determines if the palette should be optimized using
+    some heuristics, then returns the list of palette entries in use.
+
+    :param im: Image object
+    :param info: encoderinfo
+    :returns: list of indexes of palette entries in use, or None
+    """
+    if im.mode in ("P", "L") and info and info.get("optimize", 0):
+        # Potentially expensive operation.
+
+        # The palette saves 3 bytes per color not used, but palette
+        # lengths are restricted to 3*(2**N) bytes. Max saving would
+        # be 768 -> 6 bytes if we went all the way down to 2 colors.
+        # * If we're over 128 colors, we can't save any space.
+        # * If there aren't any holes, it's not worth collapsing.
+        # * If we have a 'large' image, the palette is in the noise.
+
+        # create the new palette if not every color is used
+        optimise = _FORCE_OPTIMIZE or im.mode == "L"
+        if optimise or im.width * im.height < 512 * 512:
+            # check which colors are used
+            used_palette_colors = []
+            for i, count in enumerate(im.histogram()):
+                if count:
+                    used_palette_colors.append(i)
+
+            if optimise or (
+                len(used_palette_colors) <= 128
+                and max(used_palette_colors) > len(used_palette_colors)
+            ):
+                return used_palette_colors
+
+
+def _get_color_table_size(palette_bytes):
+    # calculate the palette size for the header
+    if not palette_bytes:
+        return 0
+    elif len(palette_bytes) < 9:
+        return 1
+    else:
+        return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
+
+
+def _get_header_palette(palette_bytes):
+    """
+    Returns the palette, null padded to the next power of 2 (*3) bytes
+    suitable for direct inclusion in the GIF header
+
+    :param palette_bytes: Unpadded palette bytes, in RGBRGB form
+    :returns: Null padded palette
+    """
+    color_table_size = _get_color_table_size(palette_bytes)
+
+    # add the missing amount of bytes
+    # the palette has to be 2<<n in size
+    actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
+    if actual_target_size_diff > 0:
+        palette_bytes += o8(0) * 3 * actual_target_size_diff
+    return palette_bytes
+
+
+def _get_palette_bytes(im):
+    """
+    Gets the palette for inclusion in the gif header
+
+    :param im: Image object
+    :returns: Bytes, len<=768 suitable for inclusion in gif header
+    """
+    return im.palette.palette
+
+
+def _get_background(im, infoBackground):
+    background = 0
+    if infoBackground:
+        background = infoBackground
+        if isinstance(background, tuple):
+            # WebPImagePlugin stores an RGBA value in info["background"]
+            # So it must be converted to the same format as GifImagePlugin's
+            # info["background"] - a global color table index
+            background = im.palette.getcolor(background)
+    return background
+
+
+def _get_global_header(im, info):
+    """Return a list of strings representing a GIF header"""
+
+    # Header Block
+    # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
+
+    version = b"87a"
+    for extensionKey in ["transparency", "duration", "loop", "comment"]:
+        if info and extensionKey in info:
+            if (extensionKey == "duration" and info[extensionKey] == 0) or (
+                extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255)
+            ):
+                continue
+            version = b"89a"
+            break
+    else:
+        if im.info.get("version") == b"89a":
+            version = b"89a"
+
+    background = _get_background(im, info.get("background"))
+
+    palette_bytes = _get_palette_bytes(im)
+    color_table_size = _get_color_table_size(palette_bytes)
+
+    return [
+        b"GIF"  # signature
+        + version  # version
+        + o16(im.size[0])  # canvas width
+        + o16(im.size[1]),  # canvas height
+        # Logical Screen Descriptor
+        # size of global color table + global color table flag
+        o8(color_table_size + 128),  # packed fields
+        # background + reserved/aspect
+        o8(background) + o8(0),
+        # Global Color Table
+        _get_header_palette(palette_bytes),
+    ]
+
+
+def _write_frame_data(fp, im_frame, offset, params):
+    try:
+        im_frame.encoderinfo = params
+
+        # local image header
+        _write_local_header(fp, im_frame, offset, 0)
+
+        ImageFile._save(
+            im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
+        )
+
+        fp.write(b"\0")  # end of image data
+    finally:
+        del im_frame.encoderinfo
+
+
+# --------------------------------------------------------------------
+# Legacy GIF utilities
+
+
+def getheader(im, palette=None, info=None):
+    """
+    Legacy Method to get Gif data from image.
+
+    Warning:: May modify image data.
+
+    :param im: Image object
+    :param palette: bytes object containing the source palette, or ....
+    :param info: encoderinfo
+    :returns: tuple of(list of header items, optimized palette)
+
+    """
+    used_palette_colors = _get_optimize(im, info)
+
+    if info is None:
+        info = {}
+
+    if "background" not in info and "background" in im.info:
+        info["background"] = im.info["background"]
+
+    im_mod = _normalize_palette(im, palette, info)
+    im.palette = im_mod.palette
+    im.im = im_mod.im
+    header = _get_global_header(im, info)
+
+    return header, used_palette_colors
+
+
+# To specify duration, add the time in milliseconds to getdata(),
+# e.g. getdata(im_frame, duration=1000)
+def getdata(im, offset=(0, 0), **params):
+    """
+    Legacy Method
+
+    Return a list of strings representing this image.
+    The first string is a local image header, the rest contains
+    encoded image data.
+
+    :param im: Image object
+    :param offset: Tuple of (x, y) pixels. Defaults to (0,0)
+    :param \\**params: E.g. duration or other encoder info parameters
+    :returns: List of Bytes containing gif encoded frame data
+
+    """
+
+    class Collector:
+        data = []
+
+        def write(self, data):
+            self.data.append(data)
+
+    im.load()  # make sure raster data is available
+
+    fp = Collector()
+
+    _write_frame_data(fp, im, offset, params)
+
+    return fp.data
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GifImageFile.format, GifImageFile, _accept)
+Image.register_save(GifImageFile.format, _save)
+Image.register_save_all(GifImageFile.format, _save_all)
+Image.register_extension(GifImageFile.format, ".gif")
+Image.register_mime(GifImageFile.format, "image/gif")
+
+#
+# Uncomment the following line if you wish to use NETPBM/PBMPLUS
+# instead of the built-in "uncompressed" GIF encoder
+
+# Image.register_save(GifImageFile.format, _save_netpbm)

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GifImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5704cc8b2b5944119ac73163041f4590
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 140 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpGradientFile.py

@@ -0,0 +1,140 @@
+#
+# Python Imaging Library
+# $Id$
+#
+# stuff to read (and render) GIMP gradient files
+#
+# History:
+#       97-08-23 fl     Created
+#
+# Copyright (c) Secret Labs AB 1997.
+# Copyright (c) Fredrik Lundh 1997.
+#
+# See the README file for information on usage and redistribution.
+#
+
+"""
+Stuff to translate curve segments to palette values (derived from
+the corresponding code in GIMP, written by Federico Mena Quintero.
+See the GIMP distribution for more information.)
+"""
+
+
+from math import log, pi, sin, sqrt
+
+from ._binary import o8
+
+EPSILON = 1e-10
+""""""  # Enable auto-doc for data member
+
+
+def linear(middle, pos):
+    if pos <= middle:
+        if middle < EPSILON:
+            return 0.0
+        else:
+            return 0.5 * pos / middle
+    else:
+        pos = pos - middle
+        middle = 1.0 - middle
+        if middle < EPSILON:
+            return 1.0
+        else:
+            return 0.5 + 0.5 * pos / middle
+
+
+def curved(middle, pos):
+    return pos ** (log(0.5) / log(max(middle, EPSILON)))
+
+
+def sine(middle, pos):
+    return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
+
+
+def sphere_increasing(middle, pos):
+    return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
+
+
+def sphere_decreasing(middle, pos):
+    return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
+
+
+SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
+""""""  # Enable auto-doc for data member
+
+
+class GradientFile:
+
+    gradient = None
+
+    def getpalette(self, entries=256):
+
+        palette = []
+
+        ix = 0
+        x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+        for i in range(entries):
+
+            x = i / (entries - 1)
+
+            while x1 < x:
+                ix += 1
+                x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
+
+            w = x1 - x0
+
+            if w < EPSILON:
+                scale = segment(0.5, 0.5)
+            else:
+                scale = segment((xm - x0) / w, (x - x0) / w)
+
+            # expand to RGBA
+            r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
+            g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
+            b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
+            a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
+
+            # add to palette
+            palette.append(r + g + b + a)
+
+        return b"".join(palette), "RGBA"
+
+
+class GimpGradientFile(GradientFile):
+    """File handler for GIMP's gradient format."""
+
+    def __init__(self, fp):
+
+        if fp.readline()[:13] != b"GIMP Gradient":
+            raise SyntaxError("not a GIMP gradient file")
+
+        line = fp.readline()
+
+        # GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
+        if line.startswith(b"Name: "):
+            line = fp.readline().strip()
+
+        count = int(line)
+
+        gradient = []
+
+        for i in range(count):
+
+            s = fp.readline().split()
+            w = [float(x) for x in s[:11]]
+
+            x0, x1 = w[0], w[2]
+            xm = w[1]
+            rgb0 = w[3:7]
+            rgb1 = w[7:11]
+
+            segment = SEGMENTS[int(s[11])]
+            cspace = int(s[12])
+
+            if cspace != 0:
+                raise OSError("cannot handle HSV colour space")
+
+            gradient.append((x0, x1, xm, rgb0, rgb1, segment))
+
+        self.gradient = gradient

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpGradientFile.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 97ba37efb36c241f2a4f6f2d3878e97a
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 56 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpPaletteFile.py

@@ -0,0 +1,56 @@
+#
+# Python Imaging Library
+# $Id$
+#
+# stuff to read GIMP palette files
+#
+# History:
+# 1997-08-23 fl     Created
+# 2004-09-07 fl     Support GIMP 2.0 palette files.
+#
+# Copyright (c) Secret Labs AB 1997-2004.  All rights reserved.
+# Copyright (c) Fredrik Lundh 1997-2004.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import re
+
+from ._binary import o8
+
+
+class GimpPaletteFile:
+    """File handler for GIMP's palette format."""
+
+    rawmode = "RGB"
+
+    def __init__(self, fp):
+
+        self.palette = [o8(i) * 3 for i in range(256)]
+
+        if fp.readline()[:12] != b"GIMP Palette":
+            raise SyntaxError("not a GIMP palette file")
+
+        for i in range(256):
+
+            s = fp.readline()
+            if not s:
+                break
+
+            # skip fields and comment lines
+            if re.match(br"\w+:|#", s):
+                continue
+            if len(s) > 100:
+                raise SyntaxError("bad palette file")
+
+            v = tuple(map(int, s.split()[:3]))
+            if len(v) != 3:
+                raise ValueError("bad palette entry")
+
+            self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
+
+        self.palette = b"".join(self.palette)
+
+    def getpalette(self):
+
+        return self.palette, self.rawmode

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GimpPaletteFile.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 78935c0a6600f43da8837c41e5168509
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 74 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GribStubImagePlugin.py

@@ -0,0 +1,74 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# GRIB stub adapter
+#
+# Copyright (c) 1996-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from . import Image, ImageFile
+from ._binary import i8
+
+_handler = None
+
+
+def register_handler(handler):
+    """
+    Install application-specific GRIB image handler.
+
+    :param handler: Handler object.
+    """
+    global _handler
+    _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+    return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
+
+
+class GribStubImageFile(ImageFile.StubImageFile):
+
+    format = "GRIB"
+    format_description = "GRIB"
+
+    def _open(self):
+
+        offset = self.fp.tell()
+
+        if not _accept(self.fp.read(8)):
+            raise SyntaxError("Not a GRIB file")
+
+        self.fp.seek(offset)
+
+        # make something up
+        self.mode = "F"
+        self._size = 1, 1
+
+        loader = self._load()
+        if loader:
+            loader.open(self)
+
+    def _load(self):
+        return _handler
+
+
+def _save(im, fp, filename):
+    if _handler is None or not hasattr("_handler", "save"):
+        raise OSError("GRIB save handler not installed")
+    _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
+Image.register_save(GribStubImageFile.format, _save)
+
+Image.register_extension(GribStubImageFile.format, ".grib")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/GribStubImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 088796612827a4ef1994246eae32a48a
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 73 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/Hdf5StubImagePlugin.py

@@ -0,0 +1,73 @@
+#
+# The Python Imaging Library
+# $Id$
+#
+# HDF5 stub adapter
+#
+# Copyright (c) 2000-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+from . import Image, ImageFile
+
+_handler = None
+
+
+def register_handler(handler):
+    """
+    Install application-specific HDF5 image handler.
+
+    :param handler: Handler object.
+    """
+    global _handler
+    _handler = handler
+
+
+# --------------------------------------------------------------------
+# Image adapter
+
+
+def _accept(prefix):
+    return prefix[:8] == b"\x89HDF\r\n\x1a\n"
+
+
+class HDF5StubImageFile(ImageFile.StubImageFile):
+
+    format = "HDF5"
+    format_description = "HDF5"
+
+    def _open(self):
+
+        offset = self.fp.tell()
+
+        if not _accept(self.fp.read(8)):
+            raise SyntaxError("Not an HDF file")
+
+        self.fp.seek(offset)
+
+        # make something up
+        self.mode = "F"
+        self._size = 1, 1
+
+        loader = self._load()
+        if loader:
+            loader.open(self)
+
+    def _load(self):
+        return _handler
+
+
+def _save(im, fp, filename):
+    if _handler is None or not hasattr("_handler", "save"):
+        raise OSError("HDF5 save handler not installed")
+    _handler.save(im, fp, filename)
+
+
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
+Image.register_save(HDF5StubImageFile.format, _save)
+
+Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/Hdf5StubImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a9a640e246b5545fa9430f555746f7ca
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 384 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/IcnsImagePlugin.py

@@ -0,0 +1,384 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# macOS icns file decoder, based on icns.py by Bob Ippolito.
+#
+# history:
+# 2004-10-09 fl   Turned into a PIL plugin; removed 2.3 dependencies.
+#
+# Copyright (c) 2004 by Bob Ippolito.
+# Copyright (c) 2004 by Secret Labs.
+# Copyright (c) 2004 by Fredrik Lundh.
+# Copyright (c) 2014 by Alastair Houghton.
+#
+# See the README file for information on usage and redistribution.
+#
+
+import io
+import os
+import shutil
+import struct
+import subprocess
+import sys
+import tempfile
+
+from PIL import Image, ImageFile, PngImagePlugin, features
+from PIL._binary import i8
+
+enable_jpeg2k = features.check_codec("jpg_2000")
+if enable_jpeg2k:
+    from PIL import Jpeg2KImagePlugin
+
+HEADERSIZE = 8
+
+
+def nextheader(fobj):
+    return struct.unpack(">4sI", fobj.read(HEADERSIZE))
+
+
+def read_32t(fobj, start_length, size):
+    # The 128x128 icon seems to have an extra header for some reason.
+    (start, length) = start_length
+    fobj.seek(start)
+    sig = fobj.read(4)
+    if sig != b"\x00\x00\x00\x00":
+        raise SyntaxError("Unknown signature, expecting 0x00000000")
+    return read_32(fobj, (start + 4, length - 4), size)
+
+
+def read_32(fobj, start_length, size):
+    """
+    Read a 32bit RGB icon resource.  Seems to be either uncompressed or
+    an RLE packbits-like scheme.
+    """
+    (start, length) = start_length
+    fobj.seek(start)
+    pixel_size = (size[0] * size[2], size[1] * size[2])
+    sizesq = pixel_size[0] * pixel_size[1]
+    if length == sizesq * 3:
+        # uncompressed ("RGBRGBGB")
+        indata = fobj.read(length)
+        im = Image.frombuffer("RGB", pixel_size, indata, "raw", "RGB", 0, 1)
+    else:
+        # decode image
+        im = Image.new("RGB", pixel_size, None)
+        for band_ix in range(3):
+            data = []
+            bytesleft = sizesq
+            while bytesleft > 0:
+                byte = fobj.read(1)
+                if not byte:
+                    break
+                byte = i8(byte)
+                if byte & 0x80:
+                    blocksize = byte - 125
+                    byte = fobj.read(1)
+                    for i in range(blocksize):
+                        data.append(byte)
+                else:
+                    blocksize = byte + 1
+                    data.append(fobj.read(blocksize))
+                bytesleft -= blocksize
+                if bytesleft <= 0:
+                    break
+            if bytesleft != 0:
+                raise SyntaxError(f"Error reading channel [{repr(bytesleft)} left]")
+            band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1)
+            im.im.putband(band.im, band_ix)
+    return {"RGB": im}
+
+
+def read_mk(fobj, start_length, size):
+    # Alpha masks seem to be uncompressed
+    start = start_length[0]
+    fobj.seek(start)
+    pixel_size = (size[0] * size[2], size[1] * size[2])
+    sizesq = pixel_size[0] * pixel_size[1]
+    band = Image.frombuffer("L", pixel_size, fobj.read(sizesq), "raw", "L", 0, 1)
+    return {"A": band}
+
+
+def read_png_or_jpeg2000(fobj, start_length, size):
+    (start, length) = start_length
+    fobj.seek(start)
+    sig = fobj.read(12)
+    if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a":
+        fobj.seek(start)
+        im = PngImagePlugin.PngImageFile(fobj)
+        return {"RGBA": im}
+    elif (
+        sig[:4] == b"\xff\x4f\xff\x51"
+        or sig[:4] == b"\x0d\x0a\x87\x0a"
+        or sig == b"\x00\x00\x00\x0cjP  \x0d\x0a\x87\x0a"
+    ):
+        if not enable_jpeg2k:
+            raise ValueError(
+                "Unsupported icon subimage format (rebuild PIL "
+                "with JPEG 2000 support to fix this)"
+            )
+        # j2k, jpc or j2c
+        fobj.seek(start)
+        jp2kstream = fobj.read(length)
+        f = io.BytesIO(jp2kstream)
+        im = Jpeg2KImagePlugin.Jpeg2KImageFile(f)
+        if im.mode != "RGBA":
+            im = im.convert("RGBA")
+        return {"RGBA": im}
+    else:
+        raise ValueError("Unsupported icon subimage format")
+
+
+class IcnsFile:
+
+    SIZES = {
+        (512, 512, 2): [(b"ic10", read_png_or_jpeg2000)],
+        (512, 512, 1): [(b"ic09", read_png_or_jpeg2000)],
+        (256, 256, 2): [(b"ic14", read_png_or_jpeg2000)],
+        (256, 256, 1): [(b"ic08", read_png_or_jpeg2000)],
+        (128, 128, 2): [(b"ic13", read_png_or_jpeg2000)],
+        (128, 128, 1): [
+            (b"ic07", read_png_or_jpeg2000),
+            (b"it32", read_32t),
+            (b"t8mk", read_mk),
+        ],
+        (64, 64, 1): [(b"icp6", read_png_or_jpeg2000)],
+        (32, 32, 2): [(b"ic12", read_png_or_jpeg2000)],
+        (48, 48, 1): [(b"ih32", read_32), (b"h8mk", read_mk)],
+        (32, 32, 1): [
+            (b"icp5", read_png_or_jpeg2000),
+            (b"il32", read_32),
+            (b"l8mk", read_mk),
+        ],
+        (16, 16, 2): [(b"ic11", read_png_or_jpeg2000)],
+        (16, 16, 1): [
+            (b"icp4", read_png_or_jpeg2000),
+            (b"is32", read_32),
+            (b"s8mk", read_mk),
+        ],
+    }
+
+    def __init__(self, fobj):
+        """
+        fobj is a file-like object as an icns resource
+        """
+        # signature : (start, length)
+        self.dct = dct = {}
+        self.fobj = fobj
+        sig, filesize = nextheader(fobj)
+        if sig != b"icns":
+            raise SyntaxError("not an icns file")
+        i = HEADERSIZE
+        while i < filesize:
+            sig, blocksize = nextheader(fobj)
+            if blocksize <= 0:
+                raise SyntaxError("invalid block header")
+            i += HEADERSIZE
+            blocksize -= HEADERSIZE
+            dct[sig] = (i, blocksize)
+            fobj.seek(blocksize, io.SEEK_CUR)
+            i += blocksize
+
+    def itersizes(self):
+        sizes = []
+        for size, fmts in self.SIZES.items():
+            for (fmt, reader) in fmts:
+                if fmt in self.dct:
+                    sizes.append(size)
+                    break
+        return sizes
+
+    def bestsize(self):
+        sizes = self.itersizes()
+        if not sizes:
+            raise SyntaxError("No 32bit icon resources found")
+        return max(sizes)
+
+    def dataforsize(self, size):
+        """
+        Get an icon resource as {channel: array}.  Note that
+        the arrays are bottom-up like windows bitmaps and will likely
+        need to be flipped or transposed in some way.
+        """
+        dct = {}
+        for code, reader in self.SIZES[size]:
+            desc = self.dct.get(code)
+            if desc is not None:
+                dct.update(reader(self.fobj, desc, size))
+        return dct
+
+    def getimage(self, size=None):
+        if size is None:
+            size = self.bestsize()
+        if len(size) == 2:
+            size = (size[0], size[1], 1)
+        channels = self.dataforsize(size)
+
+        im = channels.get("RGBA", None)
+        if im:
+            return im
+
+        im = channels.get("RGB").copy()
+        try:
+            im.putalpha(channels["A"])
+        except KeyError:
+            pass
+        return im
+
+
+##
+# Image plugin for Mac OS icons.
+
+
+class IcnsImageFile(ImageFile.ImageFile):
+    """
+    PIL image support for Mac OS .icns files.
+    Chooses the best resolution, but will possibly load
+    a different size image if you mutate the size attribute
+    before calling 'load'.
+
+    The info dictionary has a key 'sizes' that is a list
+    of sizes that the icns file has.
+    """
+
+    format = "ICNS"
+    format_description = "Mac OS icns resource"
+
+    def _open(self):
+        self.icns = IcnsFile(self.fp)
+        self.mode = "RGBA"
+        self.info["sizes"] = self.icns.itersizes()
+        self.best_size = self.icns.bestsize()
+        self.size = (
+            self.best_size[0] * self.best_size[2],
+            self.best_size[1] * self.best_size[2],
+        )
+
+    @property
+    def size(self):
+        return self._size
+
+    @size.setter
+    def size(self, value):
+        info_size = value
+        if info_size not in self.info["sizes"] and len(info_size) == 2:
+            info_size = (info_size[0], info_size[1], 1)
+        if (
+            info_size not in self.info["sizes"]
+            and len(info_size) == 3
+            and info_size[2] == 1
+        ):
+            simple_sizes = [
+                (size[0] * size[2], size[1] * size[2]) for size in self.info["sizes"]
+            ]
+            if value in simple_sizes:
+                info_size = self.info["sizes"][simple_sizes.index(value)]
+        if info_size not in self.info["sizes"]:
+            raise ValueError("This is not one of the allowed sizes of this image")
+        self._size = value
+
+    def load(self):
+        if len(self.size) == 3:
+            self.best_size = self.size
+            self.size = (
+                self.best_size[0] * self.best_size[2],
+                self.best_size[1] * self.best_size[2],
+            )
+
+        Image.Image.load(self)
+        if self.im and self.im.size == self.size:
+            # Already loaded
+            return
+        self.load_prepare()
+        # This is likely NOT the best way to do it, but whatever.
+        im = self.icns.getimage(self.best_size)
+
+        # If this is a PNG or JPEG 2000, it won't be loaded yet
+        im.load()
+
+        self.im = im.im
+        self.mode = im.mode
+        self.size = im.size
+        self.load_end()
+
+
+def _save(im, fp, filename):
+    """
+    Saves the image as a series of PNG files,
+    that are then converted to a .icns file
+    using the macOS command line utility 'iconutil'.
+
+    macOS only.
+    """
+    if hasattr(fp, "flush"):
+        fp.flush()
+
+    # create the temporary set of pngs
+    with tempfile.TemporaryDirectory(".iconset") as iconset:
+        provided_images = {
+            im.width: im for im in im.encoderinfo.get("append_images", [])
+        }
+        last_w = None
+        second_path = None
+        for w in [16, 32, 128, 256, 512]:
+            prefix = f"icon_{w}x{w}"
+
+            first_path = os.path.join(iconset, prefix + ".png")
+            if last_w == w:
+                shutil.copyfile(second_path, first_path)
+            else:
+                im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS))
+                im_w.save(first_path)
+
+            second_path = os.path.join(iconset, prefix + "@2x.png")
+            im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS))
+            im_w2.save(second_path)
+            last_w = w * 2
+
+        # iconutil -c icns -o {} {}
+
+        fp_only = not filename
+        if fp_only:
+            f, filename = tempfile.mkstemp(".icns")
+            os.close(f)
+        convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
+        convert_proc = subprocess.Popen(
+            convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
+        )
+
+        convert_proc.stdout.close()
+
+        retcode = convert_proc.wait()
+
+        if retcode:
+            raise subprocess.CalledProcessError(retcode, convert_cmd)
+
+        if fp_only:
+            with open(filename, "rb") as f:
+                fp.write(f.read())
+
+
+Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
+Image.register_extension(IcnsImageFile.format, ".icns")
+
+if sys.platform == "darwin":
+    Image.register_save(IcnsImageFile.format, _save)
+
+    Image.register_mime(IcnsImageFile.format, "image/icns")
+
+
+if __name__ == "__main__":
+
+    if len(sys.argv) < 2:
+        print("Syntax: python IcnsImagePlugin.py [file]")
+        sys.exit()
+
+    with open(sys.argv[1], "rb") as fp:
+        imf = IcnsImageFile(fp)
+        for size in imf.info["sizes"]:
+            imf.size = size
+            imf.save("out-%s-%s-%s.png" % size)
+        with Image.open(sys.argv[1]) as im:
+            im.save("out.png")
+        if sys.platform == "windows":
+            os.startfile("out.png")

+ 7 - 0
Assets/Plotting/venv/lib/python3.7/site-packages/PIL/IcnsImagePlugin.py.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f41b92f2aa2614915b9c65f918238c46
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Some files were not shown because too many files changed in this diff