오브젝트 배치 모드 추가
This commit is contained in:
parent
70aeaa2957
commit
958a8888c3
@ -12,6 +12,8 @@ export const Mode = {
|
||||
CELL_POWERCON: 'cellPowercon', //파워콘
|
||||
DRAW_HELP_LINE: 'drawHelpLine', // 보조선 그리기 모드 지붕 존재해야함
|
||||
ADSORPTION_POINT: 'adsorptionPoint', //흡착점 모드
|
||||
OPENING: 'opening', //개구 모드
|
||||
SHADOW: 'shadow', //그림자 생성 모드
|
||||
DEFAULT: 'default',
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ import GridSettingsModal from './GridSettingsModal'
|
||||
import { SurfaceShapeModal } from '@/components/ui/SurfaceShape'
|
||||
import { drawDirectionStringToArrow } from '@/util/qpolygon-utils'
|
||||
import ThumbnailList from '@/components/ui/ThumbnailLIst'
|
||||
import ObjectPlacement from '@/components/ui/ObjectPlacement'
|
||||
|
||||
export default function Roof2(props) {
|
||||
const { name, userId, email, isLoggedIn } = props
|
||||
@ -753,6 +754,15 @@ export default function Roof2(props) {
|
||||
>
|
||||
면형상
|
||||
</Button>
|
||||
<Button
|
||||
className="m-1 p-2"
|
||||
onClick={() => {
|
||||
setContent(<ObjectPlacement canvas={canvas} />)
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
오브젝트 배치
|
||||
</Button>
|
||||
{/*<Button className="m-1 p-2" onClick={rotateShape}>
|
||||
회전
|
||||
</Button>*/}
|
||||
|
||||
155
src/components/ui/ObjectPlacement.jsx
Normal file
155
src/components/ui/ObjectPlacement.jsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Button, Input } from '@nextui-org/react'
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil'
|
||||
import { modalState } from '@/store/modalAtom'
|
||||
import { fabric } from 'fabric'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import { modeState, objectPlacementModeState } from '@/store/canvasAtom'
|
||||
|
||||
const BATCH_TYPE = {
|
||||
OPENING: 'opening',
|
||||
SHADOW: 'shadow',
|
||||
}
|
||||
|
||||
const INPUT_TYPE = {
|
||||
FREE: 'free',
|
||||
DIMENSION: 'dimension',
|
||||
}
|
||||
|
||||
const ObjectPlacement = ({ canvas }) => {
|
||||
const [open, setOpen] = useRecoilState(modalState)
|
||||
const [mode, setMode] = useRecoilState(modeState)
|
||||
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
|
||||
const [width, setWidth] = useState(0)
|
||||
const [height, setHeight] = useState(0)
|
||||
const [areaBoundary, setAreaBoundary] = useState(true)
|
||||
|
||||
// opening or shadow 개구 / 그림자
|
||||
const [batchType, setBatchType] = useState(BATCH_TYPE.OPENING)
|
||||
|
||||
// free or dimension 프리 / 치수
|
||||
const [inputType, setInputType] = useState(INPUT_TYPE.FREE)
|
||||
|
||||
const handleSave = () => {
|
||||
setMode(batchType)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 w-full max-w-xs border border-gray-300">
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2">오브젝트 배치</label>
|
||||
</div>
|
||||
|
||||
<div className="border-b mb-4">
|
||||
<h2 className="font-semibold">개구 · 그림자 배치</h2>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<div className="flex">
|
||||
<Button
|
||||
className={`w-1/2 py-2 ${objectPlacementMode.batchType === BATCH_TYPE.OPENING ? 'bg-blue-500 text-white rounded-l' : 'bg-gray-200 text-gray-700 rounded-r'}`}
|
||||
onClick={() => {
|
||||
setBatchType(BATCH_TYPE.OPENING)
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, batchType: BATCH_TYPE.OPENING })
|
||||
}}
|
||||
>
|
||||
개구 배치
|
||||
</Button>
|
||||
<Button
|
||||
className={`w-1/2 py-2 ${objectPlacementMode.batchType === BATCH_TYPE.SHADOW ? 'bg-blue-500 text-white rounded-l' : 'bg-gray-200 text-gray-700 rounded-r'}`}
|
||||
onClick={() => {
|
||||
setBatchType(BATCH_TYPE.SHADOW)
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, batchType: BATCH_TYPE.SHADOW })
|
||||
}}
|
||||
>
|
||||
그림자 배치
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<div className="mb-2 text-gray-700 font-semibold">설정</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<label className="inline-flex items-center">
|
||||
<Input
|
||||
type="radio"
|
||||
name="inputType"
|
||||
checked={objectPlacementMode.inputType === INPUT_TYPE.FREE}
|
||||
onClick={() => {
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, inputType: INPUT_TYPE.FREE })
|
||||
}}
|
||||
className="form-radio text-blue-500"
|
||||
/>
|
||||
<span className="ml-2">프리입력</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mb-2">
|
||||
<label className="inline-flex items-center">
|
||||
<Input
|
||||
type="radio"
|
||||
name="inputType"
|
||||
checked={objectPlacementMode.inputType === INPUT_TYPE.DIMENSION}
|
||||
onClick={() => {
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, inputType: INPUT_TYPE.DIMENSION })
|
||||
}}
|
||||
className="form-radio text-blue-500"
|
||||
/>
|
||||
<span className="ml-2">치수입력</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex mb-2">
|
||||
<div className="mr-2">
|
||||
<label className="block text-gray-700 text-sm mb-1">가로길이</label>
|
||||
<Input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border rounded"
|
||||
placeholder="mm"
|
||||
value={objectPlacementMode.width}
|
||||
onChange={(e) => {
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, width: e.target.value })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 text-sm mb-1">세로길이</label>
|
||||
<Input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border rounded"
|
||||
placeholder="mm"
|
||||
value={objectPlacementMode.height}
|
||||
onChange={(e) => {
|
||||
setObjectPlacementModeState({ ...objectPlacementMode, height: e.target.value })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label className="inline-flex items-center">
|
||||
<Input
|
||||
type="checkbox"
|
||||
name={`areaBoundary`}
|
||||
checked={objectPlacementMode.areaBoundary}
|
||||
onClick={() => setObjectPlacementModeState({ ...objectPlacementMode, areaBoundary: !objectPlacementMode.areaBoundary })}
|
||||
className="form-checkbox text-blue-500"
|
||||
/>
|
||||
<span className="ml-2">영역교차</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<Button onClick={handleSave} className="bg-gray-500 text-white py-2 px-4 rounded">
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ObjectPlacement
|
||||
@ -27,6 +27,7 @@ import {
|
||||
guideLineState,
|
||||
horiGuideLinesState,
|
||||
vertGuideLinesState,
|
||||
objectPlacementModeState,
|
||||
} from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { fabric } from 'fabric'
|
||||
@ -74,6 +75,8 @@ export function useMode() {
|
||||
const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState)
|
||||
const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState)
|
||||
|
||||
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
|
||||
|
||||
useEffect(() => {
|
||||
// if (!canvas) {
|
||||
// canvas?.setZoom(0.8)
|
||||
@ -104,11 +107,7 @@ export function useMode() {
|
||||
}, [endPoint])
|
||||
|
||||
useEffect(() => {
|
||||
canvas?.off('mouse:out', removeMouseLines)
|
||||
canvas?.on('mouse:out', removeMouseLines)
|
||||
changeMode(canvas, mode)
|
||||
canvas?.off('mouse:move')
|
||||
canvas?.on('mouse:move', drawMouseLines)
|
||||
}, [mode, horiGuideLines, vertGuideLines])
|
||||
|
||||
useEffect(() => {
|
||||
@ -423,6 +422,17 @@ export function useMode() {
|
||||
break
|
||||
case 'adsorptionPoint':
|
||||
canvas?.on('mouse:down', mouseEvent.adsorptionPoint)
|
||||
break
|
||||
case 'shadow':
|
||||
canvas?.on('mouse:down', mouseEvent.shadowMode.down)
|
||||
canvas?.on('mouse:move', mouseEvent.shadowMode.move)
|
||||
canvas?.on('mouse:up', mouseEvent.shadowMode.up)
|
||||
break
|
||||
case 'opening':
|
||||
canvas?.on('mouse:down', mouseEvent.openingMode.down)
|
||||
canvas?.on('mouse:move', mouseEvent.openingMode.move)
|
||||
canvas?.on('mouse:up', mouseEvent.openingMode.up)
|
||||
|
||||
break
|
||||
case 'default':
|
||||
canvas?.off('mouse:down')
|
||||
@ -587,6 +597,9 @@ export function useMode() {
|
||||
|
||||
const mouseAndkeyboardEventClear = () => {
|
||||
canvas?.off('mouse:down')
|
||||
canvas?.off('mouse:move')
|
||||
canvas?.off('mouse:up')
|
||||
canvas?.off('mouse:out')
|
||||
Object.keys(mouseEvent).forEach((key) => {
|
||||
canvas?.off('mouse:down', mouseEvent[key])
|
||||
document.removeEventListener('contextmenu', mouseEvent[key])
|
||||
@ -682,6 +695,9 @@ export function useMode() {
|
||||
changeMouseEvent(mode)
|
||||
changeKeyboardEvent(mode)
|
||||
|
||||
canvas?.on('mouse:move', drawMouseLines)
|
||||
canvas?.on('mouse:out', removeMouseLines)
|
||||
|
||||
switch (mode) {
|
||||
case 'template':
|
||||
templateMode()
|
||||
@ -992,6 +1008,146 @@ export function useMode() {
|
||||
canvas.add(circle)
|
||||
canvas.renderAll()
|
||||
},
|
||||
//면 형상 배치 모드
|
||||
surfaceShapeMode: (o) => {},
|
||||
// 그림자 모드
|
||||
shadowMode: {
|
||||
rect: null,
|
||||
isDown: false,
|
||||
origX: 0,
|
||||
origY: 0,
|
||||
down: (o) => {
|
||||
if (mode !== Mode.SHADOW) return
|
||||
mouseEvent.shadowMode.isDown = true
|
||||
const pointer = canvas.getPointer(o.e)
|
||||
mouseEvent.shadowMode.origX = pointer.x
|
||||
mouseEvent.shadowMode.origY = pointer.y
|
||||
mouseEvent.shadowMode.rect = new fabric.Rect({
|
||||
fill: 'grey',
|
||||
left: mouseEvent.shadowMode.origX,
|
||||
top: mouseEvent.shadowMode.origY,
|
||||
originX: 'left',
|
||||
originY: 'top',
|
||||
opacity: 0.3,
|
||||
width: 0,
|
||||
height: 0,
|
||||
angle: 0,
|
||||
transparentCorners: false,
|
||||
})
|
||||
canvas.add(mouseEvent.shadowMode.rect)
|
||||
},
|
||||
move: (e) => {
|
||||
if (!mouseEvent.shadowMode.isDown) return
|
||||
const pointer = canvas.getPointer(e.e)
|
||||
if (mouseEvent.shadowMode.origX > pointer.x) {
|
||||
mouseEvent.shadowMode.rect.set({ left: Math.abs(pointer.x) })
|
||||
}
|
||||
if (mouseEvent.shadowMode.origY > pointer.y) {
|
||||
mouseEvent.shadowMode.rect.set({ top: Math.abs(pointer.y) })
|
||||
}
|
||||
|
||||
mouseEvent.shadowMode.rect.set({ width: Math.abs(mouseEvent.shadowMode.origX - pointer.x) })
|
||||
mouseEvent.shadowMode.rect.set({ height: Math.abs(mouseEvent.shadowMode.origY - pointer.y) })
|
||||
},
|
||||
up: (o) => {
|
||||
mouseEvent.shadowMode.isDown = false
|
||||
setMode(Mode.DEFAULT)
|
||||
},
|
||||
},
|
||||
openingMode: {
|
||||
rect: null,
|
||||
isDown: false,
|
||||
origX: 0,
|
||||
origY: 0,
|
||||
down: (o) => {
|
||||
if (mode !== Mode.OPENING) return
|
||||
mouseEvent.openingMode.isDown = true
|
||||
const pointer = canvas.getPointer(o.e)
|
||||
mouseEvent.openingMode.origX = pointer.x
|
||||
mouseEvent.openingMode.origY = pointer.y
|
||||
mouseEvent.openingMode.rect = new fabric.Rect({
|
||||
fill: 'white',
|
||||
stroke: 'black',
|
||||
strokeWidth: 1,
|
||||
left: mouseEvent.openingMode.origX,
|
||||
top: mouseEvent.openingMode.origY,
|
||||
originX: 'left',
|
||||
originY: 'top',
|
||||
width: pointer.x - mouseEvent.openingMode.origX,
|
||||
height: pointer.y - mouseEvent.openingMode.origY,
|
||||
angle: 0,
|
||||
transparentCorners: false,
|
||||
})
|
||||
canvas.add(mouseEvent.openingMode.rect)
|
||||
},
|
||||
move: (e) => {
|
||||
if (!mouseEvent.openingMode.isDown) return
|
||||
const pointer = canvas.getPointer(e.e)
|
||||
if (mouseEvent.openingMode.origX > pointer.x) {
|
||||
mouseEvent.openingMode.rect.set({ left: Math.abs(pointer.x) })
|
||||
}
|
||||
if (mouseEvent.openingMode.origY > pointer.y) {
|
||||
mouseEvent.openingMode.rect.set({ top: Math.abs(pointer.y) })
|
||||
}
|
||||
|
||||
mouseEvent.openingMode.rect.set({ width: Math.abs(mouseEvent.openingMode.origX - pointer.x) })
|
||||
mouseEvent.openingMode.rect.set({ height: Math.abs(mouseEvent.openingMode.origY - pointer.y) })
|
||||
},
|
||||
up: (o) => {
|
||||
mouseEvent.openingMode.isDown = false
|
||||
|
||||
const { areaBoundary } = objectPlacementMode
|
||||
|
||||
//roof의 내부에 있는지 확인
|
||||
if (!checkInsideRoof(mouseEvent.openingMode.rect)) {
|
||||
setMode(Mode.DEFAULT)
|
||||
}
|
||||
|
||||
// 영역 교차인지 확인
|
||||
if (!areaBoundary) {
|
||||
const isCross = checkCrossAreaBoundary(mouseEvent.openingMode.rect)
|
||||
if (isCross) {
|
||||
alert('영역이 교차되었습니다.')
|
||||
canvas.remove(mouseEvent.openingMode.rect)
|
||||
}
|
||||
}
|
||||
|
||||
mouseEvent.openingMode.rect.set({ name: 'opening' })
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const checkCrossAreaBoundary = (rect) => {
|
||||
const openings = canvas?._objects.filter((obj) => obj.name === 'opening')
|
||||
if (openings.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < openings.length; i++) {
|
||||
const rect2 = openings[i]
|
||||
// Check if one rectangle is to the left of the other
|
||||
if (rect.x + rect.width <= rect2.x || rect2.x + rect2.width <= rect.x) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if one rectangle is above the other
|
||||
if (rect.y + rect.height <= rect2.y || rect2.y + rect2.height <= rect.y) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const checkInsideRoof = (rect) => {
|
||||
let result = true
|
||||
const roofs = canvas?._objects.filter((obj) => obj.name === 'roof')
|
||||
if (roofs.length === 0) {
|
||||
alert('지붕을 먼저 그려주세요')
|
||||
canvas?.remove(rect)
|
||||
return false
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const getInterSectPointByMouseLine = () => {
|
||||
|
||||
@ -150,3 +150,15 @@ export const globalCompassState = atom({
|
||||
default: 0,
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
// 면형상 배치 모드
|
||||
export const surfacePlacementModeState = atom({
|
||||
key: 'surfacePlacementMode',
|
||||
default: { width: 0, height: 0, areaBoundary: true, inputType: 'free' },
|
||||
})
|
||||
|
||||
// 오브젝트 배치 모드
|
||||
export const objectPlacementModeState = atom({
|
||||
key: 'objectPlacementMode',
|
||||
default: { width: 0, height: 0, areaBoundary: true, inputType: 'free', batchType: 'opening' },
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user