# How to Use Selector References Like #o1.2 to Inspect CAD Geometry in build123d

> Master build123d selector references like #o1.2 to inspect CAD geometry. Directly map DOM elements to CAD metadata for effective browser-based inspection.

- Repository: [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad)
- Tags: how-to-guide
- Published: 2026-08-03

---

**Selector references in build123d follow the pattern `#oI.<n>` (e.g., `#oI.2`) and map directly to DOM elements with attached CAD metadata, enabling browser-based inspection of generated geometry.**

The **text-to-cad** repository by **earthtojake** generates CAD models from natural language prompts and renders them in a lightweight, browser-based viewer. Each geometric primitive receives a deterministic DOM identifier that bridges the gap between the **ImplicitJS** engine's internal representation and standard web debugging tools. This article explains how these selectors work, where they originate in the source code, and how to leverage them for interactive geometry inspection.

## How Object IDs Are Generated

The selector system begins in [`packages/implicitjs/src/lib/viewer/surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/implicitjs/src/lib/viewer/surfaceMaterials.js). During **scene-graph construction**, the viewer assigns an `objectId` to every new geometry, prefixes it with `oI` (object-Instance), and embeds this identifier as both the element's `id` attribute and its `data-cad-id` property.

This process is deterministic: IDs increment sequentially within a single build, starting from `oI.0`. The result is a stable reference you can rely on across sessions, automated tests, or collaborative debugging.

The flow through the codebase follows three stages:

1. **Object creation** — The ImplicitJS engine instantiates primitives (boxes, cylinders, etc.) via [`packages/implicitjs/src/browser.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/implicitjs/src/browser.js), registering each with an auto-generated ID.
2. **DOM mapping** — [`surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/surfaceMaterials.js) creates corresponding HTML elements with `id="oI.<n>"` and attaches a `userData` object containing raw geometry (vertices, faces, bounding box, material).
3. **Renderer synchronization** — The Vite-powered viewer (`viewer/vite.config.mjs`) keeps the WebGL layer in sync with the DOM, ensuring selectors return live data.

## Querying Geometry with CSS Selectors

Because `#oI.<n>` identifiers are ordinary DOM IDs, you can use any browser-side selector API. The dot character requires escaping in CSS since it normally denotes a class selector.

### Basic DOM Queries

```javascript
/* Locate a specific geometry element */
const elem = document.querySelector('#oI\\.2');

/* Check for the CAD metadata bridge */
if (elem && elem.dataset.cadId) {
  console.log('CAD ID:', elem.dataset.cadId);           // "oI.2"
  console.log('Raw geometry:', elem.__cadUserData);      // full mesh data
}

```

### Visual Debugging Techniques

Once you have the element, standard DOM manipulation applies. This is useful for highlighting specific parts of complex assemblies or verifying spatial relationships.

```javascript
/* Highlight the object in the viewer */
if (elem) {
  elem.style.outline = '2px solid orange';
  elem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}

/* Batch-select multiple objects by pattern */
const allSolids = document.querySelectorAll('[id^="oI."]');
console.log(`Total objects: ${allSolids.length}`);

```

## Accessing Low-Level CAD Data

The `__cadUserData` property attached to each element contains the engine's internal representation. This bridges high-level browser inspection with low-level geometric analysis.

### Inspecting Volume and Bounding Box

```javascript
const obj = document.querySelector('#oI\\.2');
if (obj && obj.__cadUserData) {
  const { volume, bbox, vertices, faces, material } = obj.__cadUserData;
  
  console.log('Volume:', volume);
  console.log('Bounds:', bbox);           // { min: [x,y,z], max: [x,y,z] }
  console.log('Vertex count:', vertices.length / 3);  // typically flat array
}

```

### Programmatic Material Overrides

For advanced debugging, mutate the material properties directly through the user data reference. Changes propagate to the WebGL renderer on the next frame.

```javascript
/* Wireframe mode for specific object */
const mesh = obj.__cadUserData?.mesh;
if (mesh) {
  mesh.material.wireframe = true;
  mesh.material.color.setHex(0xff0000);
}

```

## Using Selectors in Automated Tests

The deterministic ID scheme enables reliable browser-based testing. Both **Jest** (with jsdom) and **Cypress** can assert on geometry properties without fragile CSS class selectors.

### Jest Example

```javascript
test('object #oI.2 has expected volume', () => {
  const obj = document.querySelector('#oI\\.2');
  expect(obj).not.toBeNull();
  
  const volume = obj.__cadUserData?.volume;
  expect(volume).toBeCloseTo(125.0, 2);  // 125.0 ± 0.01
});

```

### Cypress Example

```javascript
cy.visit('/viewer?model=cube-assembly');

cy.get('#oI\\\\.2').should('exist').then($el => {
  const userData = $el[0].__cadUserData;
  expect(userData.bbox.min[0]).to.be.closeTo(-1, 0.001);
});

```

Note the doubled backslash in Cypress selectors: the first escapes for JavaScript string parsing, the second for CSS selector parsing.

## Key Implementation Files

Understanding the full selector pipeline requires familiarity with these source files:

| File Path | Responsibility |
|-----------|---------------|
| [`packages/implicitjs/src/lib/viewer/surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/implicitjs/src/lib/viewer/surfaceMaterials.js) | Generates DOM elements, assigns `id="oI.<n>"`, attaches `__cadUserData` |
| [`packages/implicitjs/src/browser.js`](https://github.com/earthtojake/text-to-cad/blob/main/packages/implicitjs/src/browser.js) | Core ImplicitJS entry point; creates geometry objects and registers incremental IDs |
| `viewer/vite.config.mjs` | Vite bundler configuration serving the compiled viewer application |
| [`README.md`](https://github.com/earthtojake/text-to-cad/blob/main/README.md) | High-level text-to-CAD workflow documentation |
| [`AGENTS.md`](https://github.com/earthtojake/text-to-cad/blob/main/AGENTS.md) | Repository layout: `skills/` (user-facing) vs. `packages/` (core engine) |

The separation between `packages/implicitjs/` (geometry engine) and `viewer/` (rendering layer) ensures that selector-based inspection works identically whether you're debugging local development or a production deployment.

## Common Selector Patterns

| Pattern | Use Case |
|---------|----------|
| `#oI\\.2` | Single object by exact ID |
| `[id^="oI."]` | All objects in scene |
| `[id*="oI."][data-cad-id]` | Filter for elements with verified CAD metadata |
| `document.querySelectorAll('[id^="oI."]')[n]` | Index-based access when ID unknown |

## Summary

- **Selector references** like `#oI.2` are deterministic DOM IDs generated during scene construction in [`surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/surfaceMaterials.js)
- The **dot must be escaped** (`#oI\\.2`) in CSS selectors to avoid class-name interpretation
- Each element carries **`__cadUserData`** with raw CAD data: vertices, faces, volume, bounding box, and material
- **ImplicitJS** in [`browser.js`](https://github.com/earthtojake/text-to-cad/blob/main/browser.js) creates objects incrementally, ensuring stable IDs across builds
- These selectors enable **browser DevTools debugging**, **visual highlighting**, and **automated testing** of generated CAD geometry

## Frequently Asked Questions

### Why does my selector return null for `#oI.2`?

The dot in the ID is interpreted as a CSS class separator unless escaped. Use `document.querySelector('#oI\\.2')` with a backslash, or `document.getElementById('oI.2')` which requires no escaping. Also verify that the scene has finished loading—IDs are assigned only after [`surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/surfaceMaterials.js) processes the geometry batch.

### Are these IDs stable across different prompts or sessions?

Within a single build execution, yes: IDs increment deterministically from `oI.0`. However, changing the input prompt may alter the geometry count and order, shifting the ID assigned to a specific semantic part (e.g., "the third cylinder"). For cross-session stability, consider tagging objects with custom `data-*` attributes in your generator script.

### Can I assign custom IDs instead of the auto-generated `oI.<n>` format?

The core engine in [`browser.js`](https://github.com/earthtojake/text-to-cad/blob/main/browser.js) manages ID assignment opaquely. For custom identifiers, wrap the query logic: create a lookup table mapping `oI.<n>` to your semantic names after scene load, or extend [`surfaceMaterials.js`](https://github.com/earthtojake/text-to-cad/blob/main/surfaceMaterials.js) to accept an optional `customId` parameter that prefixes or replaces the default scheme.