Place a model in a real room — and the default that silently disabled WebXR on Android for the whole life of 0.0.1.

Augmented Reality

Place a model in a real room — and the default that silently disabled WebXR on Android for the whole life of 0.0.1.


The bug this release exists to fix

ar_modes in 0.0.1 defaulted to:

"basic_annotations scene-viewer quick-look"

basic_annotations is not an AR mode. It is the name of a folder in the repository's usage_tests/ directory, copy-pasted into the default value.

The consequence was not a warning or an error. <model-viewer> reads the list, does not recognise the first entry, ignores it, and falls through to the rest — so AR still worked via Scene Viewer on Android and Quick Look on iOS. What was missing was webxr, the mode that gives you in-page AR with your own UI and the only one that supports custom AR prompts and placement.

So the package's flagship feature was degraded in every default installation, for its entire published life, and nothing looked broken. The hub documentation had always listed the correct value, so the docs and the code never agreed.

1.0.0 defaults to:

"webxr scene-viewer quick-look"

tests/test_components.py asserts both that webxr is present and that basic_annotations is absent.


AR in practice

# File: docs/augmented-reality/ar_viewer.py

from dash import Input, Output, callback, html
import dash_mantine_components as dmc

import dash_model_viewer as dmv
from lib.demo_models import ASTRONAUT

component = html.Div(
    [
        dmv.ModelViewer(
            id="ar-viewer",
            src=ASTRONAUT,
            alt="An astronaut, placeable in your room with AR",
            ar=True,
            # This is the default. Shown explicitly because the equivalent
            # line in 0.0.1 read "basic_annotations scene-viewer quick-look".
            ar_modes="webxr scene-viewer quick-look",
            ar_scale="auto",
            shadow_intensity=1,
            # Two AR attributes with no named prop. They are set HERE rather
            # than on /attribute-tour because only a phone in a real AR session
            # can show whether they did anything — a desktop toggle for either
            # would be a control that demonstrates nothing.
            #
            #   ar-placement="floor"  — anchor to the floor. "wall" is the other
            #     value and is right for a frame or a television.
            #   xr-environment        — present: light the model from the room's
            #     estimated lighting instead of `environment-image`.
            #
            # `ios-src` is deliberately absent: Quick Look cannot read `.glb`,
            # so it needs a second `.usdz` file that this repo does not ship.
            # Setting it to a file that does not exist would break iOS AR to
            # document an attribute.
            attributes={"ar-placement": "floor", "xr-environment": ""},
            style={"width": "100%", "height": "400px"},
            children=[
                dmv.Slot(
                    slot="ar-button",
                    children=dmc.Button("View in your space", variant="filled"),
                ),
                dmv.Slot(
                    slot="ar-failure",
                    children=dmc.Alert("AR lost tracking — try more light.",
                                       color="red"),
                ),
            ],
        ),
        dmc.Text(id="ar-readout", size="sm", mt="sm"),
    ]
)


@callback(
    Output("ar-readout", "children"),
    Input("ar-viewer", "ar_status"),
    Input("ar-viewer", "ar_tracking"),
)
def report(status, tracking):
    if not status:
        return "On a phone, tap the button above. On desktop, nothing happens — by design."
    return f"ar_status: {status} · ar_tracking: {tracking or 'n/a'}"

AR needs a phone. On a desktop browser the AR button does not appear at all, ar_status never fires, and everything above is inert — which is correct behaviour, not a broken example.

Open modelviewer.2plot.dev/augmented-reality on an Android or iOS device to try it.


The three AR modes

ModePlatformWhat you get
webxrAndroid, ChromeIn-page AR. Your own UI overlays the camera feed; ar-prompt and ar-failure slots work; placement and scale are reported back.
scene-viewerAndroidHands off to Google's system AR viewer. Reliable, but it leaves your page.
quick-lookiOS, SafariHands off to Apple's AR Quick Look. Requires a USDZ — <model-viewer> generates one, or supply ios-src.

Order matters: it is a preference list, and the first supported mode wins. "webxr scene-viewer quick-look" means "in-page if you can, system viewer otherwise".


Reacting to the session

@callback(Output("cart", "disabled"), Input("viewer", "ar_status"))
def in_ar(status):
    return status == "session-started"

ar_status values: not-presenting, session-started, object-placed, failed. ar_tracking is tracking or not-tracking, and is how you know to tell the user their room is too dark.


iOS needs a USDZ

Quick Look does not read glTF. <model-viewer> converts on the fly, but the conversion is lossy for complex materials and costs a round trip. For anything you care about, supply your own:

dmv.ModelViewer(
    id="viewer", src="/assets/chair.glb", alt="A chair",
    mv_ios_src="/assets/chair.usdz",
)

ar_scale

"auto" (default) lets AR place the model at real-world scale, derived from the glTF's units. "fixed" keeps the model's own scene units regardless.

Use "auto" for anything a user might want to check the size of — furniture, appliances, equipment. Use "fixed" when the model is not a real object, or when its units are not trustworthy.

WebXR requires a secure context. On http:// — including a plain localhost tunnel to a phone — the AR button will not appear, and there will be no error explaining why.


Source: /augmented-reality

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: