Skip to content

Commit

Permalink
feat(fe2): Update Section Box controls to include visibility (#3333)
Browse files Browse the repository at this point in the history
* Update Section Box controls to include visibility

* Update reset v-if

* Update shortcut

* Added render requests so that section box updates are made visible

* Remove comments

* Exposed SectionToolEvent in the viewer

* Only show reset when change has been made to sectionbox

* gql

* Make state reactive

* Revert "Make state reactive"

This reverts commit 1697b12.

* Handles WEB-2039

* Handles WEB-2035

* Changes from call with Fabs

---------

Co-authored-by: AlexandruPopovici <[email protected]>
  • Loading branch information
andrewwallacespeckle and AlexandruPopovici authored Oct 25, 2024
1 parent af3857a commit 678c753
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 39 deletions.
46 changes: 31 additions & 15 deletions packages/frontend-2/components/viewer/Controls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
v-tippy="isSmallerOrEqualSm ? undefined : sectionBoxShortcut"
flat
secondary
:active="isSectionBoxEnabled"
:active="isSectionBoxVisible"
@click="toggleSectionBox()"
>
<ScissorsIcon class="h-4 w-4 md:h-5 md:w-5" />
Expand Down Expand Up @@ -238,6 +238,9 @@
</div>
</div>
</div>
<Portal v-if="isSectionBoxEnabled && isSectionBoxEdited" to="pocket-actions">
<FormButton @click="resetSectionBox()">Reset section box</FormButton>
</Portal>
</div>
<div v-else />
</template>
Expand Down Expand Up @@ -268,7 +271,6 @@ import {
useInjectedViewerState
} from '~~/lib/viewer/composables/setup'
import { useMixpanel } from '~~/lib/core/composables/mp'

import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useViewerTour } from '~/lib/viewer/composables/tour'
Expand Down Expand Up @@ -346,7 +348,13 @@ type ActiveControl =
| 'gendo'

const { resourceItems, modelsAndVersionIds } = useInjectedViewerLoadedResources()
const { toggleSectionBox, isSectionBoxEnabled } = useSectionBoxUtilities()
const {
resetSectionBox,
isSectionBoxEnabled,
isSectionBoxVisible,
toggleSectionBox,
isSectionBoxEdited
} = useSectionBoxUtilities()
const { getActiveMeasurement, removeMeasurement, enableMeasurements } =
useMeasurementUtilities()
const { showNavbar, showControls } = useViewerTour()
Expand Down Expand Up @@ -491,14 +499,6 @@ const trackAndtoggleProjection = () => {
})
}

watch(isSectionBoxEnabled, (val) => {
mp.track('Viewer Action', {
type: 'action',
name: 'section-box',
status: val
})
})

const scrollControlsToBottom = () => {
// TODO: Currently this will scroll to the very bottom, which doesn't make sense when there are multiple models loaded
// if (scrollableControlsContainer.value)
Expand All @@ -515,10 +515,6 @@ onMounted(() => {
activeControl.value = isSmallerOrEqualSm.value ? 'none' : 'models'
})

watch(isSmallerOrEqualSm, (newVal) => {
activeControl.value = newVal ? 'none' : 'models'
})

onKeyStroke('Escape', () => {
const isActiveMeasurement = getActiveMeasurement()

Expand All @@ -531,4 +527,24 @@ onKeyStroke('Escape', () => {
activeControl.value = 'none'
}
})

watch(isSmallerOrEqualSm, (newVal) => {
activeControl.value = newVal ? 'none' : 'models'
})

watch(isSectionBoxEnabled, (val) => {
mp.track('Viewer Action', {
type: 'action',
name: 'section-box',
status: val
})
})

watch(isSectionBoxVisible, (val) => {
mp.track('Viewer Action', {
type: 'action',
name: 'section-box-visibility',
status: val
})
})
</script>
8 changes: 8 additions & 0 deletions packages/frontend-2/lib/viewer/composables/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ export type InjectableViewerState = Readonly<{
enabled: Ref<boolean>
}
sectionBox: Ref<Nullable<Box3>>
sectionBoxContext: {
visible: Ref<boolean>
edited: Ref<boolean>
}
highlightedObjectIds: Ref<string[]>
lightConfig: Ref<SunLightConfiguration>
explodeFactor: Ref<number>
Expand Down Expand Up @@ -988,6 +992,10 @@ function setupInterfaceState(
isOrthoProjection
},
sectionBox: ref(null as Nullable<Box3>),
sectionBoxContext: {
visible: ref(false),
edited: ref(false)
},
filters: {
isolatedObjectIds,
hiddenObjectIds,
Expand Down
52 changes: 48 additions & 4 deletions packages/frontend-2/lib/viewer/composables/setup/postSetup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { difference, flatten, isEqual, uniq } from 'lodash-es'
import { ViewerEvent, VisualDiffMode, CameraController } from '@speckle/viewer'
import {
ViewerEvent,
VisualDiffMode,
CameraController,
UpdateFlags,
SectionOutlines,
SectionToolEvent,
SectionTool
} from '@speckle/viewer'
import type {
PropertyInfo,
StringPropertyInfo,
Expand Down Expand Up @@ -293,10 +301,20 @@ function useViewerSubscriptionEventTracker() {

function useViewerSectionBoxIntegration() {
const {
ui: { sectionBox },
ui: {
sectionBox,
sectionBoxContext: { visible, edited }
},
viewer: { instance }
} = useInjectedViewerState()

// Change edited=true when user starts changing the section box by dragging it
const sectionTool = instance.getExtension(SectionTool)
const onDragStart = () => {
edited.value = true
}
sectionTool.on(SectionToolEvent.DragStart, onDragStart)

// No two-way sync for section boxes, because once you set a Box3 into the viewer
// the viewer transforms it into something else causing the updates going into an infinite loop

Expand All @@ -308,24 +326,50 @@ function useViewerSectionBoxIntegration() {
if (!newVal && !oldVal) return

if (oldVal && !newVal) {
visible.value = false
edited.value = false

instance.sectionBoxOff()
instance.requestRender()
instance.requestRender(UpdateFlags.RENDER_RESET)
return
}

if (newVal && (!oldVal || !newVal.equals(oldVal))) {
visible.value = true
edited.value = false

instance.setSectionBox({
min: newVal.min,
max: newVal.max
})
instance.sectionBoxOn()
instance.requestRender()
const outlines = instance.getExtension(SectionOutlines)
if (outlines) outlines.requestUpdate()
instance.requestRender(UpdateFlags.RENDER_RESET)
}
},
{ immediate: true, deep: true, flush: 'sync' }
)

watch(
visible,
(newVal, oldVal) => {
if (newVal && oldVal) return
if (!newVal && !oldVal) return

if (newVal) {
sectionTool.visible = true
} else {
sectionTool.visible = false
}
instance.requestRender()
},
{ immediate: true, deep: true, flush: 'sync' }
)

onBeforeUnmount(() => {
instance.sectionBoxOff()
sectionTool.removeListener(SectionToolEvent.DragStart, onDragStart)
})
}

Expand Down
52 changes: 41 additions & 11 deletions packages/frontend-2/lib/viewer/composables/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,69 @@ import {
} from '~~/lib/viewer/composables/setup'
import { useDiffBuilderUtilities } from '~~/lib/viewer/composables/setup/diff'
import { useTourStageState } from '~~/lib/viewer/composables/tour'
import { Vector3, Box3 } from 'three'

export function useSectionBoxUtilities() {
const { instance } = useInjectedViewer()
const {
sectionBox,
filters: { selectedObjects }
sectionBoxContext: { visible, edited },
filters: { selectedObjects },
threads: {
openThread: { thread }
}
} = useInjectedViewerInterfaceState()

const isSectionBoxEnabled = computed(() => !!sectionBox.value)
const toggleSectionBox = () => {
if (isSectionBoxEnabled.value) {
sectionBox.value = null
return
}
const isSectionBoxVisible = computed(() => visible.value)
const isSectionBoxEdited = computed(() => edited.value)

const resolveSectionBoxFromSelection = () => {
const objectIds = selectedObjects.value.map((o) => o.id).filter(isNonNullable)
const box = instance.getSectionBoxFromObjects(objectIds)
sectionBox.value = box
}
const sectionBoxOn = () => {

const toggleSectionBox = () => {
if (!isSectionBoxEnabled.value) {
toggleSectionBox()
resolveSectionBoxFromSelection()
return
}

if (isSectionBoxVisible.value) {
visible.value = false
} else {
visible.value = true
}
}
const sectionBoxOff = () => {

const resetSectionBox = () => {
const serializedSectionBox = thread.value?.viewerState?.ui.sectionBox
sectionBox.value = null

if (serializedSectionBox) {
// Same logic we have in deserialization
sectionBox.value = new Box3(
new Vector3(
serializedSectionBox.min[0],
serializedSectionBox.min[1],
serializedSectionBox.min[2]
),
new Vector3(
serializedSectionBox.max[0],
serializedSectionBox.max[1],
serializedSectionBox.max[2]
)
)
}
}

return {
isSectionBoxEnabled,
isSectionBoxVisible,
isSectionBoxEdited,
toggleSectionBox,
sectionBoxOn,
sectionBoxOff,
resetSectionBox,
sectionBox
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/viewer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { type CanonicalView } from './modules/extensions/CameraController.js'
import { CameraEvent, CameraEventPayload } from './modules/objects/SpeckleCamera.js'
import {
SectionTool,
SectionToolEvent,
SectionToolEventPayload
} from './modules/extensions/SectionTool.js'
import { SectionOutlines } from './modules/extensions/SectionOutlines.js'
Expand Down Expand Up @@ -132,7 +133,8 @@ export {
SpeckleGeometryConverter,
Assets,
AssetType,
HybridCameraController
HybridCameraController,
SectionToolEvent
}

export type {
Expand Down
8 changes: 8 additions & 0 deletions packages/viewer/src/modules/World.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ export class World {
const dist = offsetBox.max.distanceTo(sizeBox.max)
return dist
}

public getRelativeOffsetBox(box: Box3, offsetAmount: number = 0.001) {
this.MatBuff.identity()
this.MatBuff.makeScale(1 + offsetAmount, 1 + offsetAmount, 1 + offsetAmount)
const offsetBox = new Box3().copy(box)
offsetBox.applyMatrix4(this.MatBuff)
return offsetBox
}
}
5 changes: 5 additions & 0 deletions packages/viewer/src/modules/extensions/SectionOutlines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ export class SectionOutlines extends Extension {
}
}

public requestUpdate() {
this.setSectionPlaneChanged(this.viewer.getRenderer().clippingPlanes)
this.updateOutlines(this.sectionPlanesChanged)
}

private updatePlaneOutline(
batches: MeshBatch[],
_plane: Plane,
Expand Down
24 changes: 16 additions & 8 deletions packages/viewer/src/modules/extensions/SectionTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,17 +467,25 @@ export class SectionTool extends Extension {
box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1))
}

let x1, y1, z1, x2, y2, z2

if (offset === 0) {
offset = this.viewer.World.getRelativeOffset()
const offsetBox = this.viewer.World.getRelativeOffsetBox(box, 0.0001)
x1 = offsetBox.min.x //box.min.x - (box.max.x - box.min.x) * offset
y1 = offsetBox.min.y //box.min.y - (box.max.y - box.min.y) * offset
z1 = offsetBox.min.z //box.min.z - (box.max.z - box.min.z) * offset
x2 = offsetBox.max.x //box.max.x + (box.max.x - box.min.x) * offset
y2 = offsetBox.max.y //box.max.y + (box.max.y - box.min.y) * offset
z2 = offsetBox.max.z //box.max.z + (box.max.z - box.min.z) * offset
} else {
x1 = box.min.x - (box.max.x - box.min.x) * offset
y1 = box.min.y - (box.max.y - box.min.y) * offset
z1 = box.min.z - (box.max.z - box.min.z) * offset
x2 = box.max.x + (box.max.x - box.min.x) * offset
y2 = box.max.y + (box.max.y - box.min.y) * offset
z2 = box.max.z + (box.max.z - box.min.z) * offset
}

const x1 = box.min.x - (box.max.x - box.min.x) * offset
const y1 = box.min.y - (box.max.y - box.min.y) * offset
const z1 = box.min.z - (box.max.z - box.min.z) * offset
const x2 = box.max.x + (box.max.x - box.min.x) * offset
const y2 = box.max.y + (box.max.y - box.min.y) * offset
const z2 = box.max.z + (box.max.z - box.min.z) * offset

const newVertices = [
x1,
y1,
Expand Down

0 comments on commit 678c753

Please sign in to comment.