Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vim-web",
"version": "1.0.0-beta.1",
"version": "1.0.0-beta.2",
"description": "WebGL and cloud-streaming 3D viewers for VIM files with BIM support",
"type": "module",
"files": [
Expand Down
706 changes: 706 additions & 0 deletions src/customInspector.tsx

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RefObject, useEffect, useRef, ChangeEvent } from 'react'
import { createRoot } from 'react-dom/client'
import * as VIM from './vim-web'
import { CustomInspector } from './customInspector'

type ViewerRef = VIM.React.Webgl.ViewerApi | VIM.React.Ultra.ViewerApi

Expand All @@ -10,7 +11,15 @@ function isWebglViewer (viewer: ViewerRef): viewer is VIM.React.Webgl.ViewerApi

const root = createRoot(document.getElementById('root')!)

root.render(<App />)
// Switch dev pages via the URL: `/?page=inspector` (or any path containing
// "inspector") renders the CustomInspector regression test for the
// RenderScene.removeScene crash; otherwise the default viewer demo loads.
const params = new URLSearchParams(window.location.search)
const isInspector =
params.get('page') === 'inspector' ||
window.location.pathname.includes('inspector')

root.render(isInspector ? <CustomInspector /> : <App />)

function App() {
const div = useRef<HTMLDivElement>(null)
Expand Down
5 changes: 5 additions & 0 deletions src/vim-web/core-viewers/webgl/loader/scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ export class Scene implements IScene {
*/
dispose () {
this.clear()
// clear() leaves the (now empty) scene registered in the renderer so it's
// ready to receive new geometry. On dispose we want it gone entirely —
// otherwise an empty scene with an undefined bounding box lingers in the
// renderer's scene list and breaks later bounding-box recomputes.
this._renderer?.remove(this)
this._renderer = null
}
}
16 changes: 10 additions & 6 deletions src/vim-web/core-viewers/webgl/viewer/rendering/renderScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,17 @@ export class RenderScene {
this.threeScene.remove(scene.meshes[i].mesh)
}

// Recompute bounding box from remaining scenes
const remainingScenes = this._vimScenesById.filter((s): s is Scene => s !== undefined)
// Recompute bounding box from remaining scenes. A scene's box can be
// undefined when its geometry isn't built yet (e.g. another scene is
// mid-clear() during a sequential load), so filter those out before the
// union — reducing over an undefined box would throw.
const boxes = this._vimScenesById
.filter((s): s is Scene => s !== undefined)
.map((s) => s.getBoundingBox())
.filter((b): b is THREE.Box3 => b !== undefined)
this._boundingBox =
remainingScenes.length > 0
? remainingScenes
.map((s) => s.getBoundingBox())
.reduce((b1, b2) => b1.union(b2))
boxes.length > 0
? boxes.reduce((b1, b2) => b1.union(b2))
: undefined
}
}
2 changes: 2 additions & 0 deletions src/vim-web/react-viewers/errors/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { webglFileError } from './webglFileError'

export { fileOpeningError } from '../ultra/errors/fileOpeningError'
export { serverFileDownloadingError } from '../ultra/errors/serverFileDownloadingError'
export { serverFileLoadingError } from '../ultra/errors/fileLoadingError'
Expand Down
37 changes: 37 additions & 0 deletions src/vim-web/react-viewers/errors/webglFileError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MessageBoxProps } from '../panels/messageBox'
import * as style from './errorStyle'

/**
* Error modal shown when a WebGL load fails. Surfaces the underlying error and
* the source so the cause isn't hidden. This is the WebGL counterpart to the
* Ultra error modals — using an Ultra modal here mislabels the failure.
*/
export function webglFileError (url: string | undefined, error?: string): MessageBoxProps {
return {
title: 'VIM File Error',
body: body(url, error),
footer: style.footer(),
canClose: true
}
}

function body (url: string | undefined, error?: string): React.ReactElement {
return (
<>
{style.mainText(<>
We encountered an error loading the VIM file.
</>)}
{style.subTitle('Details')}
{style.dotList([
url ? style.bullet('Source:', url) : null,
error ? style.bullet('Error:', error) : null
])}
{style.subTitle('Tips')}
{style.numList([
'Ensure the source points to a valid VIM file',
'Check your network connection and access policies',
'Reload the page'
])}
</>
)
}
6 changes: 6 additions & 0 deletions src/vim-web/react-viewers/state/sharedIsolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,14 @@ export function useSharedIsolation(adapter: IIsolationAdapter) {
visibility.set(adapter.computeVisibility());
})

// Push the initial state into the adapter on mount. The StateRefs may hydrate
// from localStorage to a value that differs from the underlying system's
// independent default, and useOnChange only fires on later changes — so
// without this the persisted value shows in the UI but never reaches the
// material (e.g. a stored ghostOpacity that the ghost material never applies).
useEffect(() => {
adapter.showGhost(showGhost.get());
adapter.setGhostOpacity(ghostOpacity.get());
}, []);

useSubscribe(adapter.onVisibilityChange, () => onVisibilityChange.call())
Expand Down
13 changes: 13 additions & 0 deletions src/vim-web/react-viewers/webgl/isolation.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { useRef } from 'react'
import * as Core from '../../core-viewers'
import { ISelectable } from '../../core-viewers/webgl'
import { IIsolationAdapter, useSharedIsolation, VisibilityStatus } from '../state/sharedIsolation'
import { IRenderSettingsAdapter, useRenderSettings } from '../state/renderSettings'
import { IsolationSettings } from '../webgl/settings'

export function useWebglIsolation(viewer: Core.Webgl.Viewer, initialState?: IsolationSettings) {
// Seed the material with the configured ghost opacity once, before the
// isolation StateRefs initialize from it. A persisted localStorage value still
// takes precedence (the StateRef reads it first). Done here rather than in
// createWebglAdapters because that runs on every render.
const seeded = useRef(false)
if (!seeded.current) {
seeded.current = true
if (initialState?.ghostOpacity !== undefined) {
viewer.materials.ghostOpacity = initialState.ghostOpacity
}
}

const { isolationAdapter, renderSettingsAdapter } = createWebglAdapters(viewer, initialState)
const isolation = useSharedIsolation(isolationAdapter)
const renderSettings = useRenderSettings(renderSettingsAdapter)
Expand Down
4 changes: 2 additions & 2 deletions src/vim-web/react-viewers/webgl/loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @module viw-webgl-react
*/

import { serverFileDownloadingError } from '../errors/errors'
import { webglFileError } from '../errors/errors'
import * as Core from '../../core-viewers'
import { LoadRequest } from '../helpers/loadRequest'
import { ModalApi } from '../panels/modal'
Expand Down Expand Up @@ -73,7 +73,7 @@ export class ComponentLoader {
* Event emitter for error notifications.
*/
onError (e: LoadingError) {
this._modal.current?.message(serverFileDownloadingError(e.url))
this._modal.current?.message(webglFileError(e.url, e.error))
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/vim-web/react-viewers/webgl/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export type IsolationSettings = {
showGhost: boolean
showTransparent: boolean
showRooms: boolean
/**
* Initial ghost (hidden-element) opacity, 0-1. When omitted the material's
* built-in default is used. A persisted value from the settings panel (saved
* to localStorage) takes precedence over this.
*/
ghostOpacity?: number
}

export type SectionBoxSettings = {
Expand Down