@gradio/model3d 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @gradio/model3d
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Features
6
+
7
+ - [#7315](https://github.com/gradio-app/gradio/pull/7315) [`b3a9c83`](https://github.com/gradio-app/gradio/commit/b3a9c830955a5ded8528d6118aaf1b6e019857a2) - Lite: Wasm-compatible Model3D. Thanks [@whitphx](https://github.com/whitphx)!
8
+
9
+ ### Fixes
10
+
11
+ - [#7192](https://github.com/gradio-app/gradio/pull/7192) [`8dd6f4b`](https://github.com/gradio-app/gradio/commit/8dd6f4bc1901792f05cd59e86df7b1dbab692739) - Handle the case where examples is `null` for all components. Thanks [@abidlabs](https://github.com/abidlabs)!
12
+
3
13
  ## 0.5.0
4
14
 
5
15
  ### Features
package/Example.svelte CHANGED
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- export let value: string;
2
+ export let value: string | null;
3
3
  export let type: "gallery" | "table";
4
4
  export let selected = false;
5
5
  </script>
@@ -9,7 +9,7 @@
9
9
  class:gallery={type === "gallery"}
10
10
  class:selected
11
11
  >
12
- {value}
12
+ {value ? value : ""}
13
13
  </div>
14
14
 
15
15
  <style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/model3d",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -11,12 +11,13 @@
11
11
  "babylonjs": "^4.2.1",
12
12
  "babylonjs-loaders": "^4.2.1",
13
13
  "dequal": "^2.0.2",
14
- "@gradio/atoms": "^0.5.0",
15
- "@gradio/client": "^0.10.1",
14
+ "@gradio/atoms": "^0.5.1",
15
+ "@gradio/client": "^0.11.0",
16
16
  "@gradio/icons": "^0.3.2",
17
- "@gradio/statustracker": "^0.4.4",
18
- "@gradio/utils": "^0.2.1",
19
- "@gradio/upload": "^0.7.0"
17
+ "@gradio/statustracker": "^0.4.5",
18
+ "@gradio/upload": "^0.7.1",
19
+ "@gradio/utils": "^0.2.2",
20
+ "@gradio/wasm": "^0.6.0"
20
21
  },
21
22
  "main_changeset": true,
22
23
  "main": "./Index.svelte",
@@ -0,0 +1,149 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import * as BABYLON from "babylonjs";
4
+ import * as BABYLON_LOADERS from "babylonjs-loaders";
5
+ import type { FileData } from "@gradio/client";
6
+ import { resolve_wasm_src } from "@gradio/wasm/svelte";
7
+
8
+ $: {
9
+ if (
10
+ BABYLON_LOADERS.OBJFileLoader != undefined &&
11
+ !BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS
12
+ ) {
13
+ BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true;
14
+ }
15
+ }
16
+
17
+ export let value: FileData;
18
+ export let clear_color: [number, number, number, number];
19
+ export let camera_position: [number | null, number | null, number | null];
20
+ export let zoom_speed: number;
21
+ export let pan_speed: number;
22
+
23
+ $: url = value.url;
24
+
25
+ /* URL resolution for the Wasm mode. */
26
+ export let resolved_url: typeof url = undefined; // Exposed to be bound to the download link in the parent component.
27
+ // The prop can be updated before the Promise from `resolve_wasm_src` is resolved.
28
+ // In such a case, the resolved url for the old `url` has to be discarded,
29
+ // This variable `latest_url` is used to pick up only the value resolved for the latest `url`.
30
+ let latest_url: typeof url;
31
+ $: {
32
+ // In normal (non-Wasm) Gradio, the original `url` should be used immediately
33
+ // without waiting for `resolve_wasm_src()` to resolve.
34
+ // If it waits, a blank element is displayed until the async task finishes
35
+ // and it leads to undesirable flickering.
36
+ // So set `resolved_url` immediately above, and update it with the resolved values below later.
37
+ resolved_url = url;
38
+
39
+ if (url) {
40
+ latest_url = url;
41
+ const resolving_url = url;
42
+ resolve_wasm_src(url).then((resolved) => {
43
+ if (latest_url === resolving_url) {
44
+ resolved_url = resolved ?? undefined;
45
+ } else {
46
+ resolved && URL.revokeObjectURL(resolved);
47
+ }
48
+ });
49
+ }
50
+ }
51
+
52
+ /* BabylonJS engine and scene management */
53
+ let canvas: HTMLCanvasElement;
54
+ let scene: BABYLON.Scene;
55
+ let engine: BABYLON.Engine;
56
+ let mounted = false;
57
+
58
+ onMount(() => {
59
+ // Initialize BabylonJS engine and scene
60
+ engine = new BABYLON.Engine(canvas, true);
61
+ scene = new BABYLON.Scene(engine);
62
+
63
+ scene.createDefaultCameraOrLight();
64
+ scene.clearColor = scene.clearColor = new BABYLON.Color4(...clear_color);
65
+
66
+ engine.runRenderLoop(() => {
67
+ scene.render();
68
+ });
69
+
70
+ function onWindowResize(): void {
71
+ engine.resize();
72
+ }
73
+ window.addEventListener("resize", onWindowResize);
74
+
75
+ mounted = true;
76
+
77
+ return () => {
78
+ scene.dispose();
79
+ engine.dispose();
80
+ window.removeEventListener("resize", onWindowResize);
81
+ };
82
+ });
83
+
84
+ $: mounted && load_model(resolved_url);
85
+
86
+ function load_model(url: string | undefined): void {
87
+ if (scene) {
88
+ // Dispose of the previous model before loading a new one
89
+ scene.meshes.forEach((mesh) => {
90
+ mesh.dispose();
91
+ });
92
+
93
+ // Load the new model
94
+ if (url) {
95
+ BABYLON.SceneLoader.ShowLoadingScreen = false;
96
+ BABYLON.SceneLoader.Append(
97
+ url,
98
+ "",
99
+ scene,
100
+ () => create_camera(scene, camera_position, zoom_speed, pan_speed),
101
+ undefined,
102
+ undefined,
103
+ "." + value.path.split(".").pop()
104
+ );
105
+ }
106
+ }
107
+ }
108
+
109
+ function create_camera(
110
+ scene: BABYLON.Scene,
111
+ camera_position: [number | null, number | null, number | null],
112
+ zoom_speed: number,
113
+ pan_speed: number
114
+ ): void {
115
+ scene.createDefaultCamera(true, true, true);
116
+ var helperCamera = scene.activeCamera! as BABYLON.ArcRotateCamera;
117
+ if (camera_position[0] !== null) {
118
+ helperCamera.alpha = BABYLON.Tools.ToRadians(camera_position[0]);
119
+ }
120
+ if (camera_position[1] !== null) {
121
+ helperCamera.beta = BABYLON.Tools.ToRadians(camera_position[1]);
122
+ }
123
+ if (camera_position[2] !== null) {
124
+ helperCamera.radius = camera_position[2];
125
+ }
126
+ helperCamera.lowerRadiusLimit = 0.1;
127
+ const updateCameraSensibility = (): void => {
128
+ helperCamera.wheelPrecision = 250 / (helperCamera.radius * zoom_speed);
129
+ helperCamera.panningSensibility =
130
+ (10000 * pan_speed) / helperCamera.radius;
131
+ };
132
+ updateCameraSensibility();
133
+ helperCamera.attachControl(true);
134
+ helperCamera.onAfterCheckInputsObservable.add(updateCameraSensibility);
135
+ }
136
+
137
+ export function reset_camera_position(
138
+ camera_position: [number | null, number | null, number | null],
139
+ zoom_speed: number,
140
+ pan_speed: number
141
+ ): void {
142
+ if (scene) {
143
+ scene.removeCamera(scene.activeCamera!);
144
+ create_camera(scene, camera_position, zoom_speed, pan_speed);
145
+ }
146
+ }
147
+ </script>
148
+
149
+ <canvas bind:this={canvas}></canvas>
@@ -2,10 +2,7 @@
2
2
  import type { FileData } from "@gradio/client";
3
3
  import { BlockLabel, IconButton } from "@gradio/atoms";
4
4
  import { File, Download, Undo } from "@gradio/icons";
5
- import { add_new_model, reset_camera_position } from "./utils";
6
- import { onMount } from "svelte";
7
- import * as BABYLON from "babylonjs";
8
- import * as BABYLON_LOADERS from "babylonjs-loaders";
5
+ import Canvas3D from "./Canvas3D.svelte";
9
6
  import type { I18nFormatter } from "@gradio/utils";
10
7
  import { dequal } from "dequal";
11
8
 
@@ -25,71 +22,20 @@
25
22
 
26
23
  let current_settings = { camera_position, zoom_speed, pan_speed };
27
24
 
28
- $: {
29
- if (
30
- BABYLON_LOADERS.OBJFileLoader != undefined &&
31
- !BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS
32
- ) {
33
- BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true;
34
- }
35
- }
36
-
37
- let canvas: HTMLCanvasElement;
38
- let scene: BABYLON.Scene;
39
- let engine: BABYLON.Engine | null;
40
- let mounted = false;
41
-
42
- onMount(() => {
43
- engine = new BABYLON.Engine(canvas, true);
44
- window.addEventListener("resize", () => {
45
- engine?.resize();
46
- });
47
- mounted = true;
48
- });
49
-
50
- $: ({ path } = value || {
51
- path: undefined
52
- });
53
-
54
- $: canvas && mounted && path && dispose();
55
-
56
- function dispose(): void {
57
- if (scene && !scene.isDisposed) {
58
- scene.dispose();
59
- engine?.stopRenderLoop();
60
- engine?.dispose();
61
- engine = null;
62
- engine = new BABYLON.Engine(canvas, true);
63
- window.addEventListener("resize", () => {
64
- engine?.resize();
65
- });
66
- }
67
- if (engine !== null) {
68
- scene = add_new_model(
69
- canvas,
70
- scene,
71
- engine,
72
- value,
73
- clear_color,
74
- camera_position,
75
- zoom_speed,
76
- pan_speed
77
- );
78
- }
79
- }
25
+ let canvas3d: Canvas3D;
26
+ let resolved_url: string | undefined;
80
27
 
81
28
  function handle_undo(): void {
82
- reset_camera_position(scene, camera_position, zoom_speed, pan_speed);
29
+ canvas3d.reset_camera_position(camera_position, zoom_speed, pan_speed);
83
30
  }
84
31
 
85
32
  $: {
86
33
  if (
87
- scene &&
88
- (!dequal(current_settings.camera_position, camera_position) ||
89
- current_settings.zoom_speed !== zoom_speed ||
90
- current_settings.pan_speed !== pan_speed)
34
+ !dequal(current_settings.camera_position, camera_position) ||
35
+ current_settings.zoom_speed !== zoom_speed ||
36
+ current_settings.pan_speed !== pan_speed
91
37
  ) {
92
- reset_camera_position(scene, camera_position, zoom_speed, pan_speed);
38
+ canvas3d.reset_camera_position(camera_position, zoom_speed, pan_speed);
93
39
  current_settings = { camera_position, zoom_speed, pan_speed };
94
40
  }
95
41
  }
@@ -105,7 +51,7 @@
105
51
  <div class="buttons">
106
52
  <IconButton Icon={Undo} label="Undo" on:click={() => handle_undo()} />
107
53
  <a
108
- href={value.url}
54
+ href={resolved_url}
109
55
  target={window.__is_colab__ ? "_blank" : null}
110
56
  download={window.__is_colab__ ? null : value.orig_name || value.path}
111
57
  >
@@ -113,7 +59,15 @@
113
59
  </a>
114
60
  </div>
115
61
 
116
- <canvas bind:this={canvas} />
62
+ <Canvas3D
63
+ bind:this={canvas3d}
64
+ bind:resolved_url
65
+ {value}
66
+ {clear_color}
67
+ {camera_position}
68
+ {zoom_speed}
69
+ {pan_speed}
70
+ />
117
71
  </div>
118
72
  {/if}
119
73
 
@@ -124,7 +78,7 @@
124
78
  width: var(--size-full);
125
79
  height: var(--size-full);
126
80
  }
127
- canvas {
81
+ .model3D :global(canvas) {
128
82
  width: var(--size-full);
129
83
  height: var(--size-full);
130
84
  object-fit: contain;
@@ -1,10 +1,11 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, tick, onMount } from "svelte";
2
+ import { createEventDispatcher, tick } from "svelte";
3
3
  import { Upload, ModifyUpload } from "@gradio/upload";
4
4
  import type { FileData } from "@gradio/client";
5
5
  import { BlockLabel } from "@gradio/atoms";
6
6
  import { File } from "@gradio/icons";
7
- import { add_new_model, reset_camera_position } from "./utils";
7
+ import type { I18nFormatter } from "@gradio/utils";
8
+ import Canvas3D from "./Canvas3D.svelte";
8
9
 
9
10
  export let value: null | FileData;
10
11
  export let clear_color: [number, number, number, number] = [0, 0, 0, 0];
@@ -22,60 +23,25 @@
22
23
  null
23
24
  ];
24
25
 
25
- let mounted = false;
26
- let canvas: HTMLCanvasElement;
27
- let scene: BABYLON.Scene;
28
- let engine: BABYLON.Engine;
29
-
30
- function reset_scene(): void {
31
- scene = add_new_model(
32
- canvas,
33
- scene,
34
- engine,
35
- value,
36
- clear_color,
37
- camera_position,
38
- zoom_speed,
39
- pan_speed
40
- );
41
- }
42
-
43
- onMount(() => {
44
- if (value != null) {
45
- reset_scene();
46
- }
47
- mounted = true;
48
- });
49
-
50
- $: ({ path } = value || {
51
- path: undefined
52
- });
53
-
54
- $: canvas && mounted && path != null && reset_scene();
55
-
56
26
  async function handle_upload({
57
27
  detail
58
28
  }: CustomEvent<FileData>): Promise<void> {
59
29
  value = detail;
60
30
  await tick();
61
- reset_scene();
62
31
  dispatch("change", value);
63
32
  dispatch("load", value);
64
33
  }
65
34
 
66
35
  async function handle_clear(): Promise<void> {
67
- if (scene && engine) {
68
- scene.dispose();
69
- engine.dispose();
70
- }
71
36
  value = null;
72
37
  await tick();
73
38
  dispatch("clear");
74
39
  dispatch("change");
75
40
  }
76
41
 
42
+ let canvas3D: Canvas3D;
77
43
  async function handle_undo(): Promise<void> {
78
- reset_camera_position(scene, camera_position, zoom_speed, pan_speed);
44
+ canvas3D.reset_camera_position(camera_position, zoom_speed, pan_speed);
79
45
  }
80
46
 
81
47
  const dispatch = createEventDispatcher<{
@@ -87,19 +53,6 @@
87
53
 
88
54
  let dragging = false;
89
55
 
90
- import * as BABYLON from "babylonjs";
91
- import * as BABYLON_LOADERS from "babylonjs-loaders";
92
- import type { I18nFormatter } from "@gradio/utils";
93
-
94
- $: {
95
- if (
96
- BABYLON_LOADERS.OBJFileLoader != undefined &&
97
- !BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS
98
- ) {
99
- BABYLON_LOADERS.OBJFileLoader.IMPORT_VERTEX_COLORS = true;
100
- }
101
- }
102
-
103
56
  $: dispatch("drag", dragging);
104
57
  </script>
105
58
 
@@ -123,7 +76,14 @@
123
76
  on:undo={handle_undo}
124
77
  absolute
125
78
  />
126
- <canvas bind:this={canvas} />
79
+ <Canvas3D
80
+ bind:this={canvas3D}
81
+ {value}
82
+ {clear_color}
83
+ {camera_position}
84
+ {zoom_speed}
85
+ {pan_speed}
86
+ ></Canvas3D>
127
87
  </div>
128
88
  {/if}
129
89
 
@@ -137,7 +97,7 @@
137
97
  height: var(--size-full);
138
98
  }
139
99
 
140
- canvas {
100
+ .input-model :global(canvas) {
141
101
  width: var(--size-full);
142
102
  height: var(--size-full);
143
103
  object-fit: contain;
package/shared/utils.ts DELETED
@@ -1,85 +0,0 @@
1
- import type { FileData } from "@gradio/client";
2
- import * as BABYLON from "babylonjs";
3
-
4
- const create_camera = (
5
- scene: BABYLON.Scene,
6
- camera_position: [number | null, number | null, number | null],
7
- zoom_speed: number,
8
- pan_speed: number
9
- ): void => {
10
- scene.createDefaultCamera(true, true, true);
11
- var helperCamera = scene.activeCamera! as BABYLON.ArcRotateCamera;
12
- if (camera_position[0] !== null) {
13
- helperCamera.alpha = BABYLON.Tools.ToRadians(camera_position[0]);
14
- }
15
- if (camera_position[1] !== null) {
16
- helperCamera.beta = BABYLON.Tools.ToRadians(camera_position[1]);
17
- }
18
- if (camera_position[2] !== null) {
19
- helperCamera.radius = camera_position[2];
20
- }
21
- helperCamera.lowerRadiusLimit = 0.1;
22
- const updateCameraSensibility = (): void => {
23
- helperCamera.wheelPrecision = 250 / (helperCamera.radius * zoom_speed);
24
- helperCamera.panningSensibility = (10000 * pan_speed) / helperCamera.radius;
25
- };
26
- updateCameraSensibility();
27
- helperCamera.attachControl(true);
28
- helperCamera.onAfterCheckInputsObservable.add(updateCameraSensibility);
29
- };
30
-
31
- export const add_new_model = (
32
- canvas: HTMLCanvasElement,
33
- scene: BABYLON.Scene,
34
- engine: BABYLON.Engine,
35
- value: FileData | null,
36
- clear_color: [number, number, number, number],
37
- camera_position: [number | null, number | null, number | null],
38
- zoom_speed: number,
39
- pan_speed: number
40
- ): BABYLON.Scene => {
41
- if (scene && !scene.isDisposed && engine) {
42
- scene.dispose();
43
- engine.dispose();
44
- }
45
-
46
- engine = new BABYLON.Engine(canvas, true);
47
- scene = new BABYLON.Scene(engine);
48
- scene.createDefaultCameraOrLight();
49
- scene.clearColor = scene.clearColor = new BABYLON.Color4(...clear_color);
50
-
51
- engine.runRenderLoop(() => {
52
- scene.render();
53
- });
54
-
55
- window.addEventListener("resize", () => {
56
- engine.resize();
57
- });
58
-
59
- if (!value) return scene;
60
- let url: string;
61
-
62
- url = value.url!;
63
-
64
- BABYLON.SceneLoader.ShowLoadingScreen = false;
65
- BABYLON.SceneLoader.Append(
66
- url,
67
- "",
68
- scene,
69
- () => create_camera(scene, camera_position, zoom_speed, pan_speed),
70
- undefined,
71
- undefined,
72
- "." + value.path.split(".")[1]
73
- );
74
- return scene;
75
- };
76
-
77
- export const reset_camera_position = (
78
- scene: BABYLON.Scene,
79
- camera_position: [number | null, number | null, number | null],
80
- zoom_speed: number,
81
- pan_speed: number
82
- ): void => {
83
- scene.removeCamera(scene.activeCamera!);
84
- create_camera(scene, camera_position, zoom_speed, pan_speed);
85
- };