# RealSense D400 series — the numbers this pod graph is built from
Every value in `realsense_d400.hpodgraph` comes from one of the four sources below.
Nothing here is estimated from a photograph or inferred from a rendering. Where a
number is **not published**, it says so and states exactly what was assumed instead.
| Tag | Source |
|-----|--------|
| **DS** | *RealSense Product Family D400 Series Datasheet*, doc 337029-017, rev 021 (Oct 2025) and the Mar 2026 revision (which adds D436). |
| **URDF** | `realsense2_description` xacro macros, `IntelRealSense/realsense-ros`, branch `ros2-development` — the vendor's own published frame tree. |
| **D421** | *RealSense Depth Module D421 Datasheet v1.2*. |
| **D555 / D457** | *D555 PoE Datasheet v1.1*, *RealSense D457 Datasheet Dec 2025*. |
| **ASSUMED** | Not published by any of the above. The assumption is stated inline and the graph exposes the value as an editable parameter. |
---
## 1. Frames and axes
### Vendor frame (URDF)
The vendor publishes two datums per camera:
- `bottom_screw_frame` — the centre of the **1/4-20 UNC tripod hole on the bottom face**.
This is the physical mounting datum.
- `camera_link` — the **depth origin**, which the datasheet defines (§4.8.1) as
*"the X-Y center of the left imager"*, at the Z plane where depth reads zero.
URDF body axes are ROS: **+X forward (out of the lens), +Y left, +Z up**.
Optical frames add `rpy = (-π/2, 0, -π/2)`, giving the usual optical convention
(+Z forward, +X right, +Y down).
### Graph authoring frame
The graph is authored **Z-up**, converted once by a single `SpaceTransform z_up → y_up`
immediately before `AssetOutput` (pod is Y-up native; LuckyEngine is Y-up).
Authoring axes, Z-up:
| Axis | Meaning |
|------|---------|
| **+X** | the camera's **right** (so the right IR imager is at +X) |
| **+Y** | the direction the camera **looks** (optical axis, out of the lens) |
| **+Z** | **up** |
`SpaceTransform z_up → y_up` is a −90° rotation about X, i.e. `(x, y, z) → (x, z, −y)`.
Applied to the axes above:
| Authored (Z-up) | After transform (engine, Y-up) |
|---|---|
| camera right `+X` | `+X` — engine right |
| camera up `+Z` | `+Y` — engine up |
| camera look `+Y` | `−Z` — **the direction a LuckyEngine camera looks** |
That last row is the load-bearing one. `SceneCamera` builds its projection with
`glm::perspectiveFov` (`Hazel/src/Hazel/Renderer/Camera.h`), which is right-handed
looking down **−Z** with +Y up. So an optical frame authored this way needs
**no rotation at all** — a `CameraComponent` sitting on it with an identity local
rotation points out of the lens.
> Do not "fix" the optical frames by adding a 180° yaw. If a camera looks backwards,
> the `SpaceTransform` has been changed or removed — fix that instead.
### Conversion used to derive the tables below
URDF ROS `(x, y, z)` → authored Z-up `(x', y', z')`:
```
x' = -y (ROS +Y is left; authored +X is right)
y' = x (ROS +X is forward; authored +Y is the look direction)
z' = z
```
---
## 2. Optical module properties
| Module | Baseline | Depth FOV HD 16:9 | Depth FOV VGA 4:3 | IR projector FOV | Source |
|---|---|---|---|---|---|
| D401 | 18 mm | H 84 / V 58 / D 92 | — | none (passive) | DS 3-49 |
| D415 | 55 mm | H 65 / V 40 / D 72 | H 50 / V 40 / D 61 | H 67 / V 41 / D 75 | DS 3-49 |
| D430 | 50 mm | H 87 / V 58 / D 95 | H 75 / V 62 / D 89 | H 90 / V 63 / D 99 | DS 3-49 |
| D450 | 95 mm | H 87 / V 58 / D 95 | H 75 / V 62 / D 89 | H 90 / V 63 / D 99 | DS 3-49 |
| D421 | 50 mm | H 75 / V 50 (±3) | — | yes | D421 v1.2 |
FOV tolerance is ±3° lens-to-lens (DS 3-49 note 3). Depth FOV is quoted at 2 m
(20 cm for the D401).
## 3. Colour sensor properties
| Sensor | Used by | Colour FOV | Shutter | Native |
|---|---|---|---|---|
| OV2740 | D415, D435, D435i, D435f, D435if | H 69 / V 42 / D 77 | rolling | 1920×1080 |
| OV9782 | D436, D455, D455f, D456, D457, D555 | H 90 / V 65 / D 98 | global | 1280×800 |
| (left imager via ISP) | D405 | H 84 / V 58 / D 92 | global | — |
D405 has no separate colour sensor: its colour stream is the **left imager through the
ISP** (DS table 4-4, *"Color Stream from Left RGB via ISP"*), which is why its colour
and depth origins are the same point.
## 4. Per-model geometry
All offsets are **authored Z-up millimetres from the 1/4-20 tripod-hole centre** unless
the datum column says otherwise. `leftIR.x` is negative because the left imager is to
the camera's left.
| Model | Housing W×D×H (mm) | Datum | leftIR x | rightIR x | RGB x | optical y (fwd) | optical z | IMU |
|---|---|---|---|---|---|---|---|---|
| D405 | 42 × 23 × 42 | 1/4-20 | −9.0 | +9.0 | −9.0 (same as leftIR) | 10.85 | 21.0 | — |
| D415 | 99 × 20 × 23 | 1/4-20 | −20.0 | +35.0 | −35.0 | 8.67 | 11.5 | — |
| D421 | 95 × 10.5 × 32 | module back-face centre | −25.0 | +25.0 | — | 10.5 | 16.0 | — |
| D435 | 90 × 25 × 25 | 1/4-20 | −17.5 | +32.5 | −32.5 | 10.60 | 12.5 | — |
| D435i | 90 × 25 × 25 | 1/4-20 | −17.5 | +32.5 | −32.5 | 10.60 | 12.5 | yes |
| D435f | 90 × 25.8 × 25 | 1/4-20 | −17.5 | +32.5 | −32.5 | 10.60 | 12.5 | — |
| D435if | 90 × 25.8 × 25 | 1/4-20 | −17.5 | +32.5 | −32.5 | 10.60 | 12.5 | yes |
| D436 | 90 × 25 × 25 | 1/4-20 | −17.5 | +32.5 | −32.5 | 10.60 | 12.5 | yes |
| D455 | 124 × 26 × 29 | 1/4-20 | −47.5 | +47.5 | +11.5 | 11.15 | 14.5 | yes |
| D455f | 124 × 26.8 × 29 | 1/4-20 | −47.5 | +47.5 | +11.5 | 11.15 | 14.5 | yes |
| D456 | 124 × 26 × 29 | 1/4-20 | −47.5 | +47.5 | +11.5 | 11.15 | 14.5 | yes |
| D457 | 124 × 36 × 29 | 1/4-20 | −47.5 | +47.5 | +11.5 | 11.15 **(ASSUMED)** | 14.5 | yes |
| D555 | 167 × 48 × 42 | body centre **(ASSUMED)** | −47.5 | +47.5 | +11.5 | 11.15 **(ASSUMED)** | 21.0 | yes |
### How each column was obtained
**`leftIR.x`** — DS tables 4-20/4-21/4-22 give "From centerline of 1/4-20 to left imager":
D415 20 mm, D43x 17.5 mm, D45x 47.5 ±0.1 mm, D405 9 mm. D457's own datasheet table 4-11
repeats 47.5 mm. Sign from URDF (`*_cam_depth_py`, ROS +Y = left) → authored −x.
**`rightIR.x`** — `leftIR.x + baseline`. URDF `*_cam_depth_to_infra2_offset` is
`−baseline` in ROS Y for every model, i.e. exactly one baseline to the camera's right.
**`RGB.x`** — URDF `*_cam_depth_to_color_offset`, in ROS +Y (left), negated:
| Model | URDF value (ROS +Y) | Authored Δx from leftIR |
|---|---|---|
| D405 | `0.0` | 0 |
| D415 | `+0.015` | −15 mm |
| D435 family | `+0.015` | −15 mm |
| D455 family | `−0.059` | **+59 mm** |
Note the sign flip on the D45x line: on a D435 the colour lens is outboard of the left
imager, on a D455 it is **inboard**, 11.5 mm to the right of the tripod hole. Getting
this backwards puts the colour camera 118 mm out of place on a D455.
Consistency check (this is why the table can be trusted): on every model the outermost
two optics land symmetrically about the tripod hole — D415 ±35, D435 ±32.5, D455 ±47.5,
D405 ±9.
**`optical y`** (how far forward of the screw the depth origin sits) —
`(screw → front face) − (front glass thickness) − |Z'|`, where `Z'` is the datasheet
"depth start point" (DS table 4-16/4-17) and the first two terms are URDF
`*_cam_mount_from_center_offset` and `*_glass_to_front`:
| Model | screw→front | glass | Z' | = optical y |
|---|---|---|---|---|
| D405 | 14.65 | 0.1 | −3.7 | **10.85** |
| D415 | 9.87 | 0.1 | −1.1 | **8.67** |
| D43x | 14.9 | 0.1 | −4.2 | **10.60** |
| D45x | 15.8 | 0.1 | −4.55 | **11.15** |
The `f` variants (D435f/D435if/D455f) publish a 0.5 mm larger `|Z'|` (−4.7, −5.05)
**because the NIR filter moves the cover glass 0.5 mm forward, not because the imager
moves**. Their depth origin is therefore identical to the unfiltered sibling; only the
housing gets 0.8 mm deeper. The graph models it that way.
The D415 is the one place the vendor URDF and the datasheet disagree: the xacro defines
`d415_cam_mount_from_center_offset = 0.00987` and then passes `0` for the link joint's X.
The datasheet's Z' of −1.1 mm says the depth origin is not on the screw plane, so the
graph uses the derived 8.67 mm. The discrepancy is 8.67 mm along the optical axis.
**`optical z`** — `housing height / 2` for every model (URDF `*_cam_depth_pz`); the
optical axis is on the housing's horizontal midline.
**IMU offsets** — URDF, relative to the depth origin, ROS `(x, y, z)`:
| Model | URDF | Authored Δ from leftIR (x, y, z) |
|---|---|---|
| D435i / D435if | `(−0.01174, −0.00552, +0.0051)` | (+5.52, −11.74, +5.10) mm |
| D455 family | `(−0.01602, −0.03022, +0.0074)` | (+30.22, −16.02, +7.40) mm |
D436 has a 6DoF IMU (BMI085, DS table 3-56) but no published offset; the graph uses the
D435i figures and marks them ASSUMED. D457 and D555 likewise reuse the D455 figures.
### The four assumptions, stated plainly
1. ~~**D436 colour offset.**~~ **RESOLVED.** The Mar 2026 revision carries a D436
mechanical drawing (figure 10-11) whose front view dimensions every aperture
directly, and unusually gives as-built rather than nominal figures:
baseline **49.95**, projector **29.00**, colour **15.05**, and **17.45** from the
1/4-20 centreline to the left imager. The assumed 15 mm colour offset was right to
within 0.05 mm. The graph keeps the nominal 15 / 50 / 17.5 for consistency with the
rest of the D43x family; the difference is 50 µm.
2. **D457 forward offset.** Same D450 optics and cover-glass stack as the D455
(its datasheet repeats both the 47.5 mm and the −4.55 mm figures), but it does not
publish where the 1/4-20 hole sits in its 36 mm-deep housing. The graph assumes the
front face is where the D455's is (15.8 mm ahead of the screw) and puts the extra
10 mm on the back, where the GMSL connector is.
3. **D555 datum and layout.** Its datasheet states it is *"composed of the long range
global shutter D450 optical module with IMU"* and gives baseline 95 mm, so the
intra-module layout is the D455's. It publishes no mounting datum at all. The graph
references the D555 to the **housing centre** and centres the stereo pair on it.
4. **D421 datum.** The D421 datasheet gives "left centre hole to left imager centre =
18.5 mm" but no drawing locating that hole in the 95 mm width. The graph follows the
vendor URDF, which centres the stereo pair on the module and references it to the
back-face centre.
## 5. Streaming modes — reference only
LuckyEngine cannot set these. They are shown in the graph's parameter panel so the
recorder can be matched to them by hand. See `README` in the pod graph header.
### Depth (Z16)
| Models | Resolution | Frame rates (fps) |
|---|---|---|
| D415, D43x, D436 | 1280×720 | 6, 15, 30 |
| | 848×480, 640×480, 640×360, 480×270, 424×240 | 6, 15, 30, 60, 90 |
| D45x, D457 | 1280×720 | 5, 15, 30 |
| | 848×480, 640×480, 640×360, 480×270, 424×240 | 5, 15, 30, 60, 90 |
| D405 | 1280×720 | 5, 15, 30 |
| | 848×480, 640×360, 480×270, 424×240 | 5, 15, 30, 60, 90 |
| D421 | 1280×720 | 6, 15, 30 |
| | 848×480, 640×480, 640×360, 480×270 | 6, 15, 30, 60, 90 |
| D555 | 1280×720 | 5, 15, 30 |
| | 896×504, 640×360, 448×252 | 5, 15, 30, 60 |
### Colour (YUY2)
| Models | Resolution | Frame rates (fps) |
|---|---|---|
| D415, D435, D435i, D435f, D435if | 1920×1080, 1280×720 | 6, 15, 30 |
| | 960×540, 848×480, 640×480, 640×360, 424×240 | 6, 15, 30, 60 |
| | 320×240, 320×180 | 6, 30, 60 |
| D436 | 1280×720 | 6, 15, 30 |
| | 848×480, 640×480, 640×360, 480×270 | 6, 15, 30, 60 |
| | 424×240 | 6, 30, 60 |
| D45x, D457 | 1280×800 | 5, 15, 30 |
| | 848×480, 640×480 | 5, 15, 30, 60 |
| | 640×360, 480×270, 424×240 | 5, 15, 30, 60, 90 |
| D405 | 1280×720 | 5, 15, 30 |
| | 848×480, 640×360, 480×270, 424×240 | 5, 15, 30, 60, 90 |
| D555 | 1280×720 | 5, 15, 30 |
| | 896×504, 640×360, 448×252 | 5, 15, 30, 60 |
Source: DS tables 4-2 … 4-5 (Mar 2026 rev), D421 v1.2, D555 v1.1.
The D436 and D555 product pages advertise "up to 1280×800" colour; the datasheet
streaming tables cap the USB colour stream at 1280×720 and list 1280×800 as the
calibration format only. The tables above follow the datasheet.
### Minimum-Z (mm) — DS table 4-11
| Resolution | D415 | D43x/D436 | D45x/D457 | D405 |
|---|---|---|---|---|
| 1280×720 | 450 | 280 | 520 | 100 |
| 848×480 | 310 | 195 | 350 | 70 |
| 640×480 | 310 | 175 | 320 | — |
| 640×360 | 240 | 150 | 260 | 55 |
| 480×270 | 180 | 120 | 200 | 45 |
| 424×240 | 160 | 105 | 180 | 40 |
D421: ~200 mm at VGA (D421 v1.2). D555: ~260 mm at VGA, ~520 mm at max resolution,
ideal range 0.6–6 m (D555 v1.1).
### Depth quality — DS table 4-14
Z-accuracy ±2 % for every model; fill rate ≥99 % (≥99.5 % D405); RMS spatial noise
≤2 % (≤1 % D405); temporal noise ≤1 % (≤0.5 % D405). Quoted within 80 % of the depth
FOV, at ≤2 m (D415, D43x), ≤4 m (D45x), ≤0.5 m (D405).
These are what the graph seeds `DepthSensorParams` from — see §6.
## 6. Mapping onto LuckyEngine's depth sensor model
`Hazel::Data::DepthSensorModel::ActiveStereo` is described in
`Hazel/src/Hazel/Data/DataTypes.h:265` as *"RealSense-style active-IR stereo: z² disparity
noise, specular dropout, occlusion shadows"* — it is the right model for every camera in
this pack except the **D405**, which is passive (DS table 4-14 note 4: *"KPIs for D405, a
passive camera, are measured using a textured target"*). ActiveStereo is still the closest
available model for the D405; the difference is that its noise should not be assumed to
improve with projector power.
`DepthSensorParams` fields and the value this pack recommends:
| Field | Meaning | Value |
|---|---|---|
| `Baseline` | virtual stereo baseline, drives z² noise and occlusion width | the model's real baseline (table §4) |
| `MinRange` | near blind zone | Min-Z at the resolution you are recording (table above) |
| `MaxRange` | beyond this the sensor returns nothing | D405 0.5 m, D415 3 m, D43x 3 m, D45x/D457/D555 6 m |
| `SubpixelSigma` | disparity 1-sigma noise, px | 0.08 default; the datasheet quotes error as a % of Z, not in disparity px, so this is left at the engine default |
| `DetectFloor` | angular return fraction below which a pixel drops out | 0.03 default |
`MaxRange` uses each model's stated ideal range, not an absolute maximum:
D405 "0.7 m" max ideal (DS quality spec measured ≤0.5 m), D415/D43x ≤3 m,
D45x ≤6 m, D457 "0.6 m to 6 m", D555 "60 cm to 6 m".
`DepthScale` (metres per uint16 code) should be picked as `MaxRange / 65535` rounded up —
the Settings panel tooltip says the same thing. For a 6 m D455 that is 0.0001 m/code;
for a 0.5 m D405, 0.00001 m/code. The engine default of 0.001 wastes 99 % of the 16-bit
range on a close-in camera.
---
## 7. The IR projector
The projector is not a stream, so it has no librealsense extrinsic and it appears in
no URDF. It is dimensioned on the datasheet **mechanical drawings**, whose front views
carry an aperture chain measured from the left imager:
| Model | Projector offset from the left imager | Source |
|---|---|---|
| D405 | none — passive | DS 3-49, "IR Projector: N/A" |
| D415 | **28 mm** | DS figure 10-8 |
| D435 / D435i / D435f / D435if | **29 mm** | DS figure 10-9 |
| D436 | **29.00 mm** | DS figure 10-11 (as-built) |
| D455 / D455f / D456 | **37.5 mm** | DS figure 10-12 |
| D457 | 37.5 mm **(ASSUMED)** | same D450 optical module as the D455 |
| D555 | 37.5 mm **(ASSUMED)** | same D450 optical module (D555 v1.1 says so outright) |
| D421 | 29 mm **(ASSUMED)** | not published; a wide 50 mm-baseline module, so the D43x value |
Reading those chains also re-derives the numbers in §4 from a source independent of the
URDF, and they agree: the D435 sheet gives (17.5) to the left imager, (15) to the
colour imager and (50) to the right imager; the D415 sheet gives (20), (15) and (55);
the D455 sheet gives (47.5), (59) and (95), plus IMU callouts of (30.22), (−7.4) and
Z (−16.02) that match the URDF's `d455_imu_p*` exactly.
The direction is the same on every sheet: all dimensions run from the left imager, and
the projector sits between the two IR imagers — so its offset is **positive** in the
authored frame (toward the camera's right), unlike the colour imager, which is negative
on everything except the D45x.
**Why the graph emits it as a frame rather than only as an aperture.** The projector is
a second viewpoint. A surface the stereo pair can both see but the projector cannot
reach gets no pattern, so an active stereo camera drops out in the projector's shadow
as well as in the stereo occlusion that `DepthSensorParams::Baseline` already models.
Those are different shadows with different geometry, and only the projector frame
carries the second one.
## 8. Manufacturer 3D models — what exists
Checked so a proxy can be swapped for the real shape. There is **no manufacturer FBX,
OBJ or glTF** for any of these parts.
**Official CAD**, from :
| Download | Contents | Format |
|---|---|---|
| D400 Depth Cameras | D405, D415, D435, D435f, D435i, D455, D455f, D456, D457 | SolidWorks `.SLDPRT` |
| D555 Camera | `D555_SOLID_SOC.STEP` (37.6 MB) | **STEP** — open, directly convertible |
| D400 Depth Modules | D401, D415, D430, D450 | SolidWorks `.SLDPRT` |
No D436 and no D421 in the CAD set. `.SLDPRT` is proprietary and needs SolidWorks or a
commercial converter; the D555 STEP is the only one usable without one.
**Vendor mesh assets**, from `realsense2_description/meshes` in `IntelRealSense/realsense-ros`
(open, already tessellated, Apache-2.0):
| File | Size |
|---|---|
| `d405.stl` | 195 kB |
| `d415.stl` | 21.3 MB |
| `d421.stl` | 20 kB |
| `d435.dae`, `d436.dae` | 15.8 MB each |
| `d455.stl` | 2.6 MB |
| `d585.stl` | 7.0 MB |
These are the practical swap-in candidates. Two things to know before swapping:
1. **They are outer shells with no optics.** `d455.stl` and `d405.stl` were placed into
this graph's frame and rendered front-on: a rounded housing, a flat recessed front
window, and no lens apertures or projector window at all. Swapping one in gives a
more accurate silhouette and *fewer* visual cues than the proxy in this pack, which
at least shows which opening is which.
2. **They independently confirm §4.** Placed through the URDF's own visual transform,
`d455.stl` measures 124 × 26 × 29 mm and `d405.stl` 42 × 23 × 42 mm; both straddle
the tripod datum in X, put the optical axis on the housing's vertical midline, and
put the front face 4.65 mm / 3.80 mm ahead of the depth origin — which is exactly
`front_y − opt_y` in §4 for each.
---
## 9. The model name engraved into the top face
The proxy cuts the selected model's name into the top of the housing so a scene
full of cameras can be read without selecting each one. It is a real boolean
cutout of the text -- a round-bottomed groove one stroke radius deep -- and
getting there took two fixes in pod rather than anything in this graph.
### Why it did not work at first
pod's text comes from `StringToCurves`, whose glyph table is a 36-character
*stroke* font: open polylines, not closed outlines. `CurveToMesh` gives those
thickness by sweeping a round profile, so a glyph is a set of tubes, and the
tubes pass through each other wherever two strokes meet. pod's `MeshBoolean`
is Manifold, and Manifold's boolean arranges operand A against operand B; it
never arranges one operand's shells against *each other*. Those crossings
survived into the result as interior faces, and a Difference with the text
came back with a lid of degenerate slivers where the groove should have been.
Blender's exact solver does not have this problem because it arranges every
face against every other before classifying by winding number, which is why a
text cutout "just works" there. Manifold exposes the same capability as
`Decompose()` (split shells by connectivity) plus `BatchBoolean(Add)` (arrange
them against each other); it just has to be asked.
### The two pod fixes
1. **`MeshBoolean` resolves each operand against itself first**
(`pod/src/Nodes/Boolean.cpp`, `ResolveSelfOverlap`). Decompose, batch-union,
then the real boolean. A single-shell operand -- nearly every operand there
is -- is handed straight back, so the common path pays one Decompose and no
boolean. `MeshBooleanList` folds through the same code, so it gets it too.
Regression tests assert the *volume* removed by a self-overlapping tool,
which is the number a leftover lid gets wrong.
2. **`StringToCurves` splits closed-loop glyphs** (`pod/src/Nodes/Curves.cpp`).
`0`, `6`, `8`, `O` and their like are one stroke that ends where it began.
Swept, that tube's first ring is perpendicular to the outgoing segment and
its last to the incoming one, at the same centre, so its two end caps cross
*inside a single shell* -- the one case fix 1 cannot reach, and `0` left
exactly one sliver at every stroke radius tried. A loop is now emitted as
two open strokes overlapping by one segment: each cap lands inside the other
tube and the crossing is between two shells, which fix 1 resolves. A stroke
renderer draws the same ink.
Both were found by measurement, not reasoning: cut each glyph into a box alone
and count downward-facing triangles in the cut plane. Before: `D 0 3 4 6 8 9 F`
all bad. After fix 1: only `0 6 8 9`. After fix 2: only `6 9`, which are open
hooks whose spine turns faster than a 0.45 mm tube can follow -- a genuine
sweep limit, and no model name uses either. Subdividing, resampling or
filleting the curve makes that *worse* (shorter segments beside the same turn),
so it is left alone.
### How the right name is selected
pod cannot switch a *string* on an index (`IndexSwitch` has Geometry, Float,
Bool and Material variants and no string one; a `Menu` knob carries an
`Identifier` no expression can reach). So all thirteen cutters are built at the
same spot and the **geometry** is switched: two chained `IndexSwitch` nodes
(ten slots each) forward one into the Difference. The second index is
`max(0, model - 9)`, a Float expression feeding an Int pin, which rounds
(`pod/src/Evaluator.cpp:234`). The twelve unselected chains still evaluate and
are dropped; the whole graph is ~11 ms.
### Sizing and orientation
Cap height is `min(label_cap, body_d * 0.34)`; the clamp is what lets the
7.5 mm default fit the 10.5 mm-deep D421. `label_cut` is the profile radius:
half the stroke width and exactly the groove depth, because the tubes are
centred on the top face and the inside half is what the boolean removes.
Text is built reading along +X (the camera's own right), so unrotated it reads
from *behind* the unit. `label_rot` defaults to a yaw of pi so it reads from
*in front*, where you stand to check what a camera is pointing at. The yaw is
safe only because the group aligns the text `Middle` in Y as well as `Center`
in X, putting the origin at the centre of the cap box.
### Checks
`tools/check_marking.py` measures the cut on all thirteen models: nothing
above the top face, a groove exactly one stroke radius deep, zero downward-
facing triangles in the top plane (the lid a failed cut leaves), and a groove
no wider than one name. `tools/verify_marking_selection.py` proves the switch
chain by overriding the names with *N*-stroke markers and recovering *N* from
the groove width. Both isolate the groove geometrically, since it is cut into
the housing and has no material of its own.
pod's glyph table is **uppercase only**, so `D435i` is spelled `D435I`.
## 10. Mounting hardware and threads
Everything here is read off the vendor mechanical drawings, not inferred. Sources
are the D400 series datasheet 337029-017 (October 2024) figures 10-8 through
10-14, the D457 datasheet 337029-011 (August 2025), the D421 datasheet v1.2 and
the D555 datasheet v1.1. Positions are authored Z-up millimetres from the same
datum as section 4 - the 1/4-20 tripod centre, except on the D421 and D555.
### The two mounting systems, and which face each is on
Every camera in this pack (not the D421 module) carries **two independent**
mounting provisions, and they are on **different faces**:
1. **A 1/4-20 UNC tripod hole on the BOTTOM face.** This is the pack's datum and
was already modelled; what is new here is its thread depth.
2. **Two threaded holes on the BACK face**, straddling the tripod centreline.
This is the load-bearing one for a bracket - the D457 datasheet calls the back
"the Rear Mounting Surface" and specifies a flatness tolerance for it
(Table 3-20, 0.1 mm), and §3.8.2.2 of 337029-017 recommends bracket mounting
through these screws rather than the tripod.
**The back, not the bottom.** Worth stating because the D435's drawing alone
cannot prove it: that camera is 25 mm high and 25 mm deep, so its back and
bottom views are the same size. The D415 (20 deep, 23 high) and the D455
(26 deep, 29 high) are not square in section, and on both the screw view
measures the HEIGHT - 23 and 29 - so the screws are on the back. The D435 is the
same drawing convention and the same logo placement, so it follows.
### Per model
| Model | Back-face screws | Pitch (mm) | x from datum | Max insert | Tripod 1/4-20 |
|---|---|---|---|---|---|
| D405 | 2 x M-unread | 20.00 | +/- 10.00 | not readable | yes, 5.0 mm |
| D415 | 2 x M3 | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D421 | 2 x M3 through + 4 x d3.10 corner | 87.00 x 24.00 | +/- 43.50 | through | none |
| D435 | 2 x M3 | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D435i | 2 x M3 | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D435f | 2 x M3 | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D435if | 2 x M3 | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D436 | 2 x M3 **(INHERITED)** | 45.00 | +/- 22.50 | 3 mm | yes, depth not published |
| D455 | 2 x M4 | 95.00 | +/- 47.50 | 4 mm | yes, 9.0 mm |
| D455f | 2 x M4 | 95.00 | +/- 47.50 | 4 mm | yes, 9.0 mm |
| D456 | 2 x M4 | 95.00 | +/- 47.50 | 4 mm | yes, 9.0 mm |
| D457 | 2 x M4 **(pitch INHERITED)** | 95.00 | +/- 47.50 | 4 mm | yes, depth not published |
| D555 | 2 x M4 | 125.40 +/- 0.20 | +/- 62.70 | 6 mm | not shown on its datasheet |
Recommended torque: 0.4 Nm for the M3 pairs (figures 10-8, 10-9); the D457's M4
inserts withstand <= 3.0 Nm (M4 zinc-plated class 8.8 SHCS); the D421's module
screws should not exceed 5 kgf*cm.
### Height on the back face
Measured from the drawings rather than assumed: the screw pair sits on the
back face's horizontal centreline on the D415, D435 and D455, i.e. at the
housing's mid-height. On the D405's 42 x 42 back face the pair is centred both
ways.
### That the pair straddles the tripod axis was measured, not assumed
The D435's figure 10-9 does not dimension the screws against the 1/4-20 - it
only gives the 45 mm pitch. Taking the drawing's own pixels at 900 dpi, the
midpoint of the screw pair lands at 756.0 and the midpoint of the body outline
at 756.75, i.e. they coincide to 0.07 mm at that scale. The optics are already
known to be symmetric about the tripod centreline (section 4 dimensions them
from it), so the body centre IS the tripod axis, and the screws straddle it.
This also explains why the D415 and the D435 share a 45 mm pitch despite
different widths and different baselines: the pattern is referenced to the
common datum so one bracket fits both.
### USB cable retention
Not a mounting provision for a bracket, but it is on the same faces and will be
in the way of one. The D455 carries 2 x M2 beside its USB-C on the BOTTOM, with
the connector centre 37.01 mm from the 1/4-20 toward the camera's RIGHT. The
D405 carries 2 x M2x0.4 18.00 mm apart beside its screw-locked USB 3 Micro-B
(not a USB-C) on its left side. Both are now modelled -- as cable-lock holes
in the Detailed housing -- see section 12.
### The D421 is a bare module and mounts differently
It has no tripod hole. Its datasheet gives 2 x M3 threaded-through holes on the
horizontal centreline and 4 non-threaded d3.10 corner holes on an 87.00 x 24.00
pattern, to be bolted to a flat heat sink.
**This resolves one of the pack's assumed datums.** Section 4 previously carried
the D421's datum as "back-face centre (ASSUMED)". The datasheet states the real
reference: "the Depth module D421 origin reference from the left centre hole to
the left imager centre is 18.5 mm". The left centre hole is the left M3 at
x = -43.5, so the left imager is at -43.5 + 18.5 = **-25.0** - which is exactly
the value the pack already carried. The number was right; it is no longer an
assumption.
### What is still not published
Carried as gaps rather than invented:
- **The D405's back-face thread size.** Its figure 10-13 calls out "2X M?x?.?
MAXIMUM INSERT ?mm", but the datasheet's embedded drawing raster is only
1020 x 1320 px for the whole sheet and that annotation does not survive at
any magnification. The 20.00 mm pitch and the hole positions are legible; the
thread is not. Modelled as M3, the smaller of the two sizes used elsewhere in
the family, so a bracket designed against it will not foul an M4 boss.
- **Tripod thread depth on the D415, D435 family, D436 and D457.** Only the D405
(5.0 mm) and the D455 (9.0 mm) state it. Modelled at 5.0 mm, the shallower
published figure, for the same reason.
- **The D436's screw pattern.** The D436 is not in 337029-017 and its own
datasheet has no mechanical drawing. It shares the D435's 90 x 25 x 25
housing and every optical offset, so the D435's pattern is inherited.
- **The D457's screw pitch.** Its datasheet states 2 x M4 on the rear mounting
surface and the torque limit, but its mechanical-drawing page renders blank.
It shares the D455's 124 x 29 back face, so the D455's 95 mm is inherited.
- **Whether the D555 has a 1/4-20 at all.** Its datasheet shows only the 2 x M4
at 125.40. A FRAMOS product listing claims one 1/4-20 and two M3, which
contradicts the vendor drawing on the thread size; the drawing is preferred
and the tripod is left out rather than guessed at.
## 11. Sources
- RealSense Product Family D400 Series Datasheet, doc 337029-017 rev 021, Oct 2025 —
- RealSense D400 Series Datasheet, Mar 2026 (adds D436) —
- RealSense Depth Module D421 Datasheet v1.2 —
- RealSense D555 PoE Datasheet v1.1 —
- RealSense D457 GMSL/FAKRA Datasheet, Dec 2025 —
- D436 product page —
- CAD files index —
- Vendor meshes —
- `realsense2_description` URDF xacros, branch `ros2-development` —
## 12. Quality: the Detailed housing and the real ports
The graph carries a `quality` switch (Camera group, `Proxy` / `Detailed`, default
Proxy). Everything it adds is generated by `tools/gen_detail.py` and appended AFTER
every existing parameter, because pod lowers parameters to nodes in document order
and a scene's per-instance override binds a knob by that node id. Checked by dumping
the lowered graph before and after: **all 129 existing parameter nodes keep their
ids**, `model` is still 905, `quality` is 1144.
Both qualities keep every body, frame and the collision box exactly where they were.
Only the render mesh changes.
### Proxy (quality 0)
The housing above, unchanged, plus an **opening cut 2 mm into it for every connector
the model has**, at the connector's real position and orientation, with a bright
steel plate at its floor (`mat_port_hint`) so a dark opening in a dark housing still
reads. The covered sync-port slot is indicated the same way on the top face.
### Detailed (quality 1)
**The housing** is a rounded rectangle in front view -- a STADIUM (radius = half the
height) on every model except the D405's rounded square and the D421 module's box --
clipped by two elliptic cylinders: one along X that domes the top and rounds the
bottom-front edge, one along Z that pulls the ends in toward the front and back.
The numbers are fitted to the vendor meshes of section 8 (silhouette extents binned
along each axis, each fit checked at three or more depths):
| Model | Front outline | Dome (along X) | Ends (along Z) | Source |
|---|---|---|---|---|
| D405 | rounded square, r 6 mm (17.37 at 0.5 mm from the end, 19.61 at 2.5) | none: flat | none | `d405.stl` |
| D415 | stadium, r 11.5 | top 11.5 -> 9.63 at the front: semi-axis 26.8, crown 15.4 behind the front | 49.5 -> 47.61: semi-axis 53.4 | `d415.stl` |
| D435 family, D436 | stadium, r 12.5 | 12.5 -> 10.05: 31.1 @ 19.5 (predicts 11.76 at 8.7 back, measured 11.64) | 45 -> 42.54: 58.3 | `d435.dae` |
| D455, D455f, D456 | stadium, r 14.5 | 14.5 at 12 back -> 12.34 front, 11.94 back: 22 @ 12 | 62 -> 59.84 / 59.32: 45 | `d455.stl` |
| D457 | stadium, r 14.5 | 30 @ 15 | 60 | **ASSUMED**: no mesh; D457 datasheet fig 3-7 shows the D455's domed silver body, deeper |
| D555 | stadium, r 21 | 60 @ 24 | 110 | **ASSUMED**: no mesh; its datasheet cover photograph |
| D421 | box, 1 mm corners | none | none | bare module |
Every mesh measures the bottom FLAT at the back and rounded only at the front. The
flat part starts at the dome's crown: the meshes put its start a little further
forward, but starting it there leaves a kink of up to 8 degrees and a 0.8 mm step
where the dome's underside meets it.
The front is **black gloss glass** in a 1.0-1.5 mm aluminium frame, following the
housing's front outline, with a ring and a lens disc on it at every aperture of
section 4 (the same `has_rgb_lens` / `has_projector` rules as the proxy).
The housing is **bead-blasted silver aluminium** (`mat_alu`) on the D4xx, and
`mat_dark_body` on the D555 -- dark grey in its datasheet photograph -- and on the
bare D421 module.
**The ports** are pod's own connector graphs (`assets/connectors` in the pod repo,
copied into `groups/`: `usb_c_receptacle`, `usb3_micro_b_receptacle`, `rj45_jack`,
`fakra_jack`), each set into a pocket cut to its outline:
| Model | Connector | Where | Source |
|---|---|---|---|
| D405 | USB 3 Micro-B, 2 x M2x0.4 cable-lock holes 18.00 apart | LEFT side face, long axis vertical, at mid-height, 6.82 forward of the rear face | DS fig 10-13; the vendor mesh has the 12 mm slot exactly there, on its +x face, which the URDF's `rpy (pi/2, 0, pi/2)` makes the camera's left |
| D415, D435 family, D436 | USB-C, vertical | on the floor of an OPEN POCKET in the back face at the camera's RIGHT end, x from +36 to the end, 8 mm deep; socket centred at x = +40 | a mid-height section of `d415.stl` (open end cap, socket at mesh x = -40); the rear views of DS fig 10-8 and 10-9 draw that end cap. D43x pocket depth **ASSUMED** from the D415 (the `d435.dae` is too fragmentary to section there) |
| D455, D455f, D456 | USB-C, 2 x M2 cable-lock holes | BOTTOM face, 4.06 from the rear edge, 37.01 from the 1/4-20 toward the camera's RIGHT, long axis along X | DS fig 10-11 bottom view. It sits below the front view (third-angle projection), so it shares that view's orientation, and the front view puts the left imager at page-right. The lock holes measure 15.4 apart on the drawing; the USB-IF dual-lock pitch 15.00 is used |
| D457 | USB-C as the D455 (its y is the D455's, **ASSUMED**); FAKRA on the rear, camera-LEFT end, 5 mm below the optical axis, x = -52 (**ASSUMED**) | | D457 datasheet fig 3-7: the blue FAKRA leaves the back at the end opposite the bottom USB-C |
| D555 | RJ45 at the camera's RIGHT end of the bottom, USB-C at its LEFT end, tripod between; +/-48 mm, mid-depth (**ASSUMED**) | BOTTOM face | D555 datasheet v1.1 cover photograph; the lens order (camera-right imager, RGB, projector, left imager) fixes which end is which |
| D415, D43x, D436, D45x, D457 | JST SH sync header, **under a covered slot** | TOP face, centred in X: D415 18.3 x 3.6 at 16.2 behind the front; D43x 17.6 x 3.6 at 20.7; D45x and D457 22.4 x 4.4 at 22 | the top views of DS fig 10-8, 10-9, 10-11, and D457 fig 7-12 "External Sensor Sync Connector". Drawn as the slot and its cover plate with a screw, because the header itself is under the cover |
| D421 | none modelled | | the module's board connectors are not in the connector library |
`section 10`'s correction: the pod connector README first placed the D455's USB-C
toward the camera's LEFT. The drawing says right, as above; the README is corrected.
**Shading.** The render mesh is smoothed by angle: 30 degrees on the Proxy, as it
always was, and 12 on the Detailed housing (`shade_angle`). The detailed shell's dome
and corners are faceted at 2.25 and 5.6 degrees, so they stay smooth; where the dome
crosses the stadium ends the crease angle runs from 0 to 90 degrees, and at 30 every
face under it was averaged across the flat faces' long boolean triangles. Measured on
the exported normals: faces bent more than 8 degrees from their own plane, 8.0% of the
D435i housing's area at 30 degrees, 0.7% at 12. (pod's midui editor viewport does not
read these per-corner normals -- it averages every face at a vertex -- so it shows
smears on BOTH qualities that neither the export nor LuckyEngine has.)
**The D457's engraved name moved.** Its 36 mm top carries the sync slot 22 mm back,
and a centred name cut straight through it; `label_y` now keeps a tripod-datum model
deeper than 27 mm (only ever the D457) 13.5 mm behind the front. `check_marking.py`
and `verify_marking_selection.py` measure the groove only within the name's own band,
so the slot is not read as part of it; both pass on all 13.
### Checks
`tools/verify_models.py` (all 13 bodies on their table values), `tools/check_mounts.py`
(every screw and tripod hole cut to its published depth), `tools/check_marking.py`,
`tools/verify_marking_selection.py` and `tools/check_coplanar.py` all pass on the
default (Proxy) graph. Every model bakes at both qualities in about 0.3 s; the
Detailed housing's extents equal the published housing dimensions exactly (the ports
sit inside them).
### Still open
- The D405's back-face screws: at 600 dpi the October 2025 drawing reads
"2X M3x0.5, MAXIMUM INSERT 4.0mm". Section 10 carries the thread as unreadable and
the graph models 3 mm; not changed here.
- The D436's two stacked cable-lock screws: position and pitch not dimensioned; not
modelled.
- The D555's port positions and the D457's FAKRA position are photograph-level
estimates, stated as ASSUMED above.
## 13. Graph layout and recording settings (pack 1.1)
The camera graph is arranged into labeled frames for the proxy housing, engraving,
mounting holes, detailed housing and optics, each connector family, assembly,
optical and mounting frames, the collision box, and output. Connector helper graphs
have separate frames for their shells, inserts, contacts, locking features, and
assembly. Smaller shape helpers have a single workflow frame.
This layout changes no camera dimensions, optical transforms, materials, quality
defaults, render roles, or collision behavior. The top-level authored ID ceiling
remains 904: `model` stays on node 905 and `quality` stays on node 1144. Existing
scene overrides therefore keep their bindings. The thirteen generated model-name
strings have native editor positions beside their engraving nodes. Folded numeric
and parameter nodes remain hidden by the editor's default display settings.
Resolution and FPS are configured **per camera** in Settings > Data > Camera
Bindings. For one recorded image per data step, use the same frequency for every
recorded camera, the Capture runner, and the data recorder. These graph parameters
describe the camera body and optical frames; they do not configure recording.
Enable **Invisible to Cameras** in the Pod component to exclude its generated
housing from captures while retaining it in the editor viewport.