ThreeDSViewer is an experimental native CAD client and GLB viewer for the Nintendo 3DS. It connects directly to Zoo, lets you create and edit cloud CAD projects from the touchscreen, and renders the resulting models locally with Citro3D.
This project was made for the zoo.dev Makeathon.
A small apology: sorry about the code and engineering quality in places. This was built quickly for a Makeathon, on unusual hardware, while the CAD workflow was still evolving. It works, but some modules are larger, stranger, and less polished than they should be.
This project also made heavy use of AI-assisted development for implementation, debugging, emulator testing, and documentation. That helped move quickly during the Makeathon, but it also contributed to the uneven code style and engineering quality noted above.
seq.kittycad.mp4
- OAuth device login with refresh credentials stored on the SD card
- Browsing, creating, opening, and refreshing Zoo projects
- Touch-first sketching with lines, points, circles, and rectangles
- Sketch constraints and dimensions
- Extrude with New Body, Join, Cut, and Intersect operations
- Extrude start offsets and signed distances
- Revolve, Fillet, and Chamfer creation
- Basic feature editing, selection, deletion, Undo, and Redo
- Cloud-generated Preview followed by explicit Apply
- Fresh project reopen from canonical Zoo state
- Local stereoscopic GLB rendering on the top screen
- Fourteen bundled offline renderer scenes
- Touch-draggable lists and inline 3DS button prompts
ThreeDSViewer is still a prototype. CAD actions can modify real Zoo projects; use a disposable project while experimenting.
Install the following first:
- Rust through
rustup - devkitPro with devkitARM, libctru, Citro3D, Citro2D, and Picasso
cargo-3ds, which teaches Cargo how to build and package Nintendo 3DS homebrew
The included rust-toolchain.toml selects nightly and installs rust-src.
The repository's .cargo/config.toml selects the devkitARM C compiler for the
custom Nintendo 3DS target.
Install cargo-3ds and verify that its Cargo subcommand is available:
cargo install cargo-3ds
cargo 3ds --versionThen expose the devkitPro paths in your shell:
export DEVKITPRO=/opt/devkitpro
export DEVKITARM="$DEVKITPRO/devkitARM"The exact devkitPro installation path can differ by operating system; update
those variables if yours is not under /opt/devkitpro.
export DEVKITPRO=/opt/devkitpro
export DEVKITARM="$DEVKITPRO/devkitARM"
cargo 3ds build --releaseThe application is written to:
target/armv6k-nintendo-3ds/release/threedsviewer.3dsx
Copy the .3dsx to the SD card under 3ds/threedsviewer/, or send it to a
console running the Homebrew Launcher:
cargo 3ds run --releaseThreeDSViewer was developed and tested with the Azahar Nintendo 3DS emulator. After building, open this file in Azahar:
target/armv6k-nintendo-3ds/release/threedsviewer.3dsx
Use the emulator's bottom-screen pointer for touch input. Zoo login and CAD operations require working network access inside the emulator. Complete the displayed device-login flow in a regular browser, then return to Azahar.
For repository work, tools/azahar-smoke.sh and
tools/azahar-interact.sh provide repeatable smoke and interaction harnesses.
They use workstation-specific emulator paths by default, so inspect their
environment-variable overrides before running them elsewhere.
Azahar is excellent for iteration and screenshots, but emulator success is not a substitute for testing timing, networking, touch, and rendering on a physical Nintendo 3DS.
- Launch ThreeDSViewer and touch CAD.
- Choose Sign In.
- Open the displayed Zoo verification address on another device and enter the user code.
- Return to the 3DS. The project list refreshes after authorization succeeds.
- Create a new project or open an existing disposable project.
OAuth credentials are stored at sdmc:/3ds/threedsviewer/oauth. The app uses
Zoo's device flow and does not require an API key to be embedded in the ROM.
The touchscreen is the primary input surface. On-screen action labels include sprites for the corresponding physical buttons.
| Input | Typical action |
|---|---|
| Touch | Select, draw, drag controls, and scroll lists |
| A | Open, confirm, Preview, or Apply |
| B | Back or cancel |
| X | New, tools, exact input, or retry |
| Y | Context action such as Delete |
| L / R | Undo / Redo or previous / next choice |
| D-pad | Move focus and adjust values |
| Circle Pad | Orbit or move the local cursor where available |
| START | Exit the application |
Exact actions are shown on each screen; the same button can have a different contextual action in another workspace.
CAD → New project → Create → Sketch → choose plane
→ draw profile → Finish Sketch
→ select Sketch → Extrude → choose profile and operation
→ Preview → Apply
Preview geometry is transient. Apply is the explicit mutation step, and a successful reopen reloads the canonical project from Zoo.
src/ Nintendo 3DS application, UI, cloud worker, and renderer
crates/cad-model/ Semantic CAD document and Zoo command compiler
crates/cad-core/ Input reducers and editing workspaces
crates/cad-runtime/ Preview, Apply, replay, and runtime composition
crates/cad-store/ Bounded local recovery storage
crates/glb-model/ Bounded GLB/glTF loader
crates/manul/ Blocking Zoo HTTP, OAuth, and Modeling client
crates/citro2d*/ Safe and raw Citro2D bindings
crates/ui-core/ Host-testable layout, pointer, and list state
crates/viewport-pick Host-testable trackpad and body picking
romfs/ Files bundled into the final 3DSX
tools/ Build, emulator, fixture, and focused proof utilities
The main thread owns input, application state, Citro3D/Citro2D, and frame rendering. A bounded worker owns Zoo HTTP and Modeling operations. The renderer never depends on video streaming or WebRTC: Zoo returns GLB data, and the 3DS renders it locally.
Check the production 3DS target:
cargo 3ds check --release --lockedBuild a commit-labelled emulator ROM with provenance metadata:
tools/build-emulator-rom.shRun the local debug WebSocket model server:
cargo run -p ws-test-server -- 127.0.0.1:9001 path/to/model.glbThe debug stream is disabled at startup and must be explicitly enabled in the application. It is separate from the normal Zoo CAD path.
The renderer supports GLB 2.0 triangle meshes, indexed and non-indexed primitives, float positions and normals, generated flat normals, scene/node transforms, mesh instancing, basic metallic-roughness material factors, alpha modes, and double-sided materials.
Textures, animation, skinning, morph targets, sparse accessors, Draco, Meshopt, KTX2/Basis, and advanced PBR extensions are not currently supported.
Controller prompt sprites come from Kenney's CC0 Input Prompts Pixel pack.
The exact source URL, license note, retained tile mapping, PNGs, and generated
3DS atlas are in
romfs/input-prompts-pixel/.
