From 1ca71caf455f3a0385bc5c5daaf3fd66b5ba9c82 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Fri, 6 Jun 2025 20:35:16 -0400 Subject: [PATCH 01/17] [3d] performance improvement by using threejs setViewport (#4079) --- src/extensions/core/load3d/Load3d.ts | 70 ++++--- src/extensions/core/load3d/Load3dAnimation.ts | 17 +- src/extensions/core/load3d/PreviewManager.ts | 175 +++++++++--------- src/extensions/core/load3d/interfaces.ts | 9 +- 4 files changed, 145 insertions(+), 126 deletions(-) diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts index 3a1b76789f..ddd5d61454 100644 --- a/src/extensions/core/load3d/Load3d.ts +++ b/src/extensions/core/load3d/Load3d.ts @@ -160,22 +160,48 @@ class Load3d { this.viewHelperManager.update(delta) this.controlsManager.update() + this.renderMainScene() + + if (this.previewManager.showPreview) { + this.renderPreviewScene() + } + + this.resetViewport() + + if (this.viewHelperManager.viewHelper.render) { + this.viewHelperManager.viewHelper.render(this.renderer) + } + + this.INITIAL_RENDER_DONE = true + } + + renderMainScene(): void { + const width = this.renderer.domElement.clientWidth + const height = this.renderer.domElement.clientHeight + + this.renderer.setViewport(0, 0, width, height) + this.renderer.setScissor(0, 0, width, height) + this.renderer.setScissorTest(true) + this.renderer.clear() this.sceneManager.renderBackground() this.renderer.render( this.sceneManager.scene, this.cameraManager.activeCamera ) + } - if (this.viewHelperManager.viewHelper.render) { - this.viewHelperManager.viewHelper.render(this.renderer) - } + renderPreviewScene(): void { + this.previewManager.renderPreview() + } - if (this.previewManager.showPreview) { - this.previewManager.updatePreviewRender() - } + resetViewport(): void { + const width = this.renderer.domElement.clientWidth + const height = this.renderer.domElement.clientHeight - this.INITIAL_RENDER_DONE = true + this.renderer.setViewport(0, 0, width, height) + this.renderer.setScissor(0, 0, width, height) + this.renderer.setScissorTest(false) } private getActiveCamera(): THREE.Camera { @@ -198,20 +224,17 @@ class Load3d { return } - if (this.previewManager.showPreview) { - this.previewManager.updatePreviewRender() - } - const delta = this.clock.getDelta() this.viewHelperManager.update(delta) this.controlsManager.update() - this.renderer.clear() - this.sceneManager.renderBackground() - this.renderer.render( - this.sceneManager.scene, - this.cameraManager.activeCamera - ) + this.renderMainScene() + + if (this.previewManager.showPreview) { + this.renderPreviewScene() + } + + this.resetViewport() if (this.viewHelperManager.viewHelper.render) { this.viewHelperManager.viewHelper.render(this.renderer) @@ -304,11 +327,9 @@ class Load3d { async setBackgroundImage(uploadPath: string): Promise { await this.sceneManager.setBackgroundImage(uploadPath) - if (this.previewManager.previewRenderer) { - this.previewManager.updateBackgroundTexture( - this.sceneManager.backgroundTexture - ) - } + this.previewManager.updateBackgroundTexture( + this.sceneManager.backgroundTexture + ) this.forceRender() } @@ -316,10 +337,7 @@ class Load3d { removeBackgroundImage(): void { this.sceneManager.removeBackgroundImage() - if ( - this.previewManager.previewRenderer && - this.previewManager.previewCamera - ) { + if (this.previewManager.previewCamera) { this.previewManager.updateBackgroundTexture(null) } diff --git a/src/extensions/core/load3d/Load3dAnimation.ts b/src/extensions/core/load3d/Load3dAnimation.ts index 3629250d31..597867588e 100644 --- a/src/extensions/core/load3d/Load3dAnimation.ts +++ b/src/extensions/core/load3d/Load3dAnimation.ts @@ -42,10 +42,6 @@ class Load3dAnimation extends Load3d { return } - if (this.previewManager.showPreview) { - this.previewManager.updatePreviewRender() - } - const delta = this.clock.getDelta() this.animationManager.update(delta) @@ -54,12 +50,13 @@ class Load3dAnimation extends Load3d { this.controlsManager.update() - this.renderer.clear() - this.sceneManager.renderBackground() - this.renderer.render( - this.sceneManager.scene, - this.cameraManager.activeCamera - ) + this.renderMainScene() + + if (this.previewManager.showPreview) { + this.renderPreviewScene() + } + + this.resetViewport() if (this.viewHelperManager.viewHelper.render) { this.viewHelperManager.viewHelper.render(this.renderer) diff --git a/src/extensions/core/load3d/PreviewManager.ts b/src/extensions/core/load3d/PreviewManager.ts index 80bd68c43c..3f23dac610 100644 --- a/src/extensions/core/load3d/PreviewManager.ts +++ b/src/extensions/core/load3d/PreviewManager.ts @@ -4,7 +4,6 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { EventManagerInterface, PreviewManagerInterface } from './interfaces' export class PreviewManager implements PreviewManagerInterface { - previewRenderer: THREE.WebGLRenderer | null = null previewCamera: THREE.Camera previewContainer: HTMLDivElement = {} as HTMLDivElement showPreview: boolean = true @@ -17,7 +16,6 @@ export class PreviewManager implements PreviewManagerInterface { private getControls: () => OrbitControls private eventManager: EventManagerInterface - // @ts-expect-error unused variable private getRenderer: () => THREE.WebGLRenderer private previewBackgroundScene: THREE.Scene @@ -25,6 +23,8 @@ export class PreviewManager implements PreviewManagerInterface { private previewBackgroundMesh: THREE.Mesh | null = null private previewBackgroundTexture: THREE.Texture | null = null + private previewBackgroundColor: THREE.Color = new THREE.Color(0x282828) + constructor( scene: THREE.Scene, getActiveCamera: () => THREE.Camera, @@ -61,18 +61,6 @@ export class PreviewManager implements PreviewManagerInterface { init(): void {} dispose(): void { - if (this.previewRenderer) { - this.previewRenderer.forceContextLoss() - const canvas = this.previewRenderer.domElement - const event = new Event('webglcontextlost', { - bubbles: true, - cancelable: true - }) - canvas.dispatchEvent(event) - - this.previewRenderer.dispose() - } - if (this.previewBackgroundTexture) { this.previewBackgroundTexture.dispose() } @@ -84,17 +72,6 @@ export class PreviewManager implements PreviewManagerInterface { } createCapturePreview(container: Element | HTMLElement): void { - this.previewRenderer = new THREE.WebGLRenderer({ - alpha: true, - antialias: true, - preserveDrawingBuffer: true - }) - - this.previewRenderer.setSize(this.targetWidth, this.targetHeight) - this.previewRenderer.setClearColor(0x282828) - this.previewRenderer.autoClear = false - this.previewRenderer.outputColorSpace = THREE.SRGBColorSpace - this.previewContainer = document.createElement('div') this.previewContainer.style.cssText = ` position: absolute; @@ -104,7 +81,6 @@ export class PreviewManager implements PreviewManagerInterface { display: block; transition: border-color 0.1s ease; ` - this.previewContainer.appendChild(this.previewRenderer.domElement) const MIN_PREVIEW_WIDTH = 120 const MAX_PREVIEW_WIDTH = 240 @@ -131,7 +107,6 @@ export class PreviewManager implements PreviewManagerInterface { } this.updatePreviewSize() - this.updatePreviewRender() }) this.previewContainer.style.display = this.showPreview ? 'block' : 'none' @@ -159,13 +134,48 @@ export class PreviewManager implements PreviewManagerInterface { const previewHeight = (this.previewWidth * this.targetHeight) / this.targetWidth - this.previewRenderer?.setSize(this.previewWidth, previewHeight, false) + + this.previewContainer.style.width = `${this.previewWidth}px` + this.previewContainer.style.height = `${previewHeight}px` + } + + getPreviewViewport(): { + left: number + bottom: number + width: number + height: number + } | null { + if (!this.showPreview || !this.previewContainer) { + return null + } + + const renderer = this.getRenderer() + const canvas = renderer.domElement + + const containerRect = this.previewContainer.getBoundingClientRect() + const canvasRect = canvas.getBoundingClientRect() + + if ( + containerRect.bottom < canvasRect.top || + containerRect.top > canvasRect.bottom || + containerRect.right < canvasRect.left || + containerRect.left > canvasRect.right + ) { + return null + } + + const width = parseFloat(this.previewContainer.style.width) + const height = parseFloat(this.previewContainer.style.height) + + const left = this.getRenderer().domElement.clientWidth - width + + const bottom = 0 + + return { left, bottom, width, height } } syncWithMainCamera(): void { - if (!this.previewRenderer || !this.previewContainer || !this.showPreview) { - return - } + if (!this.showPreview) return this.previewCamera = this.getActiveCamera().clone() @@ -203,85 +213,73 @@ export class PreviewManager implements PreviewManagerInterface { } this.previewCamera.lookAt(this.getControls().target) - - this.updatePreviewRender() } - updatePreviewRender(): void { - if (!this.previewRenderer || !this.previewContainer || !this.showPreview) - return + renderPreview(): void { + const viewport = this.getPreviewViewport() + if (!viewport) return - if ( - !this.previewCamera || - (this.getActiveCamera() instanceof THREE.PerspectiveCamera && - !(this.previewCamera instanceof THREE.PerspectiveCamera)) || - (this.getActiveCamera() instanceof THREE.OrthographicCamera && - !(this.previewCamera instanceof THREE.OrthographicCamera)) - ) { - this.previewCamera = this.getActiveCamera().clone() - } + const renderer = this.getRenderer() - this.previewCamera.position.copy(this.getActiveCamera().position) - this.previewCamera.rotation.copy(this.getActiveCamera().rotation) + const originalClearColor = renderer.getClearColor(new THREE.Color()) + const originalClearAlpha = renderer.getClearAlpha() - const aspect = this.targetWidth / this.targetHeight + this.syncWithMainCamera() - if (this.getActiveCamera() instanceof THREE.OrthographicCamera) { - const activeOrtho = this.getActiveCamera() as THREE.OrthographicCamera - const previewOrtho = this.previewCamera as THREE.OrthographicCamera + renderer.setViewport( + viewport.left, + viewport.bottom, + viewport.width, + viewport.height + ) + renderer.setScissor( + viewport.left, + viewport.bottom, + viewport.width, + viewport.height + ) - const frustumHeight = - (activeOrtho.top - activeOrtho.bottom) / activeOrtho.zoom - - const frustumWidth = frustumHeight * aspect - - previewOrtho.top = frustumHeight / 2 - previewOrtho.left = -frustumWidth / 2 - previewOrtho.right = frustumWidth / 2 - previewOrtho.bottom = -frustumHeight / 2 - previewOrtho.zoom = 1 - - previewOrtho.updateProjectionMatrix() - } else { - ;(this.previewCamera as THREE.PerspectiveCamera).aspect = aspect - ;(this.previewCamera as THREE.PerspectiveCamera).fov = ( - this.getActiveCamera() as THREE.PerspectiveCamera - ).fov - ;(this.previewCamera as THREE.PerspectiveCamera).updateProjectionMatrix() - } - - this.previewCamera.lookAt(this.getControls().target) - - const previewHeight = - (this.previewWidth * this.targetHeight) / this.targetWidth - this.previewRenderer.setSize(this.previewWidth, previewHeight, false) - this.previewRenderer.outputColorSpace = THREE.SRGBColorSpace - this.previewRenderer.clear() + renderer.setClearColor(this.previewBackgroundColor, 1.0) + renderer.clear() if (this.previewBackgroundMesh && this.previewBackgroundTexture) { const material = this.previewBackgroundMesh .material as THREE.MeshBasicMaterial if (material.map) { - const currentToneMapping = this.previewRenderer.toneMapping - const currentExposure = this.previewRenderer.toneMappingExposure + const currentToneMapping = renderer.toneMapping + const currentExposure = renderer.toneMappingExposure - this.previewRenderer.toneMapping = THREE.NoToneMapping - this.previewRenderer.render( + renderer.toneMapping = THREE.NoToneMapping + renderer.render( this.previewBackgroundScene, this.previewBackgroundCamera ) - this.previewRenderer.toneMapping = currentToneMapping - this.previewRenderer.toneMappingExposure = currentExposure + renderer.toneMapping = currentToneMapping + renderer.toneMappingExposure = currentExposure } } - this.previewRenderer.render(this.scene, this.previewCamera) + renderer.render(this.scene, this.previewCamera) + + renderer.setClearColor(originalClearColor, originalClearAlpha) + } + + setPreviewBackgroundColor(color: string | number): void { + this.previewBackgroundColor.set(color) + } + + getPreviewBackgroundColor(): THREE.Color { + return this.previewBackgroundColor.clone() + } + + updatePreviewRender(): void { + this.syncWithMainCamera() } togglePreview(showPreview: boolean): void { - if (this.previewRenderer) { - this.showPreview = showPreview + this.showPreview = showPreview + if (this.previewContainer) { this.previewContainer.style.display = this.showPreview ? 'block' : 'none' } @@ -306,7 +304,7 @@ export class PreviewManager implements PreviewManagerInterface { ) } - if (this.previewRenderer && this.previewCamera) { + if (this.previewCamera) { if (this.previewCamera instanceof THREE.PerspectiveCamera) { this.previewCamera.aspect = width / height this.previewCamera.updateProjectionMatrix() @@ -322,7 +320,6 @@ export class PreviewManager implements PreviewManagerInterface { handleResize(): void { this.updatePreviewSize() - this.updatePreviewRender() } updateBackgroundTexture(texture: THREE.Texture | null): void { diff --git a/src/extensions/core/load3d/interfaces.ts b/src/extensions/core/load3d/interfaces.ts index fd36efe463..70281af842 100644 --- a/src/extensions/core/load3d/interfaces.ts +++ b/src/extensions/core/load3d/interfaces.ts @@ -100,7 +100,6 @@ export interface ViewHelperManagerInterface extends BaseManager { } export interface PreviewManagerInterface extends BaseManager { - previewRenderer: THREE.WebGLRenderer | null previewCamera: THREE.Camera previewContainer: HTMLDivElement showPreview: boolean @@ -112,6 +111,14 @@ export interface PreviewManagerInterface extends BaseManager { setTargetSize(width: number, height: number): void handleResize(): void updateBackgroundTexture(texture: THREE.Texture | null): void + getPreviewViewport(): { + left: number + bottom: number + width: number + height: number + } | null + renderPreview(): void + syncWithMainCamera(): void } export interface EventManagerInterface { From 6bbe46009b2254476e9f43ff4cd3154c0143022a Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 7 Jun 2025 18:27:35 -0700 Subject: [PATCH 02/17] [docs] Add PrimeVue deprecated component guidelines (#4097) --- .cursorrules | 15 +++++++++++++++ CLAUDE.md | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/.cursorrules b/.cursorrules index 2a61e6f26e..af88564a3f 100644 --- a/.cursorrules +++ b/.cursorrules @@ -38,6 +38,20 @@ const tailwindCssBestPractices = [ "Implement responsive design with Tailwind CSS", ] +// PrimeVue deprecated components - DO NOT USE +const deprecatedPrimeVueComponents = [ + "DO NOT use deprecated PrimeVue components. Use these replacements instead:", + "Dropdown → Use Select (import from 'primevue/select')", + "OverlayPanel → Use Popover (import from 'primevue/popover')", + "Calendar → Use DatePicker (import from 'primevue/datepicker')", + "InputSwitch → Use ToggleSwitch (import from 'primevue/toggleswitch')", + "Sidebar → Use Drawer (import from 'primevue/drawer')", + "Chips → Use AutoComplete with multiple enabled and typeahead disabled", + "TabMenu → Use Tabs without panels", + "Steps → Use Stepper without panels", + "InlineMessage → Use Message component" +] + // Additional instructions const additionalInstructions = ` 1. Leverage VueUse functions for performance-enhancing styles @@ -51,4 +65,5 @@ const additionalInstructions = ` 9. Use Vite for fast development and building 10. Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json. +11. Never use deprecated PrimeVue components listed above `; diff --git a/CLAUDE.md b/CLAUDE.md index 707ca1a5e5..7979193b1d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,3 +36,13 @@ - Use Vite for fast development and building - Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json. - Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard. +- DO NOT use deprecated PrimeVue components. Use these replacements instead: + * `Dropdown` → Use `Select` (import from 'primevue/select') + * `OverlayPanel` → Use `Popover` (import from 'primevue/popover') + * `Calendar` → Use `DatePicker` (import from 'primevue/datepicker') + * `InputSwitch` → Use `ToggleSwitch` (import from 'primevue/toggleswitch') + * `Sidebar` → Use `Drawer` (import from 'primevue/drawer') + * `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled + * `TabMenu` → Use `Tabs` without panels + * `Steps` → Use `Stepper` without panels + * `InlineMessage` → Use `Message` component From 3eee7cde0bc7e28de74b7d62c7e1d3445ca73d52 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 7 Jun 2025 19:45:03 -0700 Subject: [PATCH 03/17] [docs] Convert .cursorrules to standard markdown format (#4099) --- .cursorrules | 70 +++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/.cursorrules b/.cursorrules index af88564a3f..2dd4862b8a 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,26 +1,25 @@ -// Vue 3 Composition API .cursorrules +# Vue 3 Composition API Project Rules -// Vue 3 Composition API best practices -const vue3CompositionApiBestPractices = [ - "Use setup() function for component logic", - "Utilize ref and reactive for reactive state", - "Implement computed properties with computed()", - "Use watch and watchEffect for side effects", - "Implement lifecycle hooks with onMounted, onUpdated, etc.", - "Utilize provide/inject for dependency injection", - "Use vue 3.5 style of default prop declaration. Example: +## Vue 3 Composition API Best Practices +- Use setup() function for component logic +- Utilize ref and reactive for reactive state +- Implement computed properties with computed() +- Use watch and watchEffect for side effects +- Implement lifecycle hooks with onMounted, onUpdated, etc. +- Utilize provide/inject for dependency injection +- Use vue 3.5 style of default prop declaration. Example: +```typescript const { nodes, showTotal = true } = defineProps<{ nodes: ApiNodeCost[] showTotal?: boolean }>() +``` -", - "Organize vue component in