diff --git a/.changeset/fruity-peaches-appear.md b/.changeset/fruity-peaches-appear.md new file mode 100644 index 0000000000..422649f9c8 --- /dev/null +++ b/.changeset/fruity-peaches-appear.md @@ -0,0 +1,6 @@ +--- +"@gradio/model3d": minor +"gradio": minor +--- + +feat:Model3D panning, improved UX diff --git a/gradio/components/model3d.py b/gradio/components/model3d.py index f3977b9ecf..edd7086655 100644 --- a/gradio/components/model3d.py +++ b/gradio/components/model3d.py @@ -42,6 +42,7 @@ class Model3D(Component): None, ), zoom_speed: float = 1, + pan_speed: float = 1, height: int | None = None, label: str | None = None, show_label: bool | None = None, @@ -61,6 +62,7 @@ class Model3D(Component): 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. zoom_speed: the speed of zooming in and out of the scene when the cursor wheel is rotated or when screen is pinched on a mobile device. Should be a positive float, increase this value to make zooming faster, decrease to make it slower. Affects the wheelPrecision property of the camera. + pan_speed: the speed of panning the scene when the cursor is dragged or when the screen is dragged on a mobile device. Should be a positive float, increase this value to make panning faster, decrease to make it slower. Affects the panSensibility property of the camera. height: height of the model3D component, in pixels. interactive: if True, will allow users to upload a file; if False, can only be used to display files. If not provided, this is inferred based on whether the component is used as an input or output. label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. @@ -78,6 +80,7 @@ class Model3D(Component): self.camera_position = camera_position self.height = height self.zoom_speed = zoom_speed + self.pan_speed = pan_speed super().__init__( label=label, every=every, diff --git a/js/model3D/shared/Model3D.svelte b/js/model3D/shared/Model3D.svelte index 961cd19700..4a35963fd3 100644 --- a/js/model3D/shared/Model3D.svelte +++ b/js/model3D/shared/Model3D.svelte @@ -14,6 +14,7 @@ export let show_label: boolean; export let i18n: I18nFormatter; export let zoom_speed = 1; + export let pan_speed = 1; // alpha, beta, radius export let camera_position: [number | null, number | null, number | null] = [ @@ -69,13 +70,14 @@ value, clear_color, camera_position, - zoom_speed + zoom_speed, + pan_speed ); } } function handle_undo(): void { - reset_camera_position(scene, camera_position, zoom_speed); + reset_camera_position(scene, camera_position, zoom_speed, pan_speed); } diff --git a/js/model3D/shared/Model3DUpload.svelte b/js/model3D/shared/Model3DUpload.svelte index bae644dd36..068f75b83b 100644 --- a/js/model3D/shared/Model3DUpload.svelte +++ b/js/model3D/shared/Model3DUpload.svelte @@ -13,6 +13,7 @@ export let root: string; export let i18n: I18nFormatter; export let zoom_speed = 1; + export let pan_speed = 1; // alpha, beta, radius export let camera_position: [number | null, number | null, number | null] = [ @@ -34,7 +35,8 @@ value, clear_color, camera_position, - zoom_speed + zoom_speed, + pan_speed ); } @@ -71,7 +73,7 @@ } async function handle_undo(): Promise { - reset_camera_position(scene, camera_position, zoom_speed); + reset_camera_position(scene, camera_position, zoom_speed, pan_speed); } const dispatch = createEventDispatcher<{ diff --git a/js/model3D/shared/utils.ts b/js/model3D/shared/utils.ts index 4d5cde83a5..1511f7458d 100644 --- a/js/model3D/shared/utils.ts +++ b/js/model3D/shared/utils.ts @@ -4,7 +4,8 @@ import * as BABYLON from "babylonjs"; const create_camera = ( scene: BABYLON.Scene, camera_position: [number | null, number | null, number | null], - zoom_speed: number + zoom_speed: number, + pan_speed: number ): void => { scene.createDefaultCamera(true, true, true); var helperCamera = scene.activeCamera! as BABYLON.ArcRotateCamera; @@ -17,11 +18,14 @@ const create_camera = ( if (camera_position[2] !== null) { helperCamera.radius = camera_position[2]; } - // Disable panning. Adapted from: https://playground.babylonjs.com/#4U6TVQ#3 - helperCamera.panningSensibility = 0; - helperCamera.attachControl(false, false, -1); - helperCamera.pinchToPanMaxDistance = 0; - helperCamera.wheelPrecision = 2500 / zoom_speed; + helperCamera.lowerRadiusLimit = 0.1; + const updateCameraSensibility = (): void => { + helperCamera.wheelPrecision = 250 / (helperCamera.radius * zoom_speed); + helperCamera.panningSensibility = (10000 * pan_speed) / helperCamera.radius; + }; + updateCameraSensibility(); + helperCamera.attachControl(true); + helperCamera.onAfterCheckInputsObservable.add(updateCameraSensibility); }; export const add_new_model = ( @@ -31,7 +35,8 @@ export const add_new_model = ( value: FileData | null, clear_color: [number, number, number, number], camera_position: [number | null, number | null, number | null], - zoom_speed: number + zoom_speed: number, + pan_speed: number ): BABYLON.Scene => { if (scene && !scene.isDisposed && engine) { scene.dispose(); @@ -61,7 +66,7 @@ export const add_new_model = ( url, "", scene, - () => create_camera(scene, camera_position, zoom_speed), + () => create_camera(scene, camera_position, zoom_speed, pan_speed), undefined, undefined, "." + value.path.split(".")[1] @@ -72,8 +77,9 @@ export const add_new_model = ( export const reset_camera_position = ( scene: BABYLON.Scene, camera_position: [number | null, number | null, number | null], - zoom_speed: number + zoom_speed: number, + pan_speed: number ): void => { scene.removeCamera(scene.activeCamera!); - create_camera(scene, camera_position, zoom_speed); + create_camera(scene, camera_position, zoom_speed, pan_speed); }; diff --git a/test/test_components.py b/test/test_components.py index 9f20b33014..0776247a44 100644 --- a/test/test_components.py +++ b/test/test_components.py @@ -2016,6 +2016,7 @@ class TestModel3D: "camera_position": (None, None, None), "height": None, "zoom_speed": 1, + "pan_speed": 1, "_selectable": False, }