Section Capping Extension (#5209)

* feat(viewer-sandbox): Implemented section tool caps using the current viewer API meta

* chore(viewer-lib): Added missing exports
This commit is contained in:
Alexandru Popovici
2025-08-13 18:08:17 +03:00
committed by GitHub
parent be0155a95d
commit f2e8fb9805
7 changed files with 182 additions and 0 deletions
@@ -0,0 +1,95 @@
import {
DefaultPipeline,
Extension,
IViewer,
ObjectLayers,
SectionTool,
SectionToolEvent
} from '@speckle/viewer'
import {
FrontSide,
KeepStencilOp,
Matrix4,
Mesh,
MeshBasicMaterial,
NotEqualStencilFunc,
Plane,
PlaneGeometry,
Quaternion,
Vector3
} from 'three'
import { SectionCapsPipeline } from './SectionCapsPipeline'
export class SectionCaps extends Extension {
protected planeMesh: Mesh
protected _enabled = false
public get enabled(): boolean {
return this._enabled
}
public set enabled(value: boolean) {
this._enabled = value
if (value) {
this.viewer.getRenderer().pipeline = new SectionCapsPipeline(
this.viewer.getRenderer()
)
} else {
this.viewer.getRenderer().pipeline = new DefaultPipeline(
this.viewer.getRenderer()
)
}
}
public get inject() {
return [SectionTool]
}
constructor(viewer: IViewer, protected sectionTool: SectionTool) {
super(viewer)
this.planeMesh = new Mesh(
new PlaneGeometry(1, 1),
new MeshBasicMaterial({
color: 0x047efb,
side: FrontSide,
stencilWrite: true,
stencilFunc: NotEqualStencilFunc,
stencilFail: KeepStencilOp,
stencilZFail: KeepStencilOp,
stencilZPass: KeepStencilOp
})
)
this.planeMesh.renderOrder = 5
this.planeMesh.layers.set(ObjectLayers.OVERLAY)
viewer.getRenderer().scene.add(this.planeMesh)
this.sectionTool.on(SectionToolEvent.Updated, (planes: Plane[]) => {
const obb = this.sectionTool.getBox()
this.planeMesh.matrixAutoUpdate = false
this.planeMesh.matrix.copy(
this.getPlaneTransform(
// Top facing plane
planes[5],
new Vector3(2 * obb.halfSize.x, 2 * obb.halfSize.y, 1)
)
)
})
}
protected getPlaneTransform(plane: Plane, scale: Vector3) {
const obb = this.sectionTool.getBox()
const n = plane.normal.clone().normalize().negate()
const up = Math.abs(n.z) < 0.999 ? new Vector3(0, 0, 1) : new Vector3(0, 1, 0)
const t1 = new Vector3().crossVectors(up, n).normalize()
const t2 = new Vector3().crossVectors(n, t1).normalize()
const basis = new Matrix4().makeBasis(t1, t2, n)
const q = new Quaternion().setFromRotationMatrix(basis)
const p = plane.projectPoint(obb.center.clone(), new Vector3())
const worldTRS = new Matrix4().compose(p, q, scale)
return worldTRS
}
}
@@ -0,0 +1,27 @@
import {
DefaultPipeline,
ObjectLayers,
ObjectVisibility,
PipelineOptions,
SpeckleRenderer
} from '@speckle/viewer'
import { StencilFrontPass } from './StencilFrontPass'
import { StencilBackPass } from './StencilBackPass'
export class SectionCapsPipeline extends DefaultPipeline {
constructor(speckleRenderer: SpeckleRenderer, options?: PipelineOptions) {
super(speckleRenderer, options)
const stencilFrontPass = new StencilFrontPass()
stencilFrontPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
stencilFrontPass.setVisibility(ObjectVisibility.OPAQUE)
const stencilBackPass = new StencilBackPass()
stencilBackPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
stencilBackPass.setVisibility(ObjectVisibility.OPAQUE)
this.dynamicStage.splice(3, 0, stencilFrontPass, stencilBackPass)
this.progressiveStage.splice(4, 0, stencilFrontPass, stencilBackPass)
this.passthroughStage.splice(1, 0, stencilFrontPass, stencilBackPass)
}
}
@@ -0,0 +1,27 @@
import { GeometryPass, SpeckleBasicMaterial } from '@speckle/viewer'
import { BackSide, DecrementWrapStencilOp, NoBlending } from 'three'
export class StencilBackPass extends GeometryPass {
private stencilBackMaterial: SpeckleBasicMaterial
get displayName(): string {
return 'STENCIL-BACK'
}
get overrideMaterial(): SpeckleBasicMaterial {
return this.stencilBackMaterial
}
constructor() {
super()
this.stencilBackMaterial = new SpeckleBasicMaterial({ color: 0x0000ff }, [])
// this.stencilBackMaterial.colorWrite = false
this.stencilBackMaterial.depthWrite = false
this.stencilBackMaterial.side = BackSide
this.stencilBackMaterial.stencilWrite = true
this.stencilBackMaterial.stencilFail = DecrementWrapStencilOp
this.stencilBackMaterial.stencilZFail = DecrementWrapStencilOp
this.stencilBackMaterial.stencilZPass = DecrementWrapStencilOp
this.stencilBackMaterial.blending = NoBlending
}
}
@@ -0,0 +1,25 @@
import { GeometryPass, SpeckleBasicMaterial } from '@speckle/viewer'
import { FrontSide, IncrementWrapStencilOp, NoBlending } from 'three'
export class StencilFrontPass extends GeometryPass {
private stencilFrontMaterial: SpeckleBasicMaterial
get displayName(): string {
return 'STENCIL-FRONT'
}
get overrideMaterial(): SpeckleBasicMaterial {
return this.stencilFrontMaterial
}
constructor() {
super()
this.stencilFrontMaterial = new SpeckleBasicMaterial({ color: 0xff0000 }, [])
this.stencilFrontMaterial.side = FrontSide
this.stencilFrontMaterial.stencilWrite = true
this.stencilFrontMaterial.stencilFail = IncrementWrapStencilOp
this.stencilFrontMaterial.stencilZFail = IncrementWrapStencilOp
this.stencilFrontMaterial.stencilZPass = IncrementWrapStencilOp
this.stencilFrontMaterial.blending = NoBlending
}
}
+3
View File
@@ -59,6 +59,7 @@ import {
ObjectLoader2Flags,
ObjectLoader2Factory
} from '@speckle/objectloader2'
import { SectionCaps } from './Extensions/SectionCaps.ts/SectionCaps'
export default class Sandbox {
private viewer: Viewer
@@ -433,6 +434,8 @@ export default class Sandbox {
}
this.viewer.getExtension(SectionTool).setBox(box)
this.viewer.getExtension(SectionTool).toggle()
const sectionCaps = this.viewer.getExtension(SectionCaps)
if (sectionCaps) sectionCaps.enabled = !sectionCaps.enabled
})
const toggleSectionBoxVisibility = this.tabs.pages[0].addButton({
+1
View File
@@ -56,6 +56,7 @@ const createViewer = async (containerName: string, _stream: string) => {
const boxSelect = viewer.createExtension(BoxSelection)
boxSelect.realtimeSelection = false
viewer.createExtension(PassReader)
// viewer.createExtension(SectionCaps)
const sandbox = new Sandbox(controlsContainer, viewer, multiSelectList)
+4
View File
@@ -114,6 +114,8 @@ import {
ProgressiveGPass
} from './modules/pipeline/Passes/GPass.js'
import {
PipelineOptions,
BasePipelineOptions,
DefaultPipelineOptions,
Pipeline
} from './modules/pipeline/Pipelines/Pipeline.js'
@@ -283,6 +285,8 @@ export {
ArcticViewPipeline,
TAAPipeline,
ShadedViewPipeline,
PipelineOptions,
BasePipelineOptions,
DefaultPipelineOptions,
DefaultEdgesPipelineOptions,
ViewModes,