Skip to content

Commit

Permalink
squash
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegMoshkovich committed Sep 8, 2022
1 parent 5e35a19 commit 0faf530
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 16 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@mui/styles": "^5.9.2",
"@octokit/rest": "^19.0.3",
"clsx": "^1.2.1",
"@testing-library/react-hooks": "^8.0.1",
"normalize.css": "^8.0.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
Expand Down
116 changes: 116 additions & 0 deletions src/Components/CutPlaneMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React, {useState, useEffect} from 'react'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import {makeStyles} from '@mui/styles'
import {Vector3} from 'three'
import {useLocation} from 'react-router-dom'
import CutPlaneIcon from '../assets/2D_Icons/CutPlane.svg'
import useStore from '../store/useStore'
import {TooltipIconButton} from './Buttons'
import {getModelCenter} from '../utils/cutPlane'
import {addHashParams, getHashParams, removeHashParams} from '../utils/location'


/**
* BasicMenu used when there are several option behind UI button
* show/hide from the right of the screen.
* @param {Array} listOfOptions Title for the drawer
* @return {Object} ItemPropertiesDrawer react component
*/
export default function CutPlaneMenu({listOfOptions, icon, title}) {
const [anchorEl, setAnchorEl] = useState(null)
const [cutPlaneDirection, setCutPlaneDirection] = useState('')
const open = Boolean(anchorEl)
const model = useStore((state) => state.modelStore)
const PLANE_PREFIX = 'p'
const classes = useStyles()

const handleClick = (event) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const viewer = useStore((state) => state.viewerStore)
const location = useLocation()

const createPlane = (normalDirection) => {
viewer.clipper.deleteAllPlanes()
const modelCenter = getModelCenter(model)
const planeHash = getHashParams(location, 'p')
if (normalDirection === cutPlaneDirection) {
viewer.clipper.deleteAllPlanes()
removeHashParams(window.location, PLANE_PREFIX)
setCutPlaneDirection('')
return
}
let normal
switch (normalDirection) {
case 'x':
normal = new Vector3(-1, 0, 0)
break
case 'y':
normal = new Vector3(0, -1, 0)
break
case 'z':
normal = new Vector3(0, 0, -1)
break
default:
normal = new Vector3(0, 1, 0)
break
}
if (!planeHash || planeHash !== normalDirection ) {
addHashParams(window.location, PLANE_PREFIX, {planeAxis: normalDirection})
}
setCutPlaneDirection(normalDirection)
return viewer.clipper.createFromNormalAndCoplanarPoint(normal, modelCenter)
}

useEffect(() => {
const planeHash = getHashParams(location, 'p')
if (planeHash && model && viewer) {
const planeDirection = planeHash.split(':')[1]
createPlane(planeDirection)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [model])

return (
<div>
<TooltipIconButton
title={'Cut Planes'}
icon={<CutPlaneIcon/>}
onClick={handleClick}
/>
<Menu
elevation={1}
id='basic-menu'
anchorEl={anchorEl}
open={open}
onClose={handleClose}
className={classes.root}
anchorOrigin={{vertical: 'top', horizontal: 'center'}}
transformOrigin={{vertical: 'top', horizontal: 'center'}}
PaperProps={{
style: {
left: '300px',
transform: 'translateX(-40px) translateY(-40px)',
},
}}
>
<MenuItem onClick={() => createPlane('x')} selected={cutPlaneDirection === 'x'}> X</MenuItem>
<MenuItem onClick={() => createPlane('y')} selected={cutPlaneDirection === 'y'}>Y</MenuItem>
<MenuItem onClick={() => createPlane('z')} selected={cutPlaneDirection === 'z'}>Z</MenuItem>
</Menu>
</div>
)
}


const useStyles = makeStyles({
root: {
'& .Mui-selected': {
border: '1px solid lightGray',
},
},
})
2 changes: 1 addition & 1 deletion src/Components/ItemProperties.test.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import {act, render, renderHook, screen, waitFor} from '@testing-library/react'
import {act, render, screen, waitFor, renderHook} from '@testing-library/react'
import ShareMock from '../ShareMock'
import {MockModel} from '../utils/IfcMock.test'
import useStore from '../store/useStore'
Expand Down
21 changes: 7 additions & 14 deletions src/Components/OperationsGroup.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React from 'react'
import {makeStyles} from '@mui/styles'
import useStore from '../store/useStore'
import CameraControl from './CameraControl'
import CutPlaneMenu from './CutPlaneMenu'
import ShareControl from './ShareControl'
import ShortcutsControl from './ShortcutsControl'
import {TooltipIconButton} from './Buttons'
import CutPlaneIcon from '../assets/2D_Icons/CutPlane.svg'
import ClearIcon from '../assets/2D_Icons/Clear.svg'
import MarkupIcon from '../assets/2D_Icons/Markup.svg'
import ListIcon from '../assets/2D_Icons/List.svg'
import {useIsMobile} from './Hooks'
import useStore from '../store/useStore'


/**
Expand All @@ -20,14 +19,15 @@ import useStore from '../store/useStore'
* @param {Function} unSelectItem deselects currently selected element
* @return {React.Component}
*/
export default function OperationsGroup({viewer, unSelectItem}) {
export default function OperationsGroup({unSelectItem}) {
const turnCommentsOn = useStore((state) => state.turnCommentsOn)
const toggleIsPropertiesOn = useStore((state) => state.toggleIsPropertiesOn)
const openDrawer = useStore((state) => state.openDrawer)
const selectedElement = useStore((state) => state.selectedElement)
const isCommentsOn = useStore((state) => state.isCommentsOn)
const classes = useStyles({isCommentsOn: isCommentsOn})
const viewer = useStore((state) => state.viewerStore)

const classes = useStyles({isCommentsOn: isCommentsOn})
const toggle = (panel) => {
openDrawer()
if (panel === 'Properties') {
Expand All @@ -38,6 +38,7 @@ export default function OperationsGroup({viewer, unSelectItem}) {
}
}


return (
<div className={classes.container}>
<div className={classes.topGroup}>
Expand All @@ -58,18 +59,10 @@ export default function OperationsGroup({viewer, unSelectItem}) {
/> :
null
}
{useIsMobile() ?
<TooltipIconButton
title="Section plane"
onClick={() => viewer.clipper.createPlane()}
icon={<CutPlaneIcon/>}
/> :
null
}
<CutPlaneMenu/>
<TooltipIconButton title="Clear selection" onClick={unSelectItem} icon={<ClearIcon/>}/>
<ShortcutsControl/>
</div>
{/* Invisible */}
<CameraControl viewer={viewer}/>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/Components/SideDrawer.test.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import {act, render, renderHook} from '@testing-library/react'
import {act, render, screen, waitFor, renderHook} from '@testing-library/react'
import useStore from '../store/useStore'
import ShareMock from '../ShareMock'
import SideDrawerWrapper from './SideDrawer'
Expand Down
78 changes: 78 additions & 0 deletions src/utils/cutPlane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {Box3, BufferAttribute, BufferGeometry, Mesh, Vector3} from 'three'


/* eslint-disable no-magic-numbers */
/**
* getSelectionAxisFromBoundingBox is the helper method for the cutplane logic
*
* @param {Object} boundingBox bouding box
* @return {Object}
*/
export function getSelectionAxisFromBoundingBox(boundingBox) {
return {
x: {
size: Math.abs( boundingBox.max.x - boundingBox.min.x),
center: (boundingBox.max.x + boundingBox.min.x) / 2,
},
y: {
size: Math.abs( boundingBox.max.y - boundingBox.min.y),
center: (boundingBox.max.y + boundingBox.min.y) / 2,
},
z: {
size: Math.abs(boundingBox.max.z - boundingBox.min.z ),
center: (boundingBox.max.z + boundingBox.min.z) / 2,
},
}
}


/**
* getModelCenter return the center of the model based on bounding box
*
* @param {Object} ifcModel bouding box
* @return {Object} centerCoordinates
*/
export function getModelCenter(ifcModel) {
return new Vector3(
(ifcModel?.geometry.boundingBox.max.x +
ifcModel?.geometry.boundingBox.min.x) /
2,
(ifcModel?.geometry.boundingBox.max.y +
ifcModel?.geometry.boundingBox.min.y) /
2,
(ifcModel?.geometry.boundingBox.max.z +
ifcModel?.geometry.boundingBox.min.z) /
2,
)
}


/**
* getElementBoundingBox creates a bounding box around the model
*
* @param {Object} selection seclected meshes
* @return {Object} boudingBox geometry
*/
export function getElementBoundingBox(selection) {
const geometry = new BufferGeometry()
const coordinates = []
const alreadySaved = new Set()
const position = selection.geometry.attributes['position']
const vertices = Float32Array.from(coordinates)
const mesh = new Mesh(geometry)
const boundingBox = new Box3()
geometry.setAttribute('position', new BufferAttribute(vertices, selection.geometry.index.count))
boundingBox.setFromObject(mesh)

for (let i = 0; i < selection.geometry.index.array.length; i++) {
if (!alreadySaved.has(selection.geometry.index.array[i])) {
coordinates.push(position.getX(selection.geometry.index.array[i]))
coordinates.push(position.getY(selection.geometry.index.array[i]))
coordinates.push(position.getZ(selection.geometry.index.array[i]))
alreadySaved.add(selection.geometry.index.array[i])
}
}

return boundingBox
}

0 comments on commit 0faf530

Please sign in to comment.