How to Use Selector References Like #o1.2 to Inspect CAD Geometry in build123d
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. 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:
- Object creation — The ImplicitJS engine instantiates primitives (boxes, cylinders, etc.) via
packages/implicitjs/src/browser.js, registering each with an auto-generated ID. - DOM mapping —
surfaceMaterials.jscreates corresponding HTML elements withid="oI.<n>"and attaches auserDataobject containing raw geometry (vertices, faces, bounding box, material). - 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
/* 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.
/* 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
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.
/* 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
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
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 |
Generates DOM elements, assigns id="oI.<n>", attaches __cadUserData |
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 |
High-level text-to-CAD workflow documentation |
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.2are deterministic DOM IDs generated during scene construction insurfaceMaterials.js - The dot must be escaped (
#oI\\.2) in CSS selectors to avoid class-name interpretation - Each element carries
__cadUserDatawith raw CAD data: vertices, faces, volume, bounding box, and material - ImplicitJS in
browser.jscreates 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 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 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 to accept an optional customId parameter that prefixes or replaces the default scheme.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →