mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-12 10:34:32 +08:00
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:
parent
2f771e201a
commit
499f106c9e
1
.github/workflows/ui.yml
vendored
1
.github/workflows/ui.yml
vendored
@ -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
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
BIN
demo/model3D/files/Duck.glb
Normal file
Binary file not shown.
1777
demo/model3D/files/Fox.gltf
Normal file
1777
demo/model3D/files/Fox.gltf
Normal file
File diff suppressed because one or more lines are too long
6
demo/model3D/files/source.txt
Normal file
6
demo/model3D/files/source.txt
Normal 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
20
demo/model3D/run.py
Normal 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)
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -22,7 +22,9 @@ from gradio.components import (
|
||||
KeyValues,
|
||||
Label,
|
||||
Markdown,
|
||||
Model3D,
|
||||
Number,
|
||||
Plot,
|
||||
Radio,
|
||||
Slider,
|
||||
StatusTracker,
|
||||
|
@ -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
@ -1,4 +1,5 @@
|
||||
packages/app/public/**
|
||||
pnpm-workspace.yaml
|
||||
packages/app/dist/**
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
packages/plot/src/Plot.svelte
|
@ -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"
|
||||
}
|
||||
|
33
ui/packages/app/src/components/Model3D/Model3D.svelte
Normal file
33
ui/packages/app/src/components/Model3D/Model3D.svelte
Normal 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}
|
2
ui/packages/app/src/components/Model3D/index.ts
Normal file
2
ui/packages/app/src/components/Model3D/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Component } from "./Model3D.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
11
ui/packages/app/src/components/Plot/Plot.svelte
Normal file
11
ui/packages/app/src/components/Plot/Plot.svelte
Normal 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 />
|
2
ui/packages/app/src/components/Plot/index.ts
Normal file
2
ui/packages/app/src/components/Plot/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Component } from "./Plot.svelte";
|
||||
export const modes = ["static"];
|
@ -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")
|
||||
};
|
||||
|
16
ui/packages/model3D/package.json
Normal file
16
ui/packages/model3D/package.json
Normal 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"
|
||||
}
|
||||
}
|
69
ui/packages/model3D/src/Model3D.svelte
Normal file
69
ui/packages/model3D/src/Model3D.svelte
Normal 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>
|
96
ui/packages/model3D/src/Model3DUpload.svelte
Normal file
96
ui/packages/model3D/src/Model3DUpload.svelte
Normal 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>
|
1
ui/packages/model3D/src/file.svg
Normal file
1
ui/packages/model3D/src/file.svg
Normal 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 |
2
ui/packages/model3D/src/index.ts
Normal file
2
ui/packages/model3D/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Model3D } from "./Model3D.svelte";
|
||||
export { default as Model3DUpload } from "./Model3DUpload.svelte";
|
13
ui/packages/plot/package.json
Normal file
13
ui/packages/plot/package.json
Normal 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"
|
||||
}
|
||||
}
|
74
ui/packages/plot/src/Plot.svelte
Normal file
74
ui/packages/plot/src/Plot.svelte
Normal 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>
|
1
ui/packages/plot/src/index.ts
Normal file
1
ui/packages/plot/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Plot } from "./Plot.svelte";
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user