Model3D + Plot Components (#1010)

* add model3D

* model3d component

* add plot component

* format

* ingnore plot.svelte from prettier

* update build script

* update circleci config

* remove circleci config change

* change build script

* remove change to build script

* remove plot.svelte

* remove duplicate save_flagged func

* fix node space issue + format issues

* Update ui.yml

* Update ui.yml

* Update ui.yml

* remove changes to config files

* updated codecov version

* Update ui.yml

Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
Dawood Khan 2022-04-18 15:26:30 -04:00 committed by GitHub
parent 2f771e201a
commit 499f106c9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 9935 additions and 26 deletions

View File

@ -17,6 +17,7 @@ defaults:
env:
CI: true
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
NODE_OPTIONS: "--max-old-space-size=4096"
concurrency:
group: deploy-${{ github.ref }}-${{ github.event_name == 'push' || github.event.inputs.fire != null }}

7474
demo/model3D/files/Bunny.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
demo/model3D/files/Duck.glb Normal file

Binary file not shown.

1777
demo/model3D/files/Fox.gltf Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
Stanford Bunny:
https://graphics.stanford.edu/data/3Dscanrep/
https://graphics.stanford.edu/~mdfisher/Data/Meshes/bunny.obj
Duck & Fox:
https://github.com/KhronosGroup/glTF-Sample-Models

20
demo/model3D/run.py Normal file
View File

@ -0,0 +1,20 @@
import time
import gradio as gr
def load_mesh(mesh_file_name):
time.sleep(2)
return mesh_file_name
inputs = gr.Model3D()
outputs = gr.Model3D(clear_color=[0.8, 0.2, 0.2, 1.0])
iface = gr.Interface(
fn=load_mesh,
inputs=inputs,
outputs=outputs,
examples=[["files/Bunny.obj"], ["files/Duck.glb"]]
)
if __name__ == "__main__":
iface.launch(cache_examples=True)

View File

@ -1,41 +1,65 @@
from math import sqrt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import plotly.express as px
import pandas as pd
import bokeh.plotting as bk
from bokeh.models import ColumnDataSource
from bokeh.embed import json_item
import gradio as gr
def outbreak(r, month, countries, social_distancing):
def outbreak(plot_type, r, month, countries, social_distancing):
months = ["January", "February", "March", "April", "May"]
m = months.index(month)
start_day = 30 * m
final_day = 30 * (m + 1)
x = np.arange(start_day, final_day + 1)
day_count = x.shape[0]
pop_count = {"USA": 350, "Canada": 40, "Mexico": 300, "UK": 120}
r = sqrt(r)
if social_distancing:
r = sqrt(r)
for i, country in enumerate(countries):
series = x ** (r) * (i + 1)
plt.plot(x, series)
plt.title("Outbreak in " + month)
plt.ylabel("Cases")
plt.xlabel("Days since Day 0")
plt.legend(countries)
return plt
df = pd.DataFrame({'day': x})
for country in countries:
df[country] = ( x ** (r) * (pop_count[country] + 1))
if plot_type == "Matplotlib":
fig = plt.figure()
plt.plot(df['day'], df[countries])
plt.title("Outbreak in " + month)
plt.ylabel("Cases")
plt.xlabel("Days since Day 0")
plt.legend(countries)
return fig
elif plot_type == "Plotly":
fig = px.line(df, x='day', y=countries)
fig.update_layout(title="Outbreak in " + month,
xaxis_title="Cases",
yaxis_title="Days Since Day 0")
return fig
else:
source = ColumnDataSource(df)
p = bk.figure(title="Outbreak in " + month, x_axis_label="Cases", y_axis_label="Days Since Day 0")
for country in countries:
p.line(x='day', y=country, line_width=2, source=source)
item_text = json_item(p, "plotDiv")
return item_text
demo = gr.Interface(
outbreak,
[
inputs = [
gr.Dropdown(["Matplotlib", "Plotly", "Bokeh"], label="Plot Type"),
gr.Slider(minimum=1, maximum=4, default_value=3.2, label="R"),
gr.Dropdown(["January", "February", "March", "April", "May"], label="Month"),
gr.CheckboxGroup(["USA", "Canada", "Mexico", "UK"], label="Countries"),
gr.Checkbox(label="Social Distancing?"),
],
"plot",
)
]
outputs = gr.Plot(type="auto")
demo = gr.Interface(fn=outbreak, inputs=inputs, outputs=outputs)
if __name__ == "__main__":
demo.launch()

View File

@ -1,5 +1,5 @@
aiohttp
analytics-python
aiohttp
fastapi
ffmpy
markdown-it-py[linkify,plugins]
@ -10,7 +10,7 @@ pandas
paramiko
pillow
pycryptodome
pydub
python-multipart
pydub
requests
uvicorn

View File

@ -22,7 +22,9 @@ from gradio.components import (
KeyValues,
Label,
Markdown,
Model3D,
Number,
Plot,
Radio,
Slider,
StatusTracker,

View File

@ -85,14 +85,19 @@ class Component(Block):
return data
def save_flagged_file(
self, dir: str, label: str, data: Any, encryption_key: bool
self,
dir: str,
label: str,
data: Any,
encryption_key: bool,
file_path: Optional[str] = None,
) -> Optional[str]:
"""
Saved flagged data (e.g. image or audio) as a file and returns filepath
"""
if data is None:
return None
file = processing_utils.decode_base64_to_file(data, encryption_key)
file = processing_utils.decode_base64_to_file(data, encryption_key, file_path)
label = "".join([char for char in label if char.isalnum() or char in "._- "])
old_file_name = file.name
output_dir = os.path.join(dir, label)
@ -2865,6 +2870,138 @@ class Chatbot(Component):
)
class Model3D(Component):
"""
Component creates a 3D Model component with input and output capabilities.
Input type: File object of type (.obj, glb, or .gltf)
Output type: filepath
Demos: Model3D
"""
def __init__(
self,
clear_color=None,
label: str = None,
css: Optional[Dict] = None,
**kwargs,
):
"""
Parameters:
clear_color (List[r, g, b, a]): background color of scene
label (str): component name in interface.
"""
self.clear_color = clear_color
super().__init__(label=label, css=css, **kwargs)
def get_template_context(self):
return {
"clearColor": self.clear_color,
**super().get_template_context(),
}
@classmethod
def get_shortcut_implementations(cls):
return {
"Model3D": {},
}
def preprocess_example(self, x):
return {"name": x, "data": None, "is_example": True}
def preprocess(self, x: Dict[str, str] | None) -> str | None:
"""
Parameters:
x (Dict[name: str, data: str]): JSON object with filename as 'name' property and base64 data as 'data' property
Returns:
(str): file path to 3D image model
"""
if x is None:
return x
file_name, file_data, is_example = (
x["name"],
x["data"],
x.get("is_example", False),
)
if is_example:
file = processing_utils.create_tmp_copy_of_file(file_name)
else:
file = processing_utils.decode_base64_to_file(
file_data, file_path=file_name
)
file_name = file.name
return file_name
def serialize(self, x, called_directly):
raise NotImplementedError()
def save_flagged(self, dir, label, data, encryption_key):
"""
Returns: (str) path to 3D image model file
"""
return self.save_flagged_file(
dir, label, None if data is None else data["data"], encryption_key
)
def generate_sample(self):
return media_data.BASE64_MODEL3D
# Output functions
def postprocess(self, y):
"""
Parameters:
y (str): path to the model
Returns:
(str): file name
(str): file extension
(str): base64 url data
"""
if self.clear_color is None:
self.clear_color = [0.2, 0.2, 0.2, 1.0]
return {
"name": os.path.basename(y),
"data": processing_utils.encode_file_to_base64(y),
}
def deserialize(self, x):
return processing_utils.decode_base64_to_file(x).name
def restore_flagged(self, dir, data, encryption_key):
return self.restore_flagged_file(dir, data, encryption_key)
def change(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
"""
Parameters:
fn: Callable function
inputs: List of inputs
outputs: List of outputs
Returns: None
"""
self.set_event_trigger("change", fn, inputs, outputs)
def edit(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
"""
Parameters:
fn: Callable function
inputs: List of inputs
outputs: List of outputs
Returns: None
"""
self.set_event_trigger("edit", fn, inputs, outputs)
def clear(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
"""
Parameters:
fn: Callable function
inputs: List of inputs
outputs: List of outputs
Returns: None
"""
self.set_event_trigger("clear", fn, inputs, outputs)
# Static Components
class Markdown(Component):
"""
@ -3053,6 +3190,98 @@ class Dataset(Component):
)
class Plot(Component):
"""
Used for plot output.
Output type: matplotlib plt, plotly figure, or Bokeh fig (json_item format)
Demos: outbreak_forecast
"""
def __init__(
self,
type: str = None,
label: str = None,
css: Optional[Dict] = None,
**kwargs,
):
"""
Parameters:
type (str): type of plot (matplotlib, plotly)
label (str): component name in interface.
"""
self.type = type
super().__init__(label=label, css=css, **kwargs)
def get_template_context(self):
return {**super().get_template_context()}
@classmethod
def get_shortcut_implementations(cls):
return {
"plot": {},
}
def postprocess(self, y):
"""
Parameters:
y (str): plot data
Returns:
(str): plot type
(str): plot base64 or json
"""
dtype = self.type
if self.type == "plotly":
out_y = y.to_json()
elif self.type == "matplotlib":
out_y = processing_utils.encode_plot_to_base64(y)
elif self.type == "bokeh":
out_y = json.dumps(y)
elif self.type == "auto":
if isinstance(y, (ModuleType, matplotlib.pyplot.Figure)):
dtype = "matplotlib"
out_y = processing_utils.encode_plot_to_base64(y)
elif isinstance(y, dict):
dtype = "bokeh"
out_y = json.dumps(y)
else:
dtype = "plotly"
out_y = y.to_json()
else:
raise ValueError(
"Unknown type. Please choose from: 'plotly', 'matplotlib', 'bokeh'."
)
return {"type": dtype, "plot": out_y}
def change(
self,
fn: Callable,
inputs: List[Component],
outputs: List[Component],
status_tracker: Optional[StatusTracker] = None,
):
"""
Parameters:
fn: Callable function
inputs: List of inputs
outputs: List of outputs
status: StatusTracker to visualize function progress
Returns: None
"""
self.set_event_trigger(
"change", fn, inputs, outputs, status_tracker=status_tracker
)
def clear(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
"""
Parameters:
fn: Callable function
inputs: List of inputs
outputs: List of outputs
Returns: None
"""
self.set_event_trigger("clear", fn, inputs, outputs)
class Interpretation(Component):
"""
Used to create an interpretation widget for a component.

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
packages/app/public/**
pnpm-workspace.yaml
packages/app/dist/**
pnpm-lock.yaml
pnpm-lock.yaml
packages/plot/src/Plot.svelte

View File

@ -29,6 +29,8 @@
"@gradio/theme": "workspace:^0.0.1",
"@gradio/upload": "workspace:^0.0.1",
"@gradio/video": "workspace:^0.0.1",
"@gradio/model3D": "workspace:^0.0.1",
"@gradio/plot": "workspace:^0.0.1",
"mime-types": "^2.1.34",
"svelte-i18n": "^3.3.13"
}

View File

@ -0,0 +1,33 @@
<script lang="ts">
import type { FileData } from "@gradio/upload";
import { normalise_file } from "@gradio/upload";
import { Model3D, Model3DUpload } from "@gradio/model3D";
import { _ } from "svelte-i18n";
export let value: null | FileData = null;
export let default_value: null | string = null;
export let style: string = "";
export let mode: "static" | "dynamic";
export let root: string;
export let clearColor: [];
if (default_value) value = default_value;
let _value: null | FileData;
$: _value = normalise_file(value, root);
</script>
{#if mode === "dynamic"}
<Model3DUpload
value={_value}
on:change={({ detail }) => (value = detail)}
{style}
on:change
on:clear
drop_text={$_("interface.drop_file")}
or_text={$_("or")}
upload_text={$_("interface.click_to_upload")}
/>
{:else if _value}
<Model3D value={_value} {clearColor} {style} />
{/if}

View File

@ -0,0 +1,2 @@
export { default as Component } from "./Model3D.svelte";
export const modes = ["static", "dynamic"];

View File

@ -0,0 +1,11 @@
<script lang="ts">
import { Plot } from "@gradio/plot";
export let value: null | string = null;
export let default_value: null | string = null;
export let style: string = "";
if (default_value) value = default_value;
</script>
<Plot {value} {style} on:change />

View File

@ -0,0 +1,2 @@
export { default as Component } from "./Plot.svelte";
export const modes = ["static"];

View File

@ -28,5 +28,7 @@ export const component_map: Record<string, any> = {
textbox: () => import("./Textbox"),
timeseries: () => import("./TimeSeries"),
video: () => import("./Video"),
model3d: () => import("./Model3D"),
plot: () => import("./Plot"),
variable: () => import("./Variable")
};

View File

@ -0,0 +1,16 @@
{
"name": "@gradio/model3D",
"version": "0.0.1",
"description": "Gradio UI packages",
"type": "module",
"main": "src/index.ts",
"author": "",
"license": "ISC",
"private": true,
"dependencies": {
"@gradio/atoms": "workspace:^0.0.1",
"@gradio/upload": "workspace:^0.0.1",
"babylonjs": "^4.2.1",
"babylonjs-loaders": "^4.2.1"
}
}

View File

@ -0,0 +1,69 @@
<script lang="ts">
import type { FileData } from "@gradio/upload";
export let value: FileData;
export let theme: string;
export let clearColor: Array;
import { onMount, afterUpdate } from "svelte";
import * as BABYLON from "babylonjs";
import "babylonjs-loaders";
let canvas: HTMLCanvasElement;
let scene: BABYLON.Scene;
onMount(() => {
const engine = new BABYLON.Engine(canvas, true);
scene = new BABYLON.Scene(engine);
scene.createDefaultCameraOrLight();
scene.clearColor = new BABYLON.Color4(
clearColor[0],
clearColor[1],
clearColor[2],
clearColor[3]
);
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener("resize", () => {
engine.resize();
});
});
afterUpdate(() => {
addNewModel();
});
function addNewModel() {
for (let mesh of scene.meshes) {
mesh.dispose();
}
let base64_model_content = value["data"];
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);
let url = URL.createObjectURL(blob);
BABYLON.SceneLoader.Append(
"",
url,
scene,
() => {
scene.createDefaultCamera(true, true, true);
},
undefined,
undefined,
"." + value["name"].split(".")[1]
);
}
</script>
<div
class="output-model w-full h-60 flex justify-center items-center bg-gray-200 dark:bg-gray-600 relative"
{theme}
>
<canvas class="w-full h-full object-contain" bind:this={canvas} />
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,96 @@
<script lang="ts">
import { createEventDispatcher, tick } from "svelte";
import { Upload, ModifyUpload } from "@gradio/upload";
import type { FileData } from "@gradio/upload";
import { Block, BlockLabel } from "@gradio/atoms";
import file_icon from "./file.svg";
export let value: null | FileData;
export let drop_text: string = "Drop a file";
export let or_text: string = "or";
export let upload_text: string = "click to upload";
export let label: string = "";
export let style: string;
let file_count: string;
async function handle_upload({ detail }: CustomEvent<FileData>) {
value = detail;
await tick();
dispatch("change", value);
addNewModel();
}
async function handle_clear({ detail }: CustomEvent<null>) {
value = null;
await tick();
dispatch("clear");
}
const dispatch =
createEventDispatcher<{ change: FileData | null; clear: undefined }>();
let dragging = false;
import { afterUpdate } from "svelte";
import * as BABYLON from "babylonjs";
import "babylonjs-loaders";
let canvas: HTMLCanvasElement;
let scene: BABYLON.Scene;
function addNewModel() {
const engine = new BABYLON.Engine(canvas, true);
scene = new BABYLON.Scene(engine);
scene.createDefaultCameraOrLight();
scene.clearColor = new BABYLON.Color4(0.2, 0.2, 0.2, 1);
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener("resize", () => {
engine.resize();
});
let base64_model_content = value.data;
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);
let url = URL.createObjectURL(blob);
BABYLON.SceneLoader.Append(
"",
url,
scene,
() => {
scene.createDefaultCamera(true, true, true);
},
undefined,
undefined,
"." + value.name.split(".")[1]
);
}
</script>
<Block
variant={value === null ? "dashed" : "solid"}
color={dragging ? "green" : "grey"}
padding={false}
>
<BlockLabel image={file_icon} label={label || "3D Model File"} />
{#if value === null}
<Upload on:load={handle_upload} filetype=".obj, .gltf, .glb" bind:dragging>
{drop_text}
<br />- {or_text} -<br />
{upload_text}
</Upload>
{:else}
<ModifyUpload on:clear={handle_clear} absolute />
<div
class="input-model w-full h-60 flex justify-center items-center bg-gray-200 dark:bg-gray-600 relative"
>
<canvas class="w-full h-full object-contain" bind:this={canvas} />
</div>
{/if}
</Block>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -0,0 +1,2 @@
export { default as Model3D } from "./Model3D.svelte";
export { default as Model3DUpload } from "./Model3DUpload.svelte";

View File

@ -0,0 +1,13 @@
{
"name": "@gradio/plot",
"version": "0.0.1",
"description": "Gradio UI packages",
"type": "module",
"main": "src/index.ts",
"author": "",
"license": "ISC",
"private": true,
"dependencies": {
"plotly.js-dist-min": "^2.10.1"
}
}

View File

@ -0,0 +1,74 @@
<svelte:head>
<!-- Loading Bokeh from CDN -->
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js" on:load={handleBokehLoaded} ></script>
{#if bokehLoaded}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-2.4.2.min.js" on:load={() => initializeBokeh(1)} ></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-tables-2.4.2.min.js" on:load={() => initializeBokeh(2)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-gl-2.4.2.min.js" on:load={() => initializeBokeh(3)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-api-2.4.2.min.js" on:load={() => initializeBokeh(4)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-api-2.4.2.min.js" on:load={() => initializeBokeh(5)} ></script>
{/if}
</svelte:head>
<script lang="ts">
export let value: null | string;
export let theme: string;
import { afterUpdate, onMount} from "svelte";
import Plotly from "plotly.js-dist-min";
// Bokeh
let bokehLoaded = false
const resolves = []
const bokehPromises = Array(6).fill(0).map((_, i) => createPromise(i))
const initializeBokeh = (index) => {
if (value["type"] == "bokeh") {
resolves[index]()
}
}
function createPromise(index) {
return new Promise((resolve, reject) => {
resolves[index] = resolve
})
}
function handleBokehLoaded() {
initializeBokeh(0)
bokehLoaded = true
}
Promise.all(bokehPromises).then(() => {
let plotObj = JSON.parse(value["plot"]);
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
})
// Plotly
afterUpdate(() => {
if (value && value["type"] == "plotly") {
let plotObj = JSON.parse(value["plot"]);
let plotDiv = document.getElementById("plotlyDiv");
Plotly.newPlot(plotDiv, plotObj["data"], plotObj["layout"]);
} else if (value && value["type"] == "bokeh") {
let plotObj = JSON.parse(value["plot"]);
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
}
});
</script>
{#if value && value["type"] == "plotly"}
<div id="plotlyDiv"/>
{:else if value}
<div id="bokehDiv"/>
{:else if value && value["type"] == "bokeh"}
<div
class="output-image w-full h-80 flex justify-center items-center dark:bg-gray-600 relative"
{theme}
>
<!-- svelte-ignore a11y-missing-attribute -->
<img class="w-full h-full object-contain" src={value["plot"]} />
</div>
{/if}
<style lang="postcss">
</style>

View File

@ -0,0 +1 @@
export { default as Plot } from "./Plot.svelte";

View File

@ -41,6 +41,8 @@
"@gradio/tabs": "workspace:^0.0.1",
"@gradio/theme": "workspace:^0.0.1",
"@gradio/upload": "workspace:^0.0.1",
"@gradio/video": "workspace:^0.0.1"
"@gradio/video": "workspace:^0.0.1",
"@gradio/model3D": "workspace:^0.0.1",
"@gradio/plot": "workspace:^0.0.1"
}
}

View File

@ -72,6 +72,8 @@ importers:
'@gradio/json': workspace:^0.0.1
'@gradio/label': workspace:^0.0.1
'@gradio/markdown': workspace:^0.0.1
'@gradio/model3D': workspace:^0.0.1
'@gradio/plot': workspace:^0.0.1
'@gradio/table': workspace:^0.0.1
'@gradio/tabs': workspace:^0.0.1
'@gradio/theme': workspace:^0.0.1
@ -94,6 +96,8 @@ importers:
'@gradio/json': link:../json
'@gradio/label': link:../label
'@gradio/markdown': link:../markdown
'@gradio/model3D': link:../model3D
'@gradio/plot': link:../plot
'@gradio/table': link:../table
'@gradio/tabs': link:../tabs
'@gradio/theme': link:../theme
@ -188,6 +192,24 @@ importers:
packages/markdown:
specifiers: {}
packages/model3D:
specifiers:
'@gradio/atoms': workspace:^0.0.1
'@gradio/upload': workspace:^0.0.1
babylonjs: ^4.2.1
babylonjs-loaders: ^4.2.1
dependencies:
'@gradio/atoms': link:../atoms
'@gradio/upload': link:../upload
babylonjs: 4.2.2
babylonjs-loaders: 4.2.2
packages/plot:
specifiers:
plotly.js-dist-min: ^2.10.1
dependencies:
plotly.js-dist-min: 2.11.1
packages/table:
specifiers: {}
@ -235,6 +257,8 @@ importers:
'@gradio/json': workspace:^0.0.1
'@gradio/label': workspace:^0.0.1
'@gradio/markdown': workspace:^0.0.1
'@gradio/model3D': workspace:^0.0.1
'@gradio/plot': workspace:^0.0.1
'@gradio/table': workspace:^0.0.1
'@gradio/tabs': workspace:^0.0.1
'@gradio/theme': workspace:^0.0.1
@ -266,6 +290,8 @@ importers:
'@gradio/json': link:../json
'@gradio/label': link:../label
'@gradio/markdown': link:../markdown
'@gradio/model3D': link:../model3D
'@gradio/plot': link:../plot
'@gradio/table': link:../table
'@gradio/tabs': link:../tabs
'@gradio/theme': link:../theme
@ -273,7 +299,7 @@ importers:
'@gradio/video': link:../video
devDependencies:
'@sveltejs/adapter-auto': 1.0.0-next.34
'@sveltejs/kit': 1.0.0-next.312_svelte@3.46.3
'@sveltejs/kit': 1.0.0-next.314_svelte@3.46.3
autoprefixer: 10.4.2_postcss@8.4.6
postcss: 8.4.6
postcss-load-config: 3.1.1
@ -1000,8 +1026,8 @@ packages:
esbuild: 0.14.31
dev: true
/@sveltejs/kit/1.0.0-next.312_svelte@3.46.3:
resolution: {integrity: sha512-l07ct0FJQnt+y3/NSEBvHKDKVE/7syYQnFhCVZ6H+4LEYRtTNybFxUTC8jA3xpZwPqJLZz1X+Tt+Sa8ZMuVzDg==}
/@sveltejs/kit/1.0.0-next.314_svelte@3.46.3:
resolution: {integrity: sha512-EkXjEd30XO8+AydBLARxAtI6Ak8V0KFSEJ07sNsrkGq9J70sWPL7yGenhGaeq6S852XZAqeyWd6uGQZmQLR4Wg==}
engines: {node: '>=14.13'}
hasBin: true
peerDependencies:
@ -1420,6 +1446,21 @@ packages:
object.assign: 4.1.2
dev: false
/babylonjs-gltf2interface/4.2.2:
resolution: {integrity: sha512-LCQgW1lM+EpKK4yWMiPEgi6ONwJ7W4JrSu3t9JixNRgvnic72OnN2f0bt91rE30EJr1ZaokvkXD/aEiBp/Juyg==}
dev: false
/babylonjs-loaders/4.2.2:
resolution: {integrity: sha512-IuShR5N4zkDMzKEGsCZ0uZDCn07BLLj8LlKwyiSwFR1V9KxAALkt2INTMCdXjuWELTcZEALZlyO85mKJ2pDPHw==}
dependencies:
babylonjs: 4.2.2
babylonjs-gltf2interface: 4.2.2
dev: false
/babylonjs/4.2.2:
resolution: {integrity: sha512-p7mTi6+nLuWJTLbwxEJxLOh/QMHMV2KA0bviEoQSK5VtsAq1F0JghoOZYRs4aEqAZF/deFPWvMQk1vbXJ+4eEA==}
dev: false
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -3372,6 +3413,10 @@ packages:
- utf-8-validate
dev: false
/plotly.js-dist-min/2.11.1:
resolution: {integrity: sha512-F9WWNht0D3yBLZGHbLoJNfvplXvy+GUPSsA/lCbMuYd/UwzSu6Vmyprxlps9Einw1LDS1hYBrJeioK0lE3ieXA==}
dev: false
/pn/1.1.0:
resolution: {integrity: sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==}
dev: false