4.2 KiB
4.2 KiB
name, description
| name | description |
|---|---|
| speckle-viewer-osnap | Speckle viewer measurement object-snap and orbit zoom maintenance for this repo. Use when changing packages/viewer measurement snapping, corner/edge snap candidate selection, large IFC snap performance, or SmoothOrbitControls zoom sensitivity/near-model wheel behavior. |
Speckle Viewer Osnap
Workflow
- Run GitNexus impact before editing viewer symbols. The index may not resolve TypeScript class methods; if it returns
UNKNOWN, record that result and keep the edit scoped. - Inspect these files before changing behavior:
packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.tspackages/viewer/src/modules/extensions/controls/SmoothOrbitControls.tspackages/viewer/src/modules/Intersections.tspackages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts
- Treat osnap as screen-space selection first, geometry selection second. Candidate quality should be decided by projected pixel distance to the cursor, but candidate discovery must stay cheap.
- After source edits, run:
yarn workspace @speckle/viewer lint:tscyarn workspace @speckle/viewer build:dev
- If frontend is running from
packages/viewer/dist/index.js, rebuild before browser testing. A transient Vite pre-transform error can appear while Rollup replacesdist/index.js; reload once and verify again. - Before commit, run
npx gitnexus detect-changes --repo speckle-server.
Osnap Rules
- Do not require the primary ray to land exactly on a vertex. A small screen-space probe around the cursor is acceptable when the primary ray misses geometry.
- Do not probe surrounding rays when the primary ray already has a precise mesh hit; that can make snap jump to nearby or behind objects.
- Keep probe count and scanned batch count low. Large IFCs can lag badly if every pointer frame scans many ray hits or very large edge lists.
- Do not classify every mesh vertex as a point snap. IFC meshes often split long edges into technical vertices. Point snap should prefer real feature corners: feature-edge junctions or vertices where adjacent feature-edge directions are not collinear.
- Treat vertices on straight feature edges as edge snap candidates, not point snap candidates.
- If point snap is found, skip expensive feature-edge scanning for that candidate batch unless edge snap is explicitly needed.
- Keep thresholds conservative. A practical starting point is point snap around
24px, edge snap around12px, and only a few distinct batch objects per pointer frame.
Zoom Rules
- Wheel zoom should scale from the current orbit radius, but close-range movement must not be clamped to a large fraction of the model/world diagonal.
- For large models, reduce the minimum zoom basis instead of only reducing wheel sensitivity. The previous near-model fix used
world.getRelativeOffset(0.00005)andZOOM_SENSITIVITY = 0.05.
Validation
Use the target model page through the running frontend, for example:
@'
import puppeteer from 'puppeteer'
const url = 'http://127.0.0.1:8081/projects/a4abd72149/models/252b555ee9'
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] })
const page = await browser.newPage()
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 })
const events = []
page.on('console', (msg) => {
if (['error', 'warning'].includes(msg.type())) events.push({ type: `console:${msg.type()}`, text: msg.text() })
})
page.on('pageerror', (error) => events.push({ type: 'pageerror', text: error.stack || error.message }))
page.on('requestfailed', (request) => events.push({ type: 'requestfailed', url: request.url(), failure: request.failure()?.errorText }))
page.on('response', (response) => {
if (response.status() >= 400) events.push({ type: 'http', status: response.status(), url: response.url() })
})
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 120000 })
await new Promise((resolve) => setTimeout(resolve, 25000))
console.log(JSON.stringify({
events,
title: await page.title(),
canvasCount: await page.$$eval('canvas', (nodes) => nodes.length)
}, null, 2))
await browser.close()
'@ | node --input-type=module -