diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeDrawing.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeDrawing.jsx
index c705b0eb..62bbab5d 100644
--- a/src/components/floor-plan/modal/placementShape/PlacementShapeDrawing.jsx
+++ b/src/components/floor-plan/modal/placementShape/PlacementShapeDrawing.jsx
@@ -8,6 +8,7 @@ import Diagonal from '@/components/floor-plan/modal/lineTypes/Diagonal'
import { useOuterLineWall } from '@/hooks/roofcover/useOuterLineWall'
import { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
import OuterLineWall from '@/components/floor-plan/modal/lineTypes/OuterLineWall'
+import { usePlacementShapeDrawing } from '@/hooks/surface/usePlacementShapeDrawing'
export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal }) {
const { getMessage } = useMessage()
@@ -45,7 +46,7 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
outerLineDiagonalLengthRef,
handleRollback,
handleFix,
- } = useOuterLineWall()
+ } = usePlacementShapeDrawing(setShowPlaceShapeDrawingModal)
const outerLineProps = {
length1,
@@ -145,8 +146,12 @@ export default function PlacementShapeDrawing({ setShowPlaceShapeDrawingModal })
-
-
+
+
diff --git a/src/hooks/surface/usePlacementShapeDrawing.js b/src/hooks/surface/usePlacementShapeDrawing.js
new file mode 100644
index 00000000..b07a8031
--- /dev/null
+++ b/src/hooks/surface/usePlacementShapeDrawing.js
@@ -0,0 +1,825 @@
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
+import {
+ adsorptionPointAddModeState,
+ adsorptionPointModeState,
+ adsorptionRangeState,
+ canvasState,
+ dotLineIntervalSelector,
+ verticalHorizontalModeState,
+} from '@/store/canvasAtom'
+import { useEvent } from '@/hooks/useEvent'
+import { useMouse } from '@/hooks/useMouse'
+import { useLine } from '@/hooks/useLine'
+import { useTempGrid } from '@/hooks/useTempGrid'
+import { useEffect, useRef } from 'react'
+import { distanceBetweenPoints, setSurfaceShapePattern } from '@/util/canvas-util'
+import { fabric } from 'fabric'
+import { calculateAngle } from '@/util/qpolygon-utils'
+import {
+ placementShapeDrawingAngle1State,
+ placementShapeDrawingAngle2State,
+ placementShapeDrawingArrow1State,
+ placementShapeDrawingArrow2State,
+ placementShapeDrawingDiagonalState,
+ placementShapeDrawingFixState,
+ placementShapeDrawingLength1State,
+ placementShapeDrawingLength2State,
+ placementShapeDrawingPointsState,
+ placementShapeDrawingTypeState,
+} from '@/store/placementShapeDrawingAtom'
+import { usePolygon } from '@/hooks/usePolygon'
+import { POLYGON_TYPE } from '@/common/common'
+
+// 면형상 배치
+export function usePlacementShapeDrawing(setShowPlaceShapeDrawingModal) {
+ const canvas = useRecoilValue(canvasState)
+ const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } =
+ useEvent()
+ const { getIntersectMousePoint } = useMouse()
+ const { addLine, removeLine } = useLine()
+ const { addPolygonByLines } = usePolygon()
+ const { tempGridMode } = useTempGrid()
+
+ const verticalHorizontalMode = useRecoilValue(verticalHorizontalModeState)
+ const adsorptionPointAddMode = useRecoilValue(adsorptionPointAddModeState)
+ const adsorptionPointMode = useRecoilValue(adsorptionPointModeState)
+ const adsorptionRange = useRecoilValue(adsorptionRangeState)
+ const interval = useRecoilValue(dotLineIntervalSelector) // 가로 세로 간격
+
+ const length1Ref = useRef(null)
+ const length2Ref = useRef(null)
+ const angle1Ref = useRef(null)
+ const angle2Ref = useRef(null)
+ const [length1, setLength1] = useRecoilState(placementShapeDrawingLength1State)
+ const [length2, setLength2] = useRecoilState(placementShapeDrawingLength2State)
+ const [arrow1, setArrow1] = useRecoilState(placementShapeDrawingArrow1State)
+ const [arrow2, setArrow2] = useRecoilState(placementShapeDrawingArrow2State)
+ const [points, setPoints] = useRecoilState(placementShapeDrawingPointsState)
+ const [type, setType] = useRecoilState(placementShapeDrawingTypeState)
+ const [angle1, setAngle1] = useRecoilState(placementShapeDrawingAngle1State)
+ const [angle2, setAngle2] = useRecoilState(placementShapeDrawingAngle2State)
+ const [outerLineDiagonalLength, setOuterLineDiagonalLength] = useRecoilState(placementShapeDrawingDiagonalState)
+ const setOuterLineFix = useSetRecoilState(placementShapeDrawingFixState)
+ const arrow1Ref = useRef(arrow1)
+ const arrow2Ref = useRef(arrow2)
+
+ const outerLineDiagonalLengthRef = useRef(null)
+
+ const isFix = useRef(false)
+
+ useEffect(() => {
+ if (adsorptionPointAddMode || tempGridMode) {
+ return
+ }
+
+ addCanvasMouseEventListener('mouse:down', mouseDown)
+ clear()
+ }, [verticalHorizontalMode, points, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, interval, tempGridMode])
+
+ useEffect(() => {
+ arrow1Ref.current = arrow1
+ }, [arrow1])
+
+ useEffect(() => {
+ arrow2Ref.current = arrow2
+ }, [arrow2])
+
+ useEffect(() => {
+ clear()
+ addDocumentEventListener('keydown', document, keydown[type])
+ }, [type])
+
+ const clear = () => {
+ setLength1(0)
+ setLength2(0)
+
+ setArrow1('')
+ setArrow2('')
+
+ setAngle1(0)
+ setAngle2(0)
+
+ setOuterLineDiagonalLength(0)
+ }
+
+ const mouseDown = (e) => {
+ let pointer = getIntersectMousePoint(e)
+
+ if (points.length === 0) {
+ setPoints((prev) => [...prev, pointer])
+ } else {
+ const lastPoint = points[points.length - 1]
+ let newPoint = { x: pointer.x, y: pointer.y }
+ const length = distanceBetweenPoints(lastPoint, newPoint)
+ if (verticalHorizontalMode) {
+ const vector = {
+ x: pointer.x - points[points.length - 1].x,
+ y: pointer.y - points[points.length - 1].y,
+ }
+ const slope = Math.abs(vector.y / vector.x) // 기울기 계산
+
+ let scaledVector
+ if (slope >= 1) {
+ // 기울기가 1 이상이면 x축 방향으로 그림
+ scaledVector = {
+ x: 0,
+ y: vector.y >= 0 ? Number(length) : -Number(length),
+ }
+ } else {
+ // 기울기가 1 미만이면 y축 방향으로 그림
+ scaledVector = {
+ x: vector.x >= 0 ? Number(length) : -Number(length),
+ y: 0,
+ }
+ }
+
+ const verticalLength = scaledVector.y
+ const horizontalLength = scaledVector.x
+
+ newPoint = {
+ x: lastPoint.x + horizontalLength,
+ y: lastPoint.y + verticalLength,
+ }
+ }
+ setPoints((prev) => [...prev, newPoint])
+ }
+ }
+
+ useEffect(() => {
+ canvas
+ ?.getObjects()
+ .filter((obj) => obj.name === 'placementShapeDrawingLine' || obj.name === 'helpGuideLine')
+ .forEach((obj) => {
+ removeLine(obj)
+ })
+
+ canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'placementShapeDrawingStartPoint'))
+
+ if (points.length === 0) {
+ setOuterLineFix(true)
+ removeAllDocumentEventListeners()
+ return
+ }
+
+ addDocumentEventListener('keydown', document, keydown[type])
+
+ if (points.length === 1) {
+ const point = new fabric.Circle({
+ radius: 5,
+ fill: 'transparent',
+ stroke: 'red',
+ left: points[0].x - 5,
+ top: points[0].y - 5,
+ selectable: false,
+ name: 'placementShapeDrawingStartPoint',
+ })
+
+ canvas?.add(point)
+ } else {
+ setOuterLineFix(false)
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'placementShapeDrawingPoint')
+ .forEach((obj) => {
+ canvas.remove(obj)
+ })
+ points.forEach((point, idx) => {
+ const circle = new fabric.Circle({
+ left: point.x,
+ top: point.y,
+ visible: false,
+ name: 'placementShapeDrawingPoint',
+ })
+ canvas.add(circle)
+ })
+ points.forEach((point, idx) => {
+ if (idx === 0) {
+ return
+ }
+ drawLine(points[idx - 1], point, idx)
+ })
+
+ const lastPoint = points[points.length - 1]
+ const firstPoint = points[0]
+
+ if (isFix.current) {
+ removeAllMouseEventListeners()
+ removeAllDocumentEventListeners()
+
+ const lines = canvas?.getObjects().filter((obj) => obj.name === 'placementShapeDrawingLine')
+ const roof = addPolygonByLines(lines, {
+ stroke: 'black',
+ strokeWidth: 3,
+ selectable: true,
+ name: POLYGON_TYPE.ROOF,
+ })
+
+ setSurfaceShapePattern(roof)
+
+ lines.forEach((line) => {
+ removeLine(line)
+ })
+
+ canvas?.renderAll()
+ setShowPlaceShapeDrawingModal(false)
+ }
+
+ if (points.length < 3) {
+ return
+ }
+
+ /*if (lastPoint.x === firstPoint.x && lastPoint.y === firstPoint.y) {
+ return
+ }
+
+ if (lastPoint.x === firstPoint.x || lastPoint.y === firstPoint.y) {
+ let isAllRightAngle = true
+
+ const firstPoint = points[0]
+
+ points.forEach((point, idx) => {
+ if (idx === 0 || !isAllRightAngle) {
+ return
+ }
+
+ const angle = calculateAngle(point, firstPoint)
+ if (angle % 90 !== 0) {
+ isAllRightAngle = false
+ }
+ })
+
+ if (isAllRightAngle) {
+ return
+ }
+ const line = new QLine([lastPoint.x, lastPoint.y, firstPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ selectable: false,
+ name: 'helpGuideLine',
+ })
+
+ canvas?.add(line)
+ addLineText(line)
+ } else {
+ const guideLine1 = new QLine([lastPoint.x, lastPoint.y, lastPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ strokeDashArray: [1, 1, 1],
+ name: 'helpGuideLine',
+ })
+
+ const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, firstPoint.x, firstPoint.y], {
+ stroke: 'grey',
+ strokeWidth: 1,
+ strokeDashArray: [1, 1, 1],
+ name: 'helpGuideLine',
+ })
+ if (guideLine1.length > 0) {
+ canvas?.add(guideLine1)
+ addLineText(guideLine1)
+ }
+
+ canvas?.add(guideLine2)
+
+ addLineText(guideLine2)
+ }*/
+ }
+ }, [points])
+
+ const drawLine = (point1, point2, idx) => {
+ addLine([point1.x, point1.y, point2.x, point2.y], {
+ stroke: 'black',
+ strokeWidth: 3,
+ idx: idx,
+ selectable: true,
+ name: 'placementShapeDrawingLine',
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y,
+ })
+ }
+
+ // 직각 완료될 경우 확인
+ const checkRightAngle = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ length2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const length1Num = Number(length1Ref.current.value) / 10
+ const length2Num = Number(length2Ref.current.value) / 10
+
+ if (points.length === 0) {
+ return
+ }
+
+ if (length1Num === 0 || length2Num === 0 || arrow1Ref.current === '' || arrow2Ref.current === '') {
+ return
+ }
+
+ if (arrow1Ref.current === '↓' && arrow2Ref.current === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y + length1Num }]
+ })
+ } else if (arrow1Ref.current === '↓' && arrow2Ref.current === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y + length1Num }]
+ })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Num, y: prev[prev.length - 1].y - length1Num }]
+ })
+ } else if (arrow1Ref.current === '↑' && arrow2Ref.current === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Num, y: prev[prev.length - 1].y - length1Num }]
+ })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y + length2Num }]
+ })
+ } else if (arrow1Ref.current === '→' && arrow2Ref.current === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Num, y: prev[prev.length - 1].y - length2Num }]
+ })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y + length2Num }]
+ })
+ } else if (arrow1Ref.current === '←' && arrow2Ref.current === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Num, y: prev[prev.length - 1].y - length2Num }]
+ })
+ }
+ }
+
+ //이구배 완료될 경우 확인 ↓, ↑, ←, →
+ const checkDoublePitch = (direction) => {
+ const activeElem = document.activeElement
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current || activeElem === angle1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ angle2Ref.current.focus()
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const angle1Value = angle1Ref.current.value
+ const angle2Value = angle2Ref.current.value
+ const length1Value = length1Ref.current.value
+ const length2Value = length2Ref.current.value
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length1Value / 10, y: prev[prev.length - 1].y - length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '←' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ }
+
+ angle1Ref.current.focus()
+ }
+ }
+
+ //대각선 완료될 경우 확인
+ const checkDiagonal = (direction) => {
+ const activeElem = document.activeElement
+ const diagonalLength = outerLineDiagonalLengthRef.current.value // 대각선 길이
+
+ const length1Value = length1Ref.current.value
+
+ if (diagonalLength <= length1Value) {
+ alert('대각선 길이는 직선 길이보다 길어야 합니다.')
+ return
+ }
+
+ const canDirection =
+ direction === '↓' || direction === '↑'
+ ? arrow1Ref.current === '←' || arrow1Ref.current === '→'
+ : arrow1Ref.current === '↓' || arrow1Ref.current === '↑'
+
+ if (activeElem === length1Ref.current) {
+ setArrow1(direction)
+ arrow1Ref.current = direction
+ } else if (activeElem === length2Ref.current || activeElem === angle2Ref.current) {
+ if (!canDirection) {
+ return
+ }
+ setArrow2(direction)
+ arrow2Ref.current = direction
+ }
+
+ const arrow1Value = arrow1Ref.current
+ const arrow2Value = arrow2Ref.current
+
+ const getLength2 = () => {
+ return Math.floor(Math.sqrt(diagonalLength ** 2 - length1Value ** 2))
+ }
+
+ const length2Value = getLength2()
+
+ if (diagonalLength !== 0 && length1Value !== 0 && arrow1Value !== '') {
+ setLength2(getLength2())
+ length2Ref.current.focus()
+ }
+
+ if (length1Value !== 0 && length2Value !== 0 && arrow1Value !== '' && arrow2Value !== '') {
+ if (arrow1Value === '↓' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↓' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y + length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '→') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '↑' && arrow2Value === '←') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - length2Value / 10, y: prev[prev.length - 1].y - length1Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↓') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + length1Value / 10, y: prev[prev.length - 1].y + length2Value / 10 }]
+ })
+ } else if (arrow1Value === '→' && arrow2Value === '↑') {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [
+ ...prev,
+ {
+ x: prev[prev.length - 1].x + length1Value / 10,
+ y: prev[prev.length - 1].y - length2Value / 10,
+ },
+ ]
+ })
+ }
+ }
+ }
+
+ const keydown = {
+ outerLine: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ // 포커스가 length1에 있지 않으면 length1에 포커스를 줌
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ const key = e.key
+
+ if (!length1Ref.current) {
+ return
+ }
+
+ const lengthNum = Number(length1Ref.current.value) / 10
+ if (lengthNum === 0) {
+ return
+ }
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ setArrow1('↓')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y + lengthNum }]
+ })
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ setArrow1('↑')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x, y: prev[prev.length - 1].y - lengthNum }]
+ })
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ setArrow1('←')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x - lengthNum, y: prev[prev.length - 1].y }]
+ })
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ setArrow1('→')
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ return [...prev, { x: prev[prev.length - 1].x + lengthNum, y: prev[prev.length - 1].y }]
+ })
+ break
+ }
+ },
+ rightAngle: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+
+ const activeElem = document.activeElement
+ if (activeElem !== length1Ref.current && activeElem !== length2Ref.current) {
+ length1Ref.current.focus()
+ }
+
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkRightAngle('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkRightAngle('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkRightAngle('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkRightAngle('→')
+ break
+ case 'Enter':
+ break
+ }
+ },
+ doublePitch: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDoublePitch('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDoublePitch('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDoublePitch('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDoublePitch('→')
+ break
+ }
+ },
+ angle: (e) => {
+ if (points.length === 0) {
+ return
+ }
+ const key = e.key
+ switch (key) {
+ case 'Enter': {
+ setPoints((prev) => {
+ if (prev.length === 0) {
+ return []
+ }
+ const lastPoint = prev[prev.length - 1]
+ const length = length1Ref.current.value / 10
+ const angle = angle1Ref.current.value
+ //lastPoint로부터 angle1만큼의 각도로 length1만큼의 길이를 가지는 선을 그림
+ const radian = (angle * Math.PI) / 180
+
+ const x = lastPoint.x + length * Math.cos(radian)
+ const y = lastPoint.y - length * Math.sin(radian)
+ return [...prev, { x, y }]
+ })
+ }
+ }
+ },
+ diagonalLine: (e) => {
+ if (points.length === 0) {
+ return
+ }
+
+ const key = e.key
+ switch (key) {
+ case 'Down': // IE/Edge에서 사용되는 값
+ case 'ArrowDown': {
+ checkDiagonal('↓')
+ break
+ }
+ case 'Up': // IE/Edge에서 사용되는 값
+ case 'ArrowUp':
+ checkDiagonal('↑')
+ break
+ case 'Left': // IE/Edge에서 사용되는 값
+ case 'ArrowLeft':
+ checkDiagonal('←')
+ break
+ case 'Right': // IE/Edge에서 사용되는 값
+ case 'ArrowRight':
+ checkDiagonal('→')
+ break
+ }
+ },
+ }
+
+ /**
+ * 일변전으로 돌아가기
+ */
+ const handleRollback = () => {
+ //points의 마지막 요소를 제거
+ setPoints((prev) => prev.slice(0, prev.length - 1))
+ }
+
+ const handleFix = () => {
+ if (points.length < 3) {
+ return
+ }
+
+ let isAllRightAngle = true
+
+ const firstPoint = points[0]
+
+ points.forEach((point, idx) => {
+ if (idx === 0 || !isAllRightAngle) {
+ return
+ }
+
+ const angle = calculateAngle(point, firstPoint)
+ if (angle % 90 !== 0) {
+ isAllRightAngle = false
+ }
+ })
+
+ if (isAllRightAngle) {
+ alert('부정확한 다각형입니다.')
+ return
+ }
+
+ setPoints((prev) => {
+ return [...prev, { x: prev[0].x, y: prev[0].y }]
+ })
+
+ isFix.current = true
+ }
+
+ return {
+ points,
+ setPoints,
+ length1,
+ setLength1,
+ length2,
+ setLength2,
+ length1Ref,
+ length2Ref,
+ arrow1,
+ setArrow1,
+ arrow2,
+ setArrow2,
+ arrow1Ref,
+ arrow2Ref,
+ angle1,
+ setAngle1,
+ angle1Ref,
+ angle2,
+ setAngle2,
+ angle2Ref,
+ outerLineDiagonalLength,
+ setOuterLineDiagonalLength,
+ outerLineDiagonalLengthRef,
+ type,
+ setType,
+ handleFix,
+ handleRollback,
+ }
+}
diff --git a/src/store/placementShapeDrawingAtom.js b/src/store/placementShapeDrawingAtom.js
new file mode 100644
index 00000000..b7a65107
--- /dev/null
+++ b/src/store/placementShapeDrawingAtom.js
@@ -0,0 +1,70 @@
+import { atom } from 'recoil'
+
+export const OUTER_LINE_TYPE = {
+ OUTER_LINE: 'outerLine', // 외벽선
+ RIGHT_ANGLE: 'rightAngle', // 직각
+ DOUBLE_PITCH: 'doublePitch',
+ ANGLE: 'angle', // 각도
+ DIAGONAL_LINE: 'diagonalLine', // 대각선
+}
+
+/**
+ * 외벽선 작성에서 사용하는 recoilState
+ */
+
+export const placementShapeDrawingLength1State = atom({
+ //길이1
+ key: 'placementShapeDrawingLength1State',
+ default: 0,
+})
+
+export const placementShapeDrawingLength2State = atom({
+ // 길이2
+ key: 'placementShapeDrawingLength2State',
+ default: 0,
+})
+
+export const placementShapeDrawingArrow1State = atom({
+ // 방향1
+ key: 'placementShapeDrawingArrow1State',
+ default: '',
+})
+
+export const placementShapeDrawingArrow2State = atom({
+ // 방향2
+ key: 'placementShapeDrawingArrow2State',
+ default: '',
+})
+
+export const placementShapeDrawingAngle1State = atom({
+ // 각도1
+ key: 'placementShapeDrawingAngle1State',
+ default: 0,
+})
+
+export const placementShapeDrawingAngle2State = atom({
+ // 각도2
+ key: 'placementShapeDrawingAngle2State',
+ default: 0,
+})
+
+export const placementShapeDrawingDiagonalState = atom({
+ // 대각선
+ key: 'placementShapeDrawingDiagonalState',
+ default: 0,
+})
+
+export const placementShapeDrawingTypeState = atom({
+ key: 'placementShapeDrawingTypeState',
+ default: OUTER_LINE_TYPE.OUTER_LINE,
+})
+
+export const placementShapeDrawingPointsState = atom({
+ key: 'placementShapeDrawingPointsState',
+ default: [],
+})
+
+export const placementShapeDrawingFixState = atom({
+ key: 'placementShapeDrawingFixState',
+ default: false,
+})