Skip to content

Commit

Permalink
Merge branch 'main' into aliabd/website-scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
aliabd committed Aug 30, 2023
2 parents 3be6704 + 4ccb9a8 commit 7bbee90
Show file tree
Hide file tree
Showing 13 changed files with 1,583 additions and 1,599 deletions.
6 changes: 6 additions & 0 deletions .changeset/shiny-boats-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/model3d": minor
"gradio": minor
---

feat:Makes it possible to set the initial camera position for the `Model3D` component as a tuple of (alpha, beta, radius)
28 changes: 23 additions & 5 deletions gradio/components/model3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,17 @@ def __init__(
self,
value: str | Callable | None = None,
*,
clear_color: list[float] | None = None,
clear_color: tuple[float, float, float, float] | None = None,
camera_position: tuple[
int | float | None, int | float | None, int | float | None
] = (
None,
None,
None,
),
label: str | None = None,
every: float | None = None,
show_label: bool | None = None,
every: float | None = None,
container: bool = True,
scale: int | None = None,
min_width: int = 160,
Expand All @@ -52,10 +59,11 @@ def __init__(
"""
Parameters:
value: path to (.obj, glb, or .gltf) file to show in model3D viewer. If callable, the function will be called whenever the app loads to set the initial value of the component.
clear_color: background color of scene
clear_color: background color of scene, should be a tuple of 4 floats between 0 and 1 representing RGBA values.
camera_position: initial camera position of scene, provided as a tuple of `(alpha, beta, radius)`. Each value is optional. If provided, `alpha` and `beta` should be in degrees reflecting the angular position along the longitudinal and latitudinal axes, respectively. Radius corresponds to the distance from the center of the object to the camera.
label: component name in interface.
every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
show_label: if True, will display label.
every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
container: If True, will place the component in a container - providing some extra padding around the border.
scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
Expand All @@ -64,6 +72,8 @@ def __init__(
elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
"""
self.clear_color = clear_color or [0, 0, 0, 0]
self.camera_position = camera_position

IOComponent.__init__(
self,
label=label,
Expand All @@ -81,8 +91,9 @@ def __init__(

def get_config(self):
return {
"clearColor": self.clear_color,
"clear_color": self.clear_color,
"value": self.value,
"camera_position": self.camera_position,
**IOComponent.get_config(self),
}

Expand All @@ -95,6 +106,11 @@ def example_inputs(self) -> dict[str, Any]:
@staticmethod
def update(
value: Any | Literal[_Keywords.NO_VALUE] | None = _Keywords.NO_VALUE,
camera_position: tuple[
int | float | None, int | float | None, int | float | None
]
| None = None,
clear_color: tuple[float, float, float, float] | None = None,
label: str | None = None,
show_label: bool | None = None,
container: bool | None = None,
Expand All @@ -103,6 +119,8 @@ def update(
visible: bool | None = None,
):
updated_config = {
"camera_position": camera_position,
"clear_color": clear_color,
"label": label,
"show_label": show_label,
"container": container,
Expand Down
12 changes: 10 additions & 2 deletions js/model3D/interactive/InteractiveModel3d.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
export let value: null | FileData = null;
export let root: string;
export let root_url: null | string;
export let clearColor: [number, number, number, number];
export let clear_color: [number, number, number, number];
export let loading_status: LoadingStatus;
export let label: string;
export let show_label: boolean;
Expand All @@ -27,6 +27,13 @@
clear: never;
}>;
// alpha, beta, radius
export let camera_position: [number | null, number | null, number | null] = [
null,
null,
null
];
let _value: null | FileData;
$: _value = normalise_file(value, root, root_url);
Expand All @@ -49,8 +56,9 @@
<Model3DUpload
{label}
{show_label}
{clearColor}
{clear_color}
value={_value}
{camera_position}
on:change={({ detail }) => (value = detail)}
on:drag={({ detail }) => (dragging = detail)}
on:change={({ detail }) => gradio.dispatch("change", detail)}
Expand Down
65 changes: 16 additions & 49 deletions js/model3D/interactive/Model3DUpload.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@
import type { FileData } from "@gradio/upload";
import { BlockLabel } from "@gradio/atoms";
import { File } from "@gradio/icons";
import { add_new_model } from "../shared/utils";
export let value: null | FileData;
export let clearColor: [number, number, number, number] = [0, 0, 0, 0];
export let clear_color: [number, number, number, number] = [0, 0, 0, 0];
export let label = "";
export let show_label: boolean;
// alpha, beta, radius
export let camera_position: [number | null, number | null, number | null] = [
null,
null,
null
];
let mounted = false;
onMount(() => {
if (value != null) {
addNewModel();
add_new_model(canvas, scene, engine, value, clear_color, camera_position);
}
mounted = true;
});
Expand All @@ -25,15 +33,19 @@
name: undefined
});
$: canvas && mounted && data != null && is_file && addNewModel();
$: canvas &&
mounted &&
data != null &&
is_file &&
add_new_model(canvas, scene, engine, value, clear_color, camera_position);
async function handle_upload({
detail
}: CustomEvent<FileData>): Promise<void> {
value = detail;
await tick();
dispatch("change", value);
addNewModel();
add_new_model(canvas, scene, engine, value, clear_color, camera_position);
}
async function handle_clear(): Promise<void> {
Expand Down Expand Up @@ -63,51 +75,6 @@
let scene: BABYLON.Scene;
let engine: BABYLON.Engine;
function addNewModel(): void {
if (scene && !scene.isDisposed && engine) {
scene.dispose();
engine.dispose();
}
engine = new BABYLON.Engine(canvas, true);
scene = new BABYLON.Scene(engine);
scene.createDefaultCameraOrLight();
scene.clearColor = scene.clearColor = new BABYLON.Color4(...clearColor);
engine.runRenderLoop(() => {
scene.render();
});
window.addEventListener("resize", () => {
engine.resize();
});
if (!value) return;
let url: string;
if (value.is_file) {
url = value.data;
} else {
let base64_model_content = value.data;
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);
url = URL.createObjectURL(blob);
}
BABYLON.SceneLoader.ShowLoadingScreen = false;
BABYLON.SceneLoader.Append(
url,
"",
scene,
() => {
scene.createDefaultCamera(true, true, true);
},
undefined,
undefined,
"." + value.name.split(".")[1]
);
}
$: dispatch("drag", dragging);
</script>

Expand Down
1 change: 1 addition & 0 deletions js/model3D/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@gradio/statustracker": "workspace:^",
"@gradio/upload": "workspace:^",
"@gradio/utils": "workspace:^",
"@types/babylon": "^6.16.6",
"babylonjs": "^4.2.1",
"babylonjs-loaders": "^4.2.1"
},
Expand Down
68 changes: 68 additions & 0 deletions js/model3D/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { FileData } from "@gradio/upload";
import * as BABYLON from "babylonjs";

export const add_new_model = (
canvas: HTMLCanvasElement,
scene: BABYLON.Scene,
engine: BABYLON.Engine,
value: FileData | null,
clear_color: [number, number, number, number],
camera_position: [number | null, number | null, number | null]
): void => {
if (scene && !scene.isDisposed && engine) {
scene.dispose();
engine.dispose();
}

engine = new BABYLON.Engine(canvas, true);
scene = new BABYLON.Scene(engine);
scene.createDefaultCameraOrLight();
scene.clearColor = scene.clearColor = new BABYLON.Color4(...clear_color);

engine.runRenderLoop(() => {
scene.render();
});

window.addEventListener("resize", () => {
engine.resize();
});

if (!value) return;

let url: string;
if (value.is_file) {
url = value.data;
} else {
let base64_model_content = value.data;
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);
url = URL.createObjectURL(blob);
}

BABYLON.SceneLoader.ShowLoadingScreen = false;
BABYLON.SceneLoader.Append(
url,
"",
scene,
() => {
// scene.createDefaultCamera(createArcRotateCamera, replace, attachCameraControls)
scene.createDefaultCamera(true, true, true);
// scene.activeCamera has to be an ArcRotateCamera if the call succeeds,
// we assume it does
var helperCamera = scene.activeCamera! as BABYLON.ArcRotateCamera;

if (camera_position[0] !== null) {
helperCamera.alpha = (Math.PI * camera_position[0]) / 180;
}
if (camera_position[1] !== null) {
helperCamera.beta = (Math.PI * camera_position[1]) / 180;
}
if (camera_position[2] !== null) {
helperCamera.radius = camera_position[2];
}
},
undefined,
undefined,
"." + value.name.split(".")[1]
);
};
50 changes: 11 additions & 39 deletions js/model3D/static/Model3D.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@
import type { FileData } from "@gradio/upload";
import { BlockLabel, IconButton } from "@gradio/atoms";
import { File, Download } from "@gradio/icons";
import { add_new_model } from "../shared/utils";
import { _ } from "svelte-i18n";
import { onMount } from "svelte";
import * as BABYLON from "babylonjs";
import * as BABYLON_LOADERS from "babylonjs-loaders";
export let value: FileData | null;
export let clearColor: [number, number, number, number] = [0, 0, 0, 0];
export let clear_color: [number, number, number, number] = [0, 0, 0, 0];
export let label = "";
export let show_label: boolean;
// alpha, beta, radius
export let camera_position: [number | null, number | null, number | null] = [
null,
null,
null
];
BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true;
let canvas: HTMLCanvasElement;
let scene: BABYLON.Scene;
let engine: BABYLON.Engine | null;
let mounted = false;
onMount(() => {
Expand Down Expand Up @@ -46,44 +53,9 @@
engine?.resize();
});
}
addNewModel();
}
function addNewModel(): void {
scene = new BABYLON.Scene(engine!);
scene.createDefaultCameraOrLight();
scene.clearColor = new BABYLON.Color4(...clearColor);
engine?.runRenderLoop(() => {
scene.render();
});
if (!value) return;
let url: string;
if (value.is_file) {
url = value.data;
} else {
let base64_model_content = value.data;
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);
url = URL.createObjectURL(blob);
if (engine !== null) {
add_new_model(canvas, scene, engine, value, clear_color, camera_position);
}
BABYLON.SceneLoader.ShowLoadingScreen = false;
BABYLON.SceneLoader.Append(
"",
url,
scene,
() => {
scene.createDefaultCamera(true, true, true);
},
undefined,
undefined,
"." + value["name"].split(".")[1]
);
}
</script>

Expand Down

0 comments on commit 7bbee90

Please sign in to comment.