/
Back
Unity 6 OpenXR Rhino / Blender / Grasshopper Windows + Android

Tabletop

A spatial second screen. Your CAD model stands on your real desk, live with the source document, while you keep modelling with the keyboard and mouse in front of you.

Project Overview

A model that lives on your desk and stays in sync with the document it came from. You keep working in Rhino, Blender or Grasshopper; you look up and the form is really three-dimensional. No headset to take off, no mode to switch, no "send to VR" button. Built solo in ten days from kickoff to an installer handed to colleagues, with three source-application adapters, a protocol shared as source between both ends, and one Unity client running on both a Windows PC and an Android neckband.

10
days, kickoff to distribution
376
unit tests
3
source-application adapters
5
model formats, zero dependencies
2
runtimes, one client codebase

The Empty Cell

A competitive survey before kickoff found three cells already full: 1:1 immersive walkthrough, on-site construction AR, and modelling inside a headset. The fourth — the spatial second screen — was empty. Existing AR-glasses desktops are 2D window managers; nobody had made the model itself a solid object on the desk. And that cell has an unusual requirement: you must be able to see your keyboard, see your monitor, and wear the device for hours, because you are still sitting in front of your CAD software. Birdbath optical see-through plus a neckband meets all three; a fully enclosed headset is useless here. It is not a category we could also do — it is one only this class of hardware can do.

One Hard Boundary

The glasses are a view, not a second editor: nothing written there ever goes back to the source document. You can drag pieces out, isolate layers, reset the view — and the Rhino document does not move. That boundary removes two-way conflict resolution from the product entirely, and it is also what makes people willing to try it on a file they care about.

System

A Rhino plugin listens to document events; a Blender add-on does the same job without a single line changing in the Unity client; a Grasshopper component runs inside the Rhino process rather than opening a second port. The wire protocol is an engine-independent C# assembly, and the Rhino plugin compiles the protocol source files directly into itself — so the two ends cannot drift out of version, because at compile time they are literally the same files. Model formats are read by a self-written reader covering FBX, OBJ, STL, PLY and glTF with zero external dependencies, and a single contract document is all anyone needs to write a fourth adapter.

Interaction Decisions

Scale and rotate around the midpoint between the hands, never the model origin. Scaling around the origin pushes the thing you leaned in to look at further away the moment it becomes readable — it feels like the zoom has hit its limit when it is nowhere near. Rotation has the same problem: around the origin the far side sweeps across the desk; around the midpoint the model turns in place between your palms. The scale range is deliberately far wider than "still looks like a maquette" — 0.25× to 200×, fourteen centimetres to a hundred and ten metres — because walking into the model is a real need, and a ceiling you can reach is a ceiling that gets in the way.

Pick by geometry, not by bounding box. Assets exported by material category are one object per material spread across the whole map, so every bounding box covers the entire sandbox: a box-based rule either grabs everything at once or grabs exactly one of forty-three pieces. Both versions shipped, and both came back as bug reports from inside the glasses. Each piece now stores 256 triangle centres and radii — triangles rather than vertices, because a large flat panel only has vertices at its corners and a vertex test insists you never touched its middle.

No hidden snap-back. An earlier rule returned a piece to its origin if you released it close enough. It is gone. Putting things back is the left hand's job, and a second invisible rule doing the same thing only frightens people — which it did: the "close enough" radius was in content units and grew with scale, so at five times zoom it was eighty centimetres wide and nothing you pulled out would stay out.

Left hand pinches, right hand lets go immediately. Two hands pinching means one thing and one thing only: the whole sandbox. Without that rule, a desk-scale model leaves the right hand almost permanently touching some piece, so every two-handed gesture gets stolen by the drag — and what the user sees is "move, rotate and scale are all dead," with the cause sitting in a different component.

Built to Be Diagnosed by Its Users

The users are colleagues, on their own machines, with their own models, and I am not in the room — so a deliberate share of the effort went into letting them tell "my model is wrong" apart from "the software is wrong." A "Why is it grey?" button in Blender reports the colour each object will be sent as, which field it came from, and whether it has a texture, because colour can be configured in four places that do not talk to each other. The status readout lists the texture count separately, since "this model has no textures" and "this adapter sent none" look identical on the desk. "Model 1.7 km on a 0.40 m desk" is two numbers that together are the answer: forty metres means adjust the fit, 1.7 kilometres means an outlier object is stretching the bounding box and no amount of pulling will help. "Hands 31 Hz · 88 fps" separates a tracking problem from a rendering one. And the debug skeleton is coloured by gesture state — blue tracked, yellow pinching, green close enough to grab — because a prettier skinned hand tells you nothing about what the system thinks.

Status

Shipped to colleagues as a one-click Windows installer that detects Blender or Rhino and installs only the matching plugin; dropping a sample OBJ onto the window is enough to see it work with nothing else set up. The neckband path is fine. The PC-direct display link is still open: a packaged build lands as two separate 2D panels rather than one stereo viewport, while the same project run from the Unity editor displays stereo correctly — which points at the packaged app's handshake with the runtime rather than at the project.