Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VYZN: (feature) hide/unhide and temporary isolate IFC elements #617

Merged
merged 44 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
80bdbbc
feat: custom viewer api
Jan 15, 2023
4c8f350
subset list to dict
Jan 15, 2023
08a6eda
Merge branch 'feat/518-render-elements-meshes' of https://github.com/…
Jan 15, 2023
9e3fdd7
remove log statement
Jan 15, 2023
78c9d5d
fix missing __getIFCViewerAPIMockSingleton
Jan 15, 2023
2885f81
pickByID new implementation
Jan 15, 2023
d6ef7d5
Merge branch 'master' into feat/518-render-elements-meshes
Jan 28, 2023
a723872
fix: remove resetSelection guard to fix key down callback function lo…
Jan 31, 2023
96cb739
fix: reset selection guard back to fix failing tests and call the fro…
Jan 31, 2023
8a48d2f
Merge branch 'master' into feat/518-render-elements-meshes
Feb 1, 2023
9c0d3e1
subsets revisited
Feb 5, 2023
4c4783c
unhide all hidden elements
Feb 11, 2023
025c0b3
Merge branch 'master' into feat/hide-unhide-elements
Feb 11, 2023
c4fe07a
feature: ifc isolator component, incapsulates hide and isolate operat…
Feb 12, 2023
2e6ce14
fix: ids loaded from meshes to insure having visual props
Feb 12, 2023
0886bc5
fix: remove unused ifc classes imports
Feb 12, 2023
b8ac8c9
fix: get express ids from model mesh attributes for better performance
Feb 12, 2023
6699d20
fix: reset reveal upon unhide
Feb 12, 2023
847a187
fix: turn off reveal on unhide all
Feb 12, 2023
ba9c8c9
feat: hide/unhide icons in nav tree for spatial structures and root a…
Feb 18, 2023
c90d563
Merge branch 'master' into feat/hide-unhide-elements
Feb 18, 2023
48865d6
fix: failing unit tests
Feb 18, 2023
b69fc1e
linter fixes
Feb 18, 2023
efa65f9
fix: reveal hidden elements material's depthTest
Feb 18, 2023
6f55755
re-evaluate reveal subset if on when hiding again
Feb 18, 2023
b7d8978
Merge branch 'master' into feat/hide-unhide-elements
Feb 22, 2023
f3a0f67
fix: ifc viewer highlighter
Feb 22, 2023
1d74426
remove unused imports
Feb 22, 2023
f188f83
fix: disable hide icons on temp isolation mode
Feb 22, 2023
34f7a73
reflect isoaltor status to selected elements store
Feb 22, 2023
41c6c68
unit test for hide element by the tree icons
Feb 25, 2023
9946de1
toggle hide icon e2e test
Feb 25, 2023
a5a5ed2
Merge branch 'master' into feat/hide-unhide-elements
Feb 25, 2023
8e2124a
fix post processor
Feb 25, 2023
d47f654
fix failing cadview tests
Feb 25, 2023
2d0a989
fix: placemarks broke the other post processing effects
Feb 25, 2023
071c30e
fix: remove singleton post-processor
Feb 25, 2023
7b0fdf5
fix: space typo
Mar 17, 2023
472d201
Merge branch 'master' into feat/hide-unhide-elements
Mar 17, 2023
43d4b03
fix: disable lint error
Mar 17, 2023
347babf
Merge branch 'main' into feat/hide-unhide-elements
OlegMoshkovich Mar 21, 2023
7c2c836
fix: maintain hidden elements when viewer changes
Mar 22, 2023
be86ef8
fix: align hide icons to right
Mar 22, 2023
c58628e
Merge branch 'main' into feat/hide-unhide-elements
pablo-mayrgundter Mar 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 17 additions & 6 deletions __mocks__/web-ifc-viewer.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import {Vector3} from 'three'
import {makeTestTree} from '../src/utils/TreeUtils.test'


jest.mock('../src/Infrastructure/IfcHighlighter')
const ifcjsMock = jest.createMockFromModule('web-ifc-viewer')

// Not sure why this is required, but otherwise these internal fields
// are not present in the instantiated IfcViewerAPI.
// are not present in the instantiated IfcViewerAPIExtended.
const loadedModel = {
ifcManager: {
getSpatialStructure: jest.fn(),
getSpatialStructure: jest.fn((eltId) => (makeTestTree())),
getProperties: jest.fn((eltId) => ({})),
},
getIfcType: jest.fn(),
geometry: {
boundingBox: {
getCenter: jest.fn(),
},
attributes: {
expressID: 123,
},
},
}

Expand Down Expand Up @@ -47,7 +51,6 @@ const impl = {
},
},
},
loadIfcUrl: jest.fn(jest.fn(() => loadedModel)),
setWasmPath: jest.fn(),
selector: {
unpickIfcItems: jest.fn(),
Expand All @@ -65,6 +68,11 @@ const impl = {
},
},
},
isolator: {
canBeHidden: jest.fn(() => {
return true
}),
},
clipper: {
active: false,
deleteAllPlanes: jest.fn(() => {
Expand All @@ -88,13 +96,16 @@ const impl = {
getRenderer: jest.fn(),
getScene: jest.fn(),
getCamera: jest.fn(),
getClippingPlanes: jest.fn(() => {
return []
}),
},
loadIfcUrl: jest.fn(jest.fn(() => loadedModel)),
getProperties: jest.fn((modelId, eltId) => {
return loadedModel.ifcManager.getProperties(eltId)
}),
setSelection: jest.fn(),
pickIfcItemsByID: jest.fn(),
loadIfcUrl: jest.fn(jest.fn(() => loadedModel)),
}
const constructorMock = ifcjsMock.IfcViewerAPI
constructorMock.mockImplementation(() => impl)
Expand All @@ -103,13 +114,13 @@ constructorMock.mockImplementation(() => impl)
/**
* @return {object} The single mock instance of IfcViewerAPI.
*/
function __getIfcViewerAPIMockSingleton() {
function __getIfcViewerAPIExtendedMockSingleton() {
return impl
}


export {
ifcjsMock as default,
constructorMock as IfcViewerAPI,
__getIfcViewerAPIMockSingleton,
__getIfcViewerAPIExtendedMockSingleton as __getIfcViewerAPIExtendedMockSingleton,
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bldrs",
"version": "1.0.0-r622",
"version": "1.0.0-r648",
"main": "src/index.jsx",
"license": "MIT",
"homepage": "https://github.com/bldrs-ai/Share",
Expand Down Expand Up @@ -32,6 +32,9 @@
"@bldrs-ai/ifclib": "^5.3.3",
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@iconscout/react-unicons": "^1.1.6",
"@material-ui/core": "^4.12.4",
"@mui/icons-material": "^5.8.4",
Expand Down
4 changes: 2 additions & 2 deletions src/Components/CameraControl.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import {act, render, screen, renderHook} from '@testing-library/react'
import useStore from '../store/useStore'
import ShareMock from '../ShareMock'
import {__getIfcViewerAPIMockSingleton} from 'web-ifc-viewer'
import {__getIfcViewerAPIExtendedMockSingleton} from 'web-ifc-viewer'
import CameraControl, {
onHash,
parseHashParams,
Expand All @@ -22,7 +22,7 @@ describe('CameraControl', () => {

it('CameraControl', async () => {
const {result} = renderHook(() => useStore((state) => state))
const viewer = __getIfcViewerAPIMockSingleton()
const viewer = __getIfcViewerAPIExtendedMockSingleton()
await act(() => {
result.current.setViewerStore(viewer)
})
Expand Down
10 changes: 5 additions & 5 deletions src/Components/CutPlaneMenu.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ShareControl from './ShareControl'
import ShareMock from '../ShareMock'
import useStore from '../store/useStore'
import model from '../__mocks__/MockModel.js'
import {__getIfcViewerAPIMockSingleton} from 'web-ifc-viewer'
import {__getIfcViewerAPIExtendedMockSingleton} from 'web-ifc-viewer'


describe('CutPlaneMenu', () => {
Expand All @@ -29,7 +29,7 @@ describe('CutPlaneMenu', () => {
const {getByTitle, getByText} = render(<ShareMock><CutPlaneMenu/></ShareMock>)
const sectionButton = getByTitle('Section')
const {result} = renderHook(() => useStore((state) => state))
const viewer = __getIfcViewerAPIMockSingleton()
const viewer = __getIfcViewerAPIExtendedMockSingleton()
await act(() => {
result.current.setViewerStore(viewer)
})
Expand All @@ -52,7 +52,7 @@ describe('CutPlaneMenu', () => {
<CutPlaneMenu/>
</ShareMock>)
const {result} = renderHook(() => useStore((state) => state))
const viewer = __getIfcViewerAPIMockSingleton()
const viewer = __getIfcViewerAPIExtendedMockSingleton()
await act(() => {
result.current.setViewerStore(viewer)
})
Expand All @@ -68,7 +68,7 @@ describe('CutPlaneMenu', () => {
</ShareMock>)
const {result} = renderHook(() => useStore((state) => state))
// mock contains one plane
const viewer = __getIfcViewerAPIMockSingleton()
const viewer = __getIfcViewerAPIExtendedMockSingleton()
await act(() => {
result.current.setViewerStore(viewer)
})
Expand All @@ -80,7 +80,7 @@ describe('CutPlaneMenu', () => {

it('Plane Offset is correct', async () => {
const {result} = renderHook(() => useStore((state) => state))
const viewer = __getIfcViewerAPIMockSingleton()
const viewer = __getIfcViewerAPIExtendedMockSingleton()
await act(() => {
result.current.setViewerStore(viewer)
result.current.setModelStore(model)
Expand Down
72 changes: 61 additions & 11 deletions src/Components/NavTree.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, {useEffect, useState} from 'react'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faEye, faEyeSlash, faGlasses} from '@fortawesome/free-solid-svg-icons'
import clsx from 'clsx'
import PropTypes from 'prop-types'
import {useNavigate} from 'react-router-dom'
Expand All @@ -8,6 +10,8 @@ import Typography from '@mui/material/Typography'
import TreeItem, {useTreeItem} from '@mui/lab/TreeItem'
import {computeElementPathIds} from '../utils/TreeUtils'
import {handleBeforeUnload} from '../utils/event'
import useStore from '../store/useStore'
import IfcIsolator from '../Infrastructure/IfcIsolator'


const NavTreePropTypes = {
Expand Down Expand Up @@ -39,8 +43,39 @@ const NavTreePropTypes = {
* The id of the node.
*/
nodeId: PropTypes.string.isRequired,
/**
* Determines if the tree node has a hide icon.
*/
hasHideIcon: PropTypes.bool.isRequired,
}

/**
* @param {IfcIsolator} The IFC isoaltor
* @param {number} IFC element id
* @return {object} React component
*/
function HideIcon({elementId}) {
const isHidden = useStore((state) => state.hiddenElements[elementId])
const isIsolated = useStore((state) => state.isolatedElements[elementId])
const isTempIsolationModeOn = useStore((state) => state.isTempIsolationModeOn)
const viewer = useStore((state) => state.viewer)

const toggleHide = () => {
const toBeHidden = viewer.isolator.flattenChildren(elementId)
if (!isHidden) {
viewer.isolator.hideElementsById(toBeHidden)
} else {
viewer.isolator.unHideElementsById(toBeHidden)
}
}

const iconStyle = {float: 'right', margin: '4px'}
if (isTempIsolationModeOn && !isIsolated) {
iconStyle.opacity = 0.3
}
const icon = isIsolated ? faGlasses : (!isHidden ? faEye : faEyeSlash)
return <FontAwesomeIcon disabled={isTempIsolationModeOn} style={iconStyle} onClick={toggleHide} icon={icon}/>
}

/**
* @param {object} model IFC model
Expand All @@ -63,6 +98,7 @@ export default function NavTree({
icon: iconProp,
expansionIcon,
displayIcon,
hasHideIcon,
} = props

const {
Expand All @@ -83,6 +119,8 @@ export default function NavTree({

const [selectedElement, setSelectedElement] = useState(null)

const hiddenElements = useStore((state) => state.hiddenElements)

const handleSelectionClick = (event) => {
handleSelection(event)
setSelectedElement(element)
Expand All @@ -92,12 +130,15 @@ export default function NavTree({

useEffect(() => {
if (selectedElement) {
if (hiddenElements[selectedElement.expressID]) {
return
}
const newPath =
`${pathPrefix}/${computeElementPathIds(element, (elt) => elt.expressID).join('/')}`
window.removeEventListener('beforeunload', handleBeforeUnload)
navigate(newPath)
}
}, [selectedElement, navigate])
}, [selectedElement, navigate, hiddenElements])

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
Expand All @@ -117,34 +158,43 @@ export default function NavTree({
>
{icon}
</Box>
<Typography
variant='tree'
onClick={handleSelectionClick}
>
{label}
</Typography>
<div style={{width: '80%'}}>
<Typography
variant='tree'
onClick={handleSelectionClick}
>
{label}
</Typography>
{hasHideIcon &&
<div style={{display: 'contents'}}>
<HideIcon elementId={element.expressID}/>
</div>
}
</div>
</div>
)
})


CustomContent.propTypes = NavTreePropTypes


const CustomTreeItem = (props) => {
return <TreeItem ContentComponent={CustomContent} {...props}/>
}

const viewer = useStore((state) => state.viewerStore)

let i = 0

const hasHideIcon = viewer.isolator.canBeHidden(element.expressID)

let i = 0
// TODO(pablo): Had to add this React.Fragment wrapper to get rid of
// warning about missing a unique key foreach item. Don't really understand it.
return (
<CustomTreeItem
nodeId={element.expressID.toString()}
label={reifyName({properties: model}, element)}
ContentProps={{
hasHideIcon: hasHideIcon,
}}
>
{element.children && element.children.length > 0 ?
element.children.map((child) => {
Expand Down
38 changes: 26 additions & 12 deletions src/Components/NavTree.test.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import React from 'react'
import {render} from '@testing-library/react'
import {act, render, renderHook} from '@testing-library/react'
import useStore from '../store/useStore'
import ShareMock from '../ShareMock'
import {MockViewer, newMockStringValueElt} from '../utils/IfcMock.test'
import {newMockStringValueElt} from '../utils/IfcMock.test'
import NavTree from './NavTree'
import {IfcViewerAPIExtended} from '../Infrastructure/IfcViewerAPIExtended'
import {actAsyncFlush} from '../utils/tests'


test('NavTree for single element', () => {
const testLabel = 'Test node label'
const {getByText} = render(
<ShareMock>
<NavTree
viewer={new MockViewer}
element={newMockStringValueElt(testLabel)}
/>
</ShareMock>)
expect(getByText(testLabel)).toBeInTheDocument()
describe('CadView', () => {
afterEach(() => {
jest.clearAllMocks()
})

it('NavTree for single element', async () => {
const testLabel = 'Test node label'
const {result} = renderHook(() => useStore((state) => state))
const viewer = new IfcViewerAPIExtended()
await act(() => {
result.current.setViewerStore(viewer)
})
const {getByText} = render(
<ShareMock>
<NavTree
element={newMockStringValueElt(testLabel)}
/>
</ShareMock>)
await actAsyncFlush()
expect(getByText(testLabel)).toBeInTheDocument()
})
})